feat(web,core): enterprise-managed authorization (EMA) (#1509) (#1512)

Adds enterprise-managed authorization (EMA): browser-side IdP login + RFC 8693 token-exchange and JWT-bearer resource-token mint, per-server EMA opt-in in Server Settings, IdP config in Client Settings, and IdP clientSecret keychain storage. Includes the v2/main merge and review follow-ups (400 on malformed client config; normalizeIdpIssuer import consolidation).
This commit is contained in:
Bob Dickinson 2026-06-24 19:15:12 -07:00 • committed by GitHub
commit 5de4e911e3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -12,6 +12,28 @@ export interface PresetRef {
params?: Record<string, unknown>;
}
export interface ConfigFileOAuth {
enabled: boolean;
mode?: "combined" | "protected-resource";
authorizationServers?: string[];
resource?: string;
issuerUrl?: string;
accessTokenIssuers?: string[];
jwksUri?: string;
resourceAudience?: string;
scopesSupported?: string[];
requireAuth?: boolean;
staticClients?: Array<{
clientId: string;
clientSecret?: string;
redirectUris?: string[];
}>;
supportDCR?: boolean;
supportCIMD?: boolean;
tokenExpirationSeconds?: number;
supportRefreshTokens?: boolean;
}
export interface ConfigFile {
serverInfo: {
name: string;
@ -38,6 +60,7 @@ export interface ConfigFile {
resourceTemplates?: number;
prompts?: number;
};
oauth?: ConfigFileOAuth;
transport: {
type: "stdio" | "streamable-http" | "sse";
port?: number;
@ -102,6 +125,48 @@ function validateConfig(
`Invalid config in ${filePath}: transport.type must be stdio, streamable-http, or sse`,
);
}
if (o.oauth && typeof o.oauth === "object") {
const oauth = o.oauth as Record<string, unknown>;
if (oauth.enabled !== true) {
throw new Error(
`Invalid config in ${filePath}: oauth.enabled must be true when oauth is present`,
);
}
const mode = oauth.mode;
if (
mode !== undefined &&
mode !== "combined" &&
mode !== "protected-resource"
) {
throw new Error(
`Invalid config in ${filePath}: oauth.mode must be combined or protected-resource`,
);
}
if (mode === "protected-resource") {
const servers = oauth.authorizationServers;
if (!Array.isArray(servers) || servers.length === 0) {
throw new Error(
`Invalid config in ${filePath}: oauth.authorizationServers is required when oauth.mode is protected-resource`,
);
}
for (const url of servers) {
if (typeof url !== "string" || url.trim() === "") {
throw new Error(
`Invalid config in ${filePath}: oauth.authorizationServers must be non-empty URL strings`,
);
}
}
}
if (
(transportType === "stdio") &&
oauth.enabled === true
) {
throw new Error(
`Invalid config in ${filePath}: oauth requires streamable-http or sse transport`,
);
}
}
}
/**