@trops/dash-core 0.1.610 → 0.1.612

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -19,20 +19,20 @@ var require$$6 = require('csv-parser');
19
19
  var require$$0$6 = require('quickjs-emscripten');
20
20
  var require$$11 = require('https');
21
21
  var require$$0$8 = require('@modelcontextprotocol/sdk/client/index.js');
22
- var require$$1$5 = require('@modelcontextprotocol/sdk/client/stdio.js');
22
+ var require$$1$6 = require('@modelcontextprotocol/sdk/client/stdio.js');
23
23
  var require$$0$7 = require('pkce-challenge');
24
24
  var require$$12 = require('child_process');
25
+ var require$$1$5 = require('http');
25
26
  var require$$2$2 = require('algoliasearch');
26
27
  var require$$3$2 = require('node:path');
27
28
  var require$$0$9 = require('openai');
28
- var require$$0$c = require('@anthropic-ai/sdk');
29
+ var require$$0$a = require('@anthropic-ai/sdk');
29
30
  var require$$2$3 = require('zod-to-json-schema');
30
31
  var require$$8$1 = require('zod');
31
- var require$$0$a = require('http');
32
- var require$$1$6 = require('http2');
32
+ var require$$1$7 = require('http2');
33
33
  var require$$2$4 = require('node-forge');
34
34
  var require$$0$b = require('css');
35
- var require$$1$7 = require('node-vibrant/node');
35
+ var require$$1$8 = require('node-vibrant/node');
36
36
  var require$$3$3 = require('ws');
37
37
 
38
38
  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
@@ -411,6 +411,10 @@ const MCP_RUN_AUTH$1 = "mcp-run-auth";
411
411
  const MCP_RUN_AUTH_COMPLETE = "mcp-run-auth-complete";
412
412
  const MCP_RUN_AUTH_ERROR = "mcp-run-auth-error";
413
413
 
414
+ const MCP_AUTHORIZE$1 = "mcp-authorize";
415
+ const MCP_AUTHORIZE_COMPLETE = "mcp-authorize-complete";
416
+ const MCP_AUTHORIZE_ERROR = "mcp-authorize-error";
417
+
414
418
  var mcpEvents$1 = {
415
419
  MCP_START_SERVER: MCP_START_SERVER$1,
416
420
  MCP_START_SERVER_COMPLETE,
@@ -442,6 +446,9 @@ var mcpEvents$1 = {
442
446
  MCP_RUN_AUTH: MCP_RUN_AUTH$1,
443
447
  MCP_RUN_AUTH_COMPLETE,
444
448
  MCP_RUN_AUTH_ERROR,
449
+ MCP_AUTHORIZE: MCP_AUTHORIZE$1,
450
+ MCP_AUTHORIZE_COMPLETE,
451
+ MCP_AUTHORIZE_ERROR,
445
452
  };
446
453
 
447
454
  /**
@@ -582,6 +589,7 @@ const LLM_CHECK_CLI_AVAILABLE$1 = "llm-check-cli-available";
582
589
  const LLM_CLEAR_CLI_SESSION$1 = "llm-clear-cli-session";
583
590
  const LLM_CLI_SESSION_STATUS$1 = "llm-cli-session-status";
584
591
  const LLM_CLI_END_SESSION$1 = "llm-cli-end-session";
592
+ const LLM_LIST_MODELS$1 = "llm-list-models";
585
593
 
586
594
  // --- Main → Renderer (send) ---
587
595
  const LLM_STREAM_DELTA$3 = "llm-stream-delta";
@@ -603,6 +611,7 @@ var llmEvents$1 = {
603
611
  LLM_STREAM_ERROR: LLM_STREAM_ERROR$3,
604
612
  LLM_CLI_SESSION_STATUS: LLM_CLI_SESSION_STATUS$1,
605
613
  LLM_CLI_END_SESSION: LLM_CLI_END_SESSION$1,
614
+ LLM_LIST_MODELS: LLM_LIST_MODELS$1,
606
615
  };
607
616
 
608
617
  /**
@@ -18748,6 +18757,14 @@ function requireProviderController () {
18748
18757
  providerEntry.isDefaultForType = true;
18749
18758
  }
18750
18759
 
18760
+ // Preserve any OAuth state (tokens / DCR client info / PKCE verifier)
18761
+ // written out-of-band by saveOAuthState. A fresh providerEntry would
18762
+ // otherwise drop it, wiping tokens whenever the user edits/re-saves an
18763
+ // OAuth MCP provider.
18764
+ if (prev.oauth) {
18765
+ providerEntry.oauth = prev.oauth;
18766
+ }
18767
+
18751
18768
  // Single-winner invariant: when flipping this provider's flag to
18752
18769
  // true, clear it on every other provider with the same type. Run
18753
18770
  // this BEFORE we set `providers[providerName] = providerEntry` so
@@ -19025,6 +19042,86 @@ function requireProviderController () {
19025
19042
  };
19026
19043
  }
19027
19044
  },
19045
+
19046
+ /**
19047
+ * saveOAuthState
19048
+ * Persist an MCP OAuth session blob (tokens, DCR client info, PKCE
19049
+ * verifier) encrypted alongside the provider entry. Used by the SDK
19050
+ * OAuthClientProvider (mcpOAuthProvider.js). Creates a minimal MCP
19051
+ * provider entry if one doesn't exist yet, so "Authorize" can run
19052
+ * before the provider is formally saved (the later saveProvider
19053
+ * preserves this blob).
19054
+ *
19055
+ * @param {BrowserWindow} win the main window (unused; signature parity)
19056
+ * @param {string} appId the application id
19057
+ * @param {string} providerName the provider/server name
19058
+ * @param {object} oauthState { tokens?, clientInformation?, codeVerifier? }
19059
+ */
19060
+ saveOAuthState: (win, appId, providerName, oauthState) => {
19061
+ try {
19062
+ const filename = path.join(
19063
+ app.getPath("userData"),
19064
+ appName,
19065
+ appId,
19066
+ configFilename,
19067
+ );
19068
+ ensureDirectoryExistence(filename);
19069
+
19070
+ const providers = getFileContents(filename, {});
19071
+ const encrypted = safeStorage
19072
+ .encryptString(JSON.stringify(oauthState || {}))
19073
+ .toString("base64");
19074
+
19075
+ const prev = providers[providerName] || {};
19076
+ providers[providerName] = {
19077
+ ...prev,
19078
+ providerClass: prev.providerClass || "mcp",
19079
+ oauth: encrypted,
19080
+ dateCreated: prev.dateCreated || new Date().toISOString(),
19081
+ dateUpdated: new Date().toISOString(),
19082
+ };
19083
+
19084
+ writeFileSync(filename, JSON.stringify(providers, null, 2), {
19085
+ mode: 0o600,
19086
+ });
19087
+
19088
+ return { success: true };
19089
+ } catch (error) {
19090
+ console.error("[providerController] Error saving OAuth state:", error);
19091
+ return { error: true, message: error.message };
19092
+ }
19093
+ },
19094
+
19095
+ /**
19096
+ * getOAuthState
19097
+ * Read and decrypt the OAuth session blob for a provider, or `{}` if
19098
+ * none exists. Main-process only — never surfaced to the renderer.
19099
+ *
19100
+ * @param {BrowserWindow} win the main window (unused; signature parity)
19101
+ * @param {string} appId the application id
19102
+ * @param {string} providerName the provider/server name
19103
+ * @returns {{ oauth?: object }}
19104
+ */
19105
+ getOAuthState: (win, appId, providerName) => {
19106
+ try {
19107
+ const filename = path.join(
19108
+ app.getPath("userData"),
19109
+ appName,
19110
+ appId,
19111
+ configFilename,
19112
+ );
19113
+ const providers = getFileContents(filename, {});
19114
+ const entry = providers[providerName];
19115
+ if (!entry || !entry.oauth) return {};
19116
+
19117
+ const buffer = Buffer.from(entry.oauth, "base64");
19118
+ const decrypted = safeStorage.decryptString(buffer);
19119
+ return { oauth: JSON.parse(decrypted) };
19120
+ } catch (error) {
19121
+ console.error("[providerController] Error reading OAuth state:", error);
19122
+ return {};
19123
+ }
19124
+ },
19028
19125
  };
19029
19126
 
19030
19127
  providerController_1 = providerController;
@@ -30734,11 +30831,11 @@ var __importStar$4 = (commonjsGlobal && commonjsGlobal.__importStar) || function
30734
30831
  Object.defineProperty(parse$2, "__esModule", { value: true });
30735
30832
  parse$2.safeParseAsync = parse$2.safeParse = parse$2.parseAsync = parse$2.parse = void 0;
30736
30833
  const core$5 = __importStar$4(core$8);
30737
- const errors_js_1$2 = errors$3;
30738
- parse$2.parse = core$5._parse(errors_js_1$2.ZodRealError);
30739
- parse$2.parseAsync = core$5._parseAsync(errors_js_1$2.ZodRealError);
30740
- parse$2.safeParse = core$5._safeParse(errors_js_1$2.ZodRealError);
30741
- parse$2.safeParseAsync = core$5._safeParseAsync(errors_js_1$2.ZodRealError);
30834
+ const errors_js_1$1 = errors$3;
30835
+ parse$2.parse = core$5._parse(errors_js_1$1.ZodRealError);
30836
+ parse$2.parseAsync = core$5._parseAsync(errors_js_1$1.ZodRealError);
30837
+ parse$2.safeParse = core$5._safeParse(errors_js_1$1.ZodRealError);
30838
+ parse$2.safeParseAsync = core$5._safeParseAsync(errors_js_1$1.ZodRealError);
30742
30839
 
30743
30840
  var hasRequiredSchemas;
30744
30841
 
@@ -34204,1426 +34301,1457 @@ function date$1(params) {
34204
34301
 
34205
34302
  } (types$2));
34206
34303
 
34207
- var auth$2 = {};
34208
-
34209
34304
  var auth$1 = {};
34210
34305
 
34211
- (function (exports) {
34212
- var __createBinding = (commonjsGlobal && commonjsGlobal.__createBinding) || (Object.create ? (function(o, m, k, k2) {
34213
- if (k2 === undefined) k2 = k;
34214
- var desc = Object.getOwnPropertyDescriptor(m, k);
34215
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
34216
- desc = { enumerable: true, get: function() { return m[k]; } };
34306
+ var auth = {};
34307
+
34308
+ var hasRequiredAuth$1;
34309
+
34310
+ function requireAuth$1 () {
34311
+ if (hasRequiredAuth$1) return auth;
34312
+ hasRequiredAuth$1 = 1;
34313
+ (function (exports) {
34314
+ var __createBinding = (commonjsGlobal && commonjsGlobal.__createBinding) || (Object.create ? (function(o, m, k, k2) {
34315
+ if (k2 === undefined) k2 = k;
34316
+ var desc = Object.getOwnPropertyDescriptor(m, k);
34317
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
34318
+ desc = { enumerable: true, get: function() { return m[k]; } };
34319
+ }
34320
+ Object.defineProperty(o, k2, desc);
34321
+ }) : (function(o, m, k, k2) {
34322
+ if (k2 === undefined) k2 = k;
34323
+ o[k2] = m[k];
34324
+ }));
34325
+ var __setModuleDefault = (commonjsGlobal && commonjsGlobal.__setModuleDefault) || (Object.create ? (function(o, v) {
34326
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
34327
+ }) : function(o, v) {
34328
+ o["default"] = v;
34329
+ });
34330
+ var __importStar = (commonjsGlobal && commonjsGlobal.__importStar) || function (mod) {
34331
+ if (mod && mod.__esModule) return mod;
34332
+ var result = {};
34333
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
34334
+ __setModuleDefault(result, mod);
34335
+ return result;
34336
+ };
34337
+ Object.defineProperty(exports, "__esModule", { value: true });
34338
+ exports.OAuthTokenRevocationRequestSchema = exports.OAuthClientRegistrationErrorSchema = exports.OAuthClientInformationFullSchema = exports.OAuthClientInformationSchema = exports.OAuthClientMetadataSchema = exports.OptionalSafeUrlSchema = exports.OAuthErrorResponseSchema = exports.OAuthTokensSchema = exports.OpenIdProviderDiscoveryMetadataSchema = exports.OpenIdProviderMetadataSchema = exports.OAuthMetadataSchema = exports.OAuthProtectedResourceMetadataSchema = exports.SafeUrlSchema = void 0;
34339
+ const z = __importStar(v4);
34340
+ /**
34341
+ * Reusable URL validation that disallows javascript: scheme
34342
+ */
34343
+ exports.SafeUrlSchema = z
34344
+ .url()
34345
+ .superRefine((val, ctx) => {
34346
+ if (!URL.canParse(val)) {
34347
+ ctx.addIssue({
34348
+ code: z.ZodIssueCode.custom,
34349
+ message: 'URL must be parseable',
34350
+ fatal: true
34351
+ });
34352
+ return z.NEVER;
34353
+ }
34354
+ })
34355
+ .refine(url => {
34356
+ const u = new URL(url);
34357
+ return u.protocol !== 'javascript:' && u.protocol !== 'data:' && u.protocol !== 'vbscript:';
34358
+ }, { message: 'URL cannot use javascript:, data:, or vbscript: scheme' });
34359
+ /**
34360
+ * RFC 9728 OAuth Protected Resource Metadata
34361
+ */
34362
+ exports.OAuthProtectedResourceMetadataSchema = z.looseObject({
34363
+ resource: z.string().url(),
34364
+ authorization_servers: z.array(exports.SafeUrlSchema).optional(),
34365
+ jwks_uri: z.string().url().optional(),
34366
+ scopes_supported: z.array(z.string()).optional(),
34367
+ bearer_methods_supported: z.array(z.string()).optional(),
34368
+ resource_signing_alg_values_supported: z.array(z.string()).optional(),
34369
+ resource_name: z.string().optional(),
34370
+ resource_documentation: z.string().optional(),
34371
+ resource_policy_uri: z.string().url().optional(),
34372
+ resource_tos_uri: z.string().url().optional(),
34373
+ tls_client_certificate_bound_access_tokens: z.boolean().optional(),
34374
+ authorization_details_types_supported: z.array(z.string()).optional(),
34375
+ dpop_signing_alg_values_supported: z.array(z.string()).optional(),
34376
+ dpop_bound_access_tokens_required: z.boolean().optional()
34377
+ });
34378
+ /**
34379
+ * RFC 8414 OAuth 2.0 Authorization Server Metadata
34380
+ */
34381
+ exports.OAuthMetadataSchema = z.looseObject({
34382
+ issuer: z.string(),
34383
+ authorization_endpoint: exports.SafeUrlSchema,
34384
+ token_endpoint: exports.SafeUrlSchema,
34385
+ registration_endpoint: exports.SafeUrlSchema.optional(),
34386
+ scopes_supported: z.array(z.string()).optional(),
34387
+ response_types_supported: z.array(z.string()),
34388
+ response_modes_supported: z.array(z.string()).optional(),
34389
+ grant_types_supported: z.array(z.string()).optional(),
34390
+ token_endpoint_auth_methods_supported: z.array(z.string()).optional(),
34391
+ token_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(),
34392
+ service_documentation: exports.SafeUrlSchema.optional(),
34393
+ revocation_endpoint: exports.SafeUrlSchema.optional(),
34394
+ revocation_endpoint_auth_methods_supported: z.array(z.string()).optional(),
34395
+ revocation_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(),
34396
+ introspection_endpoint: z.string().optional(),
34397
+ introspection_endpoint_auth_methods_supported: z.array(z.string()).optional(),
34398
+ introspection_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(),
34399
+ code_challenge_methods_supported: z.array(z.string()).optional(),
34400
+ client_id_metadata_document_supported: z.boolean().optional()
34401
+ });
34402
+ /**
34403
+ * OpenID Connect Discovery 1.0 Provider Metadata
34404
+ * see: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
34405
+ */
34406
+ exports.OpenIdProviderMetadataSchema = z.looseObject({
34407
+ issuer: z.string(),
34408
+ authorization_endpoint: exports.SafeUrlSchema,
34409
+ token_endpoint: exports.SafeUrlSchema,
34410
+ userinfo_endpoint: exports.SafeUrlSchema.optional(),
34411
+ jwks_uri: exports.SafeUrlSchema,
34412
+ registration_endpoint: exports.SafeUrlSchema.optional(),
34413
+ scopes_supported: z.array(z.string()).optional(),
34414
+ response_types_supported: z.array(z.string()),
34415
+ response_modes_supported: z.array(z.string()).optional(),
34416
+ grant_types_supported: z.array(z.string()).optional(),
34417
+ acr_values_supported: z.array(z.string()).optional(),
34418
+ subject_types_supported: z.array(z.string()),
34419
+ id_token_signing_alg_values_supported: z.array(z.string()),
34420
+ id_token_encryption_alg_values_supported: z.array(z.string()).optional(),
34421
+ id_token_encryption_enc_values_supported: z.array(z.string()).optional(),
34422
+ userinfo_signing_alg_values_supported: z.array(z.string()).optional(),
34423
+ userinfo_encryption_alg_values_supported: z.array(z.string()).optional(),
34424
+ userinfo_encryption_enc_values_supported: z.array(z.string()).optional(),
34425
+ request_object_signing_alg_values_supported: z.array(z.string()).optional(),
34426
+ request_object_encryption_alg_values_supported: z.array(z.string()).optional(),
34427
+ request_object_encryption_enc_values_supported: z.array(z.string()).optional(),
34428
+ token_endpoint_auth_methods_supported: z.array(z.string()).optional(),
34429
+ token_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(),
34430
+ display_values_supported: z.array(z.string()).optional(),
34431
+ claim_types_supported: z.array(z.string()).optional(),
34432
+ claims_supported: z.array(z.string()).optional(),
34433
+ service_documentation: z.string().optional(),
34434
+ claims_locales_supported: z.array(z.string()).optional(),
34435
+ ui_locales_supported: z.array(z.string()).optional(),
34436
+ claims_parameter_supported: z.boolean().optional(),
34437
+ request_parameter_supported: z.boolean().optional(),
34438
+ request_uri_parameter_supported: z.boolean().optional(),
34439
+ require_request_uri_registration: z.boolean().optional(),
34440
+ op_policy_uri: exports.SafeUrlSchema.optional(),
34441
+ op_tos_uri: exports.SafeUrlSchema.optional(),
34442
+ client_id_metadata_document_supported: z.boolean().optional()
34443
+ });
34444
+ /**
34445
+ * OpenID Connect Discovery metadata that may include OAuth 2.0 fields
34446
+ * This schema represents the real-world scenario where OIDC providers
34447
+ * return a mix of OpenID Connect and OAuth 2.0 metadata fields
34448
+ */
34449
+ exports.OpenIdProviderDiscoveryMetadataSchema = z.object({
34450
+ ...exports.OpenIdProviderMetadataSchema.shape,
34451
+ ...exports.OAuthMetadataSchema.pick({
34452
+ code_challenge_methods_supported: true
34453
+ }).shape
34454
+ });
34455
+ /**
34456
+ * OAuth 2.1 token response
34457
+ */
34458
+ exports.OAuthTokensSchema = z
34459
+ .object({
34460
+ access_token: z.string(),
34461
+ id_token: z.string().optional(), // Optional for OAuth 2.1, but necessary in OpenID Connect
34462
+ token_type: z.string(),
34463
+ expires_in: z.coerce.number().optional(),
34464
+ scope: z.string().optional(),
34465
+ refresh_token: z.string().optional()
34466
+ })
34467
+ .strip();
34468
+ /**
34469
+ * OAuth 2.1 error response
34470
+ */
34471
+ exports.OAuthErrorResponseSchema = z.object({
34472
+ error: z.string(),
34473
+ error_description: z.string().optional(),
34474
+ error_uri: z.string().optional()
34475
+ });
34476
+ /**
34477
+ * Optional version of SafeUrlSchema that allows empty string for retrocompatibility on tos_uri and logo_uri
34478
+ */
34479
+ exports.OptionalSafeUrlSchema = exports.SafeUrlSchema.optional().or(z.literal('').transform(() => undefined));
34480
+ /**
34481
+ * RFC 7591 OAuth 2.0 Dynamic Client Registration metadata
34482
+ */
34483
+ exports.OAuthClientMetadataSchema = z
34484
+ .object({
34485
+ redirect_uris: z.array(exports.SafeUrlSchema),
34486
+ token_endpoint_auth_method: z.string().optional(),
34487
+ grant_types: z.array(z.string()).optional(),
34488
+ response_types: z.array(z.string()).optional(),
34489
+ client_name: z.string().optional(),
34490
+ client_uri: exports.SafeUrlSchema.optional(),
34491
+ logo_uri: exports.OptionalSafeUrlSchema,
34492
+ scope: z.string().optional(),
34493
+ contacts: z.array(z.string()).optional(),
34494
+ tos_uri: exports.OptionalSafeUrlSchema,
34495
+ policy_uri: z.string().optional(),
34496
+ jwks_uri: exports.SafeUrlSchema.optional(),
34497
+ jwks: z.any().optional(),
34498
+ software_id: z.string().optional(),
34499
+ software_version: z.string().optional(),
34500
+ software_statement: z.string().optional()
34501
+ })
34502
+ .strip();
34503
+ /**
34504
+ * RFC 7591 OAuth 2.0 Dynamic Client Registration client information
34505
+ */
34506
+ exports.OAuthClientInformationSchema = z
34507
+ .object({
34508
+ client_id: z.string(),
34509
+ client_secret: z.string().optional(),
34510
+ client_id_issued_at: z.number().optional(),
34511
+ client_secret_expires_at: z.number().optional()
34512
+ })
34513
+ .strip();
34514
+ /**
34515
+ * RFC 7591 OAuth 2.0 Dynamic Client Registration full response (client information plus metadata)
34516
+ */
34517
+ exports.OAuthClientInformationFullSchema = exports.OAuthClientMetadataSchema.merge(exports.OAuthClientInformationSchema);
34518
+ /**
34519
+ * RFC 7591 OAuth 2.0 Dynamic Client Registration error response
34520
+ */
34521
+ exports.OAuthClientRegistrationErrorSchema = z
34522
+ .object({
34523
+ error: z.string(),
34524
+ error_description: z.string().optional()
34525
+ })
34526
+ .strip();
34527
+ /**
34528
+ * RFC 7009 OAuth 2.0 Token Revocation request
34529
+ */
34530
+ exports.OAuthTokenRevocationRequestSchema = z
34531
+ .object({
34532
+ token: z.string(),
34533
+ token_type_hint: z.string().optional()
34534
+ })
34535
+ .strip();
34536
+
34537
+ } (auth));
34538
+ return auth;
34539
+ }
34540
+
34541
+ var authUtils = {};
34542
+
34543
+ var hasRequiredAuthUtils;
34544
+
34545
+ function requireAuthUtils () {
34546
+ if (hasRequiredAuthUtils) return authUtils;
34547
+ hasRequiredAuthUtils = 1;
34548
+ /**
34549
+ * Utilities for handling OAuth resource URIs.
34550
+ */
34551
+ Object.defineProperty(authUtils, "__esModule", { value: true });
34552
+ authUtils.resourceUrlFromServerUrl = resourceUrlFromServerUrl;
34553
+ authUtils.checkResourceAllowed = checkResourceAllowed;
34554
+ /**
34555
+ * Converts a server URL to a resource URL by removing the fragment.
34556
+ * RFC 8707 section 2 states that resource URIs "MUST NOT include a fragment component".
34557
+ * Keeps everything else unchanged (scheme, domain, port, path, query).
34558
+ */
34559
+ function resourceUrlFromServerUrl(url) {
34560
+ const resourceURL = typeof url === 'string' ? new URL(url) : new URL(url.href);
34561
+ resourceURL.hash = ''; // Remove fragment
34562
+ return resourceURL;
34563
+ }
34564
+ /**
34565
+ * Checks if a requested resource URL matches a configured resource URL.
34566
+ * A requested resource matches if it has the same scheme, domain, port,
34567
+ * and its path starts with the configured resource's path.
34568
+ *
34569
+ * @param requestedResource The resource URL being requested
34570
+ * @param configuredResource The resource URL that has been configured
34571
+ * @returns true if the requested resource matches the configured resource, false otherwise
34572
+ */
34573
+ function checkResourceAllowed({ requestedResource, configuredResource }) {
34574
+ const requested = typeof requestedResource === 'string' ? new URL(requestedResource) : new URL(requestedResource.href);
34575
+ const configured = typeof configuredResource === 'string' ? new URL(configuredResource) : new URL(configuredResource.href);
34576
+ // Compare the origin (scheme, domain, and port)
34577
+ if (requested.origin !== configured.origin) {
34578
+ return false;
34217
34579
  }
34218
- Object.defineProperty(o, k2, desc);
34219
- }) : (function(o, m, k, k2) {
34220
- if (k2 === undefined) k2 = k;
34221
- o[k2] = m[k];
34222
- }));
34223
- var __setModuleDefault = (commonjsGlobal && commonjsGlobal.__setModuleDefault) || (Object.create ? (function(o, v) {
34224
- Object.defineProperty(o, "default", { enumerable: true, value: v });
34225
- }) : function(o, v) {
34226
- o["default"] = v;
34227
- });
34228
- var __importStar = (commonjsGlobal && commonjsGlobal.__importStar) || function (mod) {
34229
- if (mod && mod.__esModule) return mod;
34230
- var result = {};
34231
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
34232
- __setModuleDefault(result, mod);
34233
- return result;
34234
- };
34235
- Object.defineProperty(exports, "__esModule", { value: true });
34236
- exports.OAuthTokenRevocationRequestSchema = exports.OAuthClientRegistrationErrorSchema = exports.OAuthClientInformationFullSchema = exports.OAuthClientInformationSchema = exports.OAuthClientMetadataSchema = exports.OptionalSafeUrlSchema = exports.OAuthErrorResponseSchema = exports.OAuthTokensSchema = exports.OpenIdProviderDiscoveryMetadataSchema = exports.OpenIdProviderMetadataSchema = exports.OAuthMetadataSchema = exports.OAuthProtectedResourceMetadataSchema = exports.SafeUrlSchema = void 0;
34237
- const z = __importStar(v4);
34580
+ // Handle cases like requested=/foo and configured=/foo/
34581
+ if (requested.pathname.length < configured.pathname.length) {
34582
+ return false;
34583
+ }
34584
+ // Check if the requested path starts with the configured path
34585
+ // Ensure both paths end with / for proper comparison
34586
+ // This ensures that if we have paths like "/api" and "/api/users",
34587
+ // we properly detect that "/api/users" is a subpath of "/api"
34588
+ // By adding a trailing slash if missing, we avoid false positives
34589
+ // where paths like "/api123" would incorrectly match "/api"
34590
+ const requestedPath = requested.pathname.endsWith('/') ? requested.pathname : requested.pathname + '/';
34591
+ const configuredPath = configured.pathname.endsWith('/') ? configured.pathname : configured.pathname + '/';
34592
+ return requestedPath.startsWith(configuredPath);
34593
+ }
34594
+
34595
+ return authUtils;
34596
+ }
34597
+
34598
+ var errors$2 = {};
34599
+
34600
+ var hasRequiredErrors$1;
34601
+
34602
+ function requireErrors$1 () {
34603
+ if (hasRequiredErrors$1) return errors$2;
34604
+ hasRequiredErrors$1 = 1;
34605
+ Object.defineProperty(errors$2, "__esModule", { value: true });
34606
+ errors$2.OAUTH_ERRORS = errors$2.CustomOAuthError = errors$2.InvalidTargetError = errors$2.InsufficientScopeError = errors$2.InvalidClientMetadataError = errors$2.TooManyRequestsError = errors$2.MethodNotAllowedError = errors$2.InvalidTokenError = errors$2.UnsupportedTokenTypeError = errors$2.UnsupportedResponseTypeError = errors$2.TemporarilyUnavailableError = errors$2.ServerError = errors$2.AccessDeniedError = errors$2.InvalidScopeError = errors$2.UnsupportedGrantTypeError = errors$2.UnauthorizedClientError = errors$2.InvalidGrantError = errors$2.InvalidClientError = errors$2.InvalidRequestError = errors$2.OAuthError = void 0;
34238
34607
  /**
34239
- * Reusable URL validation that disallows javascript: scheme
34608
+ * Base class for all OAuth errors
34240
34609
  */
34241
- exports.SafeUrlSchema = z
34242
- .url()
34243
- .superRefine((val, ctx) => {
34244
- if (!URL.canParse(val)) {
34245
- ctx.addIssue({
34246
- code: z.ZodIssueCode.custom,
34247
- message: 'URL must be parseable',
34248
- fatal: true
34249
- });
34250
- return z.NEVER;
34610
+ class OAuthError extends Error {
34611
+ constructor(message, errorUri) {
34612
+ super(message);
34613
+ this.errorUri = errorUri;
34614
+ this.name = this.constructor.name;
34251
34615
  }
34252
- })
34253
- .refine(url => {
34254
- const u = new URL(url);
34255
- return u.protocol !== 'javascript:' && u.protocol !== 'data:' && u.protocol !== 'vbscript:';
34256
- }, { message: 'URL cannot use javascript:, data:, or vbscript: scheme' });
34616
+ /**
34617
+ * Converts the error to a standard OAuth error response object
34618
+ */
34619
+ toResponseObject() {
34620
+ const response = {
34621
+ error: this.errorCode,
34622
+ error_description: this.message
34623
+ };
34624
+ if (this.errorUri) {
34625
+ response.error_uri = this.errorUri;
34626
+ }
34627
+ return response;
34628
+ }
34629
+ get errorCode() {
34630
+ return this.constructor.errorCode;
34631
+ }
34632
+ }
34633
+ errors$2.OAuthError = OAuthError;
34257
34634
  /**
34258
- * RFC 9728 OAuth Protected Resource Metadata
34635
+ * Invalid request error - The request is missing a required parameter,
34636
+ * includes an invalid parameter value, includes a parameter more than once,
34637
+ * or is otherwise malformed.
34259
34638
  */
34260
- exports.OAuthProtectedResourceMetadataSchema = z.looseObject({
34261
- resource: z.string().url(),
34262
- authorization_servers: z.array(exports.SafeUrlSchema).optional(),
34263
- jwks_uri: z.string().url().optional(),
34264
- scopes_supported: z.array(z.string()).optional(),
34265
- bearer_methods_supported: z.array(z.string()).optional(),
34266
- resource_signing_alg_values_supported: z.array(z.string()).optional(),
34267
- resource_name: z.string().optional(),
34268
- resource_documentation: z.string().optional(),
34269
- resource_policy_uri: z.string().url().optional(),
34270
- resource_tos_uri: z.string().url().optional(),
34271
- tls_client_certificate_bound_access_tokens: z.boolean().optional(),
34272
- authorization_details_types_supported: z.array(z.string()).optional(),
34273
- dpop_signing_alg_values_supported: z.array(z.string()).optional(),
34274
- dpop_bound_access_tokens_required: z.boolean().optional()
34275
- });
34639
+ class InvalidRequestError extends OAuthError {
34640
+ }
34641
+ errors$2.InvalidRequestError = InvalidRequestError;
34642
+ InvalidRequestError.errorCode = 'invalid_request';
34276
34643
  /**
34277
- * RFC 8414 OAuth 2.0 Authorization Server Metadata
34644
+ * Invalid client error - Client authentication failed (e.g., unknown client, no client
34645
+ * authentication included, or unsupported authentication method).
34278
34646
  */
34279
- exports.OAuthMetadataSchema = z.looseObject({
34280
- issuer: z.string(),
34281
- authorization_endpoint: exports.SafeUrlSchema,
34282
- token_endpoint: exports.SafeUrlSchema,
34283
- registration_endpoint: exports.SafeUrlSchema.optional(),
34284
- scopes_supported: z.array(z.string()).optional(),
34285
- response_types_supported: z.array(z.string()),
34286
- response_modes_supported: z.array(z.string()).optional(),
34287
- grant_types_supported: z.array(z.string()).optional(),
34288
- token_endpoint_auth_methods_supported: z.array(z.string()).optional(),
34289
- token_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(),
34290
- service_documentation: exports.SafeUrlSchema.optional(),
34291
- revocation_endpoint: exports.SafeUrlSchema.optional(),
34292
- revocation_endpoint_auth_methods_supported: z.array(z.string()).optional(),
34293
- revocation_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(),
34294
- introspection_endpoint: z.string().optional(),
34295
- introspection_endpoint_auth_methods_supported: z.array(z.string()).optional(),
34296
- introspection_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(),
34297
- code_challenge_methods_supported: z.array(z.string()).optional(),
34298
- client_id_metadata_document_supported: z.boolean().optional()
34299
- });
34647
+ class InvalidClientError extends OAuthError {
34648
+ }
34649
+ errors$2.InvalidClientError = InvalidClientError;
34650
+ InvalidClientError.errorCode = 'invalid_client';
34300
34651
  /**
34301
- * OpenID Connect Discovery 1.0 Provider Metadata
34302
- * see: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
34652
+ * Invalid grant error - The provided authorization grant or refresh token is
34653
+ * invalid, expired, revoked, does not match the redirection URI used in the
34654
+ * authorization request, or was issued to another client.
34303
34655
  */
34304
- exports.OpenIdProviderMetadataSchema = z.looseObject({
34305
- issuer: z.string(),
34306
- authorization_endpoint: exports.SafeUrlSchema,
34307
- token_endpoint: exports.SafeUrlSchema,
34308
- userinfo_endpoint: exports.SafeUrlSchema.optional(),
34309
- jwks_uri: exports.SafeUrlSchema,
34310
- registration_endpoint: exports.SafeUrlSchema.optional(),
34311
- scopes_supported: z.array(z.string()).optional(),
34312
- response_types_supported: z.array(z.string()),
34313
- response_modes_supported: z.array(z.string()).optional(),
34314
- grant_types_supported: z.array(z.string()).optional(),
34315
- acr_values_supported: z.array(z.string()).optional(),
34316
- subject_types_supported: z.array(z.string()),
34317
- id_token_signing_alg_values_supported: z.array(z.string()),
34318
- id_token_encryption_alg_values_supported: z.array(z.string()).optional(),
34319
- id_token_encryption_enc_values_supported: z.array(z.string()).optional(),
34320
- userinfo_signing_alg_values_supported: z.array(z.string()).optional(),
34321
- userinfo_encryption_alg_values_supported: z.array(z.string()).optional(),
34322
- userinfo_encryption_enc_values_supported: z.array(z.string()).optional(),
34323
- request_object_signing_alg_values_supported: z.array(z.string()).optional(),
34324
- request_object_encryption_alg_values_supported: z.array(z.string()).optional(),
34325
- request_object_encryption_enc_values_supported: z.array(z.string()).optional(),
34326
- token_endpoint_auth_methods_supported: z.array(z.string()).optional(),
34327
- token_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(),
34328
- display_values_supported: z.array(z.string()).optional(),
34329
- claim_types_supported: z.array(z.string()).optional(),
34330
- claims_supported: z.array(z.string()).optional(),
34331
- service_documentation: z.string().optional(),
34332
- claims_locales_supported: z.array(z.string()).optional(),
34333
- ui_locales_supported: z.array(z.string()).optional(),
34334
- claims_parameter_supported: z.boolean().optional(),
34335
- request_parameter_supported: z.boolean().optional(),
34336
- request_uri_parameter_supported: z.boolean().optional(),
34337
- require_request_uri_registration: z.boolean().optional(),
34338
- op_policy_uri: exports.SafeUrlSchema.optional(),
34339
- op_tos_uri: exports.SafeUrlSchema.optional(),
34340
- client_id_metadata_document_supported: z.boolean().optional()
34341
- });
34656
+ class InvalidGrantError extends OAuthError {
34657
+ }
34658
+ errors$2.InvalidGrantError = InvalidGrantError;
34659
+ InvalidGrantError.errorCode = 'invalid_grant';
34342
34660
  /**
34343
- * OpenID Connect Discovery metadata that may include OAuth 2.0 fields
34344
- * This schema represents the real-world scenario where OIDC providers
34345
- * return a mix of OpenID Connect and OAuth 2.0 metadata fields
34661
+ * Unauthorized client error - The authenticated client is not authorized to use
34662
+ * this authorization grant type.
34346
34663
  */
34347
- exports.OpenIdProviderDiscoveryMetadataSchema = z.object({
34348
- ...exports.OpenIdProviderMetadataSchema.shape,
34349
- ...exports.OAuthMetadataSchema.pick({
34350
- code_challenge_methods_supported: true
34351
- }).shape
34352
- });
34664
+ class UnauthorizedClientError extends OAuthError {
34665
+ }
34666
+ errors$2.UnauthorizedClientError = UnauthorizedClientError;
34667
+ UnauthorizedClientError.errorCode = 'unauthorized_client';
34353
34668
  /**
34354
- * OAuth 2.1 token response
34669
+ * Unsupported grant type error - The authorization grant type is not supported
34670
+ * by the authorization server.
34355
34671
  */
34356
- exports.OAuthTokensSchema = z
34357
- .object({
34358
- access_token: z.string(),
34359
- id_token: z.string().optional(), // Optional for OAuth 2.1, but necessary in OpenID Connect
34360
- token_type: z.string(),
34361
- expires_in: z.coerce.number().optional(),
34362
- scope: z.string().optional(),
34363
- refresh_token: z.string().optional()
34364
- })
34365
- .strip();
34672
+ class UnsupportedGrantTypeError extends OAuthError {
34673
+ }
34674
+ errors$2.UnsupportedGrantTypeError = UnsupportedGrantTypeError;
34675
+ UnsupportedGrantTypeError.errorCode = 'unsupported_grant_type';
34366
34676
  /**
34367
- * OAuth 2.1 error response
34677
+ * Invalid scope error - The requested scope is invalid, unknown, malformed, or
34678
+ * exceeds the scope granted by the resource owner.
34368
34679
  */
34369
- exports.OAuthErrorResponseSchema = z.object({
34370
- error: z.string(),
34371
- error_description: z.string().optional(),
34372
- error_uri: z.string().optional()
34373
- });
34680
+ class InvalidScopeError extends OAuthError {
34681
+ }
34682
+ errors$2.InvalidScopeError = InvalidScopeError;
34683
+ InvalidScopeError.errorCode = 'invalid_scope';
34374
34684
  /**
34375
- * Optional version of SafeUrlSchema that allows empty string for retrocompatibility on tos_uri and logo_uri
34685
+ * Access denied error - The resource owner or authorization server denied the request.
34376
34686
  */
34377
- exports.OptionalSafeUrlSchema = exports.SafeUrlSchema.optional().or(z.literal('').transform(() => undefined));
34687
+ class AccessDeniedError extends OAuthError {
34688
+ }
34689
+ errors$2.AccessDeniedError = AccessDeniedError;
34690
+ AccessDeniedError.errorCode = 'access_denied';
34378
34691
  /**
34379
- * RFC 7591 OAuth 2.0 Dynamic Client Registration metadata
34692
+ * Server error - The authorization server encountered an unexpected condition
34693
+ * that prevented it from fulfilling the request.
34380
34694
  */
34381
- exports.OAuthClientMetadataSchema = z
34382
- .object({
34383
- redirect_uris: z.array(exports.SafeUrlSchema),
34384
- token_endpoint_auth_method: z.string().optional(),
34385
- grant_types: z.array(z.string()).optional(),
34386
- response_types: z.array(z.string()).optional(),
34387
- client_name: z.string().optional(),
34388
- client_uri: exports.SafeUrlSchema.optional(),
34389
- logo_uri: exports.OptionalSafeUrlSchema,
34390
- scope: z.string().optional(),
34391
- contacts: z.array(z.string()).optional(),
34392
- tos_uri: exports.OptionalSafeUrlSchema,
34393
- policy_uri: z.string().optional(),
34394
- jwks_uri: exports.SafeUrlSchema.optional(),
34395
- jwks: z.any().optional(),
34396
- software_id: z.string().optional(),
34397
- software_version: z.string().optional(),
34398
- software_statement: z.string().optional()
34399
- })
34400
- .strip();
34695
+ class ServerError extends OAuthError {
34696
+ }
34697
+ errors$2.ServerError = ServerError;
34698
+ ServerError.errorCode = 'server_error';
34401
34699
  /**
34402
- * RFC 7591 OAuth 2.0 Dynamic Client Registration client information
34700
+ * Temporarily unavailable error - The authorization server is currently unable to
34701
+ * handle the request due to a temporary overloading or maintenance of the server.
34403
34702
  */
34404
- exports.OAuthClientInformationSchema = z
34405
- .object({
34406
- client_id: z.string(),
34407
- client_secret: z.string().optional(),
34408
- client_id_issued_at: z.number().optional(),
34409
- client_secret_expires_at: z.number().optional()
34410
- })
34411
- .strip();
34703
+ class TemporarilyUnavailableError extends OAuthError {
34704
+ }
34705
+ errors$2.TemporarilyUnavailableError = TemporarilyUnavailableError;
34706
+ TemporarilyUnavailableError.errorCode = 'temporarily_unavailable';
34412
34707
  /**
34413
- * RFC 7591 OAuth 2.0 Dynamic Client Registration full response (client information plus metadata)
34708
+ * Unsupported response type error - The authorization server does not support
34709
+ * obtaining an authorization code using this method.
34414
34710
  */
34415
- exports.OAuthClientInformationFullSchema = exports.OAuthClientMetadataSchema.merge(exports.OAuthClientInformationSchema);
34711
+ class UnsupportedResponseTypeError extends OAuthError {
34712
+ }
34713
+ errors$2.UnsupportedResponseTypeError = UnsupportedResponseTypeError;
34714
+ UnsupportedResponseTypeError.errorCode = 'unsupported_response_type';
34416
34715
  /**
34417
- * RFC 7591 OAuth 2.0 Dynamic Client Registration error response
34716
+ * Unsupported token type error - The authorization server does not support
34717
+ * the requested token type.
34418
34718
  */
34419
- exports.OAuthClientRegistrationErrorSchema = z
34420
- .object({
34421
- error: z.string(),
34422
- error_description: z.string().optional()
34423
- })
34424
- .strip();
34719
+ class UnsupportedTokenTypeError extends OAuthError {
34720
+ }
34721
+ errors$2.UnsupportedTokenTypeError = UnsupportedTokenTypeError;
34722
+ UnsupportedTokenTypeError.errorCode = 'unsupported_token_type';
34425
34723
  /**
34426
- * RFC 7009 OAuth 2.0 Token Revocation request
34724
+ * Invalid token error - The access token provided is expired, revoked, malformed,
34725
+ * or invalid for other reasons.
34427
34726
  */
34428
- exports.OAuthTokenRevocationRequestSchema = z
34429
- .object({
34430
- token: z.string(),
34431
- token_type_hint: z.string().optional()
34432
- })
34433
- .strip();
34434
-
34435
- } (auth$1));
34436
-
34437
- var authUtils = {};
34438
-
34439
- /**
34440
- * Utilities for handling OAuth resource URIs.
34441
- */
34442
- Object.defineProperty(authUtils, "__esModule", { value: true });
34443
- authUtils.resourceUrlFromServerUrl = resourceUrlFromServerUrl;
34444
- authUtils.checkResourceAllowed = checkResourceAllowed;
34445
- /**
34446
- * Converts a server URL to a resource URL by removing the fragment.
34447
- * RFC 8707 section 2 states that resource URIs "MUST NOT include a fragment component".
34448
- * Keeps everything else unchanged (scheme, domain, port, path, query).
34449
- */
34450
- function resourceUrlFromServerUrl(url) {
34451
- const resourceURL = typeof url === 'string' ? new URL(url) : new URL(url.href);
34452
- resourceURL.hash = ''; // Remove fragment
34453
- return resourceURL;
34454
- }
34455
- /**
34456
- * Checks if a requested resource URL matches a configured resource URL.
34457
- * A requested resource matches if it has the same scheme, domain, port,
34458
- * and its path starts with the configured resource's path.
34459
- *
34460
- * @param requestedResource The resource URL being requested
34461
- * @param configuredResource The resource URL that has been configured
34462
- * @returns true if the requested resource matches the configured resource, false otherwise
34463
- */
34464
- function checkResourceAllowed({ requestedResource, configuredResource }) {
34465
- const requested = typeof requestedResource === 'string' ? new URL(requestedResource) : new URL(requestedResource.href);
34466
- const configured = typeof configuredResource === 'string' ? new URL(configuredResource) : new URL(configuredResource.href);
34467
- // Compare the origin (scheme, domain, and port)
34468
- if (requested.origin !== configured.origin) {
34469
- return false;
34470
- }
34471
- // Handle cases like requested=/foo and configured=/foo/
34472
- if (requested.pathname.length < configured.pathname.length) {
34473
- return false;
34474
- }
34475
- // Check if the requested path starts with the configured path
34476
- // Ensure both paths end with / for proper comparison
34477
- // This ensures that if we have paths like "/api" and "/api/users",
34478
- // we properly detect that "/api/users" is a subpath of "/api"
34479
- // By adding a trailing slash if missing, we avoid false positives
34480
- // where paths like "/api123" would incorrectly match "/api"
34481
- const requestedPath = requested.pathname.endsWith('/') ? requested.pathname : requested.pathname + '/';
34482
- const configuredPath = configured.pathname.endsWith('/') ? configured.pathname : configured.pathname + '/';
34483
- return requestedPath.startsWith(configuredPath);
34484
- }
34485
-
34486
- var errors$2 = {};
34487
-
34488
- Object.defineProperty(errors$2, "__esModule", { value: true });
34489
- errors$2.OAUTH_ERRORS = errors$2.CustomOAuthError = errors$2.InvalidTargetError = errors$2.InsufficientScopeError = errors$2.InvalidClientMetadataError = errors$2.TooManyRequestsError = errors$2.MethodNotAllowedError = errors$2.InvalidTokenError = errors$2.UnsupportedTokenTypeError = errors$2.UnsupportedResponseTypeError = errors$2.TemporarilyUnavailableError = errors$2.ServerError = errors$2.AccessDeniedError = errors$2.InvalidScopeError = errors$2.UnsupportedGrantTypeError = errors$2.UnauthorizedClientError = errors$2.InvalidGrantError = errors$2.InvalidClientError = errors$2.InvalidRequestError = errors$2.OAuthError = void 0;
34490
- /**
34491
- * Base class for all OAuth errors
34492
- */
34493
- class OAuthError extends Error {
34494
- constructor(message, errorUri) {
34495
- super(message);
34496
- this.errorUri = errorUri;
34497
- this.name = this.constructor.name;
34498
- }
34499
- /**
34500
- * Converts the error to a standard OAuth error response object
34501
- */
34502
- toResponseObject() {
34503
- const response = {
34504
- error: this.errorCode,
34505
- error_description: this.message
34506
- };
34507
- if (this.errorUri) {
34508
- response.error_uri = this.errorUri;
34509
- }
34510
- return response;
34511
- }
34512
- get errorCode() {
34513
- return this.constructor.errorCode;
34514
- }
34515
- }
34516
- errors$2.OAuthError = OAuthError;
34517
- /**
34518
- * Invalid request error - The request is missing a required parameter,
34519
- * includes an invalid parameter value, includes a parameter more than once,
34520
- * or is otherwise malformed.
34521
- */
34522
- class InvalidRequestError extends OAuthError {
34523
- }
34524
- errors$2.InvalidRequestError = InvalidRequestError;
34525
- InvalidRequestError.errorCode = 'invalid_request';
34526
- /**
34527
- * Invalid client error - Client authentication failed (e.g., unknown client, no client
34528
- * authentication included, or unsupported authentication method).
34529
- */
34530
- class InvalidClientError extends OAuthError {
34531
- }
34532
- errors$2.InvalidClientError = InvalidClientError;
34533
- InvalidClientError.errorCode = 'invalid_client';
34534
- /**
34535
- * Invalid grant error - The provided authorization grant or refresh token is
34536
- * invalid, expired, revoked, does not match the redirection URI used in the
34537
- * authorization request, or was issued to another client.
34538
- */
34539
- class InvalidGrantError extends OAuthError {
34540
- }
34541
- errors$2.InvalidGrantError = InvalidGrantError;
34542
- InvalidGrantError.errorCode = 'invalid_grant';
34543
- /**
34544
- * Unauthorized client error - The authenticated client is not authorized to use
34545
- * this authorization grant type.
34546
- */
34547
- class UnauthorizedClientError extends OAuthError {
34548
- }
34549
- errors$2.UnauthorizedClientError = UnauthorizedClientError;
34550
- UnauthorizedClientError.errorCode = 'unauthorized_client';
34551
- /**
34552
- * Unsupported grant type error - The authorization grant type is not supported
34553
- * by the authorization server.
34554
- */
34555
- class UnsupportedGrantTypeError extends OAuthError {
34556
- }
34557
- errors$2.UnsupportedGrantTypeError = UnsupportedGrantTypeError;
34558
- UnsupportedGrantTypeError.errorCode = 'unsupported_grant_type';
34559
- /**
34560
- * Invalid scope error - The requested scope is invalid, unknown, malformed, or
34561
- * exceeds the scope granted by the resource owner.
34562
- */
34563
- class InvalidScopeError extends OAuthError {
34564
- }
34565
- errors$2.InvalidScopeError = InvalidScopeError;
34566
- InvalidScopeError.errorCode = 'invalid_scope';
34567
- /**
34568
- * Access denied error - The resource owner or authorization server denied the request.
34569
- */
34570
- class AccessDeniedError extends OAuthError {
34571
- }
34572
- errors$2.AccessDeniedError = AccessDeniedError;
34573
- AccessDeniedError.errorCode = 'access_denied';
34574
- /**
34575
- * Server error - The authorization server encountered an unexpected condition
34576
- * that prevented it from fulfilling the request.
34577
- */
34578
- class ServerError extends OAuthError {
34579
- }
34580
- errors$2.ServerError = ServerError;
34581
- ServerError.errorCode = 'server_error';
34582
- /**
34583
- * Temporarily unavailable error - The authorization server is currently unable to
34584
- * handle the request due to a temporary overloading or maintenance of the server.
34585
- */
34586
- class TemporarilyUnavailableError extends OAuthError {
34587
- }
34588
- errors$2.TemporarilyUnavailableError = TemporarilyUnavailableError;
34589
- TemporarilyUnavailableError.errorCode = 'temporarily_unavailable';
34590
- /**
34591
- * Unsupported response type error - The authorization server does not support
34592
- * obtaining an authorization code using this method.
34593
- */
34594
- class UnsupportedResponseTypeError extends OAuthError {
34595
- }
34596
- errors$2.UnsupportedResponseTypeError = UnsupportedResponseTypeError;
34597
- UnsupportedResponseTypeError.errorCode = 'unsupported_response_type';
34598
- /**
34599
- * Unsupported token type error - The authorization server does not support
34600
- * the requested token type.
34601
- */
34602
- class UnsupportedTokenTypeError extends OAuthError {
34603
- }
34604
- errors$2.UnsupportedTokenTypeError = UnsupportedTokenTypeError;
34605
- UnsupportedTokenTypeError.errorCode = 'unsupported_token_type';
34606
- /**
34607
- * Invalid token error - The access token provided is expired, revoked, malformed,
34608
- * or invalid for other reasons.
34609
- */
34610
- class InvalidTokenError extends OAuthError {
34611
- }
34612
- errors$2.InvalidTokenError = InvalidTokenError;
34613
- InvalidTokenError.errorCode = 'invalid_token';
34614
- /**
34615
- * Method not allowed error - The HTTP method used is not allowed for this endpoint.
34616
- * (Custom, non-standard error)
34617
- */
34618
- class MethodNotAllowedError extends OAuthError {
34619
- }
34620
- errors$2.MethodNotAllowedError = MethodNotAllowedError;
34621
- MethodNotAllowedError.errorCode = 'method_not_allowed';
34622
- /**
34623
- * Too many requests error - Rate limit exceeded.
34624
- * (Custom, non-standard error based on RFC 6585)
34625
- */
34626
- class TooManyRequestsError extends OAuthError {
34627
- }
34628
- errors$2.TooManyRequestsError = TooManyRequestsError;
34629
- TooManyRequestsError.errorCode = 'too_many_requests';
34630
- /**
34631
- * Invalid client metadata error - The client metadata is invalid.
34632
- * (Custom error for dynamic client registration - RFC 7591)
34633
- */
34634
- class InvalidClientMetadataError extends OAuthError {
34635
- }
34636
- errors$2.InvalidClientMetadataError = InvalidClientMetadataError;
34637
- InvalidClientMetadataError.errorCode = 'invalid_client_metadata';
34638
- /**
34639
- * Insufficient scope error - The request requires higher privileges than provided by the access token.
34640
- */
34641
- class InsufficientScopeError extends OAuthError {
34642
- }
34643
- errors$2.InsufficientScopeError = InsufficientScopeError;
34644
- InsufficientScopeError.errorCode = 'insufficient_scope';
34645
- /**
34646
- * Invalid target error - The requested resource is invalid, missing, unknown, or malformed.
34647
- * (Custom error for resource indicators - RFC 8707)
34648
- */
34649
- class InvalidTargetError extends OAuthError {
34650
- }
34651
- errors$2.InvalidTargetError = InvalidTargetError;
34652
- InvalidTargetError.errorCode = 'invalid_target';
34653
- /**
34654
- * A utility class for defining one-off error codes
34655
- */
34656
- class CustomOAuthError extends OAuthError {
34657
- constructor(customErrorCode, message, errorUri) {
34658
- super(message, errorUri);
34659
- this.customErrorCode = customErrorCode;
34660
- }
34661
- get errorCode() {
34662
- return this.customErrorCode;
34663
- }
34664
- }
34665
- errors$2.CustomOAuthError = CustomOAuthError;
34666
- /**
34667
- * A full list of all OAuthErrors, enabling parsing from error responses
34668
- */
34669
- errors$2.OAUTH_ERRORS = {
34670
- [InvalidRequestError.errorCode]: InvalidRequestError,
34671
- [InvalidClientError.errorCode]: InvalidClientError,
34672
- [InvalidGrantError.errorCode]: InvalidGrantError,
34673
- [UnauthorizedClientError.errorCode]: UnauthorizedClientError,
34674
- [UnsupportedGrantTypeError.errorCode]: UnsupportedGrantTypeError,
34675
- [InvalidScopeError.errorCode]: InvalidScopeError,
34676
- [AccessDeniedError.errorCode]: AccessDeniedError,
34677
- [ServerError.errorCode]: ServerError,
34678
- [TemporarilyUnavailableError.errorCode]: TemporarilyUnavailableError,
34679
- [UnsupportedResponseTypeError.errorCode]: UnsupportedResponseTypeError,
34680
- [UnsupportedTokenTypeError.errorCode]: UnsupportedTokenTypeError,
34681
- [InvalidTokenError.errorCode]: InvalidTokenError,
34682
- [MethodNotAllowedError.errorCode]: MethodNotAllowedError,
34683
- [TooManyRequestsError.errorCode]: TooManyRequestsError,
34684
- [InvalidClientMetadataError.errorCode]: InvalidClientMetadataError,
34685
- [InsufficientScopeError.errorCode]: InsufficientScopeError,
34686
- [InvalidTargetError.errorCode]: InvalidTargetError
34687
- };
34688
-
34689
- var __importDefault$2 = (commonjsGlobal && commonjsGlobal.__importDefault) || function (mod) {
34690
- return (mod && mod.__esModule) ? mod : { "default": mod };
34691
- };
34692
- Object.defineProperty(auth$2, "__esModule", { value: true });
34693
- auth$2.UnauthorizedError = void 0;
34694
- auth$2.selectClientAuthMethod = selectClientAuthMethod;
34695
- auth$2.parseErrorResponse = parseErrorResponse;
34696
- auth$2.auth = auth;
34697
- auth$2.isHttpsUrl = isHttpsUrl;
34698
- auth$2.selectResourceURL = selectResourceURL;
34699
- auth$2.extractWWWAuthenticateParams = extractWWWAuthenticateParams;
34700
- auth$2.extractResourceMetadataUrl = extractResourceMetadataUrl;
34701
- auth$2.discoverOAuthProtectedResourceMetadata = discoverOAuthProtectedResourceMetadata;
34702
- auth$2.discoverOAuthMetadata = discoverOAuthMetadata;
34703
- auth$2.buildDiscoveryUrls = buildDiscoveryUrls;
34704
- auth$2.discoverAuthorizationServerMetadata = discoverAuthorizationServerMetadata;
34705
- auth$2.discoverOAuthServerInfo = discoverOAuthServerInfo;
34706
- auth$2.startAuthorization = startAuthorization;
34707
- auth$2.prepareAuthorizationCodeRequest = prepareAuthorizationCodeRequest;
34708
- auth$2.exchangeAuthorization = exchangeAuthorization;
34709
- auth$2.refreshAuthorization = refreshAuthorization;
34710
- auth$2.fetchToken = fetchToken;
34711
- auth$2.registerClient = registerClient;
34712
- const pkce_challenge_1 = __importDefault$2(require$$0$7);
34713
- const types_js_1$5 = types$2;
34714
- const auth_js_1$1 = auth$1;
34715
- const auth_js_2 = auth$1;
34716
- const auth_utils_js_1 = authUtils;
34717
- const errors_js_1$1 = errors$2;
34718
- class UnauthorizedError extends Error {
34719
- constructor(message) {
34720
- super(message ?? 'Unauthorized');
34721
- }
34722
- }
34723
- auth$2.UnauthorizedError = UnauthorizedError;
34724
- function isClientAuthMethod(method) {
34725
- return ['client_secret_basic', 'client_secret_post', 'none'].includes(method);
34726
- }
34727
- const AUTHORIZATION_CODE_RESPONSE_TYPE = 'code';
34728
- const AUTHORIZATION_CODE_CHALLENGE_METHOD = 'S256';
34729
- /**
34730
- * Determines the best client authentication method to use based on server support and client configuration.
34731
- *
34732
- * Priority order (highest to lowest):
34733
- * 1. client_secret_basic (if client secret is available)
34734
- * 2. client_secret_post (if client secret is available)
34735
- * 3. none (for public clients)
34736
- *
34737
- * @param clientInformation - OAuth client information containing credentials
34738
- * @param supportedMethods - Authentication methods supported by the authorization server
34739
- * @returns The selected authentication method
34740
- */
34741
- function selectClientAuthMethod(clientInformation, supportedMethods) {
34742
- const hasClientSecret = clientInformation.client_secret !== undefined;
34743
- // Prefer the method returned by the server during client registration, if valid.
34744
- // When server metadata is present we also require the method to be listed as supported;
34745
- // when supportedMethods is empty (metadata omitted the field) the DCR hint stands alone.
34746
- if ('token_endpoint_auth_method' in clientInformation &&
34747
- clientInformation.token_endpoint_auth_method &&
34748
- isClientAuthMethod(clientInformation.token_endpoint_auth_method) &&
34749
- (supportedMethods.length === 0 || supportedMethods.includes(clientInformation.token_endpoint_auth_method))) {
34750
- return clientInformation.token_endpoint_auth_method;
34751
- }
34752
- // If server metadata omits token_endpoint_auth_methods_supported, RFC 8414 §2 says the
34753
- // default is client_secret_basic. RFC 6749 §2.3.1 also requires servers to support HTTP
34754
- // Basic authentication for clients with a secret, making it the safest default.
34755
- if (supportedMethods.length === 0) {
34756
- return hasClientSecret ? 'client_secret_basic' : 'none';
34757
- }
34758
- // Try methods in priority order (most secure first)
34759
- if (hasClientSecret && supportedMethods.includes('client_secret_basic')) {
34760
- return 'client_secret_basic';
34761
- }
34762
- if (hasClientSecret && supportedMethods.includes('client_secret_post')) {
34763
- return 'client_secret_post';
34764
- }
34765
- if (supportedMethods.includes('none')) {
34766
- return 'none';
34767
- }
34768
- // Fallback: use what we have
34769
- return hasClientSecret ? 'client_secret_post' : 'none';
34770
- }
34771
- /**
34772
- * Applies client authentication to the request based on the specified method.
34773
- *
34774
- * Implements OAuth 2.1 client authentication methods:
34775
- * - client_secret_basic: HTTP Basic authentication (RFC 6749 Section 2.3.1)
34776
- * - client_secret_post: Credentials in request body (RFC 6749 Section 2.3.1)
34777
- * - none: Public client authentication (RFC 6749 Section 2.1)
34778
- *
34779
- * @param method - The authentication method to use
34780
- * @param clientInformation - OAuth client information containing credentials
34781
- * @param headers - HTTP headers object to modify
34782
- * @param params - URL search parameters to modify
34783
- * @throws {Error} When required credentials are missing
34784
- */
34785
- function applyClientAuthentication(method, clientInformation, headers, params) {
34786
- const { client_id, client_secret } = clientInformation;
34787
- switch (method) {
34788
- case 'client_secret_basic':
34789
- applyBasicAuth(client_id, client_secret, headers);
34790
- return;
34791
- case 'client_secret_post':
34792
- applyPostAuth(client_id, client_secret, params);
34793
- return;
34794
- case 'none':
34795
- applyPublicAuth(client_id, params);
34796
- return;
34797
- default:
34798
- throw new Error(`Unsupported client authentication method: ${method}`);
34799
- }
34800
- }
34801
- /**
34802
- * Applies HTTP Basic authentication (RFC 6749 Section 2.3.1)
34803
- */
34804
- function applyBasicAuth(clientId, clientSecret, headers) {
34805
- if (!clientSecret) {
34806
- throw new Error('client_secret_basic authentication requires a client_secret');
34807
- }
34808
- const credentials = btoa(`${clientId}:${clientSecret}`);
34809
- headers.set('Authorization', `Basic ${credentials}`);
34810
- }
34811
- /**
34812
- * Applies POST body authentication (RFC 6749 Section 2.3.1)
34813
- */
34814
- function applyPostAuth(clientId, clientSecret, params) {
34815
- params.set('client_id', clientId);
34816
- if (clientSecret) {
34817
- params.set('client_secret', clientSecret);
34818
- }
34819
- }
34820
- /**
34821
- * Applies public client authentication (RFC 6749 Section 2.1)
34822
- */
34823
- function applyPublicAuth(clientId, params) {
34824
- params.set('client_id', clientId);
34825
- }
34826
- /**
34827
- * Parses an OAuth error response from a string or Response object.
34828
- *
34829
- * If the input is a standard OAuth2.0 error response, it will be parsed according to the spec
34830
- * and an instance of the appropriate OAuthError subclass will be returned.
34831
- * If parsing fails, it falls back to a generic ServerError that includes
34832
- * the response status (if available) and original content.
34833
- *
34834
- * @param input - A Response object or string containing the error response
34835
- * @returns A Promise that resolves to an OAuthError instance
34836
- */
34837
- async function parseErrorResponse(input) {
34838
- const statusCode = input instanceof Response ? input.status : undefined;
34839
- const body = input instanceof Response ? await input.text() : input;
34840
- try {
34841
- const result = auth_js_1$1.OAuthErrorResponseSchema.parse(JSON.parse(body));
34842
- const { error, error_description, error_uri } = result;
34843
- const errorClass = errors_js_1$1.OAUTH_ERRORS[error] || errors_js_1$1.ServerError;
34844
- return new errorClass(error_description || '', error_uri);
34845
- }
34846
- catch (error) {
34847
- // Not a valid OAuth error response, but try to inform the user of the raw data anyway
34848
- const errorMessage = `${statusCode ? `HTTP ${statusCode}: ` : ''}Invalid OAuth error response: ${error}. Raw body: ${body}`;
34849
- return new errors_js_1$1.ServerError(errorMessage);
34850
- }
34851
- }
34852
- /**
34853
- * Orchestrates the full auth flow with a server.
34854
- *
34855
- * This can be used as a single entry point for all authorization functionality,
34856
- * instead of linking together the other lower-level functions in this module.
34857
- */
34858
- async function auth(provider, options) {
34859
- try {
34860
- return await authInternal(provider, options);
34861
- }
34862
- catch (error) {
34863
- // Handle recoverable error types by invalidating credentials and retrying
34864
- if (error instanceof errors_js_1$1.InvalidClientError || error instanceof errors_js_1$1.UnauthorizedClientError) {
34865
- await provider.invalidateCredentials?.('all');
34866
- return await authInternal(provider, options);
34867
- }
34868
- else if (error instanceof errors_js_1$1.InvalidGrantError) {
34869
- await provider.invalidateCredentials?.('tokens');
34870
- return await authInternal(provider, options);
34871
- }
34872
- // Throw otherwise
34873
- throw error;
34874
- }
34875
- }
34876
- async function authInternal(provider, { serverUrl, authorizationCode, scope, resourceMetadataUrl, fetchFn }) {
34877
- // Check if the provider has cached discovery state to skip discovery
34878
- const cachedState = await provider.discoveryState?.();
34879
- let resourceMetadata;
34880
- let authorizationServerUrl;
34881
- let metadata;
34882
- // If resourceMetadataUrl is not provided, try to load it from cached state
34883
- // This handles browser redirects where the URL was saved before navigation
34884
- let effectiveResourceMetadataUrl = resourceMetadataUrl;
34885
- if (!effectiveResourceMetadataUrl && cachedState?.resourceMetadataUrl) {
34886
- effectiveResourceMetadataUrl = new URL(cachedState.resourceMetadataUrl);
34887
- }
34888
- if (cachedState?.authorizationServerUrl) {
34889
- // Restore discovery state from cache
34890
- authorizationServerUrl = cachedState.authorizationServerUrl;
34891
- resourceMetadata = cachedState.resourceMetadata;
34892
- metadata =
34893
- cachedState.authorizationServerMetadata ?? (await discoverAuthorizationServerMetadata(authorizationServerUrl, { fetchFn }));
34894
- // If resource metadata wasn't cached, try to fetch it for selectResourceURL
34895
- if (!resourceMetadata) {
34896
- try {
34897
- resourceMetadata = await discoverOAuthProtectedResourceMetadata(serverUrl, { resourceMetadataUrl: effectiveResourceMetadataUrl }, fetchFn);
34898
- }
34899
- catch {
34900
- // RFC 9728 not available — selectResourceURL will handle undefined
34901
- }
34902
- }
34903
- // Re-save if we enriched the cached state with missing metadata
34904
- if (metadata !== cachedState.authorizationServerMetadata || resourceMetadata !== cachedState.resourceMetadata) {
34905
- await provider.saveDiscoveryState?.({
34906
- authorizationServerUrl: String(authorizationServerUrl),
34907
- resourceMetadataUrl: effectiveResourceMetadataUrl?.toString(),
34908
- resourceMetadata,
34909
- authorizationServerMetadata: metadata
34910
- });
34911
- }
34912
- }
34913
- else {
34914
- // Full discovery via RFC 9728
34915
- const serverInfo = await discoverOAuthServerInfo(serverUrl, { resourceMetadataUrl: effectiveResourceMetadataUrl, fetchFn });
34916
- authorizationServerUrl = serverInfo.authorizationServerUrl;
34917
- metadata = serverInfo.authorizationServerMetadata;
34918
- resourceMetadata = serverInfo.resourceMetadata;
34919
- // Persist discovery state for future use
34920
- // TODO: resourceMetadataUrl is only populated when explicitly provided via options
34921
- // or loaded from cached state. The URL derived internally by
34922
- // discoverOAuthProtectedResourceMetadata() is not captured back here.
34923
- await provider.saveDiscoveryState?.({
34924
- authorizationServerUrl: String(authorizationServerUrl),
34925
- resourceMetadataUrl: effectiveResourceMetadataUrl?.toString(),
34926
- resourceMetadata,
34927
- authorizationServerMetadata: metadata
34928
- });
34929
- }
34930
- const resource = await selectResourceURL(serverUrl, provider, resourceMetadata);
34931
- // Apply scope selection strategy (SEP-835):
34932
- // 1. WWW-Authenticate scope (passed via `scope` param)
34933
- // 2. PRM scopes_supported
34934
- // 3. Client metadata scope (user-configured fallback)
34935
- // The resolved scope is used consistently for both DCR and the authorization request.
34936
- const resolvedScope = scope || resourceMetadata?.scopes_supported?.join(' ') || provider.clientMetadata.scope;
34937
- // Handle client registration if needed
34938
- let clientInformation = await Promise.resolve(provider.clientInformation());
34939
- if (!clientInformation) {
34940
- if (authorizationCode !== undefined) {
34941
- throw new Error('Existing OAuth client information is required when exchanging an authorization code');
34942
- }
34943
- const supportsUrlBasedClientId = metadata?.client_id_metadata_document_supported === true;
34944
- const clientMetadataUrl = provider.clientMetadataUrl;
34945
- if (clientMetadataUrl && !isHttpsUrl(clientMetadataUrl)) {
34946
- throw new errors_js_1$1.InvalidClientMetadataError(`clientMetadataUrl must be a valid HTTPS URL with a non-root pathname, got: ${clientMetadataUrl}`);
34947
- }
34948
- const shouldUseUrlBasedClientId = supportsUrlBasedClientId && clientMetadataUrl;
34949
- if (shouldUseUrlBasedClientId) {
34950
- // SEP-991: URL-based Client IDs
34951
- clientInformation = {
34952
- client_id: clientMetadataUrl
34953
- };
34954
- await provider.saveClientInformation?.(clientInformation);
34955
- }
34956
- else {
34957
- // Fallback to dynamic registration
34958
- if (!provider.saveClientInformation) {
34959
- throw new Error('OAuth client information must be saveable for dynamic registration');
34960
- }
34961
- const fullInformation = await registerClient(authorizationServerUrl, {
34962
- metadata,
34963
- clientMetadata: provider.clientMetadata,
34964
- scope: resolvedScope,
34965
- fetchFn
34966
- });
34967
- await provider.saveClientInformation(fullInformation);
34968
- clientInformation = fullInformation;
34969
- }
34970
- }
34971
- // Non-interactive flows (e.g., client_credentials, jwt-bearer) don't need a redirect URL
34972
- const nonInteractiveFlow = !provider.redirectUrl;
34973
- // Exchange authorization code for tokens, or fetch tokens directly for non-interactive flows
34974
- if (authorizationCode !== undefined || nonInteractiveFlow) {
34975
- const tokens = await fetchToken(provider, authorizationServerUrl, {
34976
- metadata,
34977
- resource,
34978
- authorizationCode,
34979
- fetchFn
34980
- });
34981
- await provider.saveTokens(tokens);
34982
- return 'AUTHORIZED';
34983
- }
34984
- const tokens = await provider.tokens();
34985
- // Handle token refresh or new authorization
34986
- if (tokens?.refresh_token) {
34987
- try {
34988
- // Attempt to refresh the token
34989
- const newTokens = await refreshAuthorization(authorizationServerUrl, {
34990
- metadata,
34991
- clientInformation,
34992
- refreshToken: tokens.refresh_token,
34993
- resource,
34994
- addClientAuthentication: provider.addClientAuthentication,
34995
- fetchFn
34996
- });
34997
- await provider.saveTokens(newTokens);
34998
- return 'AUTHORIZED';
34999
- }
35000
- catch (error) {
35001
- // If this is a ServerError, or an unknown type, log it out and try to continue. Otherwise, escalate so we can fix things and retry.
35002
- if (!(error instanceof errors_js_1$1.OAuthError) || error instanceof errors_js_1$1.ServerError) ;
35003
- else {
35004
- // Refresh failed for another reason, re-throw
35005
- throw error;
35006
- }
35007
- }
35008
- }
35009
- const state = provider.state ? await provider.state() : undefined;
35010
- // Start new authorization flow
35011
- const { authorizationUrl, codeVerifier } = await startAuthorization(authorizationServerUrl, {
35012
- metadata,
35013
- clientInformation,
35014
- state,
35015
- redirectUrl: provider.redirectUrl,
35016
- scope: resolvedScope,
35017
- resource
35018
- });
35019
- await provider.saveCodeVerifier(codeVerifier);
35020
- await provider.redirectToAuthorization(authorizationUrl);
35021
- return 'REDIRECT';
35022
- }
35023
- /**
35024
- * SEP-991: URL-based Client IDs
35025
- * Validate that the client_id is a valid URL with https scheme
35026
- */
35027
- function isHttpsUrl(value) {
35028
- if (!value)
35029
- return false;
35030
- try {
35031
- const url = new URL(value);
35032
- return url.protocol === 'https:' && url.pathname !== '/';
35033
- }
35034
- catch {
35035
- return false;
35036
- }
35037
- }
35038
- async function selectResourceURL(serverUrl, provider, resourceMetadata) {
35039
- const defaultResource = (0, auth_utils_js_1.resourceUrlFromServerUrl)(serverUrl);
35040
- // If provider has custom validation, delegate to it
35041
- if (provider.validateResourceURL) {
35042
- return await provider.validateResourceURL(defaultResource, resourceMetadata?.resource);
35043
- }
35044
- // Only include resource parameter when Protected Resource Metadata is present
35045
- if (!resourceMetadata) {
35046
- return undefined;
35047
- }
35048
- // Validate that the metadata's resource is compatible with our request
35049
- if (!(0, auth_utils_js_1.checkResourceAllowed)({ requestedResource: defaultResource, configuredResource: resourceMetadata.resource })) {
35050
- throw new Error(`Protected resource ${resourceMetadata.resource} does not match expected ${defaultResource} (or origin)`);
35051
- }
35052
- // Prefer the resource from metadata since it's what the server is telling us to request
35053
- return new URL(resourceMetadata.resource);
35054
- }
35055
- /**
35056
- * Extract resource_metadata, scope, and error from WWW-Authenticate header.
35057
- */
35058
- function extractWWWAuthenticateParams(res) {
35059
- const authenticateHeader = res.headers.get('WWW-Authenticate');
35060
- if (!authenticateHeader) {
35061
- return {};
35062
- }
35063
- const [type, scheme] = authenticateHeader.split(' ');
35064
- if (type.toLowerCase() !== 'bearer' || !scheme) {
35065
- return {};
35066
- }
35067
- const resourceMetadataMatch = extractFieldFromWwwAuth(res, 'resource_metadata') || undefined;
35068
- let resourceMetadataUrl;
35069
- if (resourceMetadataMatch) {
35070
- try {
35071
- resourceMetadataUrl = new URL(resourceMetadataMatch);
35072
- }
35073
- catch {
35074
- // Ignore invalid URL
35075
- }
35076
- }
35077
- const scope = extractFieldFromWwwAuth(res, 'scope') || undefined;
35078
- const error = extractFieldFromWwwAuth(res, 'error') || undefined;
35079
- return {
35080
- resourceMetadataUrl,
35081
- scope,
35082
- error
35083
- };
35084
- }
35085
- /**
35086
- * Extracts a specific field's value from the WWW-Authenticate header string.
35087
- *
35088
- * @param response The HTTP response object containing the headers.
35089
- * @param fieldName The name of the field to extract (e.g., "realm", "nonce").
35090
- * @returns The field value
35091
- */
35092
- function extractFieldFromWwwAuth(response, fieldName) {
35093
- const wwwAuthHeader = response.headers.get('WWW-Authenticate');
35094
- if (!wwwAuthHeader) {
35095
- return null;
35096
- }
35097
- const pattern = new RegExp(`${fieldName}=(?:"([^"]+)"|([^\\s,]+))`);
35098
- const match = wwwAuthHeader.match(pattern);
35099
- if (match) {
35100
- // Pattern matches: field_name="value" or field_name=value (unquoted)
35101
- return match[1] || match[2];
35102
- }
35103
- return null;
35104
- }
35105
- /**
35106
- * Extract resource_metadata from response header.
35107
- * @deprecated Use `extractWWWAuthenticateParams` instead.
35108
- */
35109
- function extractResourceMetadataUrl(res) {
35110
- const authenticateHeader = res.headers.get('WWW-Authenticate');
35111
- if (!authenticateHeader) {
35112
- return undefined;
35113
- }
35114
- const [type, scheme] = authenticateHeader.split(' ');
35115
- if (type.toLowerCase() !== 'bearer' || !scheme) {
35116
- return undefined;
35117
- }
35118
- const regex = /resource_metadata="([^"]*)"/;
35119
- const match = regex.exec(authenticateHeader);
35120
- if (!match) {
35121
- return undefined;
35122
- }
35123
- try {
35124
- return new URL(match[1]);
35125
- }
35126
- catch {
35127
- return undefined;
35128
- }
35129
- }
35130
- /**
35131
- * Looks up RFC 9728 OAuth 2.0 Protected Resource Metadata.
35132
- *
35133
- * If the server returns a 404 for the well-known endpoint, this function will
35134
- * return `undefined`. Any other errors will be thrown as exceptions.
35135
- */
35136
- async function discoverOAuthProtectedResourceMetadata(serverUrl, opts, fetchFn = fetch) {
35137
- const response = await discoverMetadataWithFallback(serverUrl, 'oauth-protected-resource', fetchFn, {
35138
- protocolVersion: opts?.protocolVersion,
35139
- metadataUrl: opts?.resourceMetadataUrl
35140
- });
35141
- if (!response || response.status === 404) {
35142
- await response?.body?.cancel();
35143
- throw new Error(`Resource server does not implement OAuth 2.0 Protected Resource Metadata.`);
35144
- }
35145
- if (!response.ok) {
35146
- await response.body?.cancel();
35147
- throw new Error(`HTTP ${response.status} trying to load well-known OAuth protected resource metadata.`);
35148
- }
35149
- return auth_js_2.OAuthProtectedResourceMetadataSchema.parse(await response.json());
35150
- }
35151
- /**
35152
- * Helper function to handle fetch with CORS retry logic
35153
- */
35154
- async function fetchWithCorsRetry(url, headers, fetchFn = fetch) {
35155
- try {
35156
- return await fetchFn(url, { headers });
35157
- }
35158
- catch (error) {
35159
- if (error instanceof TypeError) {
35160
- if (headers) {
35161
- // CORS errors come back as TypeError, retry without headers
35162
- return fetchWithCorsRetry(url, undefined, fetchFn);
35163
- }
35164
- else {
35165
- // We're getting CORS errors on retry too, return undefined
35166
- return undefined;
35167
- }
35168
- }
35169
- throw error;
35170
- }
35171
- }
35172
- /**
35173
- * Constructs the well-known path for auth-related metadata discovery
35174
- */
35175
- function buildWellKnownPath(wellKnownPrefix, pathname = '', options = {}) {
35176
- // Strip trailing slash from pathname to avoid double slashes
35177
- if (pathname.endsWith('/')) {
35178
- pathname = pathname.slice(0, -1);
35179
- }
35180
- return options.prependPathname ? `${pathname}/.well-known/${wellKnownPrefix}` : `/.well-known/${wellKnownPrefix}${pathname}`;
35181
- }
35182
- /**
35183
- * Tries to discover OAuth metadata at a specific URL
35184
- */
35185
- async function tryMetadataDiscovery(url, protocolVersion, fetchFn = fetch) {
35186
- const headers = {
35187
- 'MCP-Protocol-Version': protocolVersion
35188
- };
35189
- return await fetchWithCorsRetry(url, headers, fetchFn);
35190
- }
35191
- /**
35192
- * Determines if fallback to root discovery should be attempted
35193
- */
35194
- function shouldAttemptFallback(response, pathname) {
35195
- return !response || (response.status >= 400 && response.status < 500 && pathname !== '/');
35196
- }
35197
- /**
35198
- * Generic function for discovering OAuth metadata with fallback support
35199
- */
35200
- async function discoverMetadataWithFallback(serverUrl, wellKnownType, fetchFn, opts) {
35201
- const issuer = new URL(serverUrl);
35202
- const protocolVersion = opts?.protocolVersion ?? types_js_1$5.LATEST_PROTOCOL_VERSION;
35203
- let url;
35204
- if (opts?.metadataUrl) {
35205
- url = new URL(opts.metadataUrl);
35206
- }
35207
- else {
35208
- // Try path-aware discovery first
35209
- const wellKnownPath = buildWellKnownPath(wellKnownType, issuer.pathname);
35210
- url = new URL(wellKnownPath, opts?.metadataServerUrl ?? issuer);
35211
- url.search = issuer.search;
35212
- }
35213
- let response = await tryMetadataDiscovery(url, protocolVersion, fetchFn);
35214
- // If path-aware discovery fails with 404 and we're not already at root, try fallback to root discovery
35215
- if (!opts?.metadataUrl && shouldAttemptFallback(response, issuer.pathname)) {
35216
- const rootUrl = new URL(`/.well-known/${wellKnownType}`, issuer);
35217
- response = await tryMetadataDiscovery(rootUrl, protocolVersion, fetchFn);
35218
- }
35219
- return response;
35220
- }
35221
- /**
35222
- * Looks up RFC 8414 OAuth 2.0 Authorization Server Metadata.
35223
- *
35224
- * If the server returns a 404 for the well-known endpoint, this function will
35225
- * return `undefined`. Any other errors will be thrown as exceptions.
35226
- *
35227
- * @deprecated This function is deprecated in favor of `discoverAuthorizationServerMetadata`.
35228
- */
35229
- async function discoverOAuthMetadata(issuer, { authorizationServerUrl, protocolVersion } = {}, fetchFn = fetch) {
35230
- if (typeof issuer === 'string') {
35231
- issuer = new URL(issuer);
35232
- }
35233
- if (!authorizationServerUrl) {
35234
- authorizationServerUrl = issuer;
35235
- }
35236
- if (typeof authorizationServerUrl === 'string') {
35237
- authorizationServerUrl = new URL(authorizationServerUrl);
35238
- }
35239
- protocolVersion ?? (protocolVersion = types_js_1$5.LATEST_PROTOCOL_VERSION);
35240
- const response = await discoverMetadataWithFallback(authorizationServerUrl, 'oauth-authorization-server', fetchFn, {
35241
- protocolVersion,
35242
- metadataServerUrl: authorizationServerUrl
35243
- });
35244
- if (!response || response.status === 404) {
35245
- await response?.body?.cancel();
35246
- return undefined;
35247
- }
35248
- if (!response.ok) {
35249
- await response.body?.cancel();
35250
- throw new Error(`HTTP ${response.status} trying to load well-known OAuth metadata`);
35251
- }
35252
- return auth_js_2.OAuthMetadataSchema.parse(await response.json());
35253
- }
35254
- /**
35255
- * Builds a list of discovery URLs to try for authorization server metadata.
35256
- * URLs are returned in priority order:
35257
- * 1. OAuth metadata at the given URL
35258
- * 2. OIDC metadata endpoints at the given URL
35259
- */
35260
- function buildDiscoveryUrls(authorizationServerUrl) {
35261
- const url = typeof authorizationServerUrl === 'string' ? new URL(authorizationServerUrl) : authorizationServerUrl;
35262
- const hasPath = url.pathname !== '/';
35263
- const urlsToTry = [];
35264
- if (!hasPath) {
35265
- // Root path: https://example.com/.well-known/oauth-authorization-server
35266
- urlsToTry.push({
35267
- url: new URL('/.well-known/oauth-authorization-server', url.origin),
35268
- type: 'oauth'
35269
- });
35270
- // OIDC: https://example.com/.well-known/openid-configuration
35271
- urlsToTry.push({
35272
- url: new URL(`/.well-known/openid-configuration`, url.origin),
35273
- type: 'oidc'
35274
- });
35275
- return urlsToTry;
35276
- }
35277
- // Strip trailing slash from pathname to avoid double slashes
35278
- let pathname = url.pathname;
35279
- if (pathname.endsWith('/')) {
35280
- pathname = pathname.slice(0, -1);
35281
- }
35282
- // 1. OAuth metadata at the given URL
35283
- // Insert well-known before the path: https://example.com/.well-known/oauth-authorization-server/tenant1
35284
- urlsToTry.push({
35285
- url: new URL(`/.well-known/oauth-authorization-server${pathname}`, url.origin),
35286
- type: 'oauth'
35287
- });
35288
- // 2. OIDC metadata endpoints
35289
- // RFC 8414 style: Insert /.well-known/openid-configuration before the path
35290
- urlsToTry.push({
35291
- url: new URL(`/.well-known/openid-configuration${pathname}`, url.origin),
35292
- type: 'oidc'
35293
- });
35294
- // OIDC Discovery 1.0 style: Append /.well-known/openid-configuration after the path
35295
- urlsToTry.push({
35296
- url: new URL(`${pathname}/.well-known/openid-configuration`, url.origin),
35297
- type: 'oidc'
35298
- });
35299
- return urlsToTry;
35300
- }
35301
- /**
35302
- * Discovers authorization server metadata with support for RFC 8414 OAuth 2.0 Authorization Server Metadata
35303
- * and OpenID Connect Discovery 1.0 specifications.
35304
- *
35305
- * This function implements a fallback strategy for authorization server discovery:
35306
- * 1. Attempts RFC 8414 OAuth metadata discovery first
35307
- * 2. If OAuth discovery fails, falls back to OpenID Connect Discovery
35308
- *
35309
- * @param authorizationServerUrl - The authorization server URL obtained from the MCP Server's
35310
- * protected resource metadata, or the MCP server's URL if the
35311
- * metadata was not found.
35312
- * @param options - Configuration options
35313
- * @param options.fetchFn - Optional fetch function for making HTTP requests, defaults to global fetch
35314
- * @param options.protocolVersion - MCP protocol version to use, defaults to LATEST_PROTOCOL_VERSION
35315
- * @returns Promise resolving to authorization server metadata, or undefined if discovery fails
35316
- */
35317
- async function discoverAuthorizationServerMetadata(authorizationServerUrl, { fetchFn = fetch, protocolVersion = types_js_1$5.LATEST_PROTOCOL_VERSION } = {}) {
35318
- const headers = {
35319
- 'MCP-Protocol-Version': protocolVersion,
35320
- Accept: 'application/json'
35321
- };
35322
- // Get the list of URLs to try
35323
- const urlsToTry = buildDiscoveryUrls(authorizationServerUrl);
35324
- // Try each URL in order
35325
- for (const { url: endpointUrl, type } of urlsToTry) {
35326
- const response = await fetchWithCorsRetry(endpointUrl, headers, fetchFn);
35327
- if (!response) {
35328
- /**
35329
- * CORS error occurred - don't throw as the endpoint may not allow CORS,
35330
- * continue trying other possible endpoints
35331
- */
35332
- continue;
35333
- }
35334
- if (!response.ok) {
35335
- await response.body?.cancel();
35336
- // Continue looking for any 4xx response code.
35337
- if (response.status >= 400 && response.status < 500) {
35338
- continue; // Try next URL
35339
- }
35340
- throw new Error(`HTTP ${response.status} trying to load ${type === 'oauth' ? 'OAuth' : 'OpenID provider'} metadata from ${endpointUrl}`);
35341
- }
35342
- // Parse and validate based on type
35343
- if (type === 'oauth') {
35344
- return auth_js_2.OAuthMetadataSchema.parse(await response.json());
35345
- }
35346
- else {
35347
- return auth_js_1$1.OpenIdProviderDiscoveryMetadataSchema.parse(await response.json());
35348
- }
35349
- }
35350
- return undefined;
35351
- }
35352
- /**
35353
- * Discovers the authorization server for an MCP server following
35354
- * {@link https://datatracker.ietf.org/doc/html/rfc9728 | RFC 9728} (OAuth 2.0 Protected
35355
- * Resource Metadata), with fallback to treating the server URL as the
35356
- * authorization server.
35357
- *
35358
- * This function combines two discovery steps into one call:
35359
- * 1. Probes `/.well-known/oauth-protected-resource` on the MCP server to find the
35360
- * authorization server URL (RFC 9728).
35361
- * 2. Fetches authorization server metadata from that URL (RFC 8414 / OpenID Connect Discovery).
35362
- *
35363
- * Use this when you need the authorization server metadata for operations outside the
35364
- * {@linkcode auth} orchestrator, such as token refresh or token revocation.
35365
- *
35366
- * @param serverUrl - The MCP resource server URL
35367
- * @param opts - Optional configuration
35368
- * @param opts.resourceMetadataUrl - Override URL for the protected resource metadata endpoint
35369
- * @param opts.fetchFn - Custom fetch function for HTTP requests
35370
- * @returns Authorization server URL, metadata, and resource metadata (if available)
35371
- */
35372
- async function discoverOAuthServerInfo(serverUrl, opts) {
35373
- let resourceMetadata;
35374
- let authorizationServerUrl;
35375
- try {
35376
- resourceMetadata = await discoverOAuthProtectedResourceMetadata(serverUrl, { resourceMetadataUrl: opts?.resourceMetadataUrl }, opts?.fetchFn);
35377
- if (resourceMetadata.authorization_servers && resourceMetadata.authorization_servers.length > 0) {
35378
- authorizationServerUrl = resourceMetadata.authorization_servers[0];
35379
- }
35380
- }
35381
- catch {
35382
- // RFC 9728 not supported -- fall back to treating the server URL as the authorization server
35383
- }
35384
- // If we don't get a valid authorization server from protected resource metadata,
35385
- // fall back to the legacy MCP spec behavior: MCP server base URL acts as the authorization server
35386
- if (!authorizationServerUrl) {
35387
- authorizationServerUrl = String(new URL('/', serverUrl));
35388
- }
35389
- const authorizationServerMetadata = await discoverAuthorizationServerMetadata(authorizationServerUrl, { fetchFn: opts?.fetchFn });
35390
- return {
35391
- authorizationServerUrl,
35392
- authorizationServerMetadata,
35393
- resourceMetadata
35394
- };
35395
- }
35396
- /**
35397
- * Begins the authorization flow with the given server, by generating a PKCE challenge and constructing the authorization URL.
35398
- */
35399
- async function startAuthorization(authorizationServerUrl, { metadata, clientInformation, redirectUrl, scope, state, resource }) {
35400
- let authorizationUrl;
35401
- if (metadata) {
35402
- authorizationUrl = new URL(metadata.authorization_endpoint);
35403
- if (!metadata.response_types_supported.includes(AUTHORIZATION_CODE_RESPONSE_TYPE)) {
35404
- throw new Error(`Incompatible auth server: does not support response type ${AUTHORIZATION_CODE_RESPONSE_TYPE}`);
35405
- }
35406
- if (metadata.code_challenge_methods_supported &&
35407
- !metadata.code_challenge_methods_supported.includes(AUTHORIZATION_CODE_CHALLENGE_METHOD)) {
35408
- throw new Error(`Incompatible auth server: does not support code challenge method ${AUTHORIZATION_CODE_CHALLENGE_METHOD}`);
35409
- }
35410
- }
35411
- else {
35412
- authorizationUrl = new URL('/authorize', authorizationServerUrl);
35413
- }
35414
- // Generate PKCE challenge
35415
- const challenge = await (0, pkce_challenge_1.default)();
35416
- const codeVerifier = challenge.code_verifier;
35417
- const codeChallenge = challenge.code_challenge;
35418
- authorizationUrl.searchParams.set('response_type', AUTHORIZATION_CODE_RESPONSE_TYPE);
35419
- authorizationUrl.searchParams.set('client_id', clientInformation.client_id);
35420
- authorizationUrl.searchParams.set('code_challenge', codeChallenge);
35421
- authorizationUrl.searchParams.set('code_challenge_method', AUTHORIZATION_CODE_CHALLENGE_METHOD);
35422
- authorizationUrl.searchParams.set('redirect_uri', String(redirectUrl));
35423
- if (state) {
35424
- authorizationUrl.searchParams.set('state', state);
35425
- }
35426
- if (scope) {
35427
- authorizationUrl.searchParams.set('scope', scope);
35428
- }
35429
- if (scope?.includes('offline_access')) {
35430
- // if the request includes the OIDC-only "offline_access" scope,
35431
- // we need to set the prompt to "consent" to ensure the user is prompted to grant offline access
35432
- // https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
35433
- authorizationUrl.searchParams.append('prompt', 'consent');
35434
- }
35435
- if (resource) {
35436
- authorizationUrl.searchParams.set('resource', resource.href);
35437
- }
35438
- return { authorizationUrl, codeVerifier };
35439
- }
35440
- /**
35441
- * Prepares token request parameters for an authorization code exchange.
35442
- *
35443
- * This is the default implementation used by fetchToken when the provider
35444
- * doesn't implement prepareTokenRequest.
35445
- *
35446
- * @param authorizationCode - The authorization code received from the authorization endpoint
35447
- * @param codeVerifier - The PKCE code verifier
35448
- * @param redirectUri - The redirect URI used in the authorization request
35449
- * @returns URLSearchParams for the authorization_code grant
35450
- */
35451
- function prepareAuthorizationCodeRequest(authorizationCode, codeVerifier, redirectUri) {
35452
- return new URLSearchParams({
35453
- grant_type: 'authorization_code',
35454
- code: authorizationCode,
35455
- code_verifier: codeVerifier,
35456
- redirect_uri: String(redirectUri)
35457
- });
35458
- }
35459
- /**
35460
- * Internal helper to execute a token request with the given parameters.
35461
- * Used by exchangeAuthorization, refreshAuthorization, and fetchToken.
35462
- */
35463
- async function executeTokenRequest(authorizationServerUrl, { metadata, tokenRequestParams, clientInformation, addClientAuthentication, resource, fetchFn }) {
35464
- const tokenUrl = metadata?.token_endpoint ? new URL(metadata.token_endpoint) : new URL('/token', authorizationServerUrl);
35465
- const headers = new Headers({
35466
- 'Content-Type': 'application/x-www-form-urlencoded',
35467
- Accept: 'application/json'
35468
- });
35469
- if (resource) {
35470
- tokenRequestParams.set('resource', resource.href);
35471
- }
35472
- if (addClientAuthentication) {
35473
- await addClientAuthentication(headers, tokenRequestParams, tokenUrl, metadata);
35474
- }
35475
- else if (clientInformation) {
35476
- const supportedMethods = metadata?.token_endpoint_auth_methods_supported ?? [];
35477
- const authMethod = selectClientAuthMethod(clientInformation, supportedMethods);
35478
- applyClientAuthentication(authMethod, clientInformation, headers, tokenRequestParams);
35479
- }
35480
- const response = await (fetchFn ?? fetch)(tokenUrl, {
35481
- method: 'POST',
35482
- headers,
35483
- body: tokenRequestParams
35484
- });
35485
- if (!response.ok) {
35486
- throw await parseErrorResponse(response);
35487
- }
35488
- return auth_js_2.OAuthTokensSchema.parse(await response.json());
35489
- }
35490
- /**
35491
- * Exchanges an authorization code for an access token with the given server.
35492
- *
35493
- * Supports multiple client authentication methods as specified in OAuth 2.1:
35494
- * - Automatically selects the best authentication method based on server support
35495
- * - Falls back to appropriate defaults when server metadata is unavailable
35496
- *
35497
- * @param authorizationServerUrl - The authorization server's base URL
35498
- * @param options - Configuration object containing client info, auth code, etc.
35499
- * @returns Promise resolving to OAuth tokens
35500
- * @throws {Error} When token exchange fails or authentication is invalid
35501
- */
35502
- async function exchangeAuthorization(authorizationServerUrl, { metadata, clientInformation, authorizationCode, codeVerifier, redirectUri, resource, addClientAuthentication, fetchFn }) {
35503
- const tokenRequestParams = prepareAuthorizationCodeRequest(authorizationCode, codeVerifier, redirectUri);
35504
- return executeTokenRequest(authorizationServerUrl, {
35505
- metadata,
35506
- tokenRequestParams,
35507
- clientInformation,
35508
- addClientAuthentication,
35509
- resource,
35510
- fetchFn
35511
- });
35512
- }
35513
- /**
35514
- * Exchange a refresh token for an updated access token.
35515
- *
35516
- * Supports multiple client authentication methods as specified in OAuth 2.1:
35517
- * - Automatically selects the best authentication method based on server support
35518
- * - Preserves the original refresh token if a new one is not returned
35519
- *
35520
- * @param authorizationServerUrl - The authorization server's base URL
35521
- * @param options - Configuration object containing client info, refresh token, etc.
35522
- * @returns Promise resolving to OAuth tokens (preserves original refresh_token if not replaced)
35523
- * @throws {Error} When token refresh fails or authentication is invalid
35524
- */
35525
- async function refreshAuthorization(authorizationServerUrl, { metadata, clientInformation, refreshToken, resource, addClientAuthentication, fetchFn }) {
35526
- const tokenRequestParams = new URLSearchParams({
35527
- grant_type: 'refresh_token',
35528
- refresh_token: refreshToken
35529
- });
35530
- const tokens = await executeTokenRequest(authorizationServerUrl, {
35531
- metadata,
35532
- tokenRequestParams,
35533
- clientInformation,
35534
- addClientAuthentication,
35535
- resource,
35536
- fetchFn
35537
- });
35538
- // Preserve original refresh token if server didn't return a new one
35539
- return { refresh_token: refreshToken, ...tokens };
35540
- }
35541
- /**
35542
- * Unified token fetching that works with any grant type via provider.prepareTokenRequest().
35543
- *
35544
- * This function provides a single entry point for obtaining tokens regardless of the
35545
- * OAuth grant type. The provider's prepareTokenRequest() method determines which grant
35546
- * to use and supplies the grant-specific parameters.
35547
- *
35548
- * @param provider - OAuth client provider that implements prepareTokenRequest()
35549
- * @param authorizationServerUrl - The authorization server's base URL
35550
- * @param options - Configuration for the token request
35551
- * @returns Promise resolving to OAuth tokens
35552
- * @throws {Error} When provider doesn't implement prepareTokenRequest or token fetch fails
35553
- *
35554
- * @example
35555
- * // Provider for client_credentials:
35556
- * class MyProvider implements OAuthClientProvider {
35557
- * prepareTokenRequest(scope) {
35558
- * const params = new URLSearchParams({ grant_type: 'client_credentials' });
35559
- * if (scope) params.set('scope', scope);
35560
- * return params;
35561
- * }
35562
- * // ... other methods
35563
- * }
35564
- *
35565
- * const tokens = await fetchToken(provider, authServerUrl, { metadata });
35566
- */
35567
- async function fetchToken(provider, authorizationServerUrl, { metadata, resource, authorizationCode, fetchFn } = {}) {
35568
- const scope = provider.clientMetadata.scope;
35569
- // Use provider's prepareTokenRequest if available, otherwise fall back to authorization_code
35570
- let tokenRequestParams;
35571
- if (provider.prepareTokenRequest) {
35572
- tokenRequestParams = await provider.prepareTokenRequest(scope);
35573
- }
35574
- // Default to authorization_code grant if no custom prepareTokenRequest
35575
- if (!tokenRequestParams) {
35576
- if (!authorizationCode) {
35577
- throw new Error('Either provider.prepareTokenRequest() or authorizationCode is required');
35578
- }
35579
- if (!provider.redirectUrl) {
35580
- throw new Error('redirectUrl is required for authorization_code flow');
35581
- }
35582
- const codeVerifier = await provider.codeVerifier();
35583
- tokenRequestParams = prepareAuthorizationCodeRequest(authorizationCode, codeVerifier, provider.redirectUrl);
35584
- }
35585
- const clientInformation = await provider.clientInformation();
35586
- return executeTokenRequest(authorizationServerUrl, {
35587
- metadata,
35588
- tokenRequestParams,
35589
- clientInformation: clientInformation ?? undefined,
35590
- addClientAuthentication: provider.addClientAuthentication,
35591
- resource,
35592
- fetchFn
35593
- });
34727
+ class InvalidTokenError extends OAuthError {
34728
+ }
34729
+ errors$2.InvalidTokenError = InvalidTokenError;
34730
+ InvalidTokenError.errorCode = 'invalid_token';
34731
+ /**
34732
+ * Method not allowed error - The HTTP method used is not allowed for this endpoint.
34733
+ * (Custom, non-standard error)
34734
+ */
34735
+ class MethodNotAllowedError extends OAuthError {
34736
+ }
34737
+ errors$2.MethodNotAllowedError = MethodNotAllowedError;
34738
+ MethodNotAllowedError.errorCode = 'method_not_allowed';
34739
+ /**
34740
+ * Too many requests error - Rate limit exceeded.
34741
+ * (Custom, non-standard error based on RFC 6585)
34742
+ */
34743
+ class TooManyRequestsError extends OAuthError {
34744
+ }
34745
+ errors$2.TooManyRequestsError = TooManyRequestsError;
34746
+ TooManyRequestsError.errorCode = 'too_many_requests';
34747
+ /**
34748
+ * Invalid client metadata error - The client metadata is invalid.
34749
+ * (Custom error for dynamic client registration - RFC 7591)
34750
+ */
34751
+ class InvalidClientMetadataError extends OAuthError {
34752
+ }
34753
+ errors$2.InvalidClientMetadataError = InvalidClientMetadataError;
34754
+ InvalidClientMetadataError.errorCode = 'invalid_client_metadata';
34755
+ /**
34756
+ * Insufficient scope error - The request requires higher privileges than provided by the access token.
34757
+ */
34758
+ class InsufficientScopeError extends OAuthError {
34759
+ }
34760
+ errors$2.InsufficientScopeError = InsufficientScopeError;
34761
+ InsufficientScopeError.errorCode = 'insufficient_scope';
34762
+ /**
34763
+ * Invalid target error - The requested resource is invalid, missing, unknown, or malformed.
34764
+ * (Custom error for resource indicators - RFC 8707)
34765
+ */
34766
+ class InvalidTargetError extends OAuthError {
34767
+ }
34768
+ errors$2.InvalidTargetError = InvalidTargetError;
34769
+ InvalidTargetError.errorCode = 'invalid_target';
34770
+ /**
34771
+ * A utility class for defining one-off error codes
34772
+ */
34773
+ class CustomOAuthError extends OAuthError {
34774
+ constructor(customErrorCode, message, errorUri) {
34775
+ super(message, errorUri);
34776
+ this.customErrorCode = customErrorCode;
34777
+ }
34778
+ get errorCode() {
34779
+ return this.customErrorCode;
34780
+ }
34781
+ }
34782
+ errors$2.CustomOAuthError = CustomOAuthError;
34783
+ /**
34784
+ * A full list of all OAuthErrors, enabling parsing from error responses
34785
+ */
34786
+ errors$2.OAUTH_ERRORS = {
34787
+ [InvalidRequestError.errorCode]: InvalidRequestError,
34788
+ [InvalidClientError.errorCode]: InvalidClientError,
34789
+ [InvalidGrantError.errorCode]: InvalidGrantError,
34790
+ [UnauthorizedClientError.errorCode]: UnauthorizedClientError,
34791
+ [UnsupportedGrantTypeError.errorCode]: UnsupportedGrantTypeError,
34792
+ [InvalidScopeError.errorCode]: InvalidScopeError,
34793
+ [AccessDeniedError.errorCode]: AccessDeniedError,
34794
+ [ServerError.errorCode]: ServerError,
34795
+ [TemporarilyUnavailableError.errorCode]: TemporarilyUnavailableError,
34796
+ [UnsupportedResponseTypeError.errorCode]: UnsupportedResponseTypeError,
34797
+ [UnsupportedTokenTypeError.errorCode]: UnsupportedTokenTypeError,
34798
+ [InvalidTokenError.errorCode]: InvalidTokenError,
34799
+ [MethodNotAllowedError.errorCode]: MethodNotAllowedError,
34800
+ [TooManyRequestsError.errorCode]: TooManyRequestsError,
34801
+ [InvalidClientMetadataError.errorCode]: InvalidClientMetadataError,
34802
+ [InsufficientScopeError.errorCode]: InsufficientScopeError,
34803
+ [InvalidTargetError.errorCode]: InvalidTargetError
34804
+ };
34805
+
34806
+ return errors$2;
35594
34807
  }
35595
- /**
35596
- * Performs OAuth 2.0 Dynamic Client Registration according to RFC 7591.
35597
- *
35598
- * If `scope` is provided, it overrides `clientMetadata.scope` in the registration
35599
- * request body. This allows callers to apply the Scope Selection Strategy (SEP-835)
35600
- * consistently across both DCR and the subsequent authorization request.
35601
- */
35602
- async function registerClient(authorizationServerUrl, { metadata, clientMetadata, scope, fetchFn }) {
35603
- let registrationUrl;
35604
- if (metadata) {
35605
- if (!metadata.registration_endpoint) {
35606
- throw new Error('Incompatible auth server: does not support dynamic client registration');
35607
- }
35608
- registrationUrl = new URL(metadata.registration_endpoint);
35609
- }
35610
- else {
35611
- registrationUrl = new URL('/register', authorizationServerUrl);
35612
- }
35613
- const response = await (fetchFn ?? fetch)(registrationUrl, {
35614
- method: 'POST',
35615
- headers: {
35616
- 'Content-Type': 'application/json'
35617
- },
35618
- body: JSON.stringify({
35619
- ...clientMetadata,
35620
- ...(scope !== undefined ? { scope } : {})
35621
- })
35622
- });
35623
- if (!response.ok) {
35624
- throw await parseErrorResponse(response);
35625
- }
35626
- return auth_js_2.OAuthClientInformationFullSchema.parse(await response.json());
34808
+
34809
+ var hasRequiredAuth;
34810
+
34811
+ function requireAuth () {
34812
+ if (hasRequiredAuth) return auth$1;
34813
+ hasRequiredAuth = 1;
34814
+ var __importDefault = (commonjsGlobal && commonjsGlobal.__importDefault) || function (mod) {
34815
+ return (mod && mod.__esModule) ? mod : { "default": mod };
34816
+ };
34817
+ Object.defineProperty(auth$1, "__esModule", { value: true });
34818
+ auth$1.UnauthorizedError = void 0;
34819
+ auth$1.selectClientAuthMethod = selectClientAuthMethod;
34820
+ auth$1.parseErrorResponse = parseErrorResponse;
34821
+ auth$1.auth = auth;
34822
+ auth$1.isHttpsUrl = isHttpsUrl;
34823
+ auth$1.selectResourceURL = selectResourceURL;
34824
+ auth$1.extractWWWAuthenticateParams = extractWWWAuthenticateParams;
34825
+ auth$1.extractResourceMetadataUrl = extractResourceMetadataUrl;
34826
+ auth$1.discoverOAuthProtectedResourceMetadata = discoverOAuthProtectedResourceMetadata;
34827
+ auth$1.discoverOAuthMetadata = discoverOAuthMetadata;
34828
+ auth$1.buildDiscoveryUrls = buildDiscoveryUrls;
34829
+ auth$1.discoverAuthorizationServerMetadata = discoverAuthorizationServerMetadata;
34830
+ auth$1.discoverOAuthServerInfo = discoverOAuthServerInfo;
34831
+ auth$1.startAuthorization = startAuthorization;
34832
+ auth$1.prepareAuthorizationCodeRequest = prepareAuthorizationCodeRequest;
34833
+ auth$1.exchangeAuthorization = exchangeAuthorization;
34834
+ auth$1.refreshAuthorization = refreshAuthorization;
34835
+ auth$1.fetchToken = fetchToken;
34836
+ auth$1.registerClient = registerClient;
34837
+ const pkce_challenge_1 = __importDefault(require$$0$7);
34838
+ const types_js_1 = types$2;
34839
+ const auth_js_1 = requireAuth$1();
34840
+ const auth_js_2 = requireAuth$1();
34841
+ const auth_utils_js_1 = requireAuthUtils();
34842
+ const errors_js_1 = requireErrors$1();
34843
+ class UnauthorizedError extends Error {
34844
+ constructor(message) {
34845
+ super(message ?? 'Unauthorized');
34846
+ }
34847
+ }
34848
+ auth$1.UnauthorizedError = UnauthorizedError;
34849
+ function isClientAuthMethod(method) {
34850
+ return ['client_secret_basic', 'client_secret_post', 'none'].includes(method);
34851
+ }
34852
+ const AUTHORIZATION_CODE_RESPONSE_TYPE = 'code';
34853
+ const AUTHORIZATION_CODE_CHALLENGE_METHOD = 'S256';
34854
+ /**
34855
+ * Determines the best client authentication method to use based on server support and client configuration.
34856
+ *
34857
+ * Priority order (highest to lowest):
34858
+ * 1. client_secret_basic (if client secret is available)
34859
+ * 2. client_secret_post (if client secret is available)
34860
+ * 3. none (for public clients)
34861
+ *
34862
+ * @param clientInformation - OAuth client information containing credentials
34863
+ * @param supportedMethods - Authentication methods supported by the authorization server
34864
+ * @returns The selected authentication method
34865
+ */
34866
+ function selectClientAuthMethod(clientInformation, supportedMethods) {
34867
+ const hasClientSecret = clientInformation.client_secret !== undefined;
34868
+ // Prefer the method returned by the server during client registration, if valid.
34869
+ // When server metadata is present we also require the method to be listed as supported;
34870
+ // when supportedMethods is empty (metadata omitted the field) the DCR hint stands alone.
34871
+ if ('token_endpoint_auth_method' in clientInformation &&
34872
+ clientInformation.token_endpoint_auth_method &&
34873
+ isClientAuthMethod(clientInformation.token_endpoint_auth_method) &&
34874
+ (supportedMethods.length === 0 || supportedMethods.includes(clientInformation.token_endpoint_auth_method))) {
34875
+ return clientInformation.token_endpoint_auth_method;
34876
+ }
34877
+ // If server metadata omits token_endpoint_auth_methods_supported, RFC 8414 §2 says the
34878
+ // default is client_secret_basic. RFC 6749 §2.3.1 also requires servers to support HTTP
34879
+ // Basic authentication for clients with a secret, making it the safest default.
34880
+ if (supportedMethods.length === 0) {
34881
+ return hasClientSecret ? 'client_secret_basic' : 'none';
34882
+ }
34883
+ // Try methods in priority order (most secure first)
34884
+ if (hasClientSecret && supportedMethods.includes('client_secret_basic')) {
34885
+ return 'client_secret_basic';
34886
+ }
34887
+ if (hasClientSecret && supportedMethods.includes('client_secret_post')) {
34888
+ return 'client_secret_post';
34889
+ }
34890
+ if (supportedMethods.includes('none')) {
34891
+ return 'none';
34892
+ }
34893
+ // Fallback: use what we have
34894
+ return hasClientSecret ? 'client_secret_post' : 'none';
34895
+ }
34896
+ /**
34897
+ * Applies client authentication to the request based on the specified method.
34898
+ *
34899
+ * Implements OAuth 2.1 client authentication methods:
34900
+ * - client_secret_basic: HTTP Basic authentication (RFC 6749 Section 2.3.1)
34901
+ * - client_secret_post: Credentials in request body (RFC 6749 Section 2.3.1)
34902
+ * - none: Public client authentication (RFC 6749 Section 2.1)
34903
+ *
34904
+ * @param method - The authentication method to use
34905
+ * @param clientInformation - OAuth client information containing credentials
34906
+ * @param headers - HTTP headers object to modify
34907
+ * @param params - URL search parameters to modify
34908
+ * @throws {Error} When required credentials are missing
34909
+ */
34910
+ function applyClientAuthentication(method, clientInformation, headers, params) {
34911
+ const { client_id, client_secret } = clientInformation;
34912
+ switch (method) {
34913
+ case 'client_secret_basic':
34914
+ applyBasicAuth(client_id, client_secret, headers);
34915
+ return;
34916
+ case 'client_secret_post':
34917
+ applyPostAuth(client_id, client_secret, params);
34918
+ return;
34919
+ case 'none':
34920
+ applyPublicAuth(client_id, params);
34921
+ return;
34922
+ default:
34923
+ throw new Error(`Unsupported client authentication method: ${method}`);
34924
+ }
34925
+ }
34926
+ /**
34927
+ * Applies HTTP Basic authentication (RFC 6749 Section 2.3.1)
34928
+ */
34929
+ function applyBasicAuth(clientId, clientSecret, headers) {
34930
+ if (!clientSecret) {
34931
+ throw new Error('client_secret_basic authentication requires a client_secret');
34932
+ }
34933
+ const credentials = btoa(`${clientId}:${clientSecret}`);
34934
+ headers.set('Authorization', `Basic ${credentials}`);
34935
+ }
34936
+ /**
34937
+ * Applies POST body authentication (RFC 6749 Section 2.3.1)
34938
+ */
34939
+ function applyPostAuth(clientId, clientSecret, params) {
34940
+ params.set('client_id', clientId);
34941
+ if (clientSecret) {
34942
+ params.set('client_secret', clientSecret);
34943
+ }
34944
+ }
34945
+ /**
34946
+ * Applies public client authentication (RFC 6749 Section 2.1)
34947
+ */
34948
+ function applyPublicAuth(clientId, params) {
34949
+ params.set('client_id', clientId);
34950
+ }
34951
+ /**
34952
+ * Parses an OAuth error response from a string or Response object.
34953
+ *
34954
+ * If the input is a standard OAuth2.0 error response, it will be parsed according to the spec
34955
+ * and an instance of the appropriate OAuthError subclass will be returned.
34956
+ * If parsing fails, it falls back to a generic ServerError that includes
34957
+ * the response status (if available) and original content.
34958
+ *
34959
+ * @param input - A Response object or string containing the error response
34960
+ * @returns A Promise that resolves to an OAuthError instance
34961
+ */
34962
+ async function parseErrorResponse(input) {
34963
+ const statusCode = input instanceof Response ? input.status : undefined;
34964
+ const body = input instanceof Response ? await input.text() : input;
34965
+ try {
34966
+ const result = auth_js_1.OAuthErrorResponseSchema.parse(JSON.parse(body));
34967
+ const { error, error_description, error_uri } = result;
34968
+ const errorClass = errors_js_1.OAUTH_ERRORS[error] || errors_js_1.ServerError;
34969
+ return new errorClass(error_description || '', error_uri);
34970
+ }
34971
+ catch (error) {
34972
+ // Not a valid OAuth error response, but try to inform the user of the raw data anyway
34973
+ const errorMessage = `${statusCode ? `HTTP ${statusCode}: ` : ''}Invalid OAuth error response: ${error}. Raw body: ${body}`;
34974
+ return new errors_js_1.ServerError(errorMessage);
34975
+ }
34976
+ }
34977
+ /**
34978
+ * Orchestrates the full auth flow with a server.
34979
+ *
34980
+ * This can be used as a single entry point for all authorization functionality,
34981
+ * instead of linking together the other lower-level functions in this module.
34982
+ */
34983
+ async function auth(provider, options) {
34984
+ try {
34985
+ return await authInternal(provider, options);
34986
+ }
34987
+ catch (error) {
34988
+ // Handle recoverable error types by invalidating credentials and retrying
34989
+ if (error instanceof errors_js_1.InvalidClientError || error instanceof errors_js_1.UnauthorizedClientError) {
34990
+ await provider.invalidateCredentials?.('all');
34991
+ return await authInternal(provider, options);
34992
+ }
34993
+ else if (error instanceof errors_js_1.InvalidGrantError) {
34994
+ await provider.invalidateCredentials?.('tokens');
34995
+ return await authInternal(provider, options);
34996
+ }
34997
+ // Throw otherwise
34998
+ throw error;
34999
+ }
35000
+ }
35001
+ async function authInternal(provider, { serverUrl, authorizationCode, scope, resourceMetadataUrl, fetchFn }) {
35002
+ // Check if the provider has cached discovery state to skip discovery
35003
+ const cachedState = await provider.discoveryState?.();
35004
+ let resourceMetadata;
35005
+ let authorizationServerUrl;
35006
+ let metadata;
35007
+ // If resourceMetadataUrl is not provided, try to load it from cached state
35008
+ // This handles browser redirects where the URL was saved before navigation
35009
+ let effectiveResourceMetadataUrl = resourceMetadataUrl;
35010
+ if (!effectiveResourceMetadataUrl && cachedState?.resourceMetadataUrl) {
35011
+ effectiveResourceMetadataUrl = new URL(cachedState.resourceMetadataUrl);
35012
+ }
35013
+ if (cachedState?.authorizationServerUrl) {
35014
+ // Restore discovery state from cache
35015
+ authorizationServerUrl = cachedState.authorizationServerUrl;
35016
+ resourceMetadata = cachedState.resourceMetadata;
35017
+ metadata =
35018
+ cachedState.authorizationServerMetadata ?? (await discoverAuthorizationServerMetadata(authorizationServerUrl, { fetchFn }));
35019
+ // If resource metadata wasn't cached, try to fetch it for selectResourceURL
35020
+ if (!resourceMetadata) {
35021
+ try {
35022
+ resourceMetadata = await discoverOAuthProtectedResourceMetadata(serverUrl, { resourceMetadataUrl: effectiveResourceMetadataUrl }, fetchFn);
35023
+ }
35024
+ catch {
35025
+ // RFC 9728 not available — selectResourceURL will handle undefined
35026
+ }
35027
+ }
35028
+ // Re-save if we enriched the cached state with missing metadata
35029
+ if (metadata !== cachedState.authorizationServerMetadata || resourceMetadata !== cachedState.resourceMetadata) {
35030
+ await provider.saveDiscoveryState?.({
35031
+ authorizationServerUrl: String(authorizationServerUrl),
35032
+ resourceMetadataUrl: effectiveResourceMetadataUrl?.toString(),
35033
+ resourceMetadata,
35034
+ authorizationServerMetadata: metadata
35035
+ });
35036
+ }
35037
+ }
35038
+ else {
35039
+ // Full discovery via RFC 9728
35040
+ const serverInfo = await discoverOAuthServerInfo(serverUrl, { resourceMetadataUrl: effectiveResourceMetadataUrl, fetchFn });
35041
+ authorizationServerUrl = serverInfo.authorizationServerUrl;
35042
+ metadata = serverInfo.authorizationServerMetadata;
35043
+ resourceMetadata = serverInfo.resourceMetadata;
35044
+ // Persist discovery state for future use
35045
+ // TODO: resourceMetadataUrl is only populated when explicitly provided via options
35046
+ // or loaded from cached state. The URL derived internally by
35047
+ // discoverOAuthProtectedResourceMetadata() is not captured back here.
35048
+ await provider.saveDiscoveryState?.({
35049
+ authorizationServerUrl: String(authorizationServerUrl),
35050
+ resourceMetadataUrl: effectiveResourceMetadataUrl?.toString(),
35051
+ resourceMetadata,
35052
+ authorizationServerMetadata: metadata
35053
+ });
35054
+ }
35055
+ const resource = await selectResourceURL(serverUrl, provider, resourceMetadata);
35056
+ // Apply scope selection strategy (SEP-835):
35057
+ // 1. WWW-Authenticate scope (passed via `scope` param)
35058
+ // 2. PRM scopes_supported
35059
+ // 3. Client metadata scope (user-configured fallback)
35060
+ // The resolved scope is used consistently for both DCR and the authorization request.
35061
+ const resolvedScope = scope || resourceMetadata?.scopes_supported?.join(' ') || provider.clientMetadata.scope;
35062
+ // Handle client registration if needed
35063
+ let clientInformation = await Promise.resolve(provider.clientInformation());
35064
+ if (!clientInformation) {
35065
+ if (authorizationCode !== undefined) {
35066
+ throw new Error('Existing OAuth client information is required when exchanging an authorization code');
35067
+ }
35068
+ const supportsUrlBasedClientId = metadata?.client_id_metadata_document_supported === true;
35069
+ const clientMetadataUrl = provider.clientMetadataUrl;
35070
+ if (clientMetadataUrl && !isHttpsUrl(clientMetadataUrl)) {
35071
+ throw new errors_js_1.InvalidClientMetadataError(`clientMetadataUrl must be a valid HTTPS URL with a non-root pathname, got: ${clientMetadataUrl}`);
35072
+ }
35073
+ const shouldUseUrlBasedClientId = supportsUrlBasedClientId && clientMetadataUrl;
35074
+ if (shouldUseUrlBasedClientId) {
35075
+ // SEP-991: URL-based Client IDs
35076
+ clientInformation = {
35077
+ client_id: clientMetadataUrl
35078
+ };
35079
+ await provider.saveClientInformation?.(clientInformation);
35080
+ }
35081
+ else {
35082
+ // Fallback to dynamic registration
35083
+ if (!provider.saveClientInformation) {
35084
+ throw new Error('OAuth client information must be saveable for dynamic registration');
35085
+ }
35086
+ const fullInformation = await registerClient(authorizationServerUrl, {
35087
+ metadata,
35088
+ clientMetadata: provider.clientMetadata,
35089
+ scope: resolvedScope,
35090
+ fetchFn
35091
+ });
35092
+ await provider.saveClientInformation(fullInformation);
35093
+ clientInformation = fullInformation;
35094
+ }
35095
+ }
35096
+ // Non-interactive flows (e.g., client_credentials, jwt-bearer) don't need a redirect URL
35097
+ const nonInteractiveFlow = !provider.redirectUrl;
35098
+ // Exchange authorization code for tokens, or fetch tokens directly for non-interactive flows
35099
+ if (authorizationCode !== undefined || nonInteractiveFlow) {
35100
+ const tokens = await fetchToken(provider, authorizationServerUrl, {
35101
+ metadata,
35102
+ resource,
35103
+ authorizationCode,
35104
+ fetchFn
35105
+ });
35106
+ await provider.saveTokens(tokens);
35107
+ return 'AUTHORIZED';
35108
+ }
35109
+ const tokens = await provider.tokens();
35110
+ // Handle token refresh or new authorization
35111
+ if (tokens?.refresh_token) {
35112
+ try {
35113
+ // Attempt to refresh the token
35114
+ const newTokens = await refreshAuthorization(authorizationServerUrl, {
35115
+ metadata,
35116
+ clientInformation,
35117
+ refreshToken: tokens.refresh_token,
35118
+ resource,
35119
+ addClientAuthentication: provider.addClientAuthentication,
35120
+ fetchFn
35121
+ });
35122
+ await provider.saveTokens(newTokens);
35123
+ return 'AUTHORIZED';
35124
+ }
35125
+ catch (error) {
35126
+ // If this is a ServerError, or an unknown type, log it out and try to continue. Otherwise, escalate so we can fix things and retry.
35127
+ if (!(error instanceof errors_js_1.OAuthError) || error instanceof errors_js_1.ServerError) ;
35128
+ else {
35129
+ // Refresh failed for another reason, re-throw
35130
+ throw error;
35131
+ }
35132
+ }
35133
+ }
35134
+ const state = provider.state ? await provider.state() : undefined;
35135
+ // Start new authorization flow
35136
+ const { authorizationUrl, codeVerifier } = await startAuthorization(authorizationServerUrl, {
35137
+ metadata,
35138
+ clientInformation,
35139
+ state,
35140
+ redirectUrl: provider.redirectUrl,
35141
+ scope: resolvedScope,
35142
+ resource
35143
+ });
35144
+ await provider.saveCodeVerifier(codeVerifier);
35145
+ await provider.redirectToAuthorization(authorizationUrl);
35146
+ return 'REDIRECT';
35147
+ }
35148
+ /**
35149
+ * SEP-991: URL-based Client IDs
35150
+ * Validate that the client_id is a valid URL with https scheme
35151
+ */
35152
+ function isHttpsUrl(value) {
35153
+ if (!value)
35154
+ return false;
35155
+ try {
35156
+ const url = new URL(value);
35157
+ return url.protocol === 'https:' && url.pathname !== '/';
35158
+ }
35159
+ catch {
35160
+ return false;
35161
+ }
35162
+ }
35163
+ async function selectResourceURL(serverUrl, provider, resourceMetadata) {
35164
+ const defaultResource = (0, auth_utils_js_1.resourceUrlFromServerUrl)(serverUrl);
35165
+ // If provider has custom validation, delegate to it
35166
+ if (provider.validateResourceURL) {
35167
+ return await provider.validateResourceURL(defaultResource, resourceMetadata?.resource);
35168
+ }
35169
+ // Only include resource parameter when Protected Resource Metadata is present
35170
+ if (!resourceMetadata) {
35171
+ return undefined;
35172
+ }
35173
+ // Validate that the metadata's resource is compatible with our request
35174
+ if (!(0, auth_utils_js_1.checkResourceAllowed)({ requestedResource: defaultResource, configuredResource: resourceMetadata.resource })) {
35175
+ throw new Error(`Protected resource ${resourceMetadata.resource} does not match expected ${defaultResource} (or origin)`);
35176
+ }
35177
+ // Prefer the resource from metadata since it's what the server is telling us to request
35178
+ return new URL(resourceMetadata.resource);
35179
+ }
35180
+ /**
35181
+ * Extract resource_metadata, scope, and error from WWW-Authenticate header.
35182
+ */
35183
+ function extractWWWAuthenticateParams(res) {
35184
+ const authenticateHeader = res.headers.get('WWW-Authenticate');
35185
+ if (!authenticateHeader) {
35186
+ return {};
35187
+ }
35188
+ const [type, scheme] = authenticateHeader.split(' ');
35189
+ if (type.toLowerCase() !== 'bearer' || !scheme) {
35190
+ return {};
35191
+ }
35192
+ const resourceMetadataMatch = extractFieldFromWwwAuth(res, 'resource_metadata') || undefined;
35193
+ let resourceMetadataUrl;
35194
+ if (resourceMetadataMatch) {
35195
+ try {
35196
+ resourceMetadataUrl = new URL(resourceMetadataMatch);
35197
+ }
35198
+ catch {
35199
+ // Ignore invalid URL
35200
+ }
35201
+ }
35202
+ const scope = extractFieldFromWwwAuth(res, 'scope') || undefined;
35203
+ const error = extractFieldFromWwwAuth(res, 'error') || undefined;
35204
+ return {
35205
+ resourceMetadataUrl,
35206
+ scope,
35207
+ error
35208
+ };
35209
+ }
35210
+ /**
35211
+ * Extracts a specific field's value from the WWW-Authenticate header string.
35212
+ *
35213
+ * @param response The HTTP response object containing the headers.
35214
+ * @param fieldName The name of the field to extract (e.g., "realm", "nonce").
35215
+ * @returns The field value
35216
+ */
35217
+ function extractFieldFromWwwAuth(response, fieldName) {
35218
+ const wwwAuthHeader = response.headers.get('WWW-Authenticate');
35219
+ if (!wwwAuthHeader) {
35220
+ return null;
35221
+ }
35222
+ const pattern = new RegExp(`${fieldName}=(?:"([^"]+)"|([^\\s,]+))`);
35223
+ const match = wwwAuthHeader.match(pattern);
35224
+ if (match) {
35225
+ // Pattern matches: field_name="value" or field_name=value (unquoted)
35226
+ return match[1] || match[2];
35227
+ }
35228
+ return null;
35229
+ }
35230
+ /**
35231
+ * Extract resource_metadata from response header.
35232
+ * @deprecated Use `extractWWWAuthenticateParams` instead.
35233
+ */
35234
+ function extractResourceMetadataUrl(res) {
35235
+ const authenticateHeader = res.headers.get('WWW-Authenticate');
35236
+ if (!authenticateHeader) {
35237
+ return undefined;
35238
+ }
35239
+ const [type, scheme] = authenticateHeader.split(' ');
35240
+ if (type.toLowerCase() !== 'bearer' || !scheme) {
35241
+ return undefined;
35242
+ }
35243
+ const regex = /resource_metadata="([^"]*)"/;
35244
+ const match = regex.exec(authenticateHeader);
35245
+ if (!match) {
35246
+ return undefined;
35247
+ }
35248
+ try {
35249
+ return new URL(match[1]);
35250
+ }
35251
+ catch {
35252
+ return undefined;
35253
+ }
35254
+ }
35255
+ /**
35256
+ * Looks up RFC 9728 OAuth 2.0 Protected Resource Metadata.
35257
+ *
35258
+ * If the server returns a 404 for the well-known endpoint, this function will
35259
+ * return `undefined`. Any other errors will be thrown as exceptions.
35260
+ */
35261
+ async function discoverOAuthProtectedResourceMetadata(serverUrl, opts, fetchFn = fetch) {
35262
+ const response = await discoverMetadataWithFallback(serverUrl, 'oauth-protected-resource', fetchFn, {
35263
+ protocolVersion: opts?.protocolVersion,
35264
+ metadataUrl: opts?.resourceMetadataUrl
35265
+ });
35266
+ if (!response || response.status === 404) {
35267
+ await response?.body?.cancel();
35268
+ throw new Error(`Resource server does not implement OAuth 2.0 Protected Resource Metadata.`);
35269
+ }
35270
+ if (!response.ok) {
35271
+ await response.body?.cancel();
35272
+ throw new Error(`HTTP ${response.status} trying to load well-known OAuth protected resource metadata.`);
35273
+ }
35274
+ return auth_js_2.OAuthProtectedResourceMetadataSchema.parse(await response.json());
35275
+ }
35276
+ /**
35277
+ * Helper function to handle fetch with CORS retry logic
35278
+ */
35279
+ async function fetchWithCorsRetry(url, headers, fetchFn = fetch) {
35280
+ try {
35281
+ return await fetchFn(url, { headers });
35282
+ }
35283
+ catch (error) {
35284
+ if (error instanceof TypeError) {
35285
+ if (headers) {
35286
+ // CORS errors come back as TypeError, retry without headers
35287
+ return fetchWithCorsRetry(url, undefined, fetchFn);
35288
+ }
35289
+ else {
35290
+ // We're getting CORS errors on retry too, return undefined
35291
+ return undefined;
35292
+ }
35293
+ }
35294
+ throw error;
35295
+ }
35296
+ }
35297
+ /**
35298
+ * Constructs the well-known path for auth-related metadata discovery
35299
+ */
35300
+ function buildWellKnownPath(wellKnownPrefix, pathname = '', options = {}) {
35301
+ // Strip trailing slash from pathname to avoid double slashes
35302
+ if (pathname.endsWith('/')) {
35303
+ pathname = pathname.slice(0, -1);
35304
+ }
35305
+ return options.prependPathname ? `${pathname}/.well-known/${wellKnownPrefix}` : `/.well-known/${wellKnownPrefix}${pathname}`;
35306
+ }
35307
+ /**
35308
+ * Tries to discover OAuth metadata at a specific URL
35309
+ */
35310
+ async function tryMetadataDiscovery(url, protocolVersion, fetchFn = fetch) {
35311
+ const headers = {
35312
+ 'MCP-Protocol-Version': protocolVersion
35313
+ };
35314
+ return await fetchWithCorsRetry(url, headers, fetchFn);
35315
+ }
35316
+ /**
35317
+ * Determines if fallback to root discovery should be attempted
35318
+ */
35319
+ function shouldAttemptFallback(response, pathname) {
35320
+ return !response || (response.status >= 400 && response.status < 500 && pathname !== '/');
35321
+ }
35322
+ /**
35323
+ * Generic function for discovering OAuth metadata with fallback support
35324
+ */
35325
+ async function discoverMetadataWithFallback(serverUrl, wellKnownType, fetchFn, opts) {
35326
+ const issuer = new URL(serverUrl);
35327
+ const protocolVersion = opts?.protocolVersion ?? types_js_1.LATEST_PROTOCOL_VERSION;
35328
+ let url;
35329
+ if (opts?.metadataUrl) {
35330
+ url = new URL(opts.metadataUrl);
35331
+ }
35332
+ else {
35333
+ // Try path-aware discovery first
35334
+ const wellKnownPath = buildWellKnownPath(wellKnownType, issuer.pathname);
35335
+ url = new URL(wellKnownPath, opts?.metadataServerUrl ?? issuer);
35336
+ url.search = issuer.search;
35337
+ }
35338
+ let response = await tryMetadataDiscovery(url, protocolVersion, fetchFn);
35339
+ // If path-aware discovery fails with 404 and we're not already at root, try fallback to root discovery
35340
+ if (!opts?.metadataUrl && shouldAttemptFallback(response, issuer.pathname)) {
35341
+ const rootUrl = new URL(`/.well-known/${wellKnownType}`, issuer);
35342
+ response = await tryMetadataDiscovery(rootUrl, protocolVersion, fetchFn);
35343
+ }
35344
+ return response;
35345
+ }
35346
+ /**
35347
+ * Looks up RFC 8414 OAuth 2.0 Authorization Server Metadata.
35348
+ *
35349
+ * If the server returns a 404 for the well-known endpoint, this function will
35350
+ * return `undefined`. Any other errors will be thrown as exceptions.
35351
+ *
35352
+ * @deprecated This function is deprecated in favor of `discoverAuthorizationServerMetadata`.
35353
+ */
35354
+ async function discoverOAuthMetadata(issuer, { authorizationServerUrl, protocolVersion } = {}, fetchFn = fetch) {
35355
+ if (typeof issuer === 'string') {
35356
+ issuer = new URL(issuer);
35357
+ }
35358
+ if (!authorizationServerUrl) {
35359
+ authorizationServerUrl = issuer;
35360
+ }
35361
+ if (typeof authorizationServerUrl === 'string') {
35362
+ authorizationServerUrl = new URL(authorizationServerUrl);
35363
+ }
35364
+ protocolVersion ?? (protocolVersion = types_js_1.LATEST_PROTOCOL_VERSION);
35365
+ const response = await discoverMetadataWithFallback(authorizationServerUrl, 'oauth-authorization-server', fetchFn, {
35366
+ protocolVersion,
35367
+ metadataServerUrl: authorizationServerUrl
35368
+ });
35369
+ if (!response || response.status === 404) {
35370
+ await response?.body?.cancel();
35371
+ return undefined;
35372
+ }
35373
+ if (!response.ok) {
35374
+ await response.body?.cancel();
35375
+ throw new Error(`HTTP ${response.status} trying to load well-known OAuth metadata`);
35376
+ }
35377
+ return auth_js_2.OAuthMetadataSchema.parse(await response.json());
35378
+ }
35379
+ /**
35380
+ * Builds a list of discovery URLs to try for authorization server metadata.
35381
+ * URLs are returned in priority order:
35382
+ * 1. OAuth metadata at the given URL
35383
+ * 2. OIDC metadata endpoints at the given URL
35384
+ */
35385
+ function buildDiscoveryUrls(authorizationServerUrl) {
35386
+ const url = typeof authorizationServerUrl === 'string' ? new URL(authorizationServerUrl) : authorizationServerUrl;
35387
+ const hasPath = url.pathname !== '/';
35388
+ const urlsToTry = [];
35389
+ if (!hasPath) {
35390
+ // Root path: https://example.com/.well-known/oauth-authorization-server
35391
+ urlsToTry.push({
35392
+ url: new URL('/.well-known/oauth-authorization-server', url.origin),
35393
+ type: 'oauth'
35394
+ });
35395
+ // OIDC: https://example.com/.well-known/openid-configuration
35396
+ urlsToTry.push({
35397
+ url: new URL(`/.well-known/openid-configuration`, url.origin),
35398
+ type: 'oidc'
35399
+ });
35400
+ return urlsToTry;
35401
+ }
35402
+ // Strip trailing slash from pathname to avoid double slashes
35403
+ let pathname = url.pathname;
35404
+ if (pathname.endsWith('/')) {
35405
+ pathname = pathname.slice(0, -1);
35406
+ }
35407
+ // 1. OAuth metadata at the given URL
35408
+ // Insert well-known before the path: https://example.com/.well-known/oauth-authorization-server/tenant1
35409
+ urlsToTry.push({
35410
+ url: new URL(`/.well-known/oauth-authorization-server${pathname}`, url.origin),
35411
+ type: 'oauth'
35412
+ });
35413
+ // 2. OIDC metadata endpoints
35414
+ // RFC 8414 style: Insert /.well-known/openid-configuration before the path
35415
+ urlsToTry.push({
35416
+ url: new URL(`/.well-known/openid-configuration${pathname}`, url.origin),
35417
+ type: 'oidc'
35418
+ });
35419
+ // OIDC Discovery 1.0 style: Append /.well-known/openid-configuration after the path
35420
+ urlsToTry.push({
35421
+ url: new URL(`${pathname}/.well-known/openid-configuration`, url.origin),
35422
+ type: 'oidc'
35423
+ });
35424
+ return urlsToTry;
35425
+ }
35426
+ /**
35427
+ * Discovers authorization server metadata with support for RFC 8414 OAuth 2.0 Authorization Server Metadata
35428
+ * and OpenID Connect Discovery 1.0 specifications.
35429
+ *
35430
+ * This function implements a fallback strategy for authorization server discovery:
35431
+ * 1. Attempts RFC 8414 OAuth metadata discovery first
35432
+ * 2. If OAuth discovery fails, falls back to OpenID Connect Discovery
35433
+ *
35434
+ * @param authorizationServerUrl - The authorization server URL obtained from the MCP Server's
35435
+ * protected resource metadata, or the MCP server's URL if the
35436
+ * metadata was not found.
35437
+ * @param options - Configuration options
35438
+ * @param options.fetchFn - Optional fetch function for making HTTP requests, defaults to global fetch
35439
+ * @param options.protocolVersion - MCP protocol version to use, defaults to LATEST_PROTOCOL_VERSION
35440
+ * @returns Promise resolving to authorization server metadata, or undefined if discovery fails
35441
+ */
35442
+ async function discoverAuthorizationServerMetadata(authorizationServerUrl, { fetchFn = fetch, protocolVersion = types_js_1.LATEST_PROTOCOL_VERSION } = {}) {
35443
+ const headers = {
35444
+ 'MCP-Protocol-Version': protocolVersion,
35445
+ Accept: 'application/json'
35446
+ };
35447
+ // Get the list of URLs to try
35448
+ const urlsToTry = buildDiscoveryUrls(authorizationServerUrl);
35449
+ // Try each URL in order
35450
+ for (const { url: endpointUrl, type } of urlsToTry) {
35451
+ const response = await fetchWithCorsRetry(endpointUrl, headers, fetchFn);
35452
+ if (!response) {
35453
+ /**
35454
+ * CORS error occurred - don't throw as the endpoint may not allow CORS,
35455
+ * continue trying other possible endpoints
35456
+ */
35457
+ continue;
35458
+ }
35459
+ if (!response.ok) {
35460
+ await response.body?.cancel();
35461
+ // Continue looking for any 4xx response code.
35462
+ if (response.status >= 400 && response.status < 500) {
35463
+ continue; // Try next URL
35464
+ }
35465
+ throw new Error(`HTTP ${response.status} trying to load ${type === 'oauth' ? 'OAuth' : 'OpenID provider'} metadata from ${endpointUrl}`);
35466
+ }
35467
+ // Parse and validate based on type
35468
+ if (type === 'oauth') {
35469
+ return auth_js_2.OAuthMetadataSchema.parse(await response.json());
35470
+ }
35471
+ else {
35472
+ return auth_js_1.OpenIdProviderDiscoveryMetadataSchema.parse(await response.json());
35473
+ }
35474
+ }
35475
+ return undefined;
35476
+ }
35477
+ /**
35478
+ * Discovers the authorization server for an MCP server following
35479
+ * {@link https://datatracker.ietf.org/doc/html/rfc9728 | RFC 9728} (OAuth 2.0 Protected
35480
+ * Resource Metadata), with fallback to treating the server URL as the
35481
+ * authorization server.
35482
+ *
35483
+ * This function combines two discovery steps into one call:
35484
+ * 1. Probes `/.well-known/oauth-protected-resource` on the MCP server to find the
35485
+ * authorization server URL (RFC 9728).
35486
+ * 2. Fetches authorization server metadata from that URL (RFC 8414 / OpenID Connect Discovery).
35487
+ *
35488
+ * Use this when you need the authorization server metadata for operations outside the
35489
+ * {@linkcode auth} orchestrator, such as token refresh or token revocation.
35490
+ *
35491
+ * @param serverUrl - The MCP resource server URL
35492
+ * @param opts - Optional configuration
35493
+ * @param opts.resourceMetadataUrl - Override URL for the protected resource metadata endpoint
35494
+ * @param opts.fetchFn - Custom fetch function for HTTP requests
35495
+ * @returns Authorization server URL, metadata, and resource metadata (if available)
35496
+ */
35497
+ async function discoverOAuthServerInfo(serverUrl, opts) {
35498
+ let resourceMetadata;
35499
+ let authorizationServerUrl;
35500
+ try {
35501
+ resourceMetadata = await discoverOAuthProtectedResourceMetadata(serverUrl, { resourceMetadataUrl: opts?.resourceMetadataUrl }, opts?.fetchFn);
35502
+ if (resourceMetadata.authorization_servers && resourceMetadata.authorization_servers.length > 0) {
35503
+ authorizationServerUrl = resourceMetadata.authorization_servers[0];
35504
+ }
35505
+ }
35506
+ catch {
35507
+ // RFC 9728 not supported -- fall back to treating the server URL as the authorization server
35508
+ }
35509
+ // If we don't get a valid authorization server from protected resource metadata,
35510
+ // fall back to the legacy MCP spec behavior: MCP server base URL acts as the authorization server
35511
+ if (!authorizationServerUrl) {
35512
+ authorizationServerUrl = String(new URL('/', serverUrl));
35513
+ }
35514
+ const authorizationServerMetadata = await discoverAuthorizationServerMetadata(authorizationServerUrl, { fetchFn: opts?.fetchFn });
35515
+ return {
35516
+ authorizationServerUrl,
35517
+ authorizationServerMetadata,
35518
+ resourceMetadata
35519
+ };
35520
+ }
35521
+ /**
35522
+ * Begins the authorization flow with the given server, by generating a PKCE challenge and constructing the authorization URL.
35523
+ */
35524
+ async function startAuthorization(authorizationServerUrl, { metadata, clientInformation, redirectUrl, scope, state, resource }) {
35525
+ let authorizationUrl;
35526
+ if (metadata) {
35527
+ authorizationUrl = new URL(metadata.authorization_endpoint);
35528
+ if (!metadata.response_types_supported.includes(AUTHORIZATION_CODE_RESPONSE_TYPE)) {
35529
+ throw new Error(`Incompatible auth server: does not support response type ${AUTHORIZATION_CODE_RESPONSE_TYPE}`);
35530
+ }
35531
+ if (metadata.code_challenge_methods_supported &&
35532
+ !metadata.code_challenge_methods_supported.includes(AUTHORIZATION_CODE_CHALLENGE_METHOD)) {
35533
+ throw new Error(`Incompatible auth server: does not support code challenge method ${AUTHORIZATION_CODE_CHALLENGE_METHOD}`);
35534
+ }
35535
+ }
35536
+ else {
35537
+ authorizationUrl = new URL('/authorize', authorizationServerUrl);
35538
+ }
35539
+ // Generate PKCE challenge
35540
+ const challenge = await (0, pkce_challenge_1.default)();
35541
+ const codeVerifier = challenge.code_verifier;
35542
+ const codeChallenge = challenge.code_challenge;
35543
+ authorizationUrl.searchParams.set('response_type', AUTHORIZATION_CODE_RESPONSE_TYPE);
35544
+ authorizationUrl.searchParams.set('client_id', clientInformation.client_id);
35545
+ authorizationUrl.searchParams.set('code_challenge', codeChallenge);
35546
+ authorizationUrl.searchParams.set('code_challenge_method', AUTHORIZATION_CODE_CHALLENGE_METHOD);
35547
+ authorizationUrl.searchParams.set('redirect_uri', String(redirectUrl));
35548
+ if (state) {
35549
+ authorizationUrl.searchParams.set('state', state);
35550
+ }
35551
+ if (scope) {
35552
+ authorizationUrl.searchParams.set('scope', scope);
35553
+ }
35554
+ if (scope?.includes('offline_access')) {
35555
+ // if the request includes the OIDC-only "offline_access" scope,
35556
+ // we need to set the prompt to "consent" to ensure the user is prompted to grant offline access
35557
+ // https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
35558
+ authorizationUrl.searchParams.append('prompt', 'consent');
35559
+ }
35560
+ if (resource) {
35561
+ authorizationUrl.searchParams.set('resource', resource.href);
35562
+ }
35563
+ return { authorizationUrl, codeVerifier };
35564
+ }
35565
+ /**
35566
+ * Prepares token request parameters for an authorization code exchange.
35567
+ *
35568
+ * This is the default implementation used by fetchToken when the provider
35569
+ * doesn't implement prepareTokenRequest.
35570
+ *
35571
+ * @param authorizationCode - The authorization code received from the authorization endpoint
35572
+ * @param codeVerifier - The PKCE code verifier
35573
+ * @param redirectUri - The redirect URI used in the authorization request
35574
+ * @returns URLSearchParams for the authorization_code grant
35575
+ */
35576
+ function prepareAuthorizationCodeRequest(authorizationCode, codeVerifier, redirectUri) {
35577
+ return new URLSearchParams({
35578
+ grant_type: 'authorization_code',
35579
+ code: authorizationCode,
35580
+ code_verifier: codeVerifier,
35581
+ redirect_uri: String(redirectUri)
35582
+ });
35583
+ }
35584
+ /**
35585
+ * Internal helper to execute a token request with the given parameters.
35586
+ * Used by exchangeAuthorization, refreshAuthorization, and fetchToken.
35587
+ */
35588
+ async function executeTokenRequest(authorizationServerUrl, { metadata, tokenRequestParams, clientInformation, addClientAuthentication, resource, fetchFn }) {
35589
+ const tokenUrl = metadata?.token_endpoint ? new URL(metadata.token_endpoint) : new URL('/token', authorizationServerUrl);
35590
+ const headers = new Headers({
35591
+ 'Content-Type': 'application/x-www-form-urlencoded',
35592
+ Accept: 'application/json'
35593
+ });
35594
+ if (resource) {
35595
+ tokenRequestParams.set('resource', resource.href);
35596
+ }
35597
+ if (addClientAuthentication) {
35598
+ await addClientAuthentication(headers, tokenRequestParams, tokenUrl, metadata);
35599
+ }
35600
+ else if (clientInformation) {
35601
+ const supportedMethods = metadata?.token_endpoint_auth_methods_supported ?? [];
35602
+ const authMethod = selectClientAuthMethod(clientInformation, supportedMethods);
35603
+ applyClientAuthentication(authMethod, clientInformation, headers, tokenRequestParams);
35604
+ }
35605
+ const response = await (fetchFn ?? fetch)(tokenUrl, {
35606
+ method: 'POST',
35607
+ headers,
35608
+ body: tokenRequestParams
35609
+ });
35610
+ if (!response.ok) {
35611
+ throw await parseErrorResponse(response);
35612
+ }
35613
+ return auth_js_2.OAuthTokensSchema.parse(await response.json());
35614
+ }
35615
+ /**
35616
+ * Exchanges an authorization code for an access token with the given server.
35617
+ *
35618
+ * Supports multiple client authentication methods as specified in OAuth 2.1:
35619
+ * - Automatically selects the best authentication method based on server support
35620
+ * - Falls back to appropriate defaults when server metadata is unavailable
35621
+ *
35622
+ * @param authorizationServerUrl - The authorization server's base URL
35623
+ * @param options - Configuration object containing client info, auth code, etc.
35624
+ * @returns Promise resolving to OAuth tokens
35625
+ * @throws {Error} When token exchange fails or authentication is invalid
35626
+ */
35627
+ async function exchangeAuthorization(authorizationServerUrl, { metadata, clientInformation, authorizationCode, codeVerifier, redirectUri, resource, addClientAuthentication, fetchFn }) {
35628
+ const tokenRequestParams = prepareAuthorizationCodeRequest(authorizationCode, codeVerifier, redirectUri);
35629
+ return executeTokenRequest(authorizationServerUrl, {
35630
+ metadata,
35631
+ tokenRequestParams,
35632
+ clientInformation,
35633
+ addClientAuthentication,
35634
+ resource,
35635
+ fetchFn
35636
+ });
35637
+ }
35638
+ /**
35639
+ * Exchange a refresh token for an updated access token.
35640
+ *
35641
+ * Supports multiple client authentication methods as specified in OAuth 2.1:
35642
+ * - Automatically selects the best authentication method based on server support
35643
+ * - Preserves the original refresh token if a new one is not returned
35644
+ *
35645
+ * @param authorizationServerUrl - The authorization server's base URL
35646
+ * @param options - Configuration object containing client info, refresh token, etc.
35647
+ * @returns Promise resolving to OAuth tokens (preserves original refresh_token if not replaced)
35648
+ * @throws {Error} When token refresh fails or authentication is invalid
35649
+ */
35650
+ async function refreshAuthorization(authorizationServerUrl, { metadata, clientInformation, refreshToken, resource, addClientAuthentication, fetchFn }) {
35651
+ const tokenRequestParams = new URLSearchParams({
35652
+ grant_type: 'refresh_token',
35653
+ refresh_token: refreshToken
35654
+ });
35655
+ const tokens = await executeTokenRequest(authorizationServerUrl, {
35656
+ metadata,
35657
+ tokenRequestParams,
35658
+ clientInformation,
35659
+ addClientAuthentication,
35660
+ resource,
35661
+ fetchFn
35662
+ });
35663
+ // Preserve original refresh token if server didn't return a new one
35664
+ return { refresh_token: refreshToken, ...tokens };
35665
+ }
35666
+ /**
35667
+ * Unified token fetching that works with any grant type via provider.prepareTokenRequest().
35668
+ *
35669
+ * This function provides a single entry point for obtaining tokens regardless of the
35670
+ * OAuth grant type. The provider's prepareTokenRequest() method determines which grant
35671
+ * to use and supplies the grant-specific parameters.
35672
+ *
35673
+ * @param provider - OAuth client provider that implements prepareTokenRequest()
35674
+ * @param authorizationServerUrl - The authorization server's base URL
35675
+ * @param options - Configuration for the token request
35676
+ * @returns Promise resolving to OAuth tokens
35677
+ * @throws {Error} When provider doesn't implement prepareTokenRequest or token fetch fails
35678
+ *
35679
+ * @example
35680
+ * // Provider for client_credentials:
35681
+ * class MyProvider implements OAuthClientProvider {
35682
+ * prepareTokenRequest(scope) {
35683
+ * const params = new URLSearchParams({ grant_type: 'client_credentials' });
35684
+ * if (scope) params.set('scope', scope);
35685
+ * return params;
35686
+ * }
35687
+ * // ... other methods
35688
+ * }
35689
+ *
35690
+ * const tokens = await fetchToken(provider, authServerUrl, { metadata });
35691
+ */
35692
+ async function fetchToken(provider, authorizationServerUrl, { metadata, resource, authorizationCode, fetchFn } = {}) {
35693
+ const scope = provider.clientMetadata.scope;
35694
+ // Use provider's prepareTokenRequest if available, otherwise fall back to authorization_code
35695
+ let tokenRequestParams;
35696
+ if (provider.prepareTokenRequest) {
35697
+ tokenRequestParams = await provider.prepareTokenRequest(scope);
35698
+ }
35699
+ // Default to authorization_code grant if no custom prepareTokenRequest
35700
+ if (!tokenRequestParams) {
35701
+ if (!authorizationCode) {
35702
+ throw new Error('Either provider.prepareTokenRequest() or authorizationCode is required');
35703
+ }
35704
+ if (!provider.redirectUrl) {
35705
+ throw new Error('redirectUrl is required for authorization_code flow');
35706
+ }
35707
+ const codeVerifier = await provider.codeVerifier();
35708
+ tokenRequestParams = prepareAuthorizationCodeRequest(authorizationCode, codeVerifier, provider.redirectUrl);
35709
+ }
35710
+ const clientInformation = await provider.clientInformation();
35711
+ return executeTokenRequest(authorizationServerUrl, {
35712
+ metadata,
35713
+ tokenRequestParams,
35714
+ clientInformation: clientInformation ?? undefined,
35715
+ addClientAuthentication: provider.addClientAuthentication,
35716
+ resource,
35717
+ fetchFn
35718
+ });
35719
+ }
35720
+ /**
35721
+ * Performs OAuth 2.0 Dynamic Client Registration according to RFC 7591.
35722
+ *
35723
+ * If `scope` is provided, it overrides `clientMetadata.scope` in the registration
35724
+ * request body. This allows callers to apply the Scope Selection Strategy (SEP-835)
35725
+ * consistently across both DCR and the subsequent authorization request.
35726
+ */
35727
+ async function registerClient(authorizationServerUrl, { metadata, clientMetadata, scope, fetchFn }) {
35728
+ let registrationUrl;
35729
+ if (metadata) {
35730
+ if (!metadata.registration_endpoint) {
35731
+ throw new Error('Incompatible auth server: does not support dynamic client registration');
35732
+ }
35733
+ registrationUrl = new URL(metadata.registration_endpoint);
35734
+ }
35735
+ else {
35736
+ registrationUrl = new URL('/register', authorizationServerUrl);
35737
+ }
35738
+ const response = await (fetchFn ?? fetch)(registrationUrl, {
35739
+ method: 'POST',
35740
+ headers: {
35741
+ 'Content-Type': 'application/json'
35742
+ },
35743
+ body: JSON.stringify({
35744
+ ...clientMetadata,
35745
+ ...(scope !== undefined ? { scope } : {})
35746
+ })
35747
+ });
35748
+ if (!response.ok) {
35749
+ throw await parseErrorResponse(response);
35750
+ }
35751
+ return auth_js_2.OAuthClientInformationFullSchema.parse(await response.json());
35752
+ }
35753
+
35754
+ return auth$1;
35627
35755
  }
35628
35756
 
35629
35757
  var stream = {};
@@ -35826,7 +35954,7 @@ Object.defineProperty(streamableHttp$1, "__esModule", { value: true });
35826
35954
  streamableHttp$1.StreamableHTTPClientTransport = streamableHttp$1.StreamableHTTPError = void 0;
35827
35955
  const transport_js_1 = transport$1;
35828
35956
  const types_js_1$4 = types$2;
35829
- const auth_js_1 = auth$2;
35957
+ const auth_js_1 = requireAuth();
35830
35958
  const stream_1 = stream;
35831
35959
  // Default reconnection options for StreamableHTTP connections
35832
35960
  const DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS = {
@@ -36950,6 +37078,244 @@ var mcpScopeResolver = {
36950
37078
  applyPathScopeToCredentials: applyPathScopeToCredentials$1,
36951
37079
  };
36952
37080
 
37081
+ /**
37082
+ * mcpOAuthProvider.js
37083
+ *
37084
+ * Implements the MCP SDK's `OAuthClientProvider` interface for custom
37085
+ * remote (streamable_http) MCP servers that authenticate with standard
37086
+ * OAuth 2.0 (e.g. Granola, Linear). This is the generic, SDK-native
37087
+ * alternative to the catalog-only subprocess auth model (`runAuth`)
37088
+ * used by the bundled Google servers.
37089
+ *
37090
+ * Flow (driven by mcpController.connectStreamableHttpWithOAuth):
37091
+ * 1. A loopback HTTP server is started on 127.0.0.1:<ephemeral> to
37092
+ * catch the OAuth redirect. `redirectUrl` reflects that port.
37093
+ * 2. On first connect with no/expired tokens, the SDK calls
37094
+ * `redirectToAuthorization`, which opens the system browser.
37095
+ * 3. The user consents; the authorization server redirects back to the
37096
+ * loopback `/callback?code=...`, which resolves `_waitForCode()`.
37097
+ * 4. The controller calls `transport.finishAuth(code)` → `saveTokens`.
37098
+ *
37099
+ * Tokens, Dynamic Client Registration info, and the PKCE verifier are
37100
+ * persisted encrypted in the SAME providers.json store as credentials
37101
+ * (via providerController), keyed by (appId, serverName). They are never
37102
+ * returned to the renderer.
37103
+ */
37104
+
37105
+ var mcpOAuthProvider;
37106
+ var hasRequiredMcpOAuthProvider;
37107
+
37108
+ function requireMcpOAuthProvider () {
37109
+ if (hasRequiredMcpOAuthProvider) return mcpOAuthProvider;
37110
+ hasRequiredMcpOAuthProvider = 1;
37111
+ const { shell } = require$$0$2;
37112
+ const http = require$$1$5;
37113
+ const providerController = requireProviderController();
37114
+
37115
+ /**
37116
+ * Start a loopback HTTP server bound to 127.0.0.1 on an OS-assigned port.
37117
+ * Resolves once listening with `{ server, port, codePromise }` where
37118
+ * `codePromise` resolves with the OAuth `code` (or rejects on `error`).
37119
+ */
37120
+ function startLoopbackServer() {
37121
+ let resolveCode;
37122
+ let rejectCode;
37123
+ const codePromise = new Promise((resolve, reject) => {
37124
+ resolveCode = resolve;
37125
+ rejectCode = reject;
37126
+ });
37127
+
37128
+ const server = http.createServer((req, res) => {
37129
+ try {
37130
+ const reqUrl = new URL(req.url, "http://127.0.0.1");
37131
+ if (reqUrl.pathname !== "/callback") {
37132
+ res.writeHead(404, { "Content-Type": "text/plain" });
37133
+ res.end("Not found");
37134
+ return;
37135
+ }
37136
+ const error = reqUrl.searchParams.get("error");
37137
+ const code = reqUrl.searchParams.get("code");
37138
+ const heading = error ? "Authorization failed" : "Authorization complete";
37139
+ const body = error
37140
+ ? "You can close this window and try again in Dash."
37141
+ : "You can close this window and return to Dash.";
37142
+ const html =
37143
+ "<!doctype html><html><head><meta charset='utf-8'>" +
37144
+ "<title>Dash</title></head>" +
37145
+ "<body style='font-family:system-ui,sans-serif;background:#111;color:#eee;" +
37146
+ "display:flex;align-items:center;justify-content:center;height:100vh;margin:0;'>" +
37147
+ `<div style='text-align:center;'><h2>${heading}</h2><p>${body}</p></div>` +
37148
+ "</body></html>";
37149
+ res.writeHead(error ? 400 : 200, { "Content-Type": "text/html" });
37150
+ res.end(html);
37151
+
37152
+ if (error) {
37153
+ rejectCode(new Error(`OAuth authorization error: ${error}`));
37154
+ } else if (code) {
37155
+ resolveCode(code);
37156
+ } else {
37157
+ rejectCode(new Error("OAuth callback missing authorization code"));
37158
+ }
37159
+ } catch (e) {
37160
+ rejectCode(e);
37161
+ }
37162
+ });
37163
+
37164
+ return new Promise((resolve, reject) => {
37165
+ server.once("error", reject);
37166
+ // Bind to loopback only (never 0.0.0.0) — port 0 = OS-assigned.
37167
+ server.listen(0, "127.0.0.1", () => {
37168
+ const addr = server.address();
37169
+ resolve({ server, port: addr.port, codePromise });
37170
+ });
37171
+ });
37172
+ }
37173
+
37174
+ /**
37175
+ * Create an OAuthClientProvider for one (appId, serverName) pair.
37176
+ *
37177
+ * @param {object} args
37178
+ * @param {BrowserWindow|null} args.win unused today; reserved for future
37179
+ * window-scoped flows.
37180
+ * @param {string} args.appId application id (providers.json namespace)
37181
+ * @param {string} args.serverName provider/server name (storage key)
37182
+ * @param {object} args.mcpConfig the server config; `mcpConfig.oauth` may
37183
+ * carry `{ scopes, clientId?, clientSecret? }`.
37184
+ */
37185
+ function createMcpOAuthProvider({ win, appId, serverName, mcpConfig }) {
37186
+ const oauth = (mcpConfig && mcpConfig.oauth) || {};
37187
+
37188
+ // Lazy-hydrated cache of persisted { tokens, clientInformation, codeVerifier }.
37189
+ let state = null;
37190
+ const load = () => {
37191
+ if (state) return state;
37192
+ const res = providerController.getOAuthState(win, appId, serverName);
37193
+ state = (res && res.oauth) || {};
37194
+ return state;
37195
+ };
37196
+ const persist = (patch) => {
37197
+ state = { ...load(), ...patch };
37198
+ providerController.saveOAuthState(win, appId, serverName, state);
37199
+ };
37200
+
37201
+ // Loopback server for the current flow (null when idle).
37202
+ let loopback = null;
37203
+
37204
+ return {
37205
+ get redirectUrl() {
37206
+ return loopback
37207
+ ? `http://127.0.0.1:${loopback.port}/callback`
37208
+ : undefined;
37209
+ },
37210
+
37211
+ get clientMetadata() {
37212
+ const redirectUrl = this.redirectUrl;
37213
+ return {
37214
+ client_name: `Dash – ${serverName}`,
37215
+ redirect_uris: redirectUrl ? [redirectUrl] : [],
37216
+ grant_types: ["authorization_code", "refresh_token"],
37217
+ response_types: ["code"],
37218
+ token_endpoint_auth_method: oauth.clientSecret
37219
+ ? "client_secret_post"
37220
+ : "none",
37221
+ ...(oauth.scopes ? { scope: oauth.scopes } : {}),
37222
+ };
37223
+ },
37224
+
37225
+ clientInformation() {
37226
+ // Pre-registered (manual) client wins over Dynamic Client Registration.
37227
+ if (oauth.clientId) {
37228
+ return {
37229
+ client_id: oauth.clientId,
37230
+ ...(oauth.clientSecret ? { client_secret: oauth.clientSecret } : {}),
37231
+ };
37232
+ }
37233
+ return load().clientInformation;
37234
+ },
37235
+
37236
+ saveClientInformation(clientInformation) {
37237
+ persist({ clientInformation });
37238
+ },
37239
+
37240
+ tokens() {
37241
+ return load().tokens;
37242
+ },
37243
+
37244
+ // Called by the SDK on initial authorization AND on every refresh.
37245
+ saveTokens(tokens) {
37246
+ persist({ tokens });
37247
+ },
37248
+
37249
+ saveCodeVerifier(codeVerifier) {
37250
+ persist({ codeVerifier });
37251
+ },
37252
+
37253
+ codeVerifier() {
37254
+ const v = load().codeVerifier;
37255
+ if (!v) throw new Error("No OAuth code verifier saved");
37256
+ return v;
37257
+ },
37258
+
37259
+ async redirectToAuthorization(authorizationUrl) {
37260
+ await shell.openExternal(authorizationUrl.toString());
37261
+ },
37262
+
37263
+ // Lets the SDK clear bad credentials so the next attempt re-auths
37264
+ // without manual intervention (e.g. refresh token revoked).
37265
+ invalidateCredentials(scope) {
37266
+ if (scope === "all" || scope === "tokens") persist({ tokens: undefined });
37267
+ if (scope === "all" || scope === "client")
37268
+ persist({ clientInformation: undefined });
37269
+ if (scope === "all" || scope === "verifier")
37270
+ persist({ codeVerifier: undefined });
37271
+ },
37272
+
37273
+ // --- helpers used by the controller (not part of the SDK interface) ---
37274
+
37275
+ async _startLoopback() {
37276
+ if (!loopback) {
37277
+ loopback = await startLoopbackServer();
37278
+ }
37279
+ return loopback;
37280
+ },
37281
+
37282
+ _stopLoopback() {
37283
+ if (loopback) {
37284
+ try {
37285
+ loopback.server.close();
37286
+ } catch (_e) {
37287
+ /* already closing */
37288
+ }
37289
+ loopback = null;
37290
+ }
37291
+ },
37292
+
37293
+ _waitForCode(timeoutMs = 120000) {
37294
+ if (!loopback) {
37295
+ return Promise.reject(new Error("OAuth loopback server not started"));
37296
+ }
37297
+ return Promise.race([
37298
+ loopback.codePromise,
37299
+ new Promise((_resolve, reject) =>
37300
+ setTimeout(
37301
+ () =>
37302
+ reject(
37303
+ new Error(
37304
+ "OAuth authorization timed out. Close the browser tab and try again.",
37305
+ ),
37306
+ ),
37307
+ timeoutMs,
37308
+ ),
37309
+ ),
37310
+ ]);
37311
+ },
37312
+ };
37313
+ }
37314
+
37315
+ mcpOAuthProvider = { createMcpOAuthProvider, startLoopbackServer };
37316
+ return mcpOAuthProvider;
37317
+ }
37318
+
36953
37319
  /**
36954
37320
  * mcpController.js
36955
37321
  *
@@ -36966,7 +37332,7 @@ var mcpScopeResolver = {
36966
37332
  const { Client } = require$$0$8;
36967
37333
  const {
36968
37334
  StdioClientTransport,
36969
- } = require$$1$5;
37335
+ } = require$$1$6;
36970
37336
  const {
36971
37337
  StreamableHTTPClientTransport,
36972
37338
  } = streamableHttp$1;
@@ -37377,6 +37743,57 @@ async function refreshGoogleOAuthToken(tokenRefresh) {
37377
37743
  console.log("[mcpController] Google OAuth token refreshed successfully");
37378
37744
  }
37379
37745
 
37746
+ /**
37747
+ * Connect a streamable_http MCP server that authenticates via OAuth 2.0,
37748
+ * using the SDK's native OAuthClientProvider.
37749
+ *
37750
+ * Drives the connect → UnauthorizedError → finishAuth → reconnect dance:
37751
+ * - With valid (or refreshable) tokens, the first connect succeeds and no
37752
+ * browser opens (fast path).
37753
+ * - Otherwise the SDK opens the system browser via
37754
+ * `redirectToAuthorization`, throws UnauthorizedError, and we wait for
37755
+ * the loopback callback, exchange the code, then reconnect.
37756
+ *
37757
+ * The authorized reconnect uses a FRESH transport because the first
37758
+ * transport already called `start()` and cannot be restarted. `finishAuth`
37759
+ * runs on the original transport so it reuses that attempt's discovery state.
37760
+ *
37761
+ * @returns {Promise<{ client, transport }>} a connected client + transport
37762
+ */
37763
+ async function connectStreamableHttpWithOAuth(urlObj, oauthProvider) {
37764
+ const {
37765
+ UnauthorizedError,
37766
+ } = requireAuth();
37767
+
37768
+ await oauthProvider._startLoopback();
37769
+ try {
37770
+ const client = new Client({ name: "dash", version: "1.0.0" });
37771
+ const transport = new StreamableHTTPClientTransport(urlObj, {
37772
+ authProvider: oauthProvider,
37773
+ });
37774
+ try {
37775
+ await client.connect(transport);
37776
+ return { client, transport };
37777
+ } catch (err) {
37778
+ if (!(err instanceof UnauthorizedError)) throw err;
37779
+ console.log(
37780
+ "[mcpController] OAuth required — waiting for browser authorization...",
37781
+ );
37782
+ const code = await oauthProvider._waitForCode();
37783
+ await transport.finishAuth(code);
37784
+
37785
+ const client2 = new Client({ name: "dash", version: "1.0.0" });
37786
+ const transport2 = new StreamableHTTPClientTransport(urlObj, {
37787
+ authProvider: oauthProvider,
37788
+ });
37789
+ await client2.connect(transport2);
37790
+ return { client: client2, transport: transport2 };
37791
+ }
37792
+ } finally {
37793
+ oauthProvider._stopLoopback();
37794
+ }
37795
+ }
37796
+
37380
37797
  const mcpController$3 = {
37381
37798
  /**
37382
37799
  * startServer
@@ -37409,6 +37826,7 @@ const mcpController$3 = {
37409
37826
  credentials,
37410
37827
  workspaceId,
37411
37828
  pathScope = null,
37829
+ appId = null,
37412
37830
  ) => {
37413
37831
  const key = serverKey(workspaceId, serverName);
37414
37832
  // Slice 3b: when the gate is enforced, override credentials with
@@ -37483,6 +37901,11 @@ const mcpController$3 = {
37483
37901
 
37484
37902
  // Create transport based on type
37485
37903
  let transport;
37904
+ // OAuth state, populated below for OAuth streamable_http servers.
37905
+ // When set, the connect step runs the interactive OAuth dance
37906
+ // (which builds its own transport) instead of the generic connect.
37907
+ let oauthProvider = null;
37908
+ let oauthUrl = null;
37486
37909
  if (mcpConfig.transport === "streamable_http") {
37487
37910
  // Remote HTTP transport - connect to a hosted MCP server
37488
37911
  const url = interpolate$1(mcpConfig.url, credentials);
@@ -37490,21 +37913,37 @@ const mcpController$3 = {
37490
37913
  throw new Error("Streamable HTTP transport requires a URL");
37491
37914
  }
37492
37915
 
37493
- // Build request headers from headerTemplate
37494
- const headers = {};
37495
- if (mcpConfig.headerTemplate && credentials) {
37496
- Object.entries(mcpConfig.headerTemplate).forEach(
37497
- ([headerName, template]) => {
37498
- headers[headerName] = interpolate$1(template, credentials);
37916
+ if (mcpConfig.auth === "oauth" || mcpConfig.oauth) {
37917
+ // OAuth 2.0 server (e.g. Granola, Linear): authenticate via the
37918
+ // SDK's native OAuthClientProvider + browser flow + token
37919
+ // refresh. Transport is created during the connect dance below.
37920
+ const {
37921
+ createMcpOAuthProvider,
37922
+ } = requireMcpOAuthProvider();
37923
+ oauthProvider = createMcpOAuthProvider({
37924
+ win,
37925
+ appId,
37926
+ serverName,
37927
+ mcpConfig,
37928
+ });
37929
+ oauthUrl = new URL(url);
37930
+ } else {
37931
+ // Build request headers from headerTemplate
37932
+ const headers = {};
37933
+ if (mcpConfig.headerTemplate && credentials) {
37934
+ Object.entries(mcpConfig.headerTemplate).forEach(
37935
+ ([headerName, template]) => {
37936
+ headers[headerName] = interpolate$1(template, credentials);
37937
+ },
37938
+ );
37939
+ }
37940
+
37941
+ transport = new StreamableHTTPClientTransport(new URL(url), {
37942
+ requestInit: {
37943
+ headers,
37499
37944
  },
37500
- );
37945
+ });
37501
37946
  }
37502
-
37503
- transport = new StreamableHTTPClientTransport(new URL(url), {
37504
- requestInit: {
37505
- headers,
37506
- },
37507
- });
37508
37947
  } else {
37509
37948
  // stdio transport (default) - spawn a local child process
37510
37949
  const env = cleanEnvForChildProcess();
@@ -37589,14 +38028,26 @@ const mcpController$3 = {
37589
38028
  serverName,
37590
38029
  });
37591
38030
 
37592
- // Create MCP client
37593
- const client = new Client({
37594
- name: "dash",
37595
- version: "1.0.0",
37596
- });
37597
-
37598
- // Connect to the server
37599
- await client.connect(transport);
38031
+ // Create / connect MCP client. OAuth servers run the interactive
38032
+ // connect → authorize → reconnect dance, which yields its own
38033
+ // client + transport (a fresh transport is required for the
38034
+ // authorized reconnect).
38035
+ let client;
38036
+ if (oauthProvider) {
38037
+ const oauthResult = await connectStreamableHttpWithOAuth(
38038
+ oauthUrl,
38039
+ oauthProvider,
38040
+ );
38041
+ client = oauthResult.client;
38042
+ transport = oauthResult.transport;
38043
+ } else {
38044
+ client = new Client({
38045
+ name: "dash",
38046
+ version: "1.0.0",
38047
+ });
38048
+ // Connect to the server
38049
+ await client.connect(transport);
38050
+ }
37600
38051
 
37601
38052
  // List available tools
37602
38053
  let tools = [];
@@ -37682,6 +38133,83 @@ const mcpController$3 = {
37682
38133
  return startPromise;
37683
38134
  },
37684
38135
 
38136
+ /**
38137
+ * authorizeServer
38138
+ * Run the interactive OAuth 2.0 authorization flow on demand for a
38139
+ * custom streamable_http MCP server (the Settings "Authorize" button
38140
+ * and the re-auth banner). Opens the system browser, completes the
38141
+ * flow, and persists tokens encrypted. A subsequent startServer hits
38142
+ * the valid-token fast path with no browser.
38143
+ *
38144
+ * @param {BrowserWindow} win the main window
38145
+ * @param {string} serverName the provider/server name
38146
+ * @param {object} mcpConfig must be streamable_http with auth: "oauth"
38147
+ * @param {object} credentials decrypted credentials (for URL interpolation)
38148
+ * @param {string} appId application id (token storage namespace)
38149
+ * @returns {Promise<{ success, serverName, tools } | { error, message }>}
38150
+ */
38151
+ authorizeServer: async (win, serverName, mcpConfig, credentials, appId) => {
38152
+ try {
38153
+ if (
38154
+ !mcpConfig ||
38155
+ mcpConfig.transport !== "streamable_http" ||
38156
+ !(mcpConfig.auth === "oauth" || mcpConfig.oauth)
38157
+ ) {
38158
+ return {
38159
+ error: true,
38160
+ message: "Server is not configured for OAuth authorization",
38161
+ };
38162
+ }
38163
+
38164
+ const url = interpolate$1(mcpConfig.url, credentials);
38165
+ if (!url) {
38166
+ return {
38167
+ error: true,
38168
+ message: "Streamable HTTP transport requires a URL",
38169
+ };
38170
+ }
38171
+
38172
+ const { createMcpOAuthProvider } = requireMcpOAuthProvider();
38173
+ const oauthProvider = createMcpOAuthProvider({
38174
+ win,
38175
+ appId,
38176
+ serverName,
38177
+ mcpConfig,
38178
+ });
38179
+
38180
+ const { client } = await connectStreamableHttpWithOAuth(
38181
+ new URL(url),
38182
+ oauthProvider,
38183
+ );
38184
+
38185
+ // Verify the connection by listing tools, then close — startServer
38186
+ // will reconnect later using the freshly stored tokens.
38187
+ let tools = [];
38188
+ try {
38189
+ const toolsResult = await client.listTools();
38190
+ tools = toolsResult.tools || [];
38191
+ } catch (_e) {
38192
+ // Tools optional for the auth check
38193
+ }
38194
+ try {
38195
+ await client.close();
38196
+ } catch (_e) {
38197
+ /* ignore close errors */
38198
+ }
38199
+
38200
+ console.log(
38201
+ `[mcpController] OAuth authorization succeeded for ${serverName} (${tools.length} tools)`,
38202
+ );
38203
+ return { success: true, serverName, tools };
38204
+ } catch (error) {
38205
+ console.error(
38206
+ `[mcpController] OAuth authorization failed for ${serverName}:`,
38207
+ error,
38208
+ );
38209
+ return { error: true, message: error.message };
38210
+ }
38211
+ },
38212
+
37685
38213
  /**
37686
38214
  * stopServer
37687
38215
  * Stop a running MCP server
@@ -39941,6 +40469,130 @@ var menuItemsController_1 = menuItemsController$1;
39941
40469
 
39942
40470
  var cliController$3 = {exports: {}};
39943
40471
 
40472
+ /**
40473
+ * modelProviders.js
40474
+ *
40475
+ * Provider-pluggable registry for discovering and validating LLM models.
40476
+ *
40477
+ * Each provider knows how to:
40478
+ * - list its currently-available models (live, when credentials allow)
40479
+ * - fall back to a curated list (offline / no key / no live API)
40480
+ * - migrate retired model IDs to a current replacement
40481
+ * - name its default model
40482
+ *
40483
+ * Today only `anthropic` is implemented. Adding a vendor (OpenAI, Google, …)
40484
+ * is a single new entry in PROVIDERS — the dispatchers below, the
40485
+ * LLM_LIST_MODELS IPC, and the Settings UI need no changes.
40486
+ *
40487
+ * NOTE: the renderer mirrors the retired map + default for the auto-migrate
40488
+ * notice (src/utils/modelMigration.js). Keep the two in sync.
40489
+ */
40490
+
40491
+ const Anthropic$1 = require$$0$a;
40492
+
40493
+ const PROVIDERS = {
40494
+ anthropic: {
40495
+ id: "anthropic",
40496
+ label: "Anthropic",
40497
+ defaultModel: "claude-opus-4-8",
40498
+ // Curated fallback — current, non-deprecated IDs. Used for the Claude
40499
+ // Code CLI backend (which has no Models API) and whenever a live fetch
40500
+ // can't run (no API key, offline, error).
40501
+ curatedModels: [
40502
+ { value: "claude-opus-4-8", label: "Claude Opus 4.8" },
40503
+ { value: "claude-opus-4-7", label: "Claude Opus 4.7" },
40504
+ { value: "claude-sonnet-4-6", label: "Claude Sonnet 4.6" },
40505
+ { value: "claude-haiku-4-5", label: "Claude Haiku 4.5" },
40506
+ { value: "claude-fable-5", label: "Claude Fable 5" },
40507
+ ],
40508
+ // Retired/deprecated IDs → current replacement. Lets a stale saved
40509
+ // selection self-heal instead of 404-ing at call time.
40510
+ retiredMap: {
40511
+ "claude-sonnet-4-20250514": "claude-sonnet-4-6",
40512
+ "claude-opus-4-20250514": "claude-opus-4-8",
40513
+ "claude-opus-4-1-20250805": "claude-opus-4-8",
40514
+ "claude-3-7-sonnet-20250219": "claude-sonnet-4-6",
40515
+ "claude-3-5-sonnet-20241022": "claude-sonnet-4-6",
40516
+ "claude-3-5-sonnet-20240620": "claude-sonnet-4-6",
40517
+ "claude-3-5-haiku-20241022": "claude-haiku-4-5",
40518
+ "claude-3-opus-20240229": "claude-opus-4-8",
40519
+ },
40520
+ async listModels({ apiKey } = {}) {
40521
+ const client = new Anthropic$1({ apiKey });
40522
+ const res = await client.models.list();
40523
+ return (res.data || []).map((m) => ({
40524
+ value: m.id,
40525
+ label: m.display_name || m.id,
40526
+ }));
40527
+ },
40528
+ },
40529
+ };
40530
+
40531
+ const DEFAULT_PROVIDER = "anthropic";
40532
+
40533
+ function getProvider$1(providerId) {
40534
+ return PROVIDERS[providerId] || PROVIDERS[DEFAULT_PROVIDER];
40535
+ }
40536
+
40537
+ function getCuratedModels(providerId) {
40538
+ return getProvider$1(providerId).curatedModels.slice();
40539
+ }
40540
+
40541
+ function getDefaultModel(providerId) {
40542
+ return getProvider$1(providerId).defaultModel;
40543
+ }
40544
+
40545
+ /**
40546
+ * Map a retired/deprecated model id to its current replacement. Returns the
40547
+ * provider default for a falsy id, and passes through anything not in the
40548
+ * retired map (live/curated ids are returned unchanged).
40549
+ */
40550
+ function migrateModelId$2(providerId, id) {
40551
+ if (!id) return getDefaultModel(providerId);
40552
+ const map = getProvider$1(providerId).retiredMap || {};
40553
+ return map[id] || id;
40554
+ }
40555
+
40556
+ function isKnownRetired(providerId, id) {
40557
+ const map = getProvider$1(providerId).retiredMap || {};
40558
+ return !!map[id];
40559
+ }
40560
+
40561
+ /**
40562
+ * List models for a provider. Attempts a live fetch when the provider
40563
+ * supports it and credentials are present; otherwise returns the curated
40564
+ * fallback. Never throws — always resolves to a usable list.
40565
+ *
40566
+ * @returns {Promise<{ models: Array<{value,label}>, source: "live"|"curated" }>}
40567
+ */
40568
+ async function listModels(providerId, creds = {}) {
40569
+ const provider = getProvider$1(providerId);
40570
+ if (provider.listModels && creds && creds.apiKey) {
40571
+ try {
40572
+ const models = await provider.listModels(creds);
40573
+ if (Array.isArray(models) && models.length > 0) {
40574
+ return { models, source: "live" };
40575
+ }
40576
+ } catch (e) {
40577
+ console.warn(
40578
+ `[modelProviders] live model fetch failed for ${provider.id}: ${e.message}`,
40579
+ );
40580
+ }
40581
+ }
40582
+ return { models: provider.curatedModels.slice(), source: "curated" };
40583
+ }
40584
+
40585
+ var modelProviders = {
40586
+ PROVIDERS,
40587
+ DEFAULT_PROVIDER,
40588
+ getProvider: getProvider$1,
40589
+ getCuratedModels,
40590
+ getDefaultModel,
40591
+ migrateModelId: migrateModelId$2,
40592
+ isKnownRetired,
40593
+ listModels,
40594
+ };
40595
+
39944
40596
  var mcp = {};
39945
40597
 
39946
40598
  var server$1 = {};
@@ -56494,13 +57146,13 @@ __export(src_exports, {
56494
57146
  var dist = __toCommonJS(src_exports);
56495
57147
 
56496
57148
  // src/server.ts
56497
- var import_node_http = require$$0$a;
57149
+ var import_node_http = require$$1$5;
56498
57150
 
56499
57151
  // src/listener.ts
56500
- var import_node_http22 = require$$1$6;
57152
+ var import_node_http22 = require$$1$7;
56501
57153
 
56502
57154
  // src/request.ts
56503
- var import_node_http2 = require$$1$6;
57155
+ var import_node_http2 = require$$1$7;
56504
57156
  var import_node_stream = require$$5$1;
56505
57157
  var RequestError = class extends Error {
56506
57158
  constructor(message, options) {
@@ -58792,6 +59444,7 @@ const {
58792
59444
  LLM_STREAM_COMPLETE: LLM_STREAM_COMPLETE$2,
58793
59445
  LLM_STREAM_ERROR: LLM_STREAM_ERROR$2,
58794
59446
  } = llmEvents$1;
59447
+ const { migrateModelId: migrateModelId$1 } = modelProviders;
58795
59448
 
58796
59449
  const IS_WINDOWS = process.platform === "win32";
58797
59450
 
@@ -59047,8 +59700,11 @@ const cliController$2 = {
59047
59700
  // buildClaudeCliArgs; below we only add args that depend on
59048
59701
  // runtime state (MCP config file paths) the helper can't know.
59049
59702
  const sessionIdForResume = widgetUuid ? sessions.get(widgetUuid) : null;
59703
+ // Heal a retired/stale saved model id before passing --model. Leave an
59704
+ // unset model alone so the CLI keeps its own default when none was chosen.
59705
+ const resolvedModel = model ? migrateModelId$1("anthropic", model) : model;
59050
59706
  const args = buildClaudeCliArgs({
59051
- model,
59707
+ model: resolvedModel,
59052
59708
  systemPrompt,
59053
59709
  sessionId: sessionIdForResume,
59054
59710
  replaceSystemPrompt,
@@ -60155,9 +60811,9 @@ var themeFromUrlErrors$1 = {
60155
60811
  */
60156
60812
 
60157
60813
  const css = require$$0$b;
60158
- const { Vibrant } = require$$1$7;
60814
+ const { Vibrant } = require$$1$8;
60159
60815
  const https = require$$11;
60160
- const http = require$$0$a;
60816
+ const http = require$$1$5;
60161
60817
  const { URL: URL$1 } = require$$4;
60162
60818
  const {
60163
60819
  UrlUnreachableError,
@@ -65005,9 +65661,13 @@ var toolHandlers$1 = {
65005
65661
  * per-request, receiving the full messages array each time.
65006
65662
  */
65007
65663
 
65008
- const Anthropic = require$$0$c;
65664
+ const Anthropic = require$$0$a;
65009
65665
  const mcpController$2 = mcpControllerExports;
65010
65666
  const cliController$1 = cliControllerExports;
65667
+ const {
65668
+ listModels: listProviderModels,
65669
+ migrateModelId,
65670
+ } = modelProviders;
65011
65671
  const toolDefinitions = toolDefinitions$1;
65012
65672
  const toolHandlers = toolHandlers$1;
65013
65673
  const {
@@ -65148,7 +65808,6 @@ const llmController$1 = {
65148
65808
 
65149
65809
  const {
65150
65810
  apiKey,
65151
- model = "claude-sonnet-4-20250514",
65152
65811
  messages,
65153
65812
  tools = [],
65154
65813
  toolServerMap = {},
@@ -65156,6 +65815,10 @@ const llmController$1 = {
65156
65815
  maxToolRounds = DEFAULT_MAX_TOOL_ROUNDS,
65157
65816
  } = params;
65158
65817
 
65818
+ // Heal a retired/stale saved model id (and fill the provider default when
65819
+ // none was supplied) so a stored selection can't 404 the request.
65820
+ const model = migrateModelId("anthropic", params.model);
65821
+
65159
65822
  // Set up abort controller
65160
65823
  const abortController = new AbortController();
65161
65824
  activeRequests.set(requestId, abortController);
@@ -65405,6 +66068,22 @@ const llmController$1 = {
65405
66068
  // Fallback to CLI controller
65406
66069
  return cliController$1.abortRequest(requestId);
65407
66070
  },
66071
+
66072
+ /**
66073
+ * listModels
66074
+ * Discover selectable models for a provider. Attempts a live fetch via the
66075
+ * provider's API (e.g. Anthropic Models API) when an apiKey is supplied;
66076
+ * otherwise returns the provider's curated fallback list. Provider-pluggable
66077
+ * — adding OpenAI/Google later is a new entry in modelProviders.js.
66078
+ *
66079
+ * @param {BrowserWindow} win - unused (API consistency)
66080
+ * @param {string} providerId - vendor id, e.g. "anthropic"
66081
+ * @param {string|null} apiKey - decrypted key for the live fetch (optional)
66082
+ * @returns {Promise<{ models, source }>}
66083
+ */
66084
+ listModels: async (win, providerId, apiKey) => {
66085
+ return listProviderModels(providerId || "anthropic", { apiKey });
66086
+ },
65408
66087
  };
65409
66088
 
65410
66089
  var llmController_1 = llmController$1;
@@ -74947,6 +75626,7 @@ const {
74947
75626
  MCP_INSTALL_KNOWN_EXTERNAL_CONFIRM: MCP_INSTALL_KNOWN_EXTERNAL_CONFIRM$1,
74948
75627
  MCP_INSTALL_KNOWN_EXTERNAL_RESULT: MCP_INSTALL_KNOWN_EXTERNAL_RESULT$1,
74949
75628
  MCP_RUN_AUTH,
75629
+ MCP_AUTHORIZE,
74950
75630
  } = events$8;
74951
75631
 
74952
75632
  const mcpApi$2 = {
@@ -74971,6 +75651,7 @@ const mcpApi$2 = {
74971
75651
  credentials,
74972
75652
  workspaceId = null,
74973
75653
  pathScope = null,
75654
+ appId = null,
74974
75655
  ) =>
74975
75656
  ipcRenderer$j.invoke(MCP_START_SERVER, {
74976
75657
  serverName,
@@ -74978,6 +75659,7 @@ const mcpApi$2 = {
74978
75659
  credentials,
74979
75660
  workspaceId,
74980
75661
  pathScope,
75662
+ appId,
74981
75663
  }),
74982
75664
 
74983
75665
  /**
@@ -75131,6 +75813,27 @@ const mcpApi$2 = {
75131
75813
  */
75132
75814
  runAuth: (mcpConfig, credentials, authCommand) =>
75133
75815
  ipcRenderer$j.invoke(MCP_RUN_AUTH, { mcpConfig, credentials, authCommand }),
75816
+
75817
+ /**
75818
+ * authorize
75819
+ * Run the interactive OAuth 2.0 authorization flow for a custom
75820
+ * streamable_http MCP server (opens the system browser). Tokens are
75821
+ * persisted encrypted in the main process; a later startServer reuses
75822
+ * them with no browser.
75823
+ *
75824
+ * @param {string} serverName the provider/server name (token storage key)
75825
+ * @param {object} mcpConfig { transport: "streamable_http", auth: "oauth", url, oauth }
75826
+ * @param {object} credentials decrypted credentials (URL interpolation)
75827
+ * @param {string} appId application id (token storage namespace)
75828
+ * @returns {Promise<{ success, serverName, tools } | { error, message }>}
75829
+ */
75830
+ authorize: (serverName, mcpConfig, credentials, appId = null) =>
75831
+ ipcRenderer$j.invoke(MCP_AUTHORIZE, {
75832
+ serverName,
75833
+ mcpConfig,
75834
+ credentials,
75835
+ appId,
75836
+ }),
75134
75837
  };
75135
75838
 
75136
75839
  var mcpApi_1 = mcpApi$2;
@@ -75538,6 +76241,7 @@ const {
75538
76241
  LLM_CLEAR_CLI_SESSION,
75539
76242
  LLM_CLI_SESSION_STATUS,
75540
76243
  LLM_CLI_END_SESSION,
76244
+ LLM_LIST_MODELS,
75541
76245
  LLM_STREAM_DELTA,
75542
76246
  LLM_STREAM_TOOL_CALL,
75543
76247
  LLM_STREAM_TOOL_RESULT,
@@ -75594,6 +76298,19 @@ const llmApi$2 = {
75594
76298
  */
75595
76299
  checkCliAvailable: () => ipcRenderer$d.invoke(LLM_CHECK_CLI_AVAILABLE),
75596
76300
 
76301
+ /**
76302
+ * listModels
76303
+ * Discover selectable models for a provider. With an apiKey, the main
76304
+ * process attempts a live fetch (e.g. Anthropic Models API); otherwise it
76305
+ * returns the provider's curated fallback list.
76306
+ *
76307
+ * @param {string} providerId - vendor id, e.g. "anthropic"
76308
+ * @param {string|null} apiKey - decrypted key for the live fetch (optional)
76309
+ * @returns {Promise<{ models, source }>}
76310
+ */
76311
+ listModels: (providerId, apiKey = null) =>
76312
+ ipcRenderer$d.invoke(LLM_LIST_MODELS, { providerId, apiKey }),
76313
+
75597
76314
  /**
75598
76315
  * clearCliSession
75599
76316
  * Clear the CLI conversation session for a widget (for "New Chat").