@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.
- package/dist/electron/index.js +2147 -1430
- package/dist/electron/index.js.map +1 -1
- package/dist/index.esm.js +369 -72
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +370 -71
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/electron/index.js
CHANGED
|
@@ -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$
|
|
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$
|
|
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$$
|
|
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$
|
|
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$
|
|
30738
|
-
parse$2.parse = core$5._parse(errors_js_1$
|
|
30739
|
-
parse$2.parseAsync = core$5._parseAsync(errors_js_1$
|
|
30740
|
-
parse$2.safeParse = core$5._safeParse(errors_js_1$
|
|
30741
|
-
parse$2.safeParseAsync = core$5._safeParseAsync(errors_js_1$
|
|
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
|
-
|
|
34212
|
-
|
|
34213
|
-
|
|
34214
|
-
|
|
34215
|
-
|
|
34216
|
-
|
|
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
|
-
|
|
34219
|
-
|
|
34220
|
-
|
|
34221
|
-
|
|
34222
|
-
|
|
34223
|
-
|
|
34224
|
-
|
|
34225
|
-
|
|
34226
|
-
|
|
34227
|
-
|
|
34228
|
-
|
|
34229
|
-
|
|
34230
|
-
|
|
34231
|
-
|
|
34232
|
-
|
|
34233
|
-
|
|
34234
|
-
|
|
34235
|
-
|
|
34236
|
-
|
|
34237
|
-
|
|
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
|
-
*
|
|
34608
|
+
* Base class for all OAuth errors
|
|
34240
34609
|
*/
|
|
34241
|
-
|
|
34242
|
-
|
|
34243
|
-
|
|
34244
|
-
|
|
34245
|
-
|
|
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
|
-
|
|
34254
|
-
|
|
34255
|
-
|
|
34256
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
34261
|
-
|
|
34262
|
-
|
|
34263
|
-
|
|
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
|
-
*
|
|
34644
|
+
* Invalid client error - Client authentication failed (e.g., unknown client, no client
|
|
34645
|
+
* authentication included, or unsupported authentication method).
|
|
34278
34646
|
*/
|
|
34279
|
-
|
|
34280
|
-
|
|
34281
|
-
|
|
34282
|
-
|
|
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
|
-
*
|
|
34302
|
-
*
|
|
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
|
-
|
|
34305
|
-
|
|
34306
|
-
|
|
34307
|
-
|
|
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
|
-
*
|
|
34344
|
-
*
|
|
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
|
-
|
|
34348
|
-
|
|
34349
|
-
|
|
34350
|
-
|
|
34351
|
-
}).shape
|
|
34352
|
-
});
|
|
34664
|
+
class UnauthorizedClientError extends OAuthError {
|
|
34665
|
+
}
|
|
34666
|
+
errors$2.UnauthorizedClientError = UnauthorizedClientError;
|
|
34667
|
+
UnauthorizedClientError.errorCode = 'unauthorized_client';
|
|
34353
34668
|
/**
|
|
34354
|
-
*
|
|
34669
|
+
* Unsupported grant type error - The authorization grant type is not supported
|
|
34670
|
+
* by the authorization server.
|
|
34355
34671
|
*/
|
|
34356
|
-
|
|
34357
|
-
|
|
34358
|
-
|
|
34359
|
-
|
|
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
|
-
*
|
|
34677
|
+
* Invalid scope error - The requested scope is invalid, unknown, malformed, or
|
|
34678
|
+
* exceeds the scope granted by the resource owner.
|
|
34368
34679
|
*/
|
|
34369
|
-
|
|
34370
|
-
|
|
34371
|
-
|
|
34372
|
-
|
|
34373
|
-
});
|
|
34680
|
+
class InvalidScopeError extends OAuthError {
|
|
34681
|
+
}
|
|
34682
|
+
errors$2.InvalidScopeError = InvalidScopeError;
|
|
34683
|
+
InvalidScopeError.errorCode = 'invalid_scope';
|
|
34374
34684
|
/**
|
|
34375
|
-
*
|
|
34685
|
+
* Access denied error - The resource owner or authorization server denied the request.
|
|
34376
34686
|
*/
|
|
34377
|
-
|
|
34687
|
+
class AccessDeniedError extends OAuthError {
|
|
34688
|
+
}
|
|
34689
|
+
errors$2.AccessDeniedError = AccessDeniedError;
|
|
34690
|
+
AccessDeniedError.errorCode = 'access_denied';
|
|
34378
34691
|
/**
|
|
34379
|
-
*
|
|
34692
|
+
* Server error - The authorization server encountered an unexpected condition
|
|
34693
|
+
* that prevented it from fulfilling the request.
|
|
34380
34694
|
*/
|
|
34381
|
-
|
|
34382
|
-
|
|
34383
|
-
|
|
34384
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
34405
|
-
|
|
34406
|
-
|
|
34407
|
-
|
|
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
|
-
*
|
|
34708
|
+
* Unsupported response type error - The authorization server does not support
|
|
34709
|
+
* obtaining an authorization code using this method.
|
|
34414
34710
|
*/
|
|
34415
|
-
|
|
34711
|
+
class UnsupportedResponseTypeError extends OAuthError {
|
|
34712
|
+
}
|
|
34713
|
+
errors$2.UnsupportedResponseTypeError = UnsupportedResponseTypeError;
|
|
34714
|
+
UnsupportedResponseTypeError.errorCode = 'unsupported_response_type';
|
|
34416
34715
|
/**
|
|
34417
|
-
*
|
|
34716
|
+
* Unsupported token type error - The authorization server does not support
|
|
34717
|
+
* the requested token type.
|
|
34418
34718
|
*/
|
|
34419
|
-
|
|
34420
|
-
|
|
34421
|
-
|
|
34422
|
-
|
|
34423
|
-
})
|
|
34424
|
-
.strip();
|
|
34719
|
+
class UnsupportedTokenTypeError extends OAuthError {
|
|
34720
|
+
}
|
|
34721
|
+
errors$2.UnsupportedTokenTypeError = UnsupportedTokenTypeError;
|
|
34722
|
+
UnsupportedTokenTypeError.errorCode = 'unsupported_token_type';
|
|
34425
34723
|
/**
|
|
34426
|
-
*
|
|
34724
|
+
* Invalid token error - The access token provided is expired, revoked, malformed,
|
|
34725
|
+
* or invalid for other reasons.
|
|
34427
34726
|
*/
|
|
34428
|
-
|
|
34429
|
-
|
|
34430
|
-
|
|
34431
|
-
|
|
34432
|
-
|
|
34433
|
-
|
|
34434
|
-
|
|
34435
|
-
|
|
34436
|
-
|
|
34437
|
-
|
|
34438
|
-
|
|
34439
|
-
|
|
34440
|
-
|
|
34441
|
-
|
|
34442
|
-
|
|
34443
|
-
|
|
34444
|
-
|
|
34445
|
-
|
|
34446
|
-
|
|
34447
|
-
|
|
34448
|
-
|
|
34449
|
-
|
|
34450
|
-
|
|
34451
|
-
|
|
34452
|
-
|
|
34453
|
-
|
|
34454
|
-
|
|
34455
|
-
|
|
34456
|
-
|
|
34457
|
-
|
|
34458
|
-
|
|
34459
|
-
|
|
34460
|
-
|
|
34461
|
-
|
|
34462
|
-
|
|
34463
|
-
|
|
34464
|
-
|
|
34465
|
-
|
|
34466
|
-
|
|
34467
|
-
|
|
34468
|
-
|
|
34469
|
-
|
|
34470
|
-
|
|
34471
|
-
|
|
34472
|
-
|
|
34473
|
-
|
|
34474
|
-
|
|
34475
|
-
|
|
34476
|
-
|
|
34477
|
-
|
|
34478
|
-
|
|
34479
|
-
|
|
34480
|
-
|
|
34481
|
-
|
|
34482
|
-
|
|
34483
|
-
|
|
34484
|
-
|
|
34485
|
-
|
|
34486
|
-
|
|
34487
|
-
|
|
34488
|
-
|
|
34489
|
-
|
|
34490
|
-
|
|
34491
|
-
|
|
34492
|
-
|
|
34493
|
-
|
|
34494
|
-
|
|
34495
|
-
|
|
34496
|
-
|
|
34497
|
-
|
|
34498
|
-
|
|
34499
|
-
|
|
34500
|
-
|
|
34501
|
-
|
|
34502
|
-
|
|
34503
|
-
|
|
34504
|
-
|
|
34505
|
-
|
|
34506
|
-
|
|
34507
|
-
|
|
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
|
-
|
|
35597
|
-
|
|
35598
|
-
|
|
35599
|
-
|
|
35600
|
-
|
|
35601
|
-
|
|
35602
|
-
|
|
35603
|
-
|
|
35604
|
-
|
|
35605
|
-
|
|
35606
|
-
|
|
35607
|
-
|
|
35608
|
-
|
|
35609
|
-
|
|
35610
|
-
|
|
35611
|
-
|
|
35612
|
-
|
|
35613
|
-
|
|
35614
|
-
|
|
35615
|
-
|
|
35616
|
-
|
|
35617
|
-
|
|
35618
|
-
|
|
35619
|
-
|
|
35620
|
-
|
|
35621
|
-
|
|
35622
|
-
|
|
35623
|
-
|
|
35624
|
-
|
|
35625
|
-
|
|
35626
|
-
|
|
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 =
|
|
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$
|
|
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
|
-
|
|
37494
|
-
|
|
37495
|
-
|
|
37496
|
-
|
|
37497
|
-
|
|
37498
|
-
|
|
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
|
-
|
|
37594
|
-
|
|
37595
|
-
|
|
37596
|
-
|
|
37597
|
-
|
|
37598
|
-
|
|
37599
|
-
|
|
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$$
|
|
57149
|
+
var import_node_http = require$$1$5;
|
|
56498
57150
|
|
|
56499
57151
|
// src/listener.ts
|
|
56500
|
-
var import_node_http22 = require$$1$
|
|
57152
|
+
var import_node_http22 = require$$1$7;
|
|
56501
57153
|
|
|
56502
57154
|
// src/request.ts
|
|
56503
|
-
var import_node_http2 = require$$1$
|
|
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$
|
|
60814
|
+
const { Vibrant } = require$$1$8;
|
|
60159
60815
|
const https = require$$11;
|
|
60160
|
-
const http = require$$
|
|
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$
|
|
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").
|