Merge remote-tracking branch 'origin/v2/main' into 1579-wave-1

# Conflicts:
#	clients/cli/package-lock.json
#	clients/cli/package.json
#	clients/web/src/App.tsx
#	clients/web/src/lib/environmentFactory.test.ts
#	clients/web/src/lib/environmentFactory.ts
#	clients/web/src/lib/remoteOAuthStorage.test.ts
#	clients/web/src/lib/remoteOAuthStorage.ts
#	clients/web/src/test/core/auth/connection-state.test.ts
#	clients/web/src/test/core/auth/providers.test.ts
#	clients/web/src/test/core/auth/storage-browser.test.ts
#	clients/web/src/test/core/auth/storage-remote.test.ts
#	clients/web/src/test/core/mcp/oauthManager.test.ts
#	clients/web/src/test/integration/auth/node/storage.test.ts
#	clients/web/src/test/integration/storage/adapters.test.ts
#	clients/web/src/utils/clearServerOAuthState.test.ts
#	clients/web/src/utils/clearServerOAuthState.ts
#	core/auth/connection-state.ts
#	core/auth/oauth-storage.ts
#	core/auth/providers.ts
#	core/auth/storage.ts
#	core/auth/store.ts
#	core/storage/adapters/remote-storage.ts
This commit is contained in:
cliffhall 2026-07-09 09:41:08 -04:00
commit 32a3610737
2 changed files with 5046 additions and 3271 deletions

View file

@ -3064,8 +3064,8 @@ export class InspectorClient extends InspectorClientEventTarget {
/**
* Clears OAuth tokens and client information
*/
clearOAuthTokens(): void {
this.oauthManager?.clearOAuthTokens();
async clearOAuthTokens(): Promise<void> {
await this.oauthManager?.clearOAuthTokens();
}
/**

View file

@ -139,7 +139,8 @@ export class OAuthManager {
provider.setEventTarget(this.params.getEventTarget());
const storedScope = this.oauthConfig.storage.getScope(serverUrl);
await provider.prepareForAuth();
const storedScope = provider.scope;
if (storedScope === undefined && this.oauthConfig.scope) {
await provider.saveScope(this.oauthConfig.scope);
}
@ -162,7 +163,7 @@ export class OAuthManager {
return this.oauthConfig.enterpriseManaged === true;
}
private getEmaFlowConfig(): EmaFlowConfig {
private async getEmaFlowConfig(): Promise<EmaFlowConfig> {
if (!this.oauthConfig.storage || !this.oauthConfig.redirectUrlProvider) {
throw new Error(
"OAuth environment components (storage, redirectUrlProvider) are required.",
@ -177,16 +178,17 @@ export class OAuthManager {
: "not_configured",
);
}
const storedScope = await this.oauthConfig.storage.getScope(
this.getServerUrl(),
);
return {
serverUrl: this.getServerUrl(),
idp,
resourceClientId: this.oauthConfig.clientId,
resourceClientSecret: this.oauthConfig.clientSecret,
scope:
computeScopeUnion(
this.oauthConfig.scope,
this.oauthConfig.storage.getScope(this.getServerUrl()),
) || this.oauthConfig.scope,
computeScopeUnion(this.oauthConfig.scope, storedScope) ||
this.oauthConfig.scope,
redirectUrl: this.oauthConfig.redirectUrlProvider.getRedirectUrl(),
storage: this.oauthConfig.storage,
fetchFn: this.params.effectiveAuthFetch,
@ -196,14 +198,14 @@ export class OAuthManager {
/** Attempt silent EMA (cached IdP session + legs 2–3). */
async trySilentEnterpriseManagedAuth(): Promise<boolean> {
if (!this.isEnterpriseManaged()) return false;
const result = await trySilentEmaAuth(this.getEmaFlowConfig());
const result = await trySilentEmaAuth(await this.getEmaFlowConfig());
if (result.status === "success") return true;
if (result.status === "mint_failed") throw result.error;
return false;
}
private async authenticateEnterpriseManaged(): Promise<URL | undefined> {
const config = this.getEmaFlowConfig();
const config = await this.getEmaFlowConfig();
const silent = await trySilentEmaAuth(config);
if (silent.status === "success") {
return undefined;
@ -283,11 +285,12 @@ export class OAuthManager {
): Promise<void> {
try {
if (this.isEnterpriseManaged()) {
const emaConfig = await this.getEmaFlowConfig();
const scopeForMint =
this.pendingAuthorizationScope ?? this.getEmaFlowConfig().scope;
this.pendingAuthorizationScope ?? emaConfig.scope;
const config = scopeForMint
? { ...this.getEmaFlowConfig(), scope: scopeForMint }
: this.getEmaFlowConfig();
? { ...emaConfig, scope: scopeForMint }
: emaConfig;
const tokens = await completeEmaIdpAuthorizationAndMint(
config,
authorizationCode,
@ -387,13 +390,13 @@ export class OAuthManager {
}
}
clearOAuthTokens(): void {
async clearOAuthTokens(): Promise<void> {
if (!this.oauthConfig?.storage) {
return;
}
const serverUrl = this.getServerUrl();
this.oauthConfig.storage.clear(serverUrl);
await this.oauthConfig.storage.clear(serverUrl);
this.oauthFlowState = null;
this.pendingAuthorizationScope = undefined;
@ -446,7 +449,7 @@ export class OAuthManager {
*/
async refreshEnterpriseManagedTokens(): Promise<boolean> {
if (!this.isEnterpriseManaged()) return false;
const tokens = await refreshEmaResourceTokens(this.getEmaFlowConfig());
const tokens = await refreshEmaResourceTokens(await this.getEmaFlowConfig());
return tokens !== undefined;
}
@ -492,7 +495,7 @@ export class OAuthManager {
}
const effectiveScope = resolveEffectiveGrantedScope(
storage.getScope(serverUrl),
await storage.getScope(serverUrl),
tokens.scope,
);
return !isStrictScopeSuperset(scopeForAuth, effectiveScope);
@ -568,7 +571,7 @@ export class OAuthManager {
}
const previousScope = computeScopeUnion(
storage?.getScope(serverUrl),
storage ? await storage.getScope(serverUrl) : undefined,
grantedTokenScope,
);
const requiredFromChallenge =
@ -605,8 +608,10 @@ export class OAuthManager {
return this.oauthConfig.scope?.trim() || undefined;
}
private emaFlowConfigForChallenge(challenge: AuthChallenge): EmaFlowConfig {
const base = this.getEmaFlowConfig();
private async emaFlowConfigForChallenge(
challenge: AuthChallenge,
): Promise<EmaFlowConfig> {
const base = await this.getEmaFlowConfig();
const enriched = challenge.authorizationScopes;
if (enriched && enriched.length > 0) {
return { ...base, scope: enriched.join(" ") };
@ -626,7 +631,7 @@ export class OAuthManager {
return { kind: "step_up_confirm", challenge: enriched };
}
const config = this.emaFlowConfigForChallenge(enriched);
const config = await this.emaFlowConfigForChallenge(enriched);
if (enriched.reason === "insufficient_scope") {
const silent = await trySilentEmaAuth(config);
@ -861,7 +866,10 @@ export class OAuthManager {
> {
const provider = await this.createOAuthProvider();
if (this.isEnterpriseManaged()) {
return new EmaTransportOAuthProvider(provider, this.getEmaFlowConfig());
return new EmaTransportOAuthProvider(
provider,
await this.getEmaFlowConfig(),
);
}
return provider;
}