@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/esm/index.mjs
CHANGED
|
@@ -20916,18 +20916,28 @@ var init_call_tool_flow = __esm({
|
|
|
20916
20916
|
const consentCtx = this.input.ctx;
|
|
20917
20917
|
if (!consentCtx?.internalCall) {
|
|
20918
20918
|
const consentedToolIds = getConsentedToolIds(authInfo);
|
|
20919
|
-
if (consentedToolIds && tool
|
|
20920
|
-
this.
|
|
20921
|
-
|
|
20922
|
-
)
|
|
20923
|
-
|
|
20919
|
+
if (consentedToolIds && tool) {
|
|
20920
|
+
const bareNameCount = this.scope.tools.getTools(true).filter((t) => t.name === tool.name).length;
|
|
20921
|
+
const consented = bareNameCount > 1 ? isToolConsented(consentedToolIds, tool.fullName) : isToolConsented(consentedToolIds, tool.name, tool.fullName);
|
|
20922
|
+
if (!consented) {
|
|
20923
|
+
this.logger.info(
|
|
20924
|
+
`checkToolAuthorization: tool "${tool.fullName || tool.name}" not in consented set \u2014 rejecting`
|
|
20925
|
+
);
|
|
20926
|
+
throw new ToolNotConsentedError(tool.fullName || tool.name);
|
|
20927
|
+
}
|
|
20924
20928
|
}
|
|
20925
20929
|
}
|
|
20926
20930
|
const claimAuthorizedApps = getAuthorizedAppIds(authInfo);
|
|
20927
20931
|
const legacyAuthorization = authInfo?.extra?.["authorization"];
|
|
20928
20932
|
if (!claimAuthorizedApps && !legacyAuthorization) {
|
|
20929
|
-
this.
|
|
20930
|
-
|
|
20933
|
+
const incrementalEnabled = !consentCtx?.internalCall && !!authInfo?.token && this.scope.auth?.options?.incrementalAuth?.enabled === true;
|
|
20934
|
+
if (!incrementalEnabled) {
|
|
20935
|
+
this.logger.verbose("checkToolAuthorization:skip (no auth context)");
|
|
20936
|
+
return;
|
|
20937
|
+
}
|
|
20938
|
+
this.logger.warn(
|
|
20939
|
+
"checkToolAuthorization: incrementalAuth enabled but the verified token carries no authorized_apps claim \u2014 failing closed"
|
|
20940
|
+
);
|
|
20931
20941
|
}
|
|
20932
20942
|
const appId = tool?.owner?.id;
|
|
20933
20943
|
if (!appId) {
|
|
@@ -34253,7 +34263,7 @@ var init_oauth_authorize_flow = __esm({
|
|
|
34253
34263
|
if (!result.success) {
|
|
34254
34264
|
const errors = this.formatZodErrors(result.error);
|
|
34255
34265
|
this.logger.warn(`Authorization request validation failed: ${errors.join(", ")}`);
|
|
34256
|
-
this.respondWithError(errors,
|
|
34266
|
+
this.respondWithError(errors, void 0, rawState);
|
|
34257
34267
|
return;
|
|
34258
34268
|
}
|
|
34259
34269
|
this.state.set("validatedRequest", result.data);
|
|
@@ -34308,6 +34318,22 @@ var init_oauth_authorize_flow = __esm({
|
|
|
34308
34318
|
this.respondWithError(["redirect_uri is not registered for this client"], void 0, rawState);
|
|
34309
34319
|
return;
|
|
34310
34320
|
}
|
|
34321
|
+
if (!registered) {
|
|
34322
|
+
const authOptions = this.scope.auth?.options;
|
|
34323
|
+
const requireRegistered = authOptions?.requireRegisteredClients === true;
|
|
34324
|
+
const redirectAllowlisted = registry?.hasRedirectAllowlist?.() === true;
|
|
34325
|
+
if (requireRegistered && !redirectAllowlisted) {
|
|
34326
|
+
this.logger.warn(
|
|
34327
|
+
`OAuth authorize: rejecting unregistered client_id "${client_id}" (requireRegisteredClients)`
|
|
34328
|
+
);
|
|
34329
|
+
this.respondWithError(
|
|
34330
|
+
["Unknown client_id: register the client (DCR / pre-registered) or use a CIMD client-id URL"],
|
|
34331
|
+
void 0,
|
|
34332
|
+
rawState
|
|
34333
|
+
);
|
|
34334
|
+
return;
|
|
34335
|
+
}
|
|
34336
|
+
}
|
|
34311
34337
|
}
|
|
34312
34338
|
}
|
|
34313
34339
|
async checkIfAuthorized() {
|
|
@@ -35034,6 +35060,11 @@ var init_oauth_callback_flow = __esm({
|
|
|
35034
35060
|
return;
|
|
35035
35061
|
}
|
|
35036
35062
|
}
|
|
35063
|
+
if (this.isCrossOriginSubmission()) {
|
|
35064
|
+
this.logger.warn("Cross-origin login submission rejected (CSRF)");
|
|
35065
|
+
this.respond(httpRespond.html(this.renderErrorPage("invalid_request", "Cross-origin request blocked"), 400));
|
|
35066
|
+
return;
|
|
35067
|
+
}
|
|
35037
35068
|
let userSub;
|
|
35038
35069
|
if (email) {
|
|
35039
35070
|
userSub = this.generateUserSub(email);
|
|
@@ -35490,6 +35521,33 @@ var init_oauth_callback_flow = __esm({
|
|
|
35490
35521
|
}
|
|
35491
35522
|
return void 0;
|
|
35492
35523
|
}
|
|
35524
|
+
/**
|
|
35525
|
+
* Same-origin (CSRF) check for a state-changing login/consent submission.
|
|
35526
|
+
*
|
|
35527
|
+
* Returns true when the request carries an `Origin` or `Referer` header whose
|
|
35528
|
+
* host does NOT match the request's own host — the signature of a cross-site
|
|
35529
|
+
* login-CSRF/fixation. `Origin`/`Referer` are set by the browser and cannot
|
|
35530
|
+
* be forged by a malicious page, so this reliably distinguishes a legitimate
|
|
35531
|
+
* same-origin submission (the built-in form is served by THIS server) from a
|
|
35532
|
+
* cross-site one. Returns false (do NOT block) when neither header is present
|
|
35533
|
+
* or the request's own host cannot be determined — defense-in-depth, not a
|
|
35534
|
+
* hard gate, so header-stripped GET navigations and existing flows keep working.
|
|
35535
|
+
*/
|
|
35536
|
+
isCrossOriginSubmission() {
|
|
35537
|
+
const headers = this.rawInput?.request?.headers ?? {};
|
|
35538
|
+
const first = (v) => Array.isArray(v) ? v[0] : typeof v === "string" ? v : void 0;
|
|
35539
|
+
const source = first(headers["origin"]) ?? first(headers["referer"]);
|
|
35540
|
+
if (!source) return false;
|
|
35541
|
+
let sourceHost;
|
|
35542
|
+
try {
|
|
35543
|
+
sourceHost = new URL(source).host.toLowerCase();
|
|
35544
|
+
} catch {
|
|
35545
|
+
return true;
|
|
35546
|
+
}
|
|
35547
|
+
const selfHosts = [first(headers["host"]), first(headers["x-forwarded-host"])].filter((h2) => !!h2).map((h2) => h2.toLowerCase());
|
|
35548
|
+
if (selfHosts.length === 0) return false;
|
|
35549
|
+
return !selfHosts.includes(sourceHost);
|
|
35550
|
+
}
|
|
35493
35551
|
collectLoginFields(request) {
|
|
35494
35552
|
const fields = {};
|
|
35495
35553
|
const absorb = (source) => {
|
|
@@ -36262,19 +36320,21 @@ var init_oauth_provider_callback_flow = __esm({
|
|
|
36262
36320
|
);
|
|
36263
36321
|
return;
|
|
36264
36322
|
}
|
|
36265
|
-
const
|
|
36266
|
-
if (
|
|
36267
|
-
|
|
36268
|
-
|
|
36269
|
-
|
|
36270
|
-
this.respond(
|
|
36271
|
-
httpRespond.html(
|
|
36272
|
-
this.renderErrorPage("invalid_request", "Invalid state parameter. Please restart authentication."),
|
|
36273
|
-
400
|
|
36274
|
-
)
|
|
36323
|
+
const expectedState = session.currentProviderState;
|
|
36324
|
+
if (!expectedState || expectedState !== providerState) {
|
|
36325
|
+
if (this.getStateValidation() === "format") {
|
|
36326
|
+
this.logger.warn(
|
|
36327
|
+
'federatedAuth.stateValidation:"format" no longer skips provider-state nonce verification (security hardening) \u2014 the callback state must match the value issued for this session'
|
|
36275
36328
|
);
|
|
36276
|
-
return;
|
|
36277
36329
|
}
|
|
36330
|
+
this.logger.warn("State mismatch for provider callback");
|
|
36331
|
+
this.respond(
|
|
36332
|
+
httpRespond.html(
|
|
36333
|
+
this.renderErrorPage("invalid_request", "Invalid state parameter. Please restart authentication."),
|
|
36334
|
+
400
|
|
36335
|
+
)
|
|
36336
|
+
);
|
|
36337
|
+
return;
|
|
36278
36338
|
}
|
|
36279
36339
|
this.state.set("federatedSession", session);
|
|
36280
36340
|
}
|
|
@@ -36361,11 +36421,19 @@ var init_oauth_provider_callback_flow = __esm({
|
|
|
36361
36421
|
this.state.set("providerUserInfo", userInfo);
|
|
36362
36422
|
} catch (err) {
|
|
36363
36423
|
const errMsg = err instanceof Error ? err.message : "Unknown error";
|
|
36364
|
-
this.logger.warn(`Failed to
|
|
36424
|
+
this.logger.warn(`Failed to resolve user identity from provider ${providerId}: ${errMsg}`);
|
|
36425
|
+
this.respond(
|
|
36426
|
+
httpRespond.html(
|
|
36427
|
+
this.renderErrorPage("access_denied", "Could not determine your identity from the provider"),
|
|
36428
|
+
400
|
|
36429
|
+
)
|
|
36430
|
+
);
|
|
36431
|
+
return;
|
|
36365
36432
|
}
|
|
36366
36433
|
}
|
|
36367
36434
|
this.logger.info(`Successfully exchanged code for tokens with provider: ${providerId}`);
|
|
36368
36435
|
} catch (err) {
|
|
36436
|
+
if (err instanceof FlowControl) throw err;
|
|
36369
36437
|
const errMsg = err instanceof Error ? err.message : "Unknown error";
|
|
36370
36438
|
this.logger.error(`Provider token exchange error: ${errMsg}`);
|
|
36371
36439
|
this.respond(
|
|
@@ -36745,6 +36813,24 @@ function extractBearer(headers) {
|
|
|
36745
36813
|
const match = /^Bearer\s+(.+)$/i.exec(raw.trim());
|
|
36746
36814
|
return match ? match[1].trim() : void 0;
|
|
36747
36815
|
}
|
|
36816
|
+
function isLoopbackRedirectUri(uri) {
|
|
36817
|
+
let url;
|
|
36818
|
+
try {
|
|
36819
|
+
url = new URL(uri);
|
|
36820
|
+
} catch {
|
|
36821
|
+
return false;
|
|
36822
|
+
}
|
|
36823
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return false;
|
|
36824
|
+
if (url.username || url.password) return false;
|
|
36825
|
+
const host = url.hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
36826
|
+
if (host === "localhost" || host === "::1") return true;
|
|
36827
|
+
const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
|
|
36828
|
+
if (m) {
|
|
36829
|
+
const octets = m.slice(1).map((n) => Number(n));
|
|
36830
|
+
return octets.every((o) => o >= 0 && o <= 255) && octets[0] === 127;
|
|
36831
|
+
}
|
|
36832
|
+
return false;
|
|
36833
|
+
}
|
|
36748
36834
|
var inputSchema20, outputSchema19, registrationRequestSchema, stateSchema19, plan19, name19, Stage19, OauthRegisterFlow;
|
|
36749
36835
|
var init_oauth_register_flow = __esm({
|
|
36750
36836
|
"libs/sdk/src/auth/flows/oauth.register.flow.ts"() {
|
|
@@ -36871,13 +36957,13 @@ var init_oauth_register_flow = __esm({
|
|
|
36871
36957
|
return;
|
|
36872
36958
|
}
|
|
36873
36959
|
} else {
|
|
36874
|
-
const bad = redirect_uris.find((u) =>
|
|
36960
|
+
const bad = redirect_uris.find((u) => !isLoopbackRedirectUri(u));
|
|
36875
36961
|
if (bad) {
|
|
36876
36962
|
this.respond(
|
|
36877
36963
|
httpRespond.json(
|
|
36878
36964
|
{
|
|
36879
36965
|
error: "invalid_redirect_uri",
|
|
36880
|
-
error_description: `Registration allows only localhost
|
|
36966
|
+
error_description: `Registration allows only loopback redirect_uris (localhost, 127.0.0.0/8, ::1) without an allowlist; got ${bad}`
|
|
36881
36967
|
},
|
|
36882
36968
|
{ status: 400 }
|
|
36883
36969
|
)
|
|
@@ -36918,7 +37004,20 @@ var init_oauth_register_flow = __esm({
|
|
|
36918
37004
|
created_at: now,
|
|
36919
37005
|
dev: true
|
|
36920
37006
|
};
|
|
36921
|
-
this.localAuth.dcrClientRegistry.register(this.registered);
|
|
37007
|
+
const accepted = this.localAuth.dcrClientRegistry.register(this.registered);
|
|
37008
|
+
if (!accepted) {
|
|
37009
|
+
this.scopeLogger.warn("OAuth register: dynamic client capacity reached \u2014 rejecting registration");
|
|
37010
|
+
this.respond(
|
|
37011
|
+
httpRespond.json(
|
|
37012
|
+
{
|
|
37013
|
+
error: "temporarily_unavailable",
|
|
37014
|
+
error_description: "Client registration capacity reached; please try again later."
|
|
37015
|
+
},
|
|
37016
|
+
{ status: 503 }
|
|
37017
|
+
)
|
|
37018
|
+
);
|
|
37019
|
+
return;
|
|
37020
|
+
}
|
|
36922
37021
|
}
|
|
36923
37022
|
async respondRegistration() {
|
|
36924
37023
|
const c = this.registered;
|
|
@@ -36984,6 +37083,23 @@ var init_oauth_register_flow = __esm({
|
|
|
36984
37083
|
// libs/sdk/src/auth/flows/oauth.token.flow.ts
|
|
36985
37084
|
import { z as z69 } from "@frontmcp/lazy-zod";
|
|
36986
37085
|
import { randomUUID as randomUUID19 } from "@frontmcp/utils";
|
|
37086
|
+
function parseBasicAuthorization(header) {
|
|
37087
|
+
if (typeof header !== "string") return void 0;
|
|
37088
|
+
const m = /^Basic\s+(.+)$/i.exec(header.trim());
|
|
37089
|
+
if (!m) return void 0;
|
|
37090
|
+
let decoded;
|
|
37091
|
+
try {
|
|
37092
|
+
const b64 = m[1].trim();
|
|
37093
|
+
if (typeof Buffer !== "undefined") decoded = Buffer.from(b64, "base64").toString("utf8");
|
|
37094
|
+
else if (typeof atob === "function") decoded = atob(b64);
|
|
37095
|
+
else return void 0;
|
|
37096
|
+
} catch {
|
|
37097
|
+
return void 0;
|
|
37098
|
+
}
|
|
37099
|
+
const idx = decoded.indexOf(":");
|
|
37100
|
+
if (idx < 0) return void 0;
|
|
37101
|
+
return { clientId: decoded.slice(0, idx), clientSecret: decoded.slice(idx + 1) };
|
|
37102
|
+
}
|
|
36987
37103
|
function coerceTokenRequestBody(request) {
|
|
36988
37104
|
const body = request.body;
|
|
36989
37105
|
if (body && typeof body === "object" && !Array.isArray(body) && Object.keys(body).length > 0) {
|
|
@@ -37028,6 +37144,8 @@ var init_oauth_token_flow = __esm({
|
|
|
37028
37144
|
redirect_uri: z69.string().url(),
|
|
37029
37145
|
/** Public client identifier */
|
|
37030
37146
|
client_id: z69.string().min(1),
|
|
37147
|
+
/** Client secret (confidential clients; also accepted via HTTP Basic). */
|
|
37148
|
+
client_secret: z69.string().optional(),
|
|
37031
37149
|
/** PKCE verifier bound to the code */
|
|
37032
37150
|
code_verifier: z69.string().regex(pkceVerifierRegex, "code_verifier must be 43\u2013128 chars of A\u2013Z, a\u2013z, 0\u20139, '-', '.', '_' or '~'")
|
|
37033
37151
|
});
|
|
@@ -37036,7 +37154,9 @@ var init_oauth_token_flow = __esm({
|
|
|
37036
37154
|
/** The refresh token */
|
|
37037
37155
|
refresh_token: z69.string().min(1, "refresh_token is required"),
|
|
37038
37156
|
/** Public client identifier */
|
|
37039
|
-
client_id: z69.string().min(1)
|
|
37157
|
+
client_id: z69.string().min(1),
|
|
37158
|
+
/** Client secret (confidential clients; also accepted via HTTP Basic). */
|
|
37159
|
+
client_secret: z69.string().optional()
|
|
37040
37160
|
});
|
|
37041
37161
|
anonymousGrant = z69.object({
|
|
37042
37162
|
grant_type: z69.literal("anonymous"),
|
|
@@ -37083,6 +37203,12 @@ var init_oauth_token_flow = __esm({
|
|
|
37083
37203
|
const isDefaultAuthProvider = !metadata.auth;
|
|
37084
37204
|
const isOrchestrated = !isDefaultAuthProvider;
|
|
37085
37205
|
const rawBody = coerceTokenRequestBody(request);
|
|
37206
|
+
const basic = parseBasicAuthorization(request.headers?.["authorization"]);
|
|
37207
|
+
if (basic && rawBody && typeof rawBody === "object") {
|
|
37208
|
+
const b = rawBody;
|
|
37209
|
+
if (b["client_id"] === void 0) b["client_id"] = basic.clientId;
|
|
37210
|
+
if (b["client_secret"] === void 0) b["client_secret"] = basic.clientSecret;
|
|
37211
|
+
}
|
|
37086
37212
|
const parsed = tokenRequestSchema.safeParse(rawBody);
|
|
37087
37213
|
if (parsed.success) {
|
|
37088
37214
|
this.state.set({
|
|
@@ -37133,7 +37259,13 @@ var init_oauth_token_flow = __esm({
|
|
|
37133
37259
|
return;
|
|
37134
37260
|
}
|
|
37135
37261
|
const localAuth = this.scope.auth;
|
|
37136
|
-
const result = await localAuth.exchangeCode(
|
|
37262
|
+
const result = await localAuth.exchangeCode(
|
|
37263
|
+
body.code,
|
|
37264
|
+
body.client_id,
|
|
37265
|
+
body.redirect_uri,
|
|
37266
|
+
body.code_verifier,
|
|
37267
|
+
body.client_secret
|
|
37268
|
+
);
|
|
37137
37269
|
if ("error" in result) {
|
|
37138
37270
|
this.logger.warn(`Code exchange failed: ${result.error}`);
|
|
37139
37271
|
this.respond(
|
|
@@ -37180,7 +37312,7 @@ var init_oauth_token_flow = __esm({
|
|
|
37180
37312
|
return;
|
|
37181
37313
|
}
|
|
37182
37314
|
const localAuth = this.scope.auth;
|
|
37183
|
-
const result = await localAuth.refreshAccessToken(body.refresh_token, body.client_id);
|
|
37315
|
+
const result = await localAuth.refreshAccessToken(body.refresh_token, body.client_id, body.client_secret);
|
|
37184
37316
|
if ("error" in result) {
|
|
37185
37317
|
this.logger.warn(`Refresh token failed: ${result.error}`);
|
|
37186
37318
|
this.respond(
|
|
@@ -37408,15 +37540,17 @@ import {
|
|
|
37408
37540
|
buildInsufficientScopeHeader,
|
|
37409
37541
|
buildInvalidTokenHeader,
|
|
37410
37542
|
buildUnauthorizedHeader,
|
|
37543
|
+
deriveExpectedAudience,
|
|
37411
37544
|
deriveTypedUser,
|
|
37412
37545
|
encryptJson as encryptJson2,
|
|
37413
37546
|
extractBearerToken as extractBearerToken2,
|
|
37414
37547
|
isJwt,
|
|
37415
|
-
JwksService
|
|
37548
|
+
JwksService,
|
|
37549
|
+
validateAudience
|
|
37416
37550
|
} from "@frontmcp/auth";
|
|
37417
37551
|
import { z as z71 } from "@frontmcp/lazy-zod";
|
|
37418
37552
|
import { getMachineId as getMachineId2, randomUUID as randomUUID20 } from "@frontmcp/utils";
|
|
37419
|
-
var inputSchema23, stateSchema22, UnauthorizedSchema, AuthorizedSchema, ForbiddenSchema, sessionVerifyOutputSchema, plan22, name22, Stage22, SessionVerifyFlow;
|
|
37553
|
+
var inputSchema23, stateSchema22, UnauthorizedSchema, AuthorizedSchema, ForbiddenSchema, sessionVerifyOutputSchema, plan22, name22, Stage22, warnedUnpinnedAudience, SessionVerifyFlow;
|
|
37420
37554
|
var init_session_verify_flow = __esm({
|
|
37421
37555
|
"libs/sdk/src/auth/flows/session.verify.flow.ts"() {
|
|
37422
37556
|
"use strict";
|
|
@@ -37461,6 +37595,7 @@ var init_session_verify_flow = __esm({
|
|
|
37461
37595
|
};
|
|
37462
37596
|
name22 = "session:verify";
|
|
37463
37597
|
Stage22 = StageHookOf(name22);
|
|
37598
|
+
warnedUnpinnedAudience = false;
|
|
37464
37599
|
SessionVerifyFlow = class extends FlowBase {
|
|
37465
37600
|
logger = this.scopeLogger.child("SessionVerifyFlow");
|
|
37466
37601
|
maskSub(sub) {
|
|
@@ -37478,20 +37613,20 @@ var init_session_verify_flow = __esm({
|
|
|
37478
37613
|
const machineId = getMachineId2();
|
|
37479
37614
|
if (sessionIdHeader) {
|
|
37480
37615
|
const existingPayload = decryptPublicSession(sessionIdHeader);
|
|
37481
|
-
|
|
37482
|
-
|
|
37483
|
-
|
|
37484
|
-
|
|
37485
|
-
|
|
37486
|
-
|
|
37487
|
-
|
|
37488
|
-
|
|
37489
|
-
id: sessionIdHeader,
|
|
37490
|
-
payload: finalPayload
|
|
37616
|
+
if (existingPayload && existingPayload.nodeId === machineId) {
|
|
37617
|
+
const anonId = existingPayload.uuid ?? `${existingPayload.iat * 1e3}`;
|
|
37618
|
+
const user2 = { sub: `anon:${anonId}`, iss: issuer, name: "Anonymous", scope: scopes.join(" ") };
|
|
37619
|
+
this.respond({
|
|
37620
|
+
kind: "authorized",
|
|
37621
|
+
authorization: {
|
|
37622
|
+
token: "",
|
|
37623
|
+
user: user2,
|
|
37624
|
+
session: { id: sessionIdHeader, payload: existingPayload }
|
|
37491
37625
|
}
|
|
37492
|
-
}
|
|
37493
|
-
|
|
37494
|
-
|
|
37626
|
+
});
|
|
37627
|
+
return;
|
|
37628
|
+
}
|
|
37629
|
+
this.logger.verbose("createAnonymousSession: ignoring unrecognized client session id; minting a fresh one");
|
|
37495
37630
|
}
|
|
37496
37631
|
const now = Date.now();
|
|
37497
37632
|
const user = {
|
|
@@ -37629,6 +37764,8 @@ var init_session_verify_flow = __esm({
|
|
|
37629
37764
|
{
|
|
37630
37765
|
id: primary.providerConfig?.id ?? "default",
|
|
37631
37766
|
issuerUrl: issuer,
|
|
37767
|
+
additionalIssuers: primary.providerConfig?.additionalIssuers,
|
|
37768
|
+
verifyIssuer: primary.providerConfig?.verifyIssuer,
|
|
37632
37769
|
jwks: primary.providerConfig?.jwks,
|
|
37633
37770
|
jwksUri: primary.providerConfig?.jwksUri
|
|
37634
37771
|
}
|
|
@@ -37640,6 +37777,37 @@ var init_session_verify_flow = __esm({
|
|
|
37640
37777
|
const result = await verify;
|
|
37641
37778
|
if (result.ok) {
|
|
37642
37779
|
this.state.set({ jwtPayload: result.payload });
|
|
37780
|
+
if (isTransparentMode(authOptions)) {
|
|
37781
|
+
const configuredAudience = authOptions.expectedAudience;
|
|
37782
|
+
if (!configuredAudience && !isPublicUrlPinned() && !warnedUnpinnedAudience) {
|
|
37783
|
+
warnedUnpinnedAudience = true;
|
|
37784
|
+
this.logger.warn(
|
|
37785
|
+
"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."
|
|
37786
|
+
);
|
|
37787
|
+
}
|
|
37788
|
+
const expectedAudiences = configuredAudience ? Array.isArray(configuredAudience) ? configuredAudience : [configuredAudience] : deriveExpectedAudience(this.state.required.baseUrl);
|
|
37789
|
+
const audResult = validateAudience(result.payload?.["aud"], {
|
|
37790
|
+
expectedAudiences,
|
|
37791
|
+
// By default accept tokens with no `aud` claim: many IdPs omit it, and
|
|
37792
|
+
// rejecting them here would break existing transparent deployments.
|
|
37793
|
+
// Tokens that DO carry an `aud` for another service are still rejected,
|
|
37794
|
+
// which blocks the cross-service replay in GHSA-hvvp-67p3-j379.
|
|
37795
|
+
// Operators whose IdP always sets `aud` can flip `requireAudience:true`
|
|
37796
|
+
// to reject audience-less tokens outright.
|
|
37797
|
+
allowNoAudience: authOptions.requireAudience !== true
|
|
37798
|
+
});
|
|
37799
|
+
if (!audResult.valid) {
|
|
37800
|
+
this.logger.warn("verifyIfJwt: audience validation failed", { error: audResult.error });
|
|
37801
|
+
this.respond({
|
|
37802
|
+
kind: "unauthorized",
|
|
37803
|
+
prmMetadataHeader: buildInvalidTokenHeader(
|
|
37804
|
+
this.state.required.prmUrl,
|
|
37805
|
+
audResult.error ?? "Token audience is not valid for this resource"
|
|
37806
|
+
)
|
|
37807
|
+
});
|
|
37808
|
+
return;
|
|
37809
|
+
}
|
|
37810
|
+
}
|
|
37643
37811
|
const requiredScopes = authOptions?.["requiredScopes"];
|
|
37644
37812
|
if (requiredScopes && requiredScopes.length > 0) {
|
|
37645
37813
|
const scopeClaim = result.payload?.["scope"];
|
|
@@ -37859,6 +38027,10 @@ var init_well_known_oauth_authorization_server_flow = __esm({
|
|
|
37859
38027
|
kind: z73.literal("json"),
|
|
37860
38028
|
status: z73.literal(200),
|
|
37861
38029
|
contentType: z73.literal("application/json; charset=utf-8"),
|
|
38030
|
+
// Response headers (e.g. `Cache-Control: no-store`). This flow uses a custom
|
|
38031
|
+
// output schema rather than `HttpJsonSchema`, so `headers` must be declared
|
|
38032
|
+
// explicitly for the renderer to emit it.
|
|
38033
|
+
headers: z73.record(z73.string(), z73.string()).optional(),
|
|
37862
38034
|
body: z73.object({
|
|
37863
38035
|
issuer: z73.string().min(1),
|
|
37864
38036
|
authorization_endpoint: z73.string().min(1),
|
|
@@ -37940,6 +38112,10 @@ var init_well_known_oauth_authorization_server_flow = __esm({
|
|
|
37940
38112
|
kind: "json",
|
|
37941
38113
|
contentType: "application/json; charset=utf-8",
|
|
37942
38114
|
status: 200,
|
|
38115
|
+
// Never cache: the advertised endpoints are derived from the request
|
|
38116
|
+
// origin, so a shared cache could serve a poisoned authorization/token
|
|
38117
|
+
// endpoint to another client (OAuth mix-up).
|
|
38118
|
+
headers: { "cache-control": "no-store" },
|
|
37943
38119
|
body: {
|
|
37944
38120
|
issuer: baseIssuer,
|
|
37945
38121
|
authorization_endpoint: `${oauthBaseUrl}/oauth/authorize`,
|
|
@@ -38043,6 +38219,10 @@ var init_well_known_prm_flow = __esm({
|
|
|
38043
38219
|
kind: "json",
|
|
38044
38220
|
contentType: "application/json; charset=utf-8",
|
|
38045
38221
|
status: 200,
|
|
38222
|
+
// Never cache: the body embeds the request-derived origin, so a shared
|
|
38223
|
+
// cache keyed only on (Host, path) could otherwise serve a poisoned
|
|
38224
|
+
// `authorization_servers` value to another client (OAuth mix-up).
|
|
38225
|
+
headers: { "cache-control": "no-store" },
|
|
38046
38226
|
body: {
|
|
38047
38227
|
resource,
|
|
38048
38228
|
authorization_servers: [baseUrl],
|
|
@@ -38056,6 +38236,7 @@ var init_well_known_prm_flow = __esm({
|
|
|
38056
38236
|
kind: "json",
|
|
38057
38237
|
status: 200,
|
|
38058
38238
|
contentType: "application/json; charset=utf-8",
|
|
38239
|
+
headers: { "cache-control": "no-store" },
|
|
38059
38240
|
body: {
|
|
38060
38241
|
resource,
|
|
38061
38242
|
authorization_servers: [baseUrl],
|
|
@@ -38645,7 +38826,11 @@ var init_instance_local_primary_auth = __esm({
|
|
|
38645
38826
|
/**
|
|
38646
38827
|
* Exchange an authorization code for tokens
|
|
38647
38828
|
*/
|
|
38648
|
-
async exchangeCode(code, clientId, redirectUri, codeVerifier) {
|
|
38829
|
+
async exchangeCode(code, clientId, redirectUri, codeVerifier, clientSecret) {
|
|
38830
|
+
if (this.dcrClientRegistryImpl.verifyClientSecret(clientId, clientSecret) === "invalid") {
|
|
38831
|
+
this.logger.warn("Client authentication failed at token endpoint (authorization_code)");
|
|
38832
|
+
return { error: "invalid_client", error_description: "Client authentication failed" };
|
|
38833
|
+
}
|
|
38649
38834
|
const codeRecord = await this.authorizationStore.getAuthorizationCode(code);
|
|
38650
38835
|
if (!codeRecord) {
|
|
38651
38836
|
this.logger.warn(`Authorization code not found or expired: ${code.substring(0, 8)}...`);
|
|
@@ -38656,6 +38841,13 @@ var init_instance_local_primary_auth = __esm({
|
|
|
38656
38841
|
}
|
|
38657
38842
|
if (codeRecord.used) {
|
|
38658
38843
|
this.logger.warn(`Authorization code already used: ${code.substring(0, 8)}...`);
|
|
38844
|
+
if (codeRecord.issuedRefreshToken) {
|
|
38845
|
+
try {
|
|
38846
|
+
await this.authorizationStore.revokeRefreshToken(codeRecord.issuedRefreshToken);
|
|
38847
|
+
} catch (err) {
|
|
38848
|
+
this.logger.warn(`Failed to revoke refresh token on authorization-code replay: ${err}`);
|
|
38849
|
+
}
|
|
38850
|
+
}
|
|
38659
38851
|
await this.authorizationStore.deleteAuthorizationCode(code);
|
|
38660
38852
|
return {
|
|
38661
38853
|
error: "invalid_grant",
|
|
@@ -38717,9 +38909,19 @@ var init_instance_local_primary_auth = __esm({
|
|
|
38717
38909
|
clientId,
|
|
38718
38910
|
userSub: user.sub,
|
|
38719
38911
|
scopes: codeRecord.scopes,
|
|
38720
|
-
resource: codeRecord.resource
|
|
38912
|
+
resource: codeRecord.resource,
|
|
38913
|
+
userEmail: user.email,
|
|
38914
|
+
userName: user.name,
|
|
38915
|
+
consentEnabled: codeRecord.consentEnabled,
|
|
38916
|
+
selectedToolIds: codeRecord.selectedToolIds,
|
|
38917
|
+
authorizedAppIds: codeRecord.authorizedAppIds,
|
|
38918
|
+
customClaims: codeRecord.customClaims,
|
|
38919
|
+
federatedLoginUsed: codeRecord.federatedLoginUsed,
|
|
38920
|
+
selectedProviderIds: codeRecord.selectedProviderIds,
|
|
38921
|
+
skippedProviderIds: codeRecord.skippedProviderIds
|
|
38721
38922
|
});
|
|
38722
38923
|
await this.authorizationStore.storeRefreshToken(refreshTokenRecord);
|
|
38924
|
+
await this.authorizationStore.markCodeUsed(code, refreshTokenRecord.token);
|
|
38723
38925
|
this.logger.info(`Tokens issued for user: ${user.sub}`);
|
|
38724
38926
|
return {
|
|
38725
38927
|
access_token: accessToken,
|
|
@@ -38732,7 +38934,11 @@ var init_instance_local_primary_auth = __esm({
|
|
|
38732
38934
|
/**
|
|
38733
38935
|
* Refresh an access token using a refresh token
|
|
38734
38936
|
*/
|
|
38735
|
-
async refreshAccessToken(refreshToken, clientId) {
|
|
38937
|
+
async refreshAccessToken(refreshToken, clientId, clientSecret) {
|
|
38938
|
+
if (this.dcrClientRegistryImpl.verifyClientSecret(clientId, clientSecret) === "invalid") {
|
|
38939
|
+
this.logger.warn("Client authentication failed at token endpoint (refresh_token)");
|
|
38940
|
+
return { error: "invalid_client", error_description: "Client authentication failed" };
|
|
38941
|
+
}
|
|
38736
38942
|
const tokenRecord = await this.authorizationStore.getRefreshToken(refreshToken);
|
|
38737
38943
|
if (!tokenRecord) {
|
|
38738
38944
|
this.logger.warn("Refresh token not found or expired");
|
|
@@ -38748,13 +38954,37 @@ var init_instance_local_primary_auth = __esm({
|
|
|
38748
38954
|
error_description: "Client ID does not match"
|
|
38749
38955
|
};
|
|
38750
38956
|
}
|
|
38751
|
-
const user = {
|
|
38752
|
-
|
|
38957
|
+
const user = {
|
|
38958
|
+
sub: tokenRecord.userSub,
|
|
38959
|
+
email: tokenRecord.userEmail,
|
|
38960
|
+
name: tokenRecord.userName
|
|
38961
|
+
};
|
|
38962
|
+
const hasAuthorizedApps = Array.isArray(tokenRecord.authorizedAppIds);
|
|
38963
|
+
const hasCustomClaims = !!tokenRecord.customClaims && Object.keys(tokenRecord.customClaims).length > 0;
|
|
38964
|
+
const consentMetadata = tokenRecord.consentEnabled || tokenRecord.federatedLoginUsed || hasCustomClaims || hasAuthorizedApps ? {
|
|
38965
|
+
selectedToolIds: tokenRecord.selectedToolIds,
|
|
38966
|
+
selectedProviderIds: tokenRecord.selectedProviderIds,
|
|
38967
|
+
skippedProviderIds: tokenRecord.skippedProviderIds,
|
|
38968
|
+
consentEnabled: tokenRecord.consentEnabled,
|
|
38969
|
+
federatedLoginUsed: tokenRecord.federatedLoginUsed,
|
|
38970
|
+
authorizedAppIds: tokenRecord.authorizedAppIds,
|
|
38971
|
+
customClaims: tokenRecord.customClaims
|
|
38972
|
+
} : void 0;
|
|
38973
|
+
const accessToken = await this.signAccessToken(user, tokenRecord.scopes, tokenRecord.resource, consentMetadata);
|
|
38753
38974
|
const newRefreshRecord = this.authorizationStore.createRefreshTokenRecord({
|
|
38754
38975
|
clientId,
|
|
38755
38976
|
userSub: tokenRecord.userSub,
|
|
38756
38977
|
scopes: tokenRecord.scopes,
|
|
38757
|
-
resource: tokenRecord.resource
|
|
38978
|
+
resource: tokenRecord.resource,
|
|
38979
|
+
userEmail: tokenRecord.userEmail,
|
|
38980
|
+
userName: tokenRecord.userName,
|
|
38981
|
+
consentEnabled: tokenRecord.consentEnabled,
|
|
38982
|
+
selectedToolIds: tokenRecord.selectedToolIds,
|
|
38983
|
+
authorizedAppIds: tokenRecord.authorizedAppIds,
|
|
38984
|
+
customClaims: tokenRecord.customClaims,
|
|
38985
|
+
federatedLoginUsed: tokenRecord.federatedLoginUsed,
|
|
38986
|
+
selectedProviderIds: tokenRecord.selectedProviderIds,
|
|
38987
|
+
skippedProviderIds: tokenRecord.skippedProviderIds
|
|
38758
38988
|
});
|
|
38759
38989
|
await this.authorizationStore.rotateRefreshToken(refreshToken, newRefreshRecord);
|
|
38760
38990
|
this.logger.info(`Tokens refreshed for user: ${user.sub}`);
|
|
@@ -39054,13 +39284,22 @@ var init_instance_local_primary_auth = __esm({
|
|
|
39054
39284
|
try {
|
|
39055
39285
|
const [, payloadB64] = idToken.split(".");
|
|
39056
39286
|
const payload = JSON.parse(new TextDecoder().decode(base64urlDecode2(payloadB64)));
|
|
39057
|
-
|
|
39058
|
-
|
|
39059
|
-
|
|
39060
|
-
|
|
39061
|
-
|
|
39062
|
-
|
|
39063
|
-
|
|
39287
|
+
const exp = typeof payload["exp"] === "number" ? payload["exp"] : void 0;
|
|
39288
|
+
if (exp !== void 0 && exp * 1e3 < Date.now()) {
|
|
39289
|
+
this.logger.warn(`ID token for ${providerId} is expired; falling back to userinfo`);
|
|
39290
|
+
} else {
|
|
39291
|
+
const sub = typeof payload["sub"] === "string" && payload["sub"].trim() ? payload["sub"] : void 0;
|
|
39292
|
+
if (sub) {
|
|
39293
|
+
return {
|
|
39294
|
+
sub,
|
|
39295
|
+
email: payload["email"],
|
|
39296
|
+
name: payload["name"],
|
|
39297
|
+
picture: payload["picture"],
|
|
39298
|
+
claims: payload
|
|
39299
|
+
};
|
|
39300
|
+
}
|
|
39301
|
+
this.logger.warn(`ID token for ${providerId} has no usable sub; falling back to userinfo`);
|
|
39302
|
+
}
|
|
39064
39303
|
} catch (err) {
|
|
39065
39304
|
this.logger.warn(`Failed to parse ID token for ${providerId}: ${err}`);
|
|
39066
39305
|
}
|
|
@@ -39075,21 +39314,25 @@ var init_instance_local_primary_auth = __esm({
|
|
|
39075
39314
|
});
|
|
39076
39315
|
if (response.ok) {
|
|
39077
39316
|
const userInfo = await response.json();
|
|
39078
|
-
|
|
39079
|
-
|
|
39080
|
-
|
|
39081
|
-
|
|
39082
|
-
|
|
39083
|
-
|
|
39084
|
-
|
|
39317
|
+
const sub = typeof userInfo["sub"] === "string" && userInfo["sub"].trim() ? userInfo["sub"] : void 0;
|
|
39318
|
+
if (sub) {
|
|
39319
|
+
return {
|
|
39320
|
+
sub,
|
|
39321
|
+
email: userInfo["email"],
|
|
39322
|
+
name: userInfo["name"],
|
|
39323
|
+
picture: userInfo["picture"],
|
|
39324
|
+
claims: userInfo
|
|
39325
|
+
};
|
|
39326
|
+
}
|
|
39327
|
+
this.logger.warn(`userinfo for ${providerId} returned no usable sub`);
|
|
39085
39328
|
}
|
|
39086
39329
|
} catch (err) {
|
|
39087
39330
|
this.logger.warn(`Failed to get userinfo from ${providerId}: ${err}`);
|
|
39088
39331
|
}
|
|
39089
39332
|
}
|
|
39090
|
-
|
|
39091
|
-
|
|
39092
|
-
|
|
39333
|
+
throw new Error(
|
|
39334
|
+
`Unable to determine a stable user identity from provider "${providerId}" (no id_token sub and no usable userinfo endpoint)`
|
|
39335
|
+
);
|
|
39093
39336
|
}
|
|
39094
39337
|
/**
|
|
39095
39338
|
* Refresh tokens from upstream provider
|
|
@@ -49313,6 +49556,16 @@ function decideIntent(req, cfg) {
|
|
|
49313
49556
|
debug: { key, channel: channel2, flags }
|
|
49314
49557
|
};
|
|
49315
49558
|
}
|
|
49559
|
+
function authorizeSessionTermination(verifyResult, requestedSessionId) {
|
|
49560
|
+
if (!verifyResult || verifyResult.kind !== "authorized" || !verifyResult.authorization) {
|
|
49561
|
+
return { kind: "unauthorized" };
|
|
49562
|
+
}
|
|
49563
|
+
const ownedSessionId = verifyResult.authorization.session?.id;
|
|
49564
|
+
if (!ownedSessionId || !requestedSessionId || requestedSessionId !== ownedSessionId) {
|
|
49565
|
+
return { kind: "forbidden" };
|
|
49566
|
+
}
|
|
49567
|
+
return { kind: "ok", sessionId: ownedSessionId };
|
|
49568
|
+
}
|
|
49316
49569
|
var 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;
|
|
49317
49570
|
var init_decide_request_intent_utils = __esm({
|
|
49318
49571
|
"libs/sdk/src/common/utils/decide-request-intent.utils.ts"() {
|
|
@@ -49576,13 +49829,29 @@ function readPublicUrlPin() {
|
|
|
49576
49829
|
}
|
|
49577
49830
|
return void 0;
|
|
49578
49831
|
}
|
|
49832
|
+
function isPublicUrlPinned() {
|
|
49833
|
+
return readPublicUrlPin() !== void 0;
|
|
49834
|
+
}
|
|
49835
|
+
function isProxyTrusted() {
|
|
49836
|
+
try {
|
|
49837
|
+
if (typeof process !== "undefined" && process.env) {
|
|
49838
|
+
const v = process.env["FRONTMCP_TRUST_PROXY"];
|
|
49839
|
+
return typeof v === "string" && /^(1|true|yes|on)$/i.test(v.trim());
|
|
49840
|
+
}
|
|
49841
|
+
} catch {
|
|
49842
|
+
}
|
|
49843
|
+
return false;
|
|
49844
|
+
}
|
|
49579
49845
|
function getRequestBaseUrl(req, entryPath) {
|
|
49580
49846
|
const pin = readPublicUrlPin();
|
|
49581
49847
|
if (pin) {
|
|
49582
49848
|
return `${pin}${entryPath ?? ""}`;
|
|
49583
49849
|
}
|
|
49584
|
-
const
|
|
49585
|
-
const
|
|
49850
|
+
const trustProxy = isProxyTrusted();
|
|
49851
|
+
const fwdProto = trustProxy ? req.headers["x-forwarded-proto"] : void 0;
|
|
49852
|
+
const fwdHost = trustProxy ? req.headers["x-forwarded-host"] : void 0;
|
|
49853
|
+
const proto = fwdProto || req.protocol || "http";
|
|
49854
|
+
const host = fwdHost || req.headers["host"];
|
|
49586
49855
|
return `${proto}://${host}${entryPath ?? ""}`;
|
|
49587
49856
|
}
|
|
49588
49857
|
function computeIssuer(req, entryPath, scopeBase) {
|
|
@@ -54348,6 +54617,10 @@ import {
|
|
|
54348
54617
|
} from "@frontmcp/auth";
|
|
54349
54618
|
import { z as z103 } from "@frontmcp/lazy-zod";
|
|
54350
54619
|
import { randomUUID as randomUUID27 } from "@frontmcp/utils";
|
|
54620
|
+
function parseUserScopes(user) {
|
|
54621
|
+
const s = user?.scope;
|
|
54622
|
+
return typeof s === "string" ? s.split(/\s+/).filter(Boolean) : [];
|
|
54623
|
+
}
|
|
54351
54624
|
var plan39, httpRequestStateSchema, name40, Stage40, HttpRequestFlow;
|
|
54352
54625
|
var init_http_request_flow = __esm({
|
|
54353
54626
|
"libs/sdk/src/scope/flows/http.request.flow.ts"() {
|
|
@@ -54489,7 +54762,10 @@ var init_http_request_flow = __esm({
|
|
|
54489
54762
|
ctx.updateAuthInfo({
|
|
54490
54763
|
token,
|
|
54491
54764
|
clientId: user.sub,
|
|
54492
|
-
scopes
|
|
54765
|
+
// Populate scopes from the verified token's `scope` claim instead of
|
|
54766
|
+
// hardcoding `[]`, so any consumer that authorizes on
|
|
54767
|
+
// `authInfo.scopes` sees the real grant (same accessor as the web path).
|
|
54768
|
+
scopes: parseUserScopes(user),
|
|
54493
54769
|
// JWT exp is in seconds, SDK uses milliseconds throughout (e.g., Date.now())
|
|
54494
54770
|
expiresAt: user.exp ? user.exp * 1e3 : void 0,
|
|
54495
54771
|
extra: {
|
|
@@ -54608,6 +54884,27 @@ var init_http_request_flow = __esm({
|
|
|
54608
54884
|
this.next();
|
|
54609
54885
|
return;
|
|
54610
54886
|
}
|
|
54887
|
+
const deleteVerify = this.state.required.verifyResult;
|
|
54888
|
+
const requestedSessionId = request[ServerRequestTokens.sessionId] ?? request.headers["mcp-session-id"] ?? request.query?.["sessionId"];
|
|
54889
|
+
const deleteAuthz = authorizeSessionTermination(deleteVerify, requestedSessionId);
|
|
54890
|
+
if (deleteAuthz.kind === "unauthorized") {
|
|
54891
|
+
this.logger.warn(`[${this.requestId}] DELETE session denied: request is not authenticated`);
|
|
54892
|
+
this.respond(
|
|
54893
|
+
httpRespond.unauthorized({
|
|
54894
|
+
headers: {
|
|
54895
|
+
"WWW-Authenticate": deleteVerify.prmMetadataHeader ?? "Bearer"
|
|
54896
|
+
}
|
|
54897
|
+
})
|
|
54898
|
+
);
|
|
54899
|
+
return;
|
|
54900
|
+
}
|
|
54901
|
+
if (deleteAuthz.kind === "forbidden") {
|
|
54902
|
+
this.logger.warn(`[${this.requestId}] DELETE session denied: caller does not own the requested session id`);
|
|
54903
|
+
this.respond(httpRespond.notFound("Session not found"));
|
|
54904
|
+
return;
|
|
54905
|
+
}
|
|
54906
|
+
request[ServerRequestTokens.auth] = deleteVerify.authorization;
|
|
54907
|
+
request[ServerRequestTokens.sessionId] = deleteAuthz.sessionId;
|
|
54611
54908
|
this.state.set("intent", decision.intent);
|
|
54612
54909
|
return;
|
|
54613
54910
|
}
|
|
@@ -54720,7 +55017,9 @@ var init_http_request_flow = __esm({
|
|
|
54720
55017
|
const authInfo = authorization ? {
|
|
54721
55018
|
token: authorization.token,
|
|
54722
55019
|
clientId: authorization.user?.sub,
|
|
54723
|
-
|
|
55020
|
+
// Same scope parsing as the Node path so scope-aware handlers behave
|
|
55021
|
+
// identically on the web transport.
|
|
55022
|
+
scopes: parseUserScopes(authorization.user),
|
|
54724
55023
|
expiresAt: authorization.user?.exp ? authorization.user.exp * 1e3 : void 0,
|
|
54725
55024
|
extra: {
|
|
54726
55025
|
user: authorization.user,
|
|
@@ -57686,6 +57985,17 @@ function createHostValidationMiddleware(options) {
|
|
|
57686
57985
|
});
|
|
57687
57986
|
return;
|
|
57688
57987
|
}
|
|
57988
|
+
const forwardedHost = req.headers?.["x-forwarded-host"];
|
|
57989
|
+
if (forwardedHost) {
|
|
57990
|
+
const candidates = forwardedHost.split(",").map((h2) => h2.trim()).filter(Boolean);
|
|
57991
|
+
if (candidates.some((h2) => !allowedHostsSet.has(h2))) {
|
|
57992
|
+
res.status(403).json({
|
|
57993
|
+
error: "Forbidden",
|
|
57994
|
+
message: "Invalid X-Forwarded-Host header"
|
|
57995
|
+
});
|
|
57996
|
+
return;
|
|
57997
|
+
}
|
|
57998
|
+
}
|
|
57689
57999
|
}
|
|
57690
58000
|
if (allowedOriginsSet) {
|
|
57691
58001
|
const origin = req.headers?.["origin"];
|
|
@@ -61522,6 +61832,7 @@ export {
|
|
|
61522
61832
|
authorizationRequiredMetaSchema,
|
|
61523
61833
|
authorizationRequiredParamsSchema,
|
|
61524
61834
|
authorizationSchema,
|
|
61835
|
+
authorizeSessionTermination,
|
|
61525
61836
|
buildChannelInstructions,
|
|
61526
61837
|
buildEsmPromptRecord,
|
|
61527
61838
|
buildEsmResourceRecord,
|
|
@@ -61695,6 +62006,7 @@ export {
|
|
|
61695
62006
|
isPackageSpecifier,
|
|
61696
62007
|
isPublicError,
|
|
61697
62008
|
isPublicMode,
|
|
62009
|
+
isPublicUrlPinned,
|
|
61698
62010
|
isPubsubConfigured,
|
|
61699
62011
|
isRedisProvider,
|
|
61700
62012
|
isRemoteMode,
|