@frontmcp/sdk 1.5.2 → 1.5.4
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/agent/agent.scope.d.ts +8 -0
- package/agent/agent.scope.d.ts.map +1 -1
- package/auth/flows/oauth.authorize.flow.d.ts.map +1 -1
- package/auth/flows/oauth.callback.flow.d.ts +13 -0
- package/auth/flows/oauth.callback.flow.d.ts.map +1 -1
- package/auth/flows/oauth.provider-callback.flow.d.ts.map +1 -1
- package/auth/flows/oauth.register.flow.d.ts.map +1 -1
- package/auth/flows/oauth.token.flow.d.ts +2 -0
- package/auth/flows/oauth.token.flow.d.ts.map +1 -1
- package/auth/flows/session.verify.flow.d.ts.map +1 -1
- package/auth/flows/well-known.oauth-authorization-server.flow.d.ts +1 -0
- package/auth/flows/well-known.oauth-authorization-server.flow.d.ts.map +1 -1
- package/auth/flows/well-known.prm.flow.d.ts.map +1 -1
- package/auth/instances/instance.local-primary-auth.d.ts +2 -2
- package/auth/instances/instance.local-primary-auth.d.ts.map +1 -1
- package/common/metadata/app.metadata.d.ts +20 -4
- package/common/metadata/app.metadata.d.ts.map +1 -1
- package/common/metadata/flow.metadata.d.ts +1 -1
- package/common/metadata/front-mcp.metadata.d.ts +72 -8
- package/common/metadata/front-mcp.metadata.d.ts.map +1 -1
- package/common/metadata/tool.metadata.d.ts +1 -1
- package/common/utils/decide-request-intent.utils.d.ts +25 -0
- package/common/utils/decide-request-intent.utils.d.ts.map +1 -1
- package/common/utils/path.utils.d.ts +2 -0
- package/common/utils/path.utils.d.ts.map +1 -1
- package/esm/index.mjs +380 -68
- package/front-mcp/front-mcp.providers.d.ts +32 -0
- package/front-mcp/front-mcp.providers.d.ts.map +1 -1
- package/index.js +379 -67
- package/job/enclave/job-enclave.bridge.d.ts +6 -0
- package/job/enclave/job-enclave.bridge.d.ts.map +1 -1
- package/package.json +11 -11
- package/scope/flows/http.request.flow.d.ts.map +1 -1
- package/server/middleware/host-validation.middleware.d.ts.map +1 -1
- package/tool/flows/call-tool.flow.d.ts.map +1 -1
- package/transport/mcp-handlers/index.d.ts +1 -1
package/index.js
CHANGED
|
@@ -20862,18 +20862,28 @@ var init_call_tool_flow = __esm({
|
|
|
20862
20862
|
const consentCtx = this.input.ctx;
|
|
20863
20863
|
if (!consentCtx?.internalCall) {
|
|
20864
20864
|
const consentedToolIds = getConsentedToolIds(authInfo);
|
|
20865
|
-
if (consentedToolIds && tool
|
|
20866
|
-
this.
|
|
20867
|
-
|
|
20868
|
-
)
|
|
20869
|
-
|
|
20865
|
+
if (consentedToolIds && tool) {
|
|
20866
|
+
const bareNameCount = this.scope.tools.getTools(true).filter((t) => t.name === tool.name).length;
|
|
20867
|
+
const consented = bareNameCount > 1 ? isToolConsented(consentedToolIds, tool.fullName) : isToolConsented(consentedToolIds, tool.name, tool.fullName);
|
|
20868
|
+
if (!consented) {
|
|
20869
|
+
this.logger.info(
|
|
20870
|
+
`checkToolAuthorization: tool "${tool.fullName || tool.name}" not in consented set \u2014 rejecting`
|
|
20871
|
+
);
|
|
20872
|
+
throw new ToolNotConsentedError(tool.fullName || tool.name);
|
|
20873
|
+
}
|
|
20870
20874
|
}
|
|
20871
20875
|
}
|
|
20872
20876
|
const claimAuthorizedApps = getAuthorizedAppIds(authInfo);
|
|
20873
20877
|
const legacyAuthorization = authInfo?.extra?.["authorization"];
|
|
20874
20878
|
if (!claimAuthorizedApps && !legacyAuthorization) {
|
|
20875
|
-
this.
|
|
20876
|
-
|
|
20879
|
+
const incrementalEnabled = !consentCtx?.internalCall && !!authInfo?.token && this.scope.auth?.options?.incrementalAuth?.enabled === true;
|
|
20880
|
+
if (!incrementalEnabled) {
|
|
20881
|
+
this.logger.verbose("checkToolAuthorization:skip (no auth context)");
|
|
20882
|
+
return;
|
|
20883
|
+
}
|
|
20884
|
+
this.logger.warn(
|
|
20885
|
+
"checkToolAuthorization: incrementalAuth enabled but the verified token carries no authorized_apps claim \u2014 failing closed"
|
|
20886
|
+
);
|
|
20877
20887
|
}
|
|
20878
20888
|
const appId = tool?.owner?.id;
|
|
20879
20889
|
if (!appId) {
|
|
@@ -34171,7 +34181,7 @@ var init_oauth_authorize_flow = __esm({
|
|
|
34171
34181
|
if (!result.success) {
|
|
34172
34182
|
const errors = this.formatZodErrors(result.error);
|
|
34173
34183
|
this.logger.warn(`Authorization request validation failed: ${errors.join(", ")}`);
|
|
34174
|
-
this.respondWithError(errors,
|
|
34184
|
+
this.respondWithError(errors, void 0, rawState);
|
|
34175
34185
|
return;
|
|
34176
34186
|
}
|
|
34177
34187
|
this.state.set("validatedRequest", result.data);
|
|
@@ -34226,6 +34236,22 @@ var init_oauth_authorize_flow = __esm({
|
|
|
34226
34236
|
this.respondWithError(["redirect_uri is not registered for this client"], void 0, rawState);
|
|
34227
34237
|
return;
|
|
34228
34238
|
}
|
|
34239
|
+
if (!registered) {
|
|
34240
|
+
const authOptions = this.scope.auth?.options;
|
|
34241
|
+
const requireRegistered = authOptions?.requireRegisteredClients === true;
|
|
34242
|
+
const redirectAllowlisted = registry?.hasRedirectAllowlist?.() === true;
|
|
34243
|
+
if (requireRegistered && !redirectAllowlisted) {
|
|
34244
|
+
this.logger.warn(
|
|
34245
|
+
`OAuth authorize: rejecting unregistered client_id "${client_id}" (requireRegisteredClients)`
|
|
34246
|
+
);
|
|
34247
|
+
this.respondWithError(
|
|
34248
|
+
["Unknown client_id: register the client (DCR / pre-registered) or use a CIMD client-id URL"],
|
|
34249
|
+
void 0,
|
|
34250
|
+
rawState
|
|
34251
|
+
);
|
|
34252
|
+
return;
|
|
34253
|
+
}
|
|
34254
|
+
}
|
|
34229
34255
|
}
|
|
34230
34256
|
}
|
|
34231
34257
|
async checkIfAuthorized() {
|
|
@@ -34946,6 +34972,11 @@ var init_oauth_callback_flow = __esm({
|
|
|
34946
34972
|
return;
|
|
34947
34973
|
}
|
|
34948
34974
|
}
|
|
34975
|
+
if (this.isCrossOriginSubmission()) {
|
|
34976
|
+
this.logger.warn("Cross-origin login submission rejected (CSRF)");
|
|
34977
|
+
this.respond(httpRespond.html(this.renderErrorPage("invalid_request", "Cross-origin request blocked"), 400));
|
|
34978
|
+
return;
|
|
34979
|
+
}
|
|
34949
34980
|
let userSub;
|
|
34950
34981
|
if (email) {
|
|
34951
34982
|
userSub = this.generateUserSub(email);
|
|
@@ -35402,6 +35433,33 @@ var init_oauth_callback_flow = __esm({
|
|
|
35402
35433
|
}
|
|
35403
35434
|
return void 0;
|
|
35404
35435
|
}
|
|
35436
|
+
/**
|
|
35437
|
+
* Same-origin (CSRF) check for a state-changing login/consent submission.
|
|
35438
|
+
*
|
|
35439
|
+
* Returns true when the request carries an `Origin` or `Referer` header whose
|
|
35440
|
+
* host does NOT match the request's own host — the signature of a cross-site
|
|
35441
|
+
* login-CSRF/fixation. `Origin`/`Referer` are set by the browser and cannot
|
|
35442
|
+
* be forged by a malicious page, so this reliably distinguishes a legitimate
|
|
35443
|
+
* same-origin submission (the built-in form is served by THIS server) from a
|
|
35444
|
+
* cross-site one. Returns false (do NOT block) when neither header is present
|
|
35445
|
+
* or the request's own host cannot be determined — defense-in-depth, not a
|
|
35446
|
+
* hard gate, so header-stripped GET navigations and existing flows keep working.
|
|
35447
|
+
*/
|
|
35448
|
+
isCrossOriginSubmission() {
|
|
35449
|
+
const headers = this.rawInput?.request?.headers ?? {};
|
|
35450
|
+
const first = (v) => Array.isArray(v) ? v[0] : typeof v === "string" ? v : void 0;
|
|
35451
|
+
const source = first(headers["origin"]) ?? first(headers["referer"]);
|
|
35452
|
+
if (!source) return false;
|
|
35453
|
+
let sourceHost;
|
|
35454
|
+
try {
|
|
35455
|
+
sourceHost = new URL(source).host.toLowerCase();
|
|
35456
|
+
} catch {
|
|
35457
|
+
return true;
|
|
35458
|
+
}
|
|
35459
|
+
const selfHosts = [first(headers["host"]), first(headers["x-forwarded-host"])].filter((h2) => !!h2).map((h2) => h2.toLowerCase());
|
|
35460
|
+
if (selfHosts.length === 0) return false;
|
|
35461
|
+
return !selfHosts.includes(sourceHost);
|
|
35462
|
+
}
|
|
35405
35463
|
collectLoginFields(request) {
|
|
35406
35464
|
const fields = {};
|
|
35407
35465
|
const absorb = (source) => {
|
|
@@ -36161,19 +36219,21 @@ var init_oauth_provider_callback_flow = __esm({
|
|
|
36161
36219
|
);
|
|
36162
36220
|
return;
|
|
36163
36221
|
}
|
|
36164
|
-
const
|
|
36165
|
-
if (
|
|
36166
|
-
|
|
36167
|
-
|
|
36168
|
-
|
|
36169
|
-
this.respond(
|
|
36170
|
-
httpRespond.html(
|
|
36171
|
-
this.renderErrorPage("invalid_request", "Invalid state parameter. Please restart authentication."),
|
|
36172
|
-
400
|
|
36173
|
-
)
|
|
36222
|
+
const expectedState = session.currentProviderState;
|
|
36223
|
+
if (!expectedState || expectedState !== providerState) {
|
|
36224
|
+
if (this.getStateValidation() === "format") {
|
|
36225
|
+
this.logger.warn(
|
|
36226
|
+
'federatedAuth.stateValidation:"format" no longer skips provider-state nonce verification (security hardening) \u2014 the callback state must match the value issued for this session'
|
|
36174
36227
|
);
|
|
36175
|
-
return;
|
|
36176
36228
|
}
|
|
36229
|
+
this.logger.warn("State mismatch for provider callback");
|
|
36230
|
+
this.respond(
|
|
36231
|
+
httpRespond.html(
|
|
36232
|
+
this.renderErrorPage("invalid_request", "Invalid state parameter. Please restart authentication."),
|
|
36233
|
+
400
|
|
36234
|
+
)
|
|
36235
|
+
);
|
|
36236
|
+
return;
|
|
36177
36237
|
}
|
|
36178
36238
|
this.state.set("federatedSession", session);
|
|
36179
36239
|
}
|
|
@@ -36260,11 +36320,19 @@ var init_oauth_provider_callback_flow = __esm({
|
|
|
36260
36320
|
this.state.set("providerUserInfo", userInfo);
|
|
36261
36321
|
} catch (err) {
|
|
36262
36322
|
const errMsg = err instanceof Error ? err.message : "Unknown error";
|
|
36263
|
-
this.logger.warn(`Failed to
|
|
36323
|
+
this.logger.warn(`Failed to resolve user identity from provider ${providerId}: ${errMsg}`);
|
|
36324
|
+
this.respond(
|
|
36325
|
+
httpRespond.html(
|
|
36326
|
+
this.renderErrorPage("access_denied", "Could not determine your identity from the provider"),
|
|
36327
|
+
400
|
|
36328
|
+
)
|
|
36329
|
+
);
|
|
36330
|
+
return;
|
|
36264
36331
|
}
|
|
36265
36332
|
}
|
|
36266
36333
|
this.logger.info(`Successfully exchanged code for tokens with provider: ${providerId}`);
|
|
36267
36334
|
} catch (err) {
|
|
36335
|
+
if (err instanceof FlowControl) throw err;
|
|
36268
36336
|
const errMsg = err instanceof Error ? err.message : "Unknown error";
|
|
36269
36337
|
this.logger.error(`Provider token exchange error: ${errMsg}`);
|
|
36270
36338
|
this.respond(
|
|
@@ -36642,6 +36710,24 @@ function extractBearer(headers) {
|
|
|
36642
36710
|
const match = /^Bearer\s+(.+)$/i.exec(raw.trim());
|
|
36643
36711
|
return match ? match[1].trim() : void 0;
|
|
36644
36712
|
}
|
|
36713
|
+
function isLoopbackRedirectUri(uri) {
|
|
36714
|
+
let url;
|
|
36715
|
+
try {
|
|
36716
|
+
url = new URL(uri);
|
|
36717
|
+
} catch {
|
|
36718
|
+
return false;
|
|
36719
|
+
}
|
|
36720
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return false;
|
|
36721
|
+
if (url.username || url.password) return false;
|
|
36722
|
+
const host = url.hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
36723
|
+
if (host === "localhost" || host === "::1") return true;
|
|
36724
|
+
const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
|
|
36725
|
+
if (m) {
|
|
36726
|
+
const octets = m.slice(1).map((n) => Number(n));
|
|
36727
|
+
return octets.every((o) => o >= 0 && o <= 255) && octets[0] === 127;
|
|
36728
|
+
}
|
|
36729
|
+
return false;
|
|
36730
|
+
}
|
|
36645
36731
|
var import_lazy_zod68, import_utils67, inputSchema20, outputSchema19, registrationRequestSchema, stateSchema19, plan19, name19, Stage19, OauthRegisterFlow;
|
|
36646
36732
|
var init_oauth_register_flow = __esm({
|
|
36647
36733
|
"libs/sdk/src/auth/flows/oauth.register.flow.ts"() {
|
|
@@ -36770,13 +36856,13 @@ var init_oauth_register_flow = __esm({
|
|
|
36770
36856
|
return;
|
|
36771
36857
|
}
|
|
36772
36858
|
} else {
|
|
36773
|
-
const bad = redirect_uris.find((u) =>
|
|
36859
|
+
const bad = redirect_uris.find((u) => !isLoopbackRedirectUri(u));
|
|
36774
36860
|
if (bad) {
|
|
36775
36861
|
this.respond(
|
|
36776
36862
|
httpRespond.json(
|
|
36777
36863
|
{
|
|
36778
36864
|
error: "invalid_redirect_uri",
|
|
36779
|
-
error_description: `Registration allows only localhost
|
|
36865
|
+
error_description: `Registration allows only loopback redirect_uris (localhost, 127.0.0.0/8, ::1) without an allowlist; got ${bad}`
|
|
36780
36866
|
},
|
|
36781
36867
|
{ status: 400 }
|
|
36782
36868
|
)
|
|
@@ -36817,7 +36903,20 @@ var init_oauth_register_flow = __esm({
|
|
|
36817
36903
|
created_at: now,
|
|
36818
36904
|
dev: true
|
|
36819
36905
|
};
|
|
36820
|
-
this.localAuth.dcrClientRegistry.register(this.registered);
|
|
36906
|
+
const accepted = this.localAuth.dcrClientRegistry.register(this.registered);
|
|
36907
|
+
if (!accepted) {
|
|
36908
|
+
this.scopeLogger.warn("OAuth register: dynamic client capacity reached \u2014 rejecting registration");
|
|
36909
|
+
this.respond(
|
|
36910
|
+
httpRespond.json(
|
|
36911
|
+
{
|
|
36912
|
+
error: "temporarily_unavailable",
|
|
36913
|
+
error_description: "Client registration capacity reached; please try again later."
|
|
36914
|
+
},
|
|
36915
|
+
{ status: 503 }
|
|
36916
|
+
)
|
|
36917
|
+
);
|
|
36918
|
+
return;
|
|
36919
|
+
}
|
|
36821
36920
|
}
|
|
36822
36921
|
async respondRegistration() {
|
|
36823
36922
|
const c = this.registered;
|
|
@@ -36881,6 +36980,23 @@ var init_oauth_register_flow = __esm({
|
|
|
36881
36980
|
});
|
|
36882
36981
|
|
|
36883
36982
|
// libs/sdk/src/auth/flows/oauth.token.flow.ts
|
|
36983
|
+
function parseBasicAuthorization(header) {
|
|
36984
|
+
if (typeof header !== "string") return void 0;
|
|
36985
|
+
const m = /^Basic\s+(.+)$/i.exec(header.trim());
|
|
36986
|
+
if (!m) return void 0;
|
|
36987
|
+
let decoded;
|
|
36988
|
+
try {
|
|
36989
|
+
const b64 = m[1].trim();
|
|
36990
|
+
if (typeof Buffer !== "undefined") decoded = Buffer.from(b64, "base64").toString("utf8");
|
|
36991
|
+
else if (typeof atob === "function") decoded = atob(b64);
|
|
36992
|
+
else return void 0;
|
|
36993
|
+
} catch {
|
|
36994
|
+
return void 0;
|
|
36995
|
+
}
|
|
36996
|
+
const idx = decoded.indexOf(":");
|
|
36997
|
+
if (idx < 0) return void 0;
|
|
36998
|
+
return { clientId: decoded.slice(0, idx), clientSecret: decoded.slice(idx + 1) };
|
|
36999
|
+
}
|
|
36884
37000
|
function coerceTokenRequestBody(request) {
|
|
36885
37001
|
const body = request.body;
|
|
36886
37002
|
if (body && typeof body === "object" && !Array.isArray(body) && Object.keys(body).length > 0) {
|
|
@@ -36927,6 +37043,8 @@ var init_oauth_token_flow = __esm({
|
|
|
36927
37043
|
redirect_uri: import_lazy_zod69.z.string().url(),
|
|
36928
37044
|
/** Public client identifier */
|
|
36929
37045
|
client_id: import_lazy_zod69.z.string().min(1),
|
|
37046
|
+
/** Client secret (confidential clients; also accepted via HTTP Basic). */
|
|
37047
|
+
client_secret: import_lazy_zod69.z.string().optional(),
|
|
36930
37048
|
/** PKCE verifier bound to the code */
|
|
36931
37049
|
code_verifier: import_lazy_zod69.z.string().regex(pkceVerifierRegex, "code_verifier must be 43\u2013128 chars of A\u2013Z, a\u2013z, 0\u20139, '-', '.', '_' or '~'")
|
|
36932
37050
|
});
|
|
@@ -36935,7 +37053,9 @@ var init_oauth_token_flow = __esm({
|
|
|
36935
37053
|
/** The refresh token */
|
|
36936
37054
|
refresh_token: import_lazy_zod69.z.string().min(1, "refresh_token is required"),
|
|
36937
37055
|
/** Public client identifier */
|
|
36938
|
-
client_id: import_lazy_zod69.z.string().min(1)
|
|
37056
|
+
client_id: import_lazy_zod69.z.string().min(1),
|
|
37057
|
+
/** Client secret (confidential clients; also accepted via HTTP Basic). */
|
|
37058
|
+
client_secret: import_lazy_zod69.z.string().optional()
|
|
36939
37059
|
});
|
|
36940
37060
|
anonymousGrant = import_lazy_zod69.z.object({
|
|
36941
37061
|
grant_type: import_lazy_zod69.z.literal("anonymous"),
|
|
@@ -36982,6 +37102,12 @@ var init_oauth_token_flow = __esm({
|
|
|
36982
37102
|
const isDefaultAuthProvider = !metadata.auth;
|
|
36983
37103
|
const isOrchestrated = !isDefaultAuthProvider;
|
|
36984
37104
|
const rawBody = coerceTokenRequestBody(request);
|
|
37105
|
+
const basic = parseBasicAuthorization(request.headers?.["authorization"]);
|
|
37106
|
+
if (basic && rawBody && typeof rawBody === "object") {
|
|
37107
|
+
const b = rawBody;
|
|
37108
|
+
if (b["client_id"] === void 0) b["client_id"] = basic.clientId;
|
|
37109
|
+
if (b["client_secret"] === void 0) b["client_secret"] = basic.clientSecret;
|
|
37110
|
+
}
|
|
36985
37111
|
const parsed = tokenRequestSchema.safeParse(rawBody);
|
|
36986
37112
|
if (parsed.success) {
|
|
36987
37113
|
this.state.set({
|
|
@@ -37032,7 +37158,13 @@ var init_oauth_token_flow = __esm({
|
|
|
37032
37158
|
return;
|
|
37033
37159
|
}
|
|
37034
37160
|
const localAuth = this.scope.auth;
|
|
37035
|
-
const result = await localAuth.exchangeCode(
|
|
37161
|
+
const result = await localAuth.exchangeCode(
|
|
37162
|
+
body.code,
|
|
37163
|
+
body.client_id,
|
|
37164
|
+
body.redirect_uri,
|
|
37165
|
+
body.code_verifier,
|
|
37166
|
+
body.client_secret
|
|
37167
|
+
);
|
|
37036
37168
|
if ("error" in result) {
|
|
37037
37169
|
this.logger.warn(`Code exchange failed: ${result.error}`);
|
|
37038
37170
|
this.respond(
|
|
@@ -37079,7 +37211,7 @@ var init_oauth_token_flow = __esm({
|
|
|
37079
37211
|
return;
|
|
37080
37212
|
}
|
|
37081
37213
|
const localAuth = this.scope.auth;
|
|
37082
|
-
const result = await localAuth.refreshAccessToken(body.refresh_token, body.client_id);
|
|
37214
|
+
const result = await localAuth.refreshAccessToken(body.refresh_token, body.client_id, body.client_secret);
|
|
37083
37215
|
if ("error" in result) {
|
|
37084
37216
|
this.logger.warn(`Refresh token failed: ${result.error}`);
|
|
37085
37217
|
this.respond(
|
|
@@ -37302,7 +37434,7 @@ var init_oauth_userinfo_flow = __esm({
|
|
|
37302
37434
|
});
|
|
37303
37435
|
|
|
37304
37436
|
// libs/sdk/src/auth/flows/session.verify.flow.ts
|
|
37305
|
-
var import_reflect_metadata17, import_auth26, import_lazy_zod71, import_utils69, inputSchema23, stateSchema22, UnauthorizedSchema, AuthorizedSchema, ForbiddenSchema, sessionVerifyOutputSchema, plan22, name22, Stage22, SessionVerifyFlow;
|
|
37437
|
+
var import_reflect_metadata17, import_auth26, import_lazy_zod71, import_utils69, inputSchema23, stateSchema22, UnauthorizedSchema, AuthorizedSchema, ForbiddenSchema, sessionVerifyOutputSchema, plan22, name22, Stage22, warnedUnpinnedAudience, SessionVerifyFlow;
|
|
37306
37438
|
var init_session_verify_flow = __esm({
|
|
37307
37439
|
"libs/sdk/src/auth/flows/session.verify.flow.ts"() {
|
|
37308
37440
|
"use strict";
|
|
@@ -37351,6 +37483,7 @@ var init_session_verify_flow = __esm({
|
|
|
37351
37483
|
};
|
|
37352
37484
|
name22 = "session:verify";
|
|
37353
37485
|
Stage22 = StageHookOf(name22);
|
|
37486
|
+
warnedUnpinnedAudience = false;
|
|
37354
37487
|
SessionVerifyFlow = class extends FlowBase {
|
|
37355
37488
|
logger = this.scopeLogger.child("SessionVerifyFlow");
|
|
37356
37489
|
maskSub(sub) {
|
|
@@ -37368,20 +37501,20 @@ var init_session_verify_flow = __esm({
|
|
|
37368
37501
|
const machineId = (0, import_utils69.getMachineId)();
|
|
37369
37502
|
if (sessionIdHeader) {
|
|
37370
37503
|
const existingPayload = decryptPublicSession(sessionIdHeader);
|
|
37371
|
-
|
|
37372
|
-
|
|
37373
|
-
|
|
37374
|
-
|
|
37375
|
-
|
|
37376
|
-
|
|
37377
|
-
|
|
37378
|
-
|
|
37379
|
-
id: sessionIdHeader,
|
|
37380
|
-
payload: finalPayload
|
|
37504
|
+
if (existingPayload && existingPayload.nodeId === machineId) {
|
|
37505
|
+
const anonId = existingPayload.uuid ?? `${existingPayload.iat * 1e3}`;
|
|
37506
|
+
const user2 = { sub: `anon:${anonId}`, iss: issuer, name: "Anonymous", scope: scopes.join(" ") };
|
|
37507
|
+
this.respond({
|
|
37508
|
+
kind: "authorized",
|
|
37509
|
+
authorization: {
|
|
37510
|
+
token: "",
|
|
37511
|
+
user: user2,
|
|
37512
|
+
session: { id: sessionIdHeader, payload: existingPayload }
|
|
37381
37513
|
}
|
|
37382
|
-
}
|
|
37383
|
-
|
|
37384
|
-
|
|
37514
|
+
});
|
|
37515
|
+
return;
|
|
37516
|
+
}
|
|
37517
|
+
this.logger.verbose("createAnonymousSession: ignoring unrecognized client session id; minting a fresh one");
|
|
37385
37518
|
}
|
|
37386
37519
|
const now = Date.now();
|
|
37387
37520
|
const user = {
|
|
@@ -37519,6 +37652,8 @@ var init_session_verify_flow = __esm({
|
|
|
37519
37652
|
{
|
|
37520
37653
|
id: primary.providerConfig?.id ?? "default",
|
|
37521
37654
|
issuerUrl: issuer,
|
|
37655
|
+
additionalIssuers: primary.providerConfig?.additionalIssuers,
|
|
37656
|
+
verifyIssuer: primary.providerConfig?.verifyIssuer,
|
|
37522
37657
|
jwks: primary.providerConfig?.jwks,
|
|
37523
37658
|
jwksUri: primary.providerConfig?.jwksUri
|
|
37524
37659
|
}
|
|
@@ -37530,6 +37665,37 @@ var init_session_verify_flow = __esm({
|
|
|
37530
37665
|
const result = await verify;
|
|
37531
37666
|
if (result.ok) {
|
|
37532
37667
|
this.state.set({ jwtPayload: result.payload });
|
|
37668
|
+
if ((0, import_auth10.isTransparentMode)(authOptions)) {
|
|
37669
|
+
const configuredAudience = authOptions.expectedAudience;
|
|
37670
|
+
if (!configuredAudience && !isPublicUrlPinned() && !warnedUnpinnedAudience) {
|
|
37671
|
+
warnedUnpinnedAudience = true;
|
|
37672
|
+
this.logger.warn(
|
|
37673
|
+
"transparent audience validation is deriving the expected audience from the request Host: set auth.expectedAudience or FRONTMCP_PUBLIC_URL to bind tokens to a fixed resource. X-Forwarded-Host is ignored unless FRONTMCP_TRUST_PROXY is enabled."
|
|
37674
|
+
);
|
|
37675
|
+
}
|
|
37676
|
+
const expectedAudiences = configuredAudience ? Array.isArray(configuredAudience) ? configuredAudience : [configuredAudience] : (0, import_auth26.deriveExpectedAudience)(this.state.required.baseUrl);
|
|
37677
|
+
const audResult = (0, import_auth26.validateAudience)(result.payload?.["aud"], {
|
|
37678
|
+
expectedAudiences,
|
|
37679
|
+
// By default accept tokens with no `aud` claim: many IdPs omit it, and
|
|
37680
|
+
// rejecting them here would break existing transparent deployments.
|
|
37681
|
+
// Tokens that DO carry an `aud` for another service are still rejected,
|
|
37682
|
+
// which blocks the cross-service replay in GHSA-hvvp-67p3-j379.
|
|
37683
|
+
// Operators whose IdP always sets `aud` can flip `requireAudience:true`
|
|
37684
|
+
// to reject audience-less tokens outright.
|
|
37685
|
+
allowNoAudience: authOptions.requireAudience !== true
|
|
37686
|
+
});
|
|
37687
|
+
if (!audResult.valid) {
|
|
37688
|
+
this.logger.warn("verifyIfJwt: audience validation failed", { error: audResult.error });
|
|
37689
|
+
this.respond({
|
|
37690
|
+
kind: "unauthorized",
|
|
37691
|
+
prmMetadataHeader: (0, import_auth26.buildInvalidTokenHeader)(
|
|
37692
|
+
this.state.required.prmUrl,
|
|
37693
|
+
audResult.error ?? "Token audience is not valid for this resource"
|
|
37694
|
+
)
|
|
37695
|
+
});
|
|
37696
|
+
return;
|
|
37697
|
+
}
|
|
37698
|
+
}
|
|
37533
37699
|
const requiredScopes = authOptions?.["requiredScopes"];
|
|
37534
37700
|
if (requiredScopes && requiredScopes.length > 0) {
|
|
37535
37701
|
const scopeClaim = result.payload?.["scope"];
|
|
@@ -37749,6 +37915,10 @@ var init_well_known_oauth_authorization_server_flow = __esm({
|
|
|
37749
37915
|
kind: import_lazy_zod73.z.literal("json"),
|
|
37750
37916
|
status: import_lazy_zod73.z.literal(200),
|
|
37751
37917
|
contentType: import_lazy_zod73.z.literal("application/json; charset=utf-8"),
|
|
37918
|
+
// Response headers (e.g. `Cache-Control: no-store`). This flow uses a custom
|
|
37919
|
+
// output schema rather than `HttpJsonSchema`, so `headers` must be declared
|
|
37920
|
+
// explicitly for the renderer to emit it.
|
|
37921
|
+
headers: import_lazy_zod73.z.record(import_lazy_zod73.z.string(), import_lazy_zod73.z.string()).optional(),
|
|
37752
37922
|
body: import_lazy_zod73.z.object({
|
|
37753
37923
|
issuer: import_lazy_zod73.z.string().min(1),
|
|
37754
37924
|
authorization_endpoint: import_lazy_zod73.z.string().min(1),
|
|
@@ -37830,6 +38000,10 @@ var init_well_known_oauth_authorization_server_flow = __esm({
|
|
|
37830
38000
|
kind: "json",
|
|
37831
38001
|
contentType: "application/json; charset=utf-8",
|
|
37832
38002
|
status: 200,
|
|
38003
|
+
// Never cache: the advertised endpoints are derived from the request
|
|
38004
|
+
// origin, so a shared cache could serve a poisoned authorization/token
|
|
38005
|
+
// endpoint to another client (OAuth mix-up).
|
|
38006
|
+
headers: { "cache-control": "no-store" },
|
|
37833
38007
|
body: {
|
|
37834
38008
|
issuer: baseIssuer,
|
|
37835
38009
|
authorization_endpoint: `${oauthBaseUrl}/oauth/authorize`,
|
|
@@ -37933,6 +38107,10 @@ var init_well_known_prm_flow = __esm({
|
|
|
37933
38107
|
kind: "json",
|
|
37934
38108
|
contentType: "application/json; charset=utf-8",
|
|
37935
38109
|
status: 200,
|
|
38110
|
+
// Never cache: the body embeds the request-derived origin, so a shared
|
|
38111
|
+
// cache keyed only on (Host, path) could otherwise serve a poisoned
|
|
38112
|
+
// `authorization_servers` value to another client (OAuth mix-up).
|
|
38113
|
+
headers: { "cache-control": "no-store" },
|
|
37936
38114
|
body: {
|
|
37937
38115
|
resource,
|
|
37938
38116
|
authorization_servers: [baseUrl],
|
|
@@ -37946,6 +38124,7 @@ var init_well_known_prm_flow = __esm({
|
|
|
37946
38124
|
kind: "json",
|
|
37947
38125
|
status: 200,
|
|
37948
38126
|
contentType: "application/json; charset=utf-8",
|
|
38127
|
+
headers: { "cache-control": "no-store" },
|
|
37949
38128
|
body: {
|
|
37950
38129
|
resource,
|
|
37951
38130
|
authorization_servers: [baseUrl],
|
|
@@ -38506,7 +38685,11 @@ var init_instance_local_primary_auth = __esm({
|
|
|
38506
38685
|
/**
|
|
38507
38686
|
* Exchange an authorization code for tokens
|
|
38508
38687
|
*/
|
|
38509
|
-
async exchangeCode(code, clientId, redirectUri, codeVerifier) {
|
|
38688
|
+
async exchangeCode(code, clientId, redirectUri, codeVerifier, clientSecret) {
|
|
38689
|
+
if (this.dcrClientRegistryImpl.verifyClientSecret(clientId, clientSecret) === "invalid") {
|
|
38690
|
+
this.logger.warn("Client authentication failed at token endpoint (authorization_code)");
|
|
38691
|
+
return { error: "invalid_client", error_description: "Client authentication failed" };
|
|
38692
|
+
}
|
|
38510
38693
|
const codeRecord = await this.authorizationStore.getAuthorizationCode(code);
|
|
38511
38694
|
if (!codeRecord) {
|
|
38512
38695
|
this.logger.warn(`Authorization code not found or expired: ${code.substring(0, 8)}...`);
|
|
@@ -38517,6 +38700,13 @@ var init_instance_local_primary_auth = __esm({
|
|
|
38517
38700
|
}
|
|
38518
38701
|
if (codeRecord.used) {
|
|
38519
38702
|
this.logger.warn(`Authorization code already used: ${code.substring(0, 8)}...`);
|
|
38703
|
+
if (codeRecord.issuedRefreshToken) {
|
|
38704
|
+
try {
|
|
38705
|
+
await this.authorizationStore.revokeRefreshToken(codeRecord.issuedRefreshToken);
|
|
38706
|
+
} catch (err) {
|
|
38707
|
+
this.logger.warn(`Failed to revoke refresh token on authorization-code replay: ${err}`);
|
|
38708
|
+
}
|
|
38709
|
+
}
|
|
38520
38710
|
await this.authorizationStore.deleteAuthorizationCode(code);
|
|
38521
38711
|
return {
|
|
38522
38712
|
error: "invalid_grant",
|
|
@@ -38578,9 +38768,19 @@ var init_instance_local_primary_auth = __esm({
|
|
|
38578
38768
|
clientId,
|
|
38579
38769
|
userSub: user.sub,
|
|
38580
38770
|
scopes: codeRecord.scopes,
|
|
38581
|
-
resource: codeRecord.resource
|
|
38771
|
+
resource: codeRecord.resource,
|
|
38772
|
+
userEmail: user.email,
|
|
38773
|
+
userName: user.name,
|
|
38774
|
+
consentEnabled: codeRecord.consentEnabled,
|
|
38775
|
+
selectedToolIds: codeRecord.selectedToolIds,
|
|
38776
|
+
authorizedAppIds: codeRecord.authorizedAppIds,
|
|
38777
|
+
customClaims: codeRecord.customClaims,
|
|
38778
|
+
federatedLoginUsed: codeRecord.federatedLoginUsed,
|
|
38779
|
+
selectedProviderIds: codeRecord.selectedProviderIds,
|
|
38780
|
+
skippedProviderIds: codeRecord.skippedProviderIds
|
|
38582
38781
|
});
|
|
38583
38782
|
await this.authorizationStore.storeRefreshToken(refreshTokenRecord);
|
|
38783
|
+
await this.authorizationStore.markCodeUsed(code, refreshTokenRecord.token);
|
|
38584
38784
|
this.logger.info(`Tokens issued for user: ${user.sub}`);
|
|
38585
38785
|
return {
|
|
38586
38786
|
access_token: accessToken,
|
|
@@ -38593,7 +38793,11 @@ var init_instance_local_primary_auth = __esm({
|
|
|
38593
38793
|
/**
|
|
38594
38794
|
* Refresh an access token using a refresh token
|
|
38595
38795
|
*/
|
|
38596
|
-
async refreshAccessToken(refreshToken, clientId) {
|
|
38796
|
+
async refreshAccessToken(refreshToken, clientId, clientSecret) {
|
|
38797
|
+
if (this.dcrClientRegistryImpl.verifyClientSecret(clientId, clientSecret) === "invalid") {
|
|
38798
|
+
this.logger.warn("Client authentication failed at token endpoint (refresh_token)");
|
|
38799
|
+
return { error: "invalid_client", error_description: "Client authentication failed" };
|
|
38800
|
+
}
|
|
38597
38801
|
const tokenRecord = await this.authorizationStore.getRefreshToken(refreshToken);
|
|
38598
38802
|
if (!tokenRecord) {
|
|
38599
38803
|
this.logger.warn("Refresh token not found or expired");
|
|
@@ -38609,13 +38813,37 @@ var init_instance_local_primary_auth = __esm({
|
|
|
38609
38813
|
error_description: "Client ID does not match"
|
|
38610
38814
|
};
|
|
38611
38815
|
}
|
|
38612
|
-
const user = {
|
|
38613
|
-
|
|
38816
|
+
const user = {
|
|
38817
|
+
sub: tokenRecord.userSub,
|
|
38818
|
+
email: tokenRecord.userEmail,
|
|
38819
|
+
name: tokenRecord.userName
|
|
38820
|
+
};
|
|
38821
|
+
const hasAuthorizedApps = Array.isArray(tokenRecord.authorizedAppIds);
|
|
38822
|
+
const hasCustomClaims = !!tokenRecord.customClaims && Object.keys(tokenRecord.customClaims).length > 0;
|
|
38823
|
+
const consentMetadata = tokenRecord.consentEnabled || tokenRecord.federatedLoginUsed || hasCustomClaims || hasAuthorizedApps ? {
|
|
38824
|
+
selectedToolIds: tokenRecord.selectedToolIds,
|
|
38825
|
+
selectedProviderIds: tokenRecord.selectedProviderIds,
|
|
38826
|
+
skippedProviderIds: tokenRecord.skippedProviderIds,
|
|
38827
|
+
consentEnabled: tokenRecord.consentEnabled,
|
|
38828
|
+
federatedLoginUsed: tokenRecord.federatedLoginUsed,
|
|
38829
|
+
authorizedAppIds: tokenRecord.authorizedAppIds,
|
|
38830
|
+
customClaims: tokenRecord.customClaims
|
|
38831
|
+
} : void 0;
|
|
38832
|
+
const accessToken = await this.signAccessToken(user, tokenRecord.scopes, tokenRecord.resource, consentMetadata);
|
|
38614
38833
|
const newRefreshRecord = this.authorizationStore.createRefreshTokenRecord({
|
|
38615
38834
|
clientId,
|
|
38616
38835
|
userSub: tokenRecord.userSub,
|
|
38617
38836
|
scopes: tokenRecord.scopes,
|
|
38618
|
-
resource: tokenRecord.resource
|
|
38837
|
+
resource: tokenRecord.resource,
|
|
38838
|
+
userEmail: tokenRecord.userEmail,
|
|
38839
|
+
userName: tokenRecord.userName,
|
|
38840
|
+
consentEnabled: tokenRecord.consentEnabled,
|
|
38841
|
+
selectedToolIds: tokenRecord.selectedToolIds,
|
|
38842
|
+
authorizedAppIds: tokenRecord.authorizedAppIds,
|
|
38843
|
+
customClaims: tokenRecord.customClaims,
|
|
38844
|
+
federatedLoginUsed: tokenRecord.federatedLoginUsed,
|
|
38845
|
+
selectedProviderIds: tokenRecord.selectedProviderIds,
|
|
38846
|
+
skippedProviderIds: tokenRecord.skippedProviderIds
|
|
38619
38847
|
});
|
|
38620
38848
|
await this.authorizationStore.rotateRefreshToken(refreshToken, newRefreshRecord);
|
|
38621
38849
|
this.logger.info(`Tokens refreshed for user: ${user.sub}`);
|
|
@@ -38915,13 +39143,22 @@ var init_instance_local_primary_auth = __esm({
|
|
|
38915
39143
|
try {
|
|
38916
39144
|
const [, payloadB64] = idToken.split(".");
|
|
38917
39145
|
const payload = JSON.parse(new TextDecoder().decode((0, import_utils71.base64urlDecode)(payloadB64)));
|
|
38918
|
-
|
|
38919
|
-
|
|
38920
|
-
|
|
38921
|
-
|
|
38922
|
-
|
|
38923
|
-
|
|
38924
|
-
|
|
39146
|
+
const exp = typeof payload["exp"] === "number" ? payload["exp"] : void 0;
|
|
39147
|
+
if (exp !== void 0 && exp * 1e3 < Date.now()) {
|
|
39148
|
+
this.logger.warn(`ID token for ${providerId} is expired; falling back to userinfo`);
|
|
39149
|
+
} else {
|
|
39150
|
+
const sub = typeof payload["sub"] === "string" && payload["sub"].trim() ? payload["sub"] : void 0;
|
|
39151
|
+
if (sub) {
|
|
39152
|
+
return {
|
|
39153
|
+
sub,
|
|
39154
|
+
email: payload["email"],
|
|
39155
|
+
name: payload["name"],
|
|
39156
|
+
picture: payload["picture"],
|
|
39157
|
+
claims: payload
|
|
39158
|
+
};
|
|
39159
|
+
}
|
|
39160
|
+
this.logger.warn(`ID token for ${providerId} has no usable sub; falling back to userinfo`);
|
|
39161
|
+
}
|
|
38925
39162
|
} catch (err) {
|
|
38926
39163
|
this.logger.warn(`Failed to parse ID token for ${providerId}: ${err}`);
|
|
38927
39164
|
}
|
|
@@ -38936,21 +39173,25 @@ var init_instance_local_primary_auth = __esm({
|
|
|
38936
39173
|
});
|
|
38937
39174
|
if (response.ok) {
|
|
38938
39175
|
const userInfo = await response.json();
|
|
38939
|
-
|
|
38940
|
-
|
|
38941
|
-
|
|
38942
|
-
|
|
38943
|
-
|
|
38944
|
-
|
|
38945
|
-
|
|
39176
|
+
const sub = typeof userInfo["sub"] === "string" && userInfo["sub"].trim() ? userInfo["sub"] : void 0;
|
|
39177
|
+
if (sub) {
|
|
39178
|
+
return {
|
|
39179
|
+
sub,
|
|
39180
|
+
email: userInfo["email"],
|
|
39181
|
+
name: userInfo["name"],
|
|
39182
|
+
picture: userInfo["picture"],
|
|
39183
|
+
claims: userInfo
|
|
39184
|
+
};
|
|
39185
|
+
}
|
|
39186
|
+
this.logger.warn(`userinfo for ${providerId} returned no usable sub`);
|
|
38946
39187
|
}
|
|
38947
39188
|
} catch (err) {
|
|
38948
39189
|
this.logger.warn(`Failed to get userinfo from ${providerId}: ${err}`);
|
|
38949
39190
|
}
|
|
38950
39191
|
}
|
|
38951
|
-
|
|
38952
|
-
|
|
38953
|
-
|
|
39192
|
+
throw new Error(
|
|
39193
|
+
`Unable to determine a stable user identity from provider "${providerId}" (no id_token sub and no usable userinfo endpoint)`
|
|
39194
|
+
);
|
|
38954
39195
|
}
|
|
38955
39196
|
/**
|
|
38956
39197
|
* Refresh tokens from upstream provider
|
|
@@ -49169,6 +49410,16 @@ function decideIntent(req, cfg) {
|
|
|
49169
49410
|
debug: { key, channel: channel2, flags }
|
|
49170
49411
|
};
|
|
49171
49412
|
}
|
|
49413
|
+
function authorizeSessionTermination(verifyResult, requestedSessionId) {
|
|
49414
|
+
if (!verifyResult || verifyResult.kind !== "authorized" || !verifyResult.authorization) {
|
|
49415
|
+
return { kind: "unauthorized" };
|
|
49416
|
+
}
|
|
49417
|
+
const ownedSessionId = verifyResult.authorization.session?.id;
|
|
49418
|
+
if (!ownedSessionId || !requestedSessionId || requestedSessionId !== ownedSessionId) {
|
|
49419
|
+
return { kind: "forbidden" };
|
|
49420
|
+
}
|
|
49421
|
+
return { kind: "ok", sessionId: ownedSessionId };
|
|
49422
|
+
}
|
|
49172
49423
|
var import_lazy_zod99, intentSchema, decisionSchema, CH_OTHER, CH_GET_SSE, CH_POST_INIT_JSON, CH_POST_INIT_SSE, CH_POST_JSON, CH_POST_SSE, CH_POST_MESSAGE, CH_GET_SSE_PATH, CH_MASK, B_HAS_SESSION, B_STATELESS_EN, B_REQ_SESSION, B_LEGACY_HINT, B_SSE_LISTENER_EN, B_STREAMABLE_EN, B_STATEFUL_EN, B_LEGACY_EN, h, wantsSSE, wantsJSON, isInitialize, RULES;
|
|
49173
49424
|
var init_decide_request_intent_utils = __esm({
|
|
49174
49425
|
"libs/sdk/src/common/utils/decide-request-intent.utils.ts"() {
|
|
@@ -49431,13 +49682,29 @@ function readPublicUrlPin() {
|
|
|
49431
49682
|
}
|
|
49432
49683
|
return void 0;
|
|
49433
49684
|
}
|
|
49685
|
+
function isPublicUrlPinned() {
|
|
49686
|
+
return readPublicUrlPin() !== void 0;
|
|
49687
|
+
}
|
|
49688
|
+
function isProxyTrusted() {
|
|
49689
|
+
try {
|
|
49690
|
+
if (typeof process !== "undefined" && process.env) {
|
|
49691
|
+
const v = process.env["FRONTMCP_TRUST_PROXY"];
|
|
49692
|
+
return typeof v === "string" && /^(1|true|yes|on)$/i.test(v.trim());
|
|
49693
|
+
}
|
|
49694
|
+
} catch {
|
|
49695
|
+
}
|
|
49696
|
+
return false;
|
|
49697
|
+
}
|
|
49434
49698
|
function getRequestBaseUrl(req, entryPath) {
|
|
49435
49699
|
const pin = readPublicUrlPin();
|
|
49436
49700
|
if (pin) {
|
|
49437
49701
|
return `${pin}${entryPath ?? ""}`;
|
|
49438
49702
|
}
|
|
49439
|
-
const
|
|
49440
|
-
const
|
|
49703
|
+
const trustProxy = isProxyTrusted();
|
|
49704
|
+
const fwdProto = trustProxy ? req.headers["x-forwarded-proto"] : void 0;
|
|
49705
|
+
const fwdHost = trustProxy ? req.headers["x-forwarded-host"] : void 0;
|
|
49706
|
+
const proto = fwdProto || req.protocol || "http";
|
|
49707
|
+
const host = fwdHost || req.headers["host"];
|
|
49441
49708
|
return `${proto}://${host}${entryPath ?? ""}`;
|
|
49442
49709
|
}
|
|
49443
49710
|
function computeIssuer(req, entryPath, scopeBase) {
|
|
@@ -54196,6 +54463,10 @@ var init_web_standard_mcp = __esm({
|
|
|
54196
54463
|
});
|
|
54197
54464
|
|
|
54198
54465
|
// libs/sdk/src/scope/flows/http.request.flow.ts
|
|
54466
|
+
function parseUserScopes(user) {
|
|
54467
|
+
const s = user?.scope;
|
|
54468
|
+
return typeof s === "string" ? s.split(/\s+/).filter(Boolean) : [];
|
|
54469
|
+
}
|
|
54199
54470
|
var import_auth38, import_lazy_zod105, import_utils94, plan39, httpRequestStateSchema, name40, Stage40, HttpRequestFlow;
|
|
54200
54471
|
var init_http_request_flow = __esm({
|
|
54201
54472
|
"libs/sdk/src/scope/flows/http.request.flow.ts"() {
|
|
@@ -54340,7 +54611,10 @@ var init_http_request_flow = __esm({
|
|
|
54340
54611
|
ctx.updateAuthInfo({
|
|
54341
54612
|
token,
|
|
54342
54613
|
clientId: user.sub,
|
|
54343
|
-
scopes
|
|
54614
|
+
// Populate scopes from the verified token's `scope` claim instead of
|
|
54615
|
+
// hardcoding `[]`, so any consumer that authorizes on
|
|
54616
|
+
// `authInfo.scopes` sees the real grant (same accessor as the web path).
|
|
54617
|
+
scopes: parseUserScopes(user),
|
|
54344
54618
|
// JWT exp is in seconds, SDK uses milliseconds throughout (e.g., Date.now())
|
|
54345
54619
|
expiresAt: user.exp ? user.exp * 1e3 : void 0,
|
|
54346
54620
|
extra: {
|
|
@@ -54459,6 +54733,27 @@ var init_http_request_flow = __esm({
|
|
|
54459
54733
|
this.next();
|
|
54460
54734
|
return;
|
|
54461
54735
|
}
|
|
54736
|
+
const deleteVerify = this.state.required.verifyResult;
|
|
54737
|
+
const requestedSessionId = request[ServerRequestTokens.sessionId] ?? request.headers["mcp-session-id"] ?? request.query?.["sessionId"];
|
|
54738
|
+
const deleteAuthz = authorizeSessionTermination(deleteVerify, requestedSessionId);
|
|
54739
|
+
if (deleteAuthz.kind === "unauthorized") {
|
|
54740
|
+
this.logger.warn(`[${this.requestId}] DELETE session denied: request is not authenticated`);
|
|
54741
|
+
this.respond(
|
|
54742
|
+
httpRespond.unauthorized({
|
|
54743
|
+
headers: {
|
|
54744
|
+
"WWW-Authenticate": deleteVerify.prmMetadataHeader ?? "Bearer"
|
|
54745
|
+
}
|
|
54746
|
+
})
|
|
54747
|
+
);
|
|
54748
|
+
return;
|
|
54749
|
+
}
|
|
54750
|
+
if (deleteAuthz.kind === "forbidden") {
|
|
54751
|
+
this.logger.warn(`[${this.requestId}] DELETE session denied: caller does not own the requested session id`);
|
|
54752
|
+
this.respond(httpRespond.notFound("Session not found"));
|
|
54753
|
+
return;
|
|
54754
|
+
}
|
|
54755
|
+
request[ServerRequestTokens.auth] = deleteVerify.authorization;
|
|
54756
|
+
request[ServerRequestTokens.sessionId] = deleteAuthz.sessionId;
|
|
54462
54757
|
this.state.set("intent", decision.intent);
|
|
54463
54758
|
return;
|
|
54464
54759
|
}
|
|
@@ -54571,7 +54866,9 @@ var init_http_request_flow = __esm({
|
|
|
54571
54866
|
const authInfo = authorization ? {
|
|
54572
54867
|
token: authorization.token,
|
|
54573
54868
|
clientId: authorization.user?.sub,
|
|
54574
|
-
|
|
54869
|
+
// Same scope parsing as the Node path so scope-aware handlers behave
|
|
54870
|
+
// identically on the web transport.
|
|
54871
|
+
scopes: parseUserScopes(authorization.user),
|
|
54575
54872
|
expiresAt: authorization.user?.exp ? authorization.user.exp * 1e3 : void 0,
|
|
54576
54873
|
extra: {
|
|
54577
54874
|
user: authorization.user,
|
|
@@ -57530,6 +57827,17 @@ function createHostValidationMiddleware(options) {
|
|
|
57530
57827
|
});
|
|
57531
57828
|
return;
|
|
57532
57829
|
}
|
|
57830
|
+
const forwardedHost = req.headers?.["x-forwarded-host"];
|
|
57831
|
+
if (forwardedHost) {
|
|
57832
|
+
const candidates = forwardedHost.split(",").map((h2) => h2.trim()).filter(Boolean);
|
|
57833
|
+
if (candidates.some((h2) => !allowedHostsSet.has(h2))) {
|
|
57834
|
+
res.status(403).json({
|
|
57835
|
+
error: "Forbidden",
|
|
57836
|
+
message: "Invalid X-Forwarded-Host header"
|
|
57837
|
+
});
|
|
57838
|
+
return;
|
|
57839
|
+
}
|
|
57840
|
+
}
|
|
57533
57841
|
}
|
|
57534
57842
|
if (allowedOriginsSet) {
|
|
57535
57843
|
const origin = req.headers?.["origin"];
|
|
@@ -60958,6 +61266,7 @@ __export(index_exports, {
|
|
|
60958
61266
|
authorizationRequiredMetaSchema: () => authorizationRequiredMetaSchema,
|
|
60959
61267
|
authorizationRequiredParamsSchema: () => authorizationRequiredParamsSchema,
|
|
60960
61268
|
authorizationSchema: () => authorizationSchema,
|
|
61269
|
+
authorizeSessionTermination: () => authorizeSessionTermination,
|
|
60961
61270
|
buildChannelInstructions: () => buildChannelInstructions,
|
|
60962
61271
|
buildEsmPromptRecord: () => buildEsmPromptRecord,
|
|
60963
61272
|
buildEsmResourceRecord: () => buildEsmResourceRecord,
|
|
@@ -61131,6 +61440,7 @@ __export(index_exports, {
|
|
|
61131
61440
|
isPackageSpecifier: () => isPackageSpecifier,
|
|
61132
61441
|
isPublicError: () => isPublicError,
|
|
61133
61442
|
isPublicMode: () => import_auth10.isPublicMode,
|
|
61443
|
+
isPublicUrlPinned: () => isPublicUrlPinned,
|
|
61134
61444
|
isPubsubConfigured: () => isPubsubConfigured,
|
|
61135
61445
|
isRedisProvider: () => isRedisProvider,
|
|
61136
61446
|
isRemoteMode: () => import_auth10.isRemoteMode,
|
|
@@ -62144,6 +62454,7 @@ var ChannelListHook = FlowHooksOf("channels:list");
|
|
|
62144
62454
|
authorizationRequiredMetaSchema,
|
|
62145
62455
|
authorizationRequiredParamsSchema,
|
|
62146
62456
|
authorizationSchema,
|
|
62457
|
+
authorizeSessionTermination,
|
|
62147
62458
|
buildChannelInstructions,
|
|
62148
62459
|
buildEsmPromptRecord,
|
|
62149
62460
|
buildEsmResourceRecord,
|
|
@@ -62316,6 +62627,7 @@ var ChannelListHook = FlowHooksOf("channels:list");
|
|
|
62316
62627
|
isPackageSpecifier,
|
|
62317
62628
|
isPublicError,
|
|
62318
62629
|
isPublicMode,
|
|
62630
|
+
isPublicUrlPinned,
|
|
62319
62631
|
isPubsubConfigured,
|
|
62320
62632
|
isRedisProvider,
|
|
62321
62633
|
isRemoteMode,
|