Skip to content

Azure: scoped delegation in the Share panel no longer fails with 503 - #182

Merged
Keith McGrane (Keithland89) merged 1 commit into
mainfrom
keithmcgrane-microsoft-row-32-share-503-fix
Oct 23, 2026
Merged

Keith McGrane (Keithland89) merged 1 commit into
mainfrom
keithmcgrane-microsoft-row-32-share-503-fix

Conversation

@Keithland89

Copy link
Copy Markdown
Contributor

Summary

Row 32 of 0.6.8 (finding 41). On Azure with org-scoped access on, every /api/delegations request failed at once with 503 DatabaseUnavailable, "Couldn't reach the Analytics Hub database. Try again in a minute.", even with the database online. So the Share panel's scoped delegation couldn't show, give, take back or approve scopes. A test install on 0.6.8-rc.2 showed it on 22 Oct, and its web app logged:

SQL connection failed: The "config.authentication.options.credential" property must be an instance of the token credential class.

Cause. SqlScopeStore handed mssql its credential, a ManagedIdentityCredential by default, as authentication.options.credential (azure/web/src/scope-store.js:35). The versions below are the lockfile's, the same at every installer-v0.6.* tag and on main.

  • mssql 11.0.2's ConnectionPool constructor deep-copies its config with rfdc 1.4.1 (node_modules/mssql/lib/base/connection-pool.js:56, this.config = clone(config)). rfdc copies only own enumerable properties, into plain objects. So the copy loses getToken, which the credential's class defines on its prototype.
  • tedious 18.6.2 checks the copy with isTokenCredential from @azure/core-auth 1.11.0, and throws the TypeError above (node_modules/tedious/lib/connection.js:313–315).
  • That message doesn't match the store's RESUMING pattern (scope-store.js:15), so connect() gives up on attempt 1 (:44), and unavailable() returns the 503 (:132–135).

Fix (scope-store.js:21–24). The store hands the driver { getToken: (scopes, options) => source.getToken(scopes, options) }, where source is the injected credential or the ManagedIdentityCredential. rfdc copies a function in an own property by reference, so getToken survives the copy, and isTokenCredential accepts the object. The identity, the token scope and the pool settings are unchanged.

Why CI missed it. The store's existing test (azure/web/test/delegations.test.js:285–316) stubs ConnectionPool, so nothing copies the config, and it checks only the auth type.

Reach. Only DelegationService uses the store (azure/web/src/delegations.js:31), and nothing else in the repo uses mssql. The store hasn't changed since #31 (f08be05), which every tag from installer-v0.6.0 contains, so the fault has been in the web app since 0.6.0. But the app couldn't reach it before 0.6.8-rc.2:

  • The Share panel shows scoped delegation only when config.host === "azure" && config.access?.rlsOrgAttribute (app/src/components/share-panel.tsx:49, the same at installer-v0.6.7). Only that component, share-delegations.tsx, calls /api/delegations.
  • At installer-v0.6.7, as at every tag back to 0.6.0, /app.config.json (azure/web/src/server.js:35–37) sends access: publicAccess(config.access). publicAccess (:109–112) keeps only groupId, groupName, contact and requestUrl.
  • App: the Executive summary opens with 8 queries rather than 18 (cut 1, the landing diet) #155 (bf273f1, first tagged in installer-v0.6.8-rc.2) added rlsOrgAttribute to publicAccess (server.js:112–114 there).

So released versions, 0.6.7 included, could reach the fault only by calling the API directly, and 0.6.7 needs no Known issue.

Scope

Parts affected (installer / app / Fabric notebooks and pipeline / Azure infra, jobs or web / core / Power BI models): Azure web (azure/web) only.

Source contracts, optional inputs, install record or deployed item names changed: none.

Files:

  • azure/web/src/scope-store.js: the SqlScopeStore constructor (:21–24). Nothing else changed.
  • azure/web/test/delegations.test.js: one new test (:318–356). The stubbed test at :285–316 is unchanged.
  • CHANGELOG.md: one **Fixed:** entry, last under ## Unreleased. It comes after App: start on the last 12 weeks for a large tenant #177's **Changed:** entry, just before ## Analytics Hub installer 0.6.8-rc.2 (unreleased) (now :267).

Doc lines changed: only CHANGELOG.md:255–265. No README or docs/ page describes the fault. azure/web/README.md:13 says the delegation routes write as the managed identity, which still holds.

Validation

  • Ran python -B -m pytest tests -q and recorded results below.
  • Ran npm test (and npm run typecheck for the installer) in each changed Node project.
  • Updated directly related documentation and checked changed links.
  • No credentials, tenant exports, prompts or customer names are included.
  • If .pbit files changed, documented the rebuild method and Desktop refresh / visual checks. (N/A: no .pbit changes.)

Results, skipped checks and limitations:

The new test drives the real mssql and tedious, not the stub:

  • RecordingPool extends mssql's ConnectionPool and gives tedious a connector that refuses ("no sockets in tests"). So nothing looks up a name or opens a socket. The server is sql.example.invalid as a backstop.
  • The credential is a class with getToken on its prototype, like ManagedIdentityCredential.
  • The test checks that tedious took the credential: there's no "must be an instance of the token credential class" warning, only the connector's refusal. console.warn is restored afterwards. connect() still ends in 503, because the connector refuses.
  • It checks that the credential mssql handed tedious has a getToken that calls the test's credential and returns its token.
  • It checks that the default path, a real ManagedIdentityCredential, is taken the same way. That credential is never asked for a token.

It takes about 0.35 s.

Fail before, pass after. All run in azure/web after npm.cmd ci (node v24.19.0, npm 11.17.0):

  • node --test --test-name-pattern="survives mssql copying" test/delegations.test.js:
    • before the fix: 1 test, 0 pass, 1 fail, with SQL connection failed: The "config.authentication.options.credential" property must be an instance of the token credential class.
    • after the fix: 1 test, 1 pass.
  • npm.cmd test:
    • with only the fix reverted: 85 tests, 84 pass, 1 fail (the new test).
    • on the head: 85 tests, 85 pass, which is 84 before plus the new one.

Not run here: python -B -m pytest tests -q, and the app and installer suites, which this PR doesn't touch. This PC is shared with live speed tests, so CI runs them. I did check the new CHANGELOG paragraph against tests/test_parked_ideas.py's front-door patterns, with no hits. git diff --check is clean. Nothing calls live Azure, Entra, SQL or a tenant.

Follow-up, not in this PR: the 503 says "Try again in a minute." for every connection failure except a resuming database, where waiting won't help. Its wording could be a follow-up.

Compatibility

  • Picking up the fix: an Azure install gets it when Repair or change, with an installer that includes it, updates the web app's image. The image tag is the installer's version unless the install pins images.tag (installer/src/steps/azure/index.js:94). Scoped delegation in the Share panel then works with org-scoped access on.
  • Migration: nothing to migrate. The identity, the database, the scope tables and the routes are unchanged.
  • Without org-scoped access: installs never connect to the store, so nothing changes for them.
  • Fabric: unaffected, because it has no Azure web app.
  • ValueLens: the Power BI templates need no matching change, since this is Azure web only.

With org-scoped access on, every /api/delegations request failed at once
with 503 DatabaseUnavailable, even with the database online. mssql's
ConnectionPool deep-copies its config with rfdc, which copies only own
enumerable properties into plain objects, so the getToken that
ManagedIdentityCredential defines on its prototype was lost. tedious then
refused the copy: 'The "config.authentication.options.credential" property
must be an instance of the token credential class.' That isn't an error a
resuming database gives, so the store gave up on the first attempt.

SqlScopeStore now hands the driver an object with a getToken of its own
that calls the credential it uses, injected or the managed identity, and
that survives the copy.

The new test drives the real mssql and tedious with a credential whose
getToken is on its class's prototype. A connector that refuses keeps it
offline: no name lookup, no socket. It fails without the fix and passes
with it.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@Keithland89
Keith McGrane (Keithland89) merged commit 5d5d80a into main Oct 23, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant