src/vs/workbench/api/common/extHostXaaAuthProvider.ts

402 LOC · 83 covered · 319 uncovered · 5 ranges · 38 concepts · 1 introducers · 33 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the filesrc/vs/workbench/api/common/extHostAuthentication.ts · 1040 LOCcommon/extHostAuthentica…src/vs/workbench/api/common/extHostExtensionService.ts · 1285 LOCcommon/extHostExtensionS…src/vs/workbench/api/common/extHostFileSystemConsumer.ts · 260 LOCcommon/extHostFileSystem…src/vs/workbench/api/common/extHostLanguageModels.ts · 768 LOCcommon/extHostLanguageMo…src/vs/workbench/api/common/extHostLocalizationService.ts · 109 LOCcommon/extHostLocalizati…src/vs/workbench/api/common/extHostManagedSockets.ts · 121 LOCcommon/extHostManagedSoc…src/vs/workbench/api/common/extHostMemento.ts · 122 LOCcommon/extHostMemento.tssrc/vs/workbench/api/common/extHostProgress.ts · 107 LOCcommon/extHostProgress.t…src/vs/workbench/api/common/extHostSecretState.ts · 42 LOCcommon/extHostSecretStat…src/vs/workbench/api/common/extHostSecrets.ts · 51 LOCcommon/extHostSecrets.tssrc/vs/workbench/api/common/extHostStorage.ts · 75 LOCcommon/extHostStorage.tssrc/vs/workbench/api/common/extHostStoragePaths.ts · 95 LOCcommon/extHostStoragePat…src/vs/workbench/api/common/extHostUrls.ts · 70 LOCcommon/extHostUrls.tssrc/vs/workbench/api/common/extHostWindow.ts · 116 LOCcommon/extHostWindow.tssrc/vs/workbench/services/extensions/common/workspaceContains.ts · 139 LOCcommon/workspaceContains…extHostMcp.test|title=ExtHostMcp IAuthMetadata properties should expose readonly properties|occurrence=1, extHostMcp.test|title=ExtHostMcp createAuthMetadata should create IAuthMetadata with fetched server metadata|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.ts ×1 · 3 introduced LOCextHostMcp.ts ×1extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should handle multiple Bearer challenges and use first scope|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when scopes are the same|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true and update scopes when WWW-Authenticate header contains new scopes|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when scopes are same but in different order|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true when updating from defined scopes to undefined (no scope in header)|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle non-401 status codes in update()|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should ignore non-Bearer schemes|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true when updating from undefined scopes to defined scopes|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.ts ×1 · 1 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×1 · 2 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×1 · 6 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×1 · 4 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×2 · 4 introduced LOCextHostMcp.ts ×2extHostMcp.ts ×1 · 1 introduced LOCextHostMcp.ts ×1extHostMcp.test|title=ExtHostMcp createAuthMetadata should fall back to default metadata when server metadata fetch fails|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.ts ×4 · 6 introduced LOCextHostMcp.ts ×4extHostMcp.test|title=ExtHostMcp createAuthMetadata should use scopes from WWW-Authenticate header when resource metadata has none|occurrence=1, extHostMcp.test|title=ExtHostMcp createAuthMetadata should use scopes from WWW-Authenticate header even when resource metadata has scopes_supported|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.ts ×1 · 2 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×1 · 6 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×4 · 13 introduced LOCextHostMcp.ts ×4extHostMcpNode.ts ×8 · 45 introduced LOCextHostMcpNode.ts ×8extHostMcp.test|title=ExtHostMcp createAuthMetadata should pass launch headers when fetching metadata from same origin|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.ts ×5 · 25 introduced LOCextHostMcp.ts ×5extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle invalid JSON in resource metadata response|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.ts ×3 · 23 introduced LOCextHostMcp.ts ×3extHostMcp.ts ×1 · 2 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×8 · 60 introduced LOCextHostMcp.ts ×8extHostMcp.ts ×43 · 874 introduced LOCextHostMcp.ts ×43extensionHostMain.ts ×1 · 2 introduced LOCextensionHostMain.ts ×1reset prepareStackTrace-callback|occurrence=1, extensionHostMain.test|title=ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slowdown and eventual stack overflow #184926 prevent rewrapping|occurrence=1 · 0 introduced LOCreset prepareStackTrace-…extensionHostMain.ts ×5 · 26 introduced LOCextensionHostMain.ts ×5f0f2e182082072efdaf0f8e1537d2cce Restored, too many uses before restoration|occurrence=1 · 0 introduced LOCf0f2e182082072efdaf0f8e1…extensionHostMain.ts ×1 · 3 introduced LOCextensionHostMain.ts ×1extensionHostMain.ts ×1 · 3 introduced LOCextensionHostMain.ts ×1extensionHostMain.ts ×11 · 164 introduced LOCextensionHostMain.ts ×11extHostExtensionService.ts ×64 · 1246 introduced LOCextHostExtensionService.…extHostMcp.test|title=ExtHostMcp IAuthMetadata properties should allow undefined scopes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata properties should allow undefined scopes|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata properties should expose readonly properties|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata properties should expose readonly properties|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should handle multiple Bearer challenges and use first scope|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should handle multiple Bearer challenges and use first scope|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should ignore non-Bearer schemes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should ignore non-Bearer schemes|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when no WWW-Authenticate header and scopes are already undefined|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when no WWW-Authenticate header and scopes are already undefined|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when scopes are same but in different order|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when scopes are same but in different order|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when scopes are the same|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when scopes are the same|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true and update scopes when WWW-Authenticate header contains new scopes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true and update scopes when WWW-Authenticate header contains new scopes|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true when updating from defined scopes to undefined (no scope in header)|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true when updating from defined scopes to undefined (no scope in header)|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true when updating from undefined scopes to defined scopes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true when updating from undefined scopes to defined scopes|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should create IAuthMetadata with fetched server metadata|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should create IAuthMetadata with fetched server metadata|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should fall back to default metadata when server metadata fetch fails|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should fall back to default metadata when server metadata fetch fails|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle empty scope string in WWW-Authenticate header|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle empty scope string in WWW-Authenticate header|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle invalid JSON in resource metadata response|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle invalid JSON in resource metadata response|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle malformed WWW-Authenticate header gracefully|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle malformed WWW-Authenticate header gracefully|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle non-401 status codes in update()|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle non-401 status codes in update()|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should pass launch headers when fetching metadata from same origin|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should pass launch headers when fetching metadata from same origin|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should use resource_metadata challenge URL from WWW-Authenticate header|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should use resource_metadata challenge URL from WWW-Authenticate header|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should use scopes from WWW-Authenticate header even when resource metadata has scopes_supported|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should use scopes from WWW-Authenticate header even when resource metadata has scopes_supported|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should use scopes from WWW-Authenticate header when resource metadata has none|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should use scopes from WWW-Authenticate header when resource metadata has none|occurrence=1extHostMcp.test|title=Ex…extensionHostMain.test|title=ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slowdown and eventual stack overflow #184926 basics|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extensionHostMain.test|title=ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slowdown and eventual stack overflow #184926 basics|occurrence=1extensionHostMain.test|t…f0f2e182082072efdaf0f8e1537d2cce Never restored, separate operations|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extensionHostMain.test|title=ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slowdown and eventual stack overflow #184926 https://gist.github.com/thecrypticace/f0f2e182082072efdaf0f8e1537d2cce Never restored, separate operations|occurrence=1f0f2e182082072efdaf0f8e1…f0f2e182082072efdaf0f8e1537d2cce Restored, separate operations|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extensionHostMain.test|title=ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slowdown and eventual stack overflow #184926 https://gist.github.com/thecrypticace/f0f2e182082072efdaf0f8e1537d2cce Restored, separate operations|occurrence=1f0f2e182082072efdaf0f8e1…f0f2e182082072efdaf0f8e1537d2cce Restored, too many uses before restoration|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extensionHostMain.test|title=ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slowdown and eventual stack overflow #184926 https://gist.github.com/thecrypticace/f0f2e182082072efdaf0f8e1537d2cce Restored, too many uses before restoration|occurrence=1f0f2e182082072efdaf0f8e1…extensionHostMain.test|title=ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slowdown and eventual stack overflow #184926 prevent rewrapping|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extensionHostMain.test|title=ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slowdown and eventual stack overflow #184926 prevent rewrapping|occurrence=1extensionHostMain.test|t…reset prepareStackTrace-callback|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extensionHostMain.test|title=ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slowdown and eventual stack overflow #184926 set/reset prepareStackTrace-callback|occurrence=1reset prepareStackTrace-…extensionHostMain.test|title=ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slowdown and eventual stack overflow #184926 wrap prepareStackTrace-callback|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extensionHostMain.test|title=ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slowdown and eventual stack overflow #184926 wrap prepareStackTrace-callback|occurrence=1extensionHostMain.test|t…extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg does not add stray ^ to argument values|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg does not add stray ^ to argument values|occurrence=1extHostMcpNode.test|titl…extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg doubles embedded double quotes (cmd.exe convention)|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg doubles embedded double quotes (cmd.exe convention)|occurrence=1extHostMcpNode.test|titl…extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg neutralizes cmd.exe metacharacters inside quotes (CVE-2024-27980)|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg neutralizes cmd.exe metacharacters inside quotes (CVE-2024-27980)|occurrence=1extHostMcpNode.test|titl…extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg preserves paths with parentheses without injecting ^|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg preserves paths with parentheses without injecting ^|occurrence=1extHostMcpNode.test|titl…extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg preserves paths with spaces|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg preserves paths with spaces|occurrence=1extHostMcpNode.test|titl…extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg wraps simple values in double quotes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg wraps simple values in double quotes|occurrence=1extHostMcpNode.test|titl…Focused file · src/vs/workbench/api/common/extHostXaaAuthProvider.ts · 402 LOCcommon/extHostXaaAuthPro…

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- extHostExtensionService.ts ×64
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import type * as vscode from 'vscode';
7 > import { stringHash } from '../../../base/common/hash.js';
8 > import { buildIdJagExchangeBody, buildResourceRedemptionBody, fetchAuthorizationServerMetadata, getClaimsFromJWT, IAuthorizationJWTClaims, IAuthorizationTokenResponse, isAuthorizationTokenResponse } from '../../../base/common/oauth.js';
9 > import { DynamicAuthProvider } from './extHostAuthentication.js';
10 >
11 > // eslint-disable-next-line @typescript-eslint/no-explicit-any
12 > type Ctor<T> = new (...args: any[]) => T;
13 >
14 > /**
15 > * Scopes used when bootstrapping the IdP session for an XAA flow.
16 > *
17 > * `openid` is required because the ID-JAG token exchange uses the IdP-issued
18 > * `id_token` as `subject_token` (per draft-ietf-oauth-identity-assertion-authz-grant
19 > * section 3.1, the subject token MUST be of type `urn:ietf:params:oauth:token-type:id_token`).
20 > * `offline_access` is requested so we get a refresh token for the IdP session.
21 > */
22 > export const IDP_SCOPES: readonly string[] = ['openid', 'offline_access'];
23 >
24 > interface IResourceCacheEntry {
25 > readonly resource: string;
26 > readonly scopes: readonly string[];
27 > readonly token: IAuthorizationTokenResponse;
28 > /** Fallback identity (the IdP login account) for sessions built from this token, used when the resource token has no id_token of its own. */
29 > readonly account: vscode.AuthenticationSessionAccountInformation;
30 > readonly created_at: number;
31 > }
32 >
33 > /** Cache key for resource-scoped tokens. Exported for testing. */
34 > export function cacheKey(resource: string, scopes: readonly string[]): string {
35 return resource + '|' + [...scopes].sort().join(' ');
36 }
38 > /**
39 > * Returns true if the cached token is past (or within 60s of) its expiry. Pure
40 > * and exported for testing.
41 > *
42 > * Mints fresh ID-JAG assertions are usually short-lived (minutes). We treat tokens as expired
43 > * 60s before their nominal expiry to avoid clock skew and in-flight redemptions racing past
44 > * `exp`. Tokens without `expires_in` defined are treated as never-expiring (cached
45 > * until the process exits); `expires_in: 0` is treated as immediately expired.
46 > */
47 > export function isExpired(entry: { token: { expires_in?: number }; created_at: number }, now: number = Date.now()): boolean {
48 if (entry.token.expires_in === undefined) {
49 return false;
50 }
51 return now > entry.created_at + (entry.token.expires_in * 1000) - 60_000;
52 }
54 > /**
55 > * (Preview) Mixin that turns a {@link DynamicAuthProvider} subclass into a
56 > * Cross App Access (XAA) / enterprise-managed authentication provider, per
57 > * `draft-ietf-oauth-identity-assertion-authz-grant`.
58 > *
59 > * The IdP login leg is identical to the base class — Auth Code + PKCE against
60 > * the org-configured issuer, using the pre-registered client credentials. On
61 > * top of that:
62 > *
63 > * 1. `createSession` ensures an IdP session exists (delegated to the base
64 > * class with {@link IDP_SCOPES}).
65 > * 2. It POSTs to the IdP token endpoint with `grant_type=token-exchange`,
66 > * `subject_token=<id_token>`, `subject_token_type=id_token`,
67 > * `requested_token_type=id-jag`, `audience=<resource AS>`,
68 > * `resource=<resource indicator>`, `scope=<requested scopes>` to mint an
69 > * ID-JAG.
70 > * 3. It discovers the resource's authorization server metadata (the audience
71 > * URL) and POSTs the ID-JAG to its token endpoint with
72 > * `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer`,
73 > * `assertion=<id-jag>`, `resource=<resource indicator>`,
74 > * `scope=<requested scopes>` to obtain a resource-scoped access token.
75 > * 4. The resource-scoped token is cached in-memory per `(resource, scopes)`
76 > * and returned as the session's access token.
77 > *
78 > * The resource indicator is read from `options.resource` (RFC 8707) and the
79 > * resource's authorization server URL from `options.audience` on
80 > * {@link vscode.AuthenticationProviderSessionOptions}.
81 > */
82 > export function XaaifyAuthProvider<TBase extends Ctor<DynamicAuthProvider>>(Base: TBase): TBase {
83 return class XaaAuthenticationProvider extends Base {
84 private readonly _resourceTokens = new Map<string, IResourceCacheEntry>();
85 /**
86 * Per-(resource, client_id) client secrets. Lazily populated via the main-thread
87 * prompt. Keyed by both the resource indicator and the client_id because two
88 * different resources may legitimately share a client_id but require different
89 * secrets — keying by client_id alone could send the wrong secret to the wrong AS.
90 */
91 private readonly _resourceClientSecrets = new Map<string, string>();
92
93 /** Compound key for {@link _resourceClientSecrets}, matching main-thread secret storage scoping. */
94 private _resourceClientSecretKey(resource: string, clientId: string): string {
95 return `${resource}|${clientId}`;
96 }
97
98 // eslint-disable-next-line @typescript-eslint/no-explicit-any
99 constructor(...args: any[]) {
100 super(...args);
101 // `authorizationServer` is exposed as a readonly field by the base class — use it
102 // directly instead of indexing into `args` so this can't silently break if the
103 // base constructor signature changes.
104 const issuer = this.authorizationServer;
105 this.id = `xaa:${issuer.toString(true)}`;
106 this._logger.trace(`[XAA] Provider constructed for issuer ${issuer.toString(true)}. authorization_endpoint=${this._serverMetadata.authorization_endpoint}, token_endpoint=${this._serverMetadata.token_endpoint}`);
107 }
108
109 override async getSessions(scopes: readonly string[] | undefined, options: vscode.AuthenticationProviderSessionOptions): Promise<vscode.AuthenticationSession[]> {
110 const resource = options.resource;
111 const audience = options.audience;
112 // Account-enumeration call (getAccounts): no resource to mint against, so surface the IdP
113 // session(s) from the base store. Read-only, so it honors the no-prompt getSessions contract.
114 if (!scopes && !resource && !audience) {
115 return super.getSessions(scopes, options);
116 }
117 if (!resource || !scopes || !audience) {
118 return [];
119 }
120 // 1. Fast path: in-memory cache from a prior createSession/getSessions in this window.
121 const key = cacheKey(resource, scopes);
122 const entry = this._resourceTokens.get(key);
123 if (entry && !isExpired(entry)) {
124 return [toSession(entry.token, entry.scopes, entry.account)];
125 }
126 if (entry) {
127 // Expired — drop and try to silently re-mint below.
128 this._resourceTokens.delete(key);
129 }
130
131 // 2. Silent re-mint: the base DynamicAuthProvider persists the IdP session in secret
132 // storage, so on window reload we can pick it up and re-run legs 2-4 (ID-JAG exchange
133 // + resource redemption) without any user interaction. Per the IAuthenticationProvider
134 // contract, getSessions MUST NOT prompt — if anything is missing we just return [].
135 const idpSession = await this._tryGetSilentIdpSession();
136 if (!idpSession?.idToken) {
137 return [];
138 }
139 try {
140 const minted = await this._mintResourceToken(idpSession, [...scopes], audience, resource, options, /* silent */ true);
141 if (!minted) {
142 return [];
143 }
144 return [toSession(minted.token, minted.scopes, minted.account)];
145 } catch (err) {
146 // Silent path: log and fall back to "no session" so the caller decides whether
147 // to escalate to createSession (which is allowed to interact).
148 this._logger.warn(`[XAA] Silent token mint failed for resource=${resource}; falling back to interactive. Error: ${(err as Error).message}`);
149 return [];
150 }
151 }
152
153 override async createSession(scopes: string[], options: vscode.AuthenticationProviderSessionOptions): Promise<vscode.AuthenticationSession> {
154 const audience = options.audience;
155 const resource = options.resource;
156 this._logger.trace(`[XAA] createSession scopes=[${scopes.join(' ')}] audience=${audience} resource=${resource}`);
157 if (!audience) {
158 throw new Error('Enterprise-managed authentication requires `options.audience` (the resource\'s authorization server URL) but none was provided.');
159 }
160 if (!resource) {
161 throw new Error('Enterprise-managed authentication requires `options.resource` (the resource indicator / MCP server URL) but none was provided.');
162 }
163
164 // Ensure IdP session via the base class (may interact). Don't pass the XAA options through —
165 // the IdP login leg is unrelated to the resource/audience, and the base provider would
166 // otherwise look for cached tokens scoped by a foreign audience.
167 const idpSession = await this._ensureIdpSession();
168 if (!idpSession.idToken) {
169 throw new Error('IdP session is missing an id_token; the issuer must support OpenID Connect and the `openid` scope.');
170 }
171
172 const minted = await this._mintResourceToken(idpSession, scopes, audience, resource, options, /* silent */ false);
173 if (!minted) {
174 // `silent=false` only returns undefined if the mint logic itself decided to bail.
175 // Today the only such path is missing resource client_secret, which prompts the user;
176 // if the prompt is dismissed we still try the redemption with `undefined` (valid for
177 // `token_endpoint_auth_method=none`). So in practice this branch is unreachable for
178 // silent=false — guard defensively anyway.
179 throw new Error('Failed to mint a resource access token for the enterprise-managed MCP server.');
180 }
181 return toSession(minted.token, minted.scopes, minted.account);
182 }
183
184 /**
185 * Mints a resource-scoped access token by running legs 2-4 of the XAA flow:
186 * 2. Exchange IdP id_token → ID-JAG (RFC 8693 token exchange at issuer)
187 * 3. Discover the resource AS token endpoint
188 * 4. Redeem the ID-JAG at the resource AS for an access token (RFC 7523 jwt-bearer grant)
189 *
190 * When `silent` is true, this method MUST NOT prompt the user. If the resource AS uses a
191 * distinct client_id (xaa.dev's "{client}-at-{resource}" pattern) and no client_secret can
192 * be resolved without prompting, this returns `undefined`.
193 *
194 * Caches the resulting token in `_resourceTokens` so subsequent getSessions are O(1).
195 */
196 private async _mintResourceToken(
197 idpSession: vscode.AuthenticationSession,
198 scopes: string[],
199 audience: string,
200 resource: string,
201 options: vscode.AuthenticationProviderSessionOptions,
202 silent: boolean,
203 ): Promise<IResourceCacheEntry | undefined> {
204 // Leg 2: id_token → ID-JAG
205 const jag = await this._exchangeForIdJag(idpSession.idToken!, audience, resource, scopes);
206
207 // Leg 3: resource AS token endpoint
208 const resourceTokenEndpoint = await this._discoverResourceTokenEndpoint(audience);
209
210 // Leg 4 prep: resolve the resource client_id.
211 // Per draft-ietf-oauth-identity-assertion-authz-grant section 3.2, the ID-JAG carries a
212 // `client_id` claim identifying the requesting app to the resource AS. This is often
213 // distinct from the IdP `client_id` (xaa.dev for example uses a
214 // `{idp_client_id}-at-{resource}` form), so we extract it from the assertion rather than
215 // reusing `this._clientId`. Caller-supplied `options.clientId` (from the MCP server's
216 // `oauth.clientId` config) takes precedence over the JAG-extracted value.
217 let resourceClientId = this._clientId;
218 let resourceClientIdFromJag = false;
219 const configuredResourceClientId = typeof options.clientId === 'string' && options.clientId.length > 0 ? options.clientId : undefined;
220 if (configuredResourceClientId) {
221 resourceClientId = configuredResourceClientId;
222 resourceClientIdFromJag = resourceClientId !== this._clientId;
223 } else {
224 try {
225 const jagClaims = getClaimsFromJWT(jag);
226 if (typeof jagClaims.client_id === 'string' && jagClaims.client_id.length > 0) {
227 resourceClientId = jagClaims.client_id;
228 resourceClientIdFromJag = resourceClientId !== this._clientId;
229 }
230 } catch (err) {
231 this._logger.warn(`[XAA] Could not decode ID-JAG to read resource client_id; falling back to IdP client_id. Error: ${(err as Error).message}`);
232 }
233 }
234
235 // Leg 4 prep: resolve the resource client_secret.
236 // If the resource AS uses a distinct client_id, it will reject `this._clientSecret`
237 // (the IdP secret) with `invalid_client`. The caller may supply the resource secret
238 // directly via `options.clientSecret` (resolved in `mainThreadMcp` from URL-scoped
239 // secret storage via the "Set Client Secret" code lens above `oauth.clientId` in
240 // mcp.json); otherwise we fall back to a cached per-resource secret or prompt the
241 // user. We pass `undefined` if the user leaves the prompt blank — that's valid for
242 // clients registered with `token_endpoint_auth_method=none`.
243 let resourceClientSecret: string | undefined = this._clientSecret;
244 const configuredResourceClientSecret = typeof options.clientSecret === 'string' && options.clientSecret.length > 0 ? options.clientSecret : undefined;
245 const secretCacheKey = this._resourceClientSecretKey(resource, resourceClientId);
246 if (configuredResourceClientSecret) {
247 resourceClientSecret = configuredResourceClientSecret;
248 this._resourceClientSecrets.set(secretCacheKey, configuredResourceClientSecret);
249 } else if (resourceClientIdFromJag) {
250 if (this._resourceClientSecrets.has(secretCacheKey)) {
251 resourceClientSecret = this._resourceClientSecrets.get(secretCacheKey);
252 } else if (silent) {
253 // Silent path: the only way to obtain the resource client_secret here is to
254 // prompt the user — which we can't do. Bail; the caller will escalate to
255 // createSession (allowed to interact) if it needs the token.
256 this._logger.info(`[XAA] Silent mint requires resource client_secret for '${resourceClientId}' but none is cached or configured; deferring to interactive flow.`);
257 return undefined;
258 } else {
259 this._logger.info(`[XAA] Resource AS requires a distinct client_id '${resourceClientId}' — prompting for matching client_secret.`);
260 const promptedSecret = await this._proxy.$promptForResourceClientSecret(resourceClientId, resource);
261 if (promptedSecret === undefined) {
262 // User cancelled — don't cache, so re-prompt is possible on next call.
263 return undefined;
264 }
265 // Blank-on-confirm is a valid answer (public client / token_endpoint_auth_method=none).
266 // The main thread returns '' for that case, undefined for cancel.
267 this._resourceClientSecrets.set(secretCacheKey, promptedSecret);
268 resourceClientSecret = promptedSecret.length > 0 ? promptedSecret : undefined;
269 }
270 }
271
272 // Leg 4: redemption.
273 const resourceToken = await this._redeemAtResource(resourceTokenEndpoint, jag, resource, scopes, resourceClientId, resourceClientSecret);
274
275 const entry: IResourceCacheEntry = {
276 resource,
277 scopes,
278 token: resourceToken,
279 // Fallback identity, used when the resource token carries no id_token of its own (the usual case).
280 account: idpSession.account,
281 created_at: Date.now(),
282 };
283 this._resourceTokens.set(cacheKey(resource, scopes), entry);
284 return entry;
285 }
286
287 /**
288 * Returns the IdP session if one is available without any user interaction, otherwise
289 * `undefined`. Critically does NOT call `super.createSession`, so this is safe to use
290 * from {@link getSessions}.
291 */
292 private async _tryGetSilentIdpSession(): Promise<vscode.AuthenticationSession | undefined> {
293 const cleanOptions: vscode.AuthenticationProviderSessionOptions = {};
294 const existing = await super.getSessions(IDP_SCOPES as string[], cleanOptions);
295 return existing.length ? existing[0] : undefined;
296 }
297
298 private async _ensureIdpSession(): Promise<vscode.AuthenticationSession> {
299 this._logger.trace(`[XAA] _ensureIdpSession: scopes=[${IDP_SCOPES.join(' ')}] authorization_endpoint=${this._serverMetadata.authorization_endpoint}`);
300 const silent = await this._tryGetSilentIdpSession();
301 if (silent?.idToken) {
302 this._logger.trace(`[XAA] _ensureIdpSession: reusing existing IdP session`);
303 return silent;
304 }
305 this._logger.trace(`[XAA] _ensureIdpSession: creating new IdP session via super.createSession`);
306 return super.createSession([...IDP_SCOPES], {});
307 }
308
309 private async _exchangeForIdJag(idToken: string, audience: string, resource: string, scopes: string[]): Promise<string> {
310 const tokenEndpoint = this._serverMetadata.token_endpoint;
311 if (!tokenEndpoint) {
312 throw new Error('Issuer metadata is missing token_endpoint; cannot perform XAA token exchange.');
313 }
314 const body = buildIdJagExchangeBody(this._clientId, this._clientSecret, idToken, audience, resource, scopes);
315 this._logger.trace(`[XAA] POST ${tokenEndpoint} (ID-JAG exchange) audience=${audience} resource=${resource} scope=${scopes.join(' ')}`);
316 const response = await fetch(tokenEndpoint, {
317 method: 'POST',
318 headers: {
319 'Content-Type': 'application/x-www-form-urlencoded',
320 'Accept': 'application/json',
321 },
322 body: body.toString(),
323 });
324 if (!response.ok) {
325 throw new Error(`XAA token exchange (IdP) failed: ${response.status} ${await safeText(response)}`);
326 }
327 const data: unknown = await response.json();
328 const issued = (data && typeof data === 'object' && typeof (data as { access_token?: unknown }).access_token === 'string')
329 ? (data as { access_token: string }).access_token
330 : undefined;
331 if (!issued) {
332 throw new Error(`XAA token exchange (IdP) returned no access_token. Response: ${JSON.stringify(data)}`);
333 }
334 return issued;
335 }
336
337 private async _discoverResourceTokenEndpoint(audience: string): Promise<string> {
338 const { metadata, errors } = await fetchAuthorizationServerMetadata(audience);
339 if (!metadata?.token_endpoint) {
340 throw new Error(`Failed to discover resource authorization server metadata for '${audience}': ${errors.map(e => e.message).join('; ') || 'no token_endpoint in metadata'}`);
341 }
342 return metadata.token_endpoint;
343 }
344
345 private async _redeemAtResource(tokenEndpoint: string, idJag: string, resource: string, scopes: string[], resourceClientId: string, resourceClientSecret: string | undefined): Promise<IAuthorizationTokenResponse> {
346 const body = buildResourceRedemptionBody(resourceClientId, resourceClientSecret, idJag, resource, scopes);
347 this._logger.trace(`[XAA] POST ${tokenEndpoint} (ID-JAG redemption) client_id=${resourceClientId} resource=${resource} scope=${scopes.join(' ')}`);
348 const response = await fetch(tokenEndpoint, {
349 method: 'POST',
350 headers: {
351 'Content-Type': 'application/x-www-form-urlencoded',
352 'Accept': 'application/json',
353 },
354 body: body.toString(),
355 });
356 if (!response.ok) {
357 throw new Error(`XAA token exchange (resource) failed: ${response.status} ${await safeText(response)}`);
358 }
359 const data = await response.json();
360 if (!isAuthorizationTokenResponse(data)) {
361 throw new Error(`XAA token exchange (resource) returned an invalid token response: ${JSON.stringify(data)}`);
362 }
363 return data;
364 }
365 };
366 }
368 > /**
369 > * Builds a session from a token response. Identity precedence: the token's own `id_token`, then
370 > * `fallbackAccount` (the IdP login identity), then a generic default. Never the `access_token`, which
371 > * for XAA is an opaque resource credential. Exported for testing.
372 > */
373 > export function toSession(token: IAuthorizationTokenResponse, scopes: readonly string[], fallbackAccount?: vscode.AuthenticationSessionAccountInformation): vscode.AuthenticationSession {
374 let account: vscode.AuthenticationSessionAccountInformation | undefined;
375 if (token.id_token) {
376 try {
377 const claims: IAuthorizationJWTClaims = getClaimsFromJWT(token.id_token);
378 account = {
379 id: claims.sub || 'unknown',
380 label: claims.preferred_username || claims.name || claims.email || 'XAA',
381 };
382 } catch {
383 // ignore — the id_token wasn't a decodable JWT
384 }
385 }
386 account ??= fallbackAccount ?? { id: 'unknown', label: 'XAA' };
387 return {
388 id: stringHash(token.access_token, 0).toString(),
389 accessToken: token.access_token,
390 account,
391 scopes: [...scopes],
392 idToken: token.id_token,
393 };
394 }
396 async function safeText(response: Response): Promise<string> {
397 try {
398 return await response.text();
399 } catch {
400 return response.statusText;
401 }
402 }