@frontmcp/sdk 1.7.1 → 1.7.2
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/auth/flows/oauth.authorize.flow.d.ts +30 -0
- package/auth/flows/oauth.authorize.flow.d.ts.map +1 -1
- package/auth/flows/oauth.callback.flow.d.ts.map +1 -1
- package/auth/instances/instance.local-primary-auth.d.ts +35 -0
- package/auth/instances/instance.local-primary-auth.d.ts.map +1 -1
- package/builtin/config/providers/config-loader.d.ts +5 -0
- package/builtin/config/providers/config-loader.d.ts.map +1 -1
- package/common/metadata/front-mcp.metadata.d.ts +26 -14
- package/common/metadata/front-mcp.metadata.d.ts.map +1 -1
- package/common/types/options/transport/schema.d.ts +2 -0
- package/common/types/options/transport/schema.d.ts.map +1 -1
- package/common/utils/index.d.ts +1 -0
- package/common/utils/index.d.ts.map +1 -1
- package/common/utils/principal.utils.d.ts +29 -0
- package/common/utils/principal.utils.d.ts.map +1 -0
- package/direct/llm-platform.d.ts +2 -2
- package/direct/llm-platform.d.ts.map +1 -1
- package/errors/index.d.ts +1 -0
- package/errors/index.d.ts.map +1 -1
- package/errors/job.errors.d.ts +26 -0
- package/errors/job.errors.d.ts.map +1 -0
- package/esm/index.mjs +899 -185
- package/front-mcp/front-mcp.providers.d.ts +4 -4
- package/index.js +1012 -298
- package/job/execution/job-execution.manager.d.ts +15 -0
- package/job/execution/job-execution.manager.d.ts.map +1 -1
- package/job/job-permission.guard.d.ts +23 -4
- package/job/job-permission.guard.d.ts.map +1 -1
- package/job/job-scope.helper.d.ts +12 -0
- package/job/job-scope.helper.d.ts.map +1 -1
- package/job/store/job-state.interface.d.ts +6 -0
- package/job/store/job-state.interface.d.ts.map +1 -1
- package/job/tools/execute-job.tool.d.ts.map +1 -1
- package/job/tools/get-job-status.tool.d.ts +12 -0
- package/job/tools/get-job-status.tool.d.ts.map +1 -1
- package/job/tools/list-jobs.tool.d.ts +6 -0
- package/job/tools/list-jobs.tool.d.ts.map +1 -1
- package/job/tools/register-job.tool.d.ts +1 -0
- package/job/tools/register-job.tool.d.ts.map +1 -1
- package/job/tools/remove-job.tool.d.ts.map +1 -1
- package/package.json +10 -10
- package/scope/scope.utils.d.ts.map +1 -1
- package/server/adapters/express.host.adapter.d.ts +32 -0
- package/server/adapters/express.host.adapter.d.ts.map +1 -1
- package/server/middleware/host-validation.middleware.d.ts +8 -4
- package/server/middleware/host-validation.middleware.d.ts.map +1 -1
- package/server/security/host-validation.d.ts +66 -0
- package/server/security/host-validation.d.ts.map +1 -0
- package/server/security/resolve-allowed-hosts.d.ts +63 -0
- package/server/security/resolve-allowed-hosts.d.ts.map +1 -0
- package/server/server.instance.d.ts.map +1 -1
- package/server/server.types.d.ts +11 -6
- package/server/server.types.d.ts.map +1 -1
- package/tool/flows/call-tool.flow.d.ts +17 -1
- package/tool/flows/call-tool.flow.d.ts.map +1 -1
- package/transport/adapters/base-sse-transport.d.ts +7 -0
- package/transport/adapters/base-sse-transport.d.ts.map +1 -1
- package/transport/adapters/transport.local.adapter.d.ts.map +1 -1
- package/transport/adapters/transport.streamable-http.adapter.d.ts.map +1 -1
- package/transport/event-stores/index.d.ts +2 -0
- package/transport/event-stores/index.d.ts.map +1 -1
- package/transport/event-stores/memory.event-store.d.ts +9 -2
- package/transport/event-stores/memory.event-store.d.ts.map +1 -1
- package/transport/event-stores/redis.event-store.d.ts +24 -3
- package/transport/event-stores/redis.event-store.d.ts.map +1 -1
- package/transport/event-stores/session-scoped.event-store.d.ts +17 -0
- package/transport/event-stores/session-scoped.event-store.d.ts.map +1 -0
- package/transport/web-fetch-handler.d.ts.map +1 -1
- package/workflow/tools/execute-workflow.tool.d.ts.map +1 -1
- package/workflow/tools/get-workflow-status.tool.d.ts +12 -0
- package/workflow/tools/get-workflow-status.tool.d.ts.map +1 -1
- package/workflow/tools/list-workflows.tool.d.ts +1 -0
- package/workflow/tools/list-workflows.tool.d.ts.map +1 -1
- package/workflow/tools/register-workflow.tool.d.ts +1 -0
- package/workflow/tools/register-workflow.tool.d.ts.map +1 -1
- package/workflow/tools/remove-workflow.tool.d.ts.map +1 -1
package/index.js
CHANGED
|
@@ -2309,7 +2309,10 @@ var init_schema7 = __esm({
|
|
|
2309
2309
|
});
|
|
2310
2310
|
eventStoreConfigSchema = import_lazy_zod12.z.object({
|
|
2311
2311
|
enabled: import_lazy_zod12.z.boolean().default(false),
|
|
2312
|
-
|
|
2312
|
+
// 'sqlite' is supported by createEventStore and the TS interface but was
|
|
2313
|
+
// missing from this enum, so a valid `provider: 'sqlite'` config failed
|
|
2314
|
+
// validation.
|
|
2315
|
+
provider: import_lazy_zod12.z.enum(["memory", "redis", "sqlite"]).optional().default("memory"),
|
|
2313
2316
|
maxEvents: import_lazy_zod12.z.number().int().positive().optional().default(1e4),
|
|
2314
2317
|
ttlMs: import_lazy_zod12.z.number().int().positive().optional().default(3e5),
|
|
2315
2318
|
redis: redisOptionsSchema.optional()
|
|
@@ -4038,7 +4041,7 @@ function formatToolsForPlatform(tools, platform) {
|
|
|
4038
4041
|
})
|
|
4039
4042
|
);
|
|
4040
4043
|
case "vercel-ai": {
|
|
4041
|
-
const result =
|
|
4044
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
4042
4045
|
for (const tool of tools) {
|
|
4043
4046
|
result[tool.name] = {
|
|
4044
4047
|
description: tool.description ?? `Execute ${tool.name}`,
|
|
@@ -5576,6 +5579,32 @@ var init_sdk_errors = __esm({
|
|
|
5576
5579
|
}
|
|
5577
5580
|
});
|
|
5578
5581
|
|
|
5582
|
+
// libs/sdk/src/errors/job.errors.ts
|
|
5583
|
+
var JobNotAuthorizedError, DynamicJobRegistrationDisabledError;
|
|
5584
|
+
var init_job_errors = __esm({
|
|
5585
|
+
"libs/sdk/src/errors/job.errors.ts"() {
|
|
5586
|
+
"use strict";
|
|
5587
|
+
init_mcp_error();
|
|
5588
|
+
JobNotAuthorizedError = class extends PublicMcpError {
|
|
5589
|
+
/** The requested name, for internal logs only — never surfaced publicly. */
|
|
5590
|
+
requestedName;
|
|
5591
|
+
constructor(requestedName) {
|
|
5592
|
+
super(`Job or workflow "${requestedName}" not found or not permitted`, "JOB_NOT_AUTHORIZED", 403);
|
|
5593
|
+
this.requestedName = requestedName;
|
|
5594
|
+
}
|
|
5595
|
+
};
|
|
5596
|
+
DynamicJobRegistrationDisabledError = class extends PublicMcpError {
|
|
5597
|
+
constructor(kind = "job") {
|
|
5598
|
+
super(
|
|
5599
|
+
`Dynamic ${kind} registration is disabled. Enable it with jobs: { allowDynamicRegistration: true }.`,
|
|
5600
|
+
"DYNAMIC_REGISTRATION_DISABLED",
|
|
5601
|
+
403
|
|
5602
|
+
);
|
|
5603
|
+
}
|
|
5604
|
+
};
|
|
5605
|
+
}
|
|
5606
|
+
});
|
|
5607
|
+
|
|
5579
5608
|
// libs/sdk/src/errors/index.ts
|
|
5580
5609
|
var errors_exports = {};
|
|
5581
5610
|
__export(errors_exports, {
|
|
@@ -5601,6 +5630,7 @@ __export(errors_exports, {
|
|
|
5601
5630
|
DependencyNotFoundError: () => DependencyNotFoundError,
|
|
5602
5631
|
DynamicAdapterNameError: () => DynamicAdapterNameError,
|
|
5603
5632
|
DynamicJobDirectExecutionError: () => DynamicJobDirectExecutionError,
|
|
5633
|
+
DynamicJobRegistrationDisabledError: () => DynamicJobRegistrationDisabledError,
|
|
5604
5634
|
ElicitationDisabledError: () => ElicitationDisabledError,
|
|
5605
5635
|
ElicitationEncryptionError: () => ElicitationEncryptionError,
|
|
5606
5636
|
ElicitationFallbackRequired: () => ElicitationFallbackRequired,
|
|
@@ -5649,6 +5679,7 @@ __export(errors_exports, {
|
|
|
5649
5679
|
InvalidUseFactoryError: () => InvalidUseFactoryError,
|
|
5650
5680
|
InvalidUseValueError: () => InvalidUseValueError,
|
|
5651
5681
|
InvokeStateMissingKeyError: () => InvokeStateMissingKeyError,
|
|
5682
|
+
JobNotAuthorizedError: () => JobNotAuthorizedError,
|
|
5652
5683
|
MCP_ERROR_CODES: () => MCP_ERROR_CODES,
|
|
5653
5684
|
McpError: () => McpError,
|
|
5654
5685
|
MethodNotImplementedError: () => MethodNotImplementedError,
|
|
@@ -5771,6 +5802,7 @@ var init_errors = __esm({
|
|
|
5771
5802
|
init_workflow_errors();
|
|
5772
5803
|
init_esm_errors();
|
|
5773
5804
|
init_sdk_errors();
|
|
5805
|
+
init_job_errors();
|
|
5774
5806
|
}
|
|
5775
5807
|
});
|
|
5776
5808
|
|
|
@@ -7093,6 +7125,10 @@ var require_dist = __commonJS({
|
|
|
7093
7125
|
}
|
|
7094
7126
|
return id;
|
|
7095
7127
|
}
|
|
7128
|
+
async getStreamIdForEventId(eventId) {
|
|
7129
|
+
const row = this.prepared().getStreamId.get(eventId);
|
|
7130
|
+
return row?.stream_id;
|
|
7131
|
+
}
|
|
7096
7132
|
async replayEventsAfter(lastEventId, { send }) {
|
|
7097
7133
|
const stmts = this.prepared();
|
|
7098
7134
|
const streamRow = stmts.getStreamId.get(lastEventId);
|
|
@@ -14015,22 +14051,38 @@ async function loadYamlConfig(basePath, configPath) {
|
|
|
14015
14051
|
}
|
|
14016
14052
|
return {};
|
|
14017
14053
|
}
|
|
14054
|
+
function stripUnsafeKeys(value) {
|
|
14055
|
+
if (Array.isArray(value)) return value.map(stripUnsafeKeys);
|
|
14056
|
+
if (!isPlainObject(value)) return value;
|
|
14057
|
+
const clean = {};
|
|
14058
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
14059
|
+
if (UNSAFE_MERGE_KEYS.has(key)) continue;
|
|
14060
|
+
clean[key] = stripUnsafeKeys(nested);
|
|
14061
|
+
}
|
|
14062
|
+
return clean;
|
|
14063
|
+
}
|
|
14064
|
+
function isPlainObject(value) {
|
|
14065
|
+
if (!value || typeof value !== "object") return false;
|
|
14066
|
+
const proto = Object.getPrototypeOf(value);
|
|
14067
|
+
return proto === Object.prototype || proto === null;
|
|
14068
|
+
}
|
|
14018
14069
|
function deepMerge(target, source) {
|
|
14019
14070
|
const result = { ...target };
|
|
14020
14071
|
for (const key in source) {
|
|
14072
|
+
if (UNSAFE_MERGE_KEYS.has(key)) continue;
|
|
14021
14073
|
const sourceVal = source[key];
|
|
14022
14074
|
const targetVal = result[key];
|
|
14023
14075
|
if (sourceVal !== void 0 && sourceVal !== null) {
|
|
14024
14076
|
if (typeof sourceVal === "object" && !Array.isArray(sourceVal) && typeof targetVal === "object" && !Array.isArray(targetVal) && targetVal !== null) {
|
|
14025
14077
|
result[key] = deepMerge(targetVal, sourceVal);
|
|
14026
14078
|
} else {
|
|
14027
|
-
result[key] = sourceVal;
|
|
14079
|
+
result[key] = stripUnsafeKeys(sourceVal);
|
|
14028
14080
|
}
|
|
14029
14081
|
}
|
|
14030
14082
|
}
|
|
14031
14083
|
return result;
|
|
14032
14084
|
}
|
|
14033
|
-
var yaml2, import_utils32;
|
|
14085
|
+
var yaml2, import_utils32, UNSAFE_MERGE_KEYS;
|
|
14034
14086
|
var init_config_loader = __esm({
|
|
14035
14087
|
"libs/sdk/src/builtin/config/providers/config-loader.ts"() {
|
|
14036
14088
|
"use strict";
|
|
@@ -14038,6 +14090,7 @@ var init_config_loader = __esm({
|
|
|
14038
14090
|
import_utils32 = require("@frontmcp/utils");
|
|
14039
14091
|
init_config_service();
|
|
14040
14092
|
init_env_loader();
|
|
14093
|
+
UNSAFE_MERGE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
14041
14094
|
}
|
|
14042
14095
|
});
|
|
14043
14096
|
|
|
@@ -20704,10 +20757,11 @@ var init_tool_credentials_gate = __esm({
|
|
|
20704
20757
|
});
|
|
20705
20758
|
|
|
20706
20759
|
// libs/sdk/src/tool/flows/call-tool.flow.ts
|
|
20707
|
-
var import_guard4, import_lazy_zod56, import_protocol38, import_adapters4, import_utils45, inputSchema13, outputSchema12, stateSchema12, plan12, name12, Stage12, CallToolFlow;
|
|
20760
|
+
var import_auth17, import_guard4, import_lazy_zod56, import_protocol38, import_adapters4, import_utils45, inputSchema13, outputSchema12, stateSchema12, plan12, name12, Stage12, CallToolFlow;
|
|
20708
20761
|
var init_call_tool_flow = __esm({
|
|
20709
20762
|
"libs/sdk/src/tool/flows/call-tool.flow.ts"() {
|
|
20710
20763
|
"use strict";
|
|
20764
|
+
import_auth17 = require("@frontmcp/auth");
|
|
20711
20765
|
import_guard4 = require("@frontmcp/guard");
|
|
20712
20766
|
import_lazy_zod56 = require("@frontmcp/lazy-zod");
|
|
20713
20767
|
import_protocol38 = require("@frontmcp/protocol");
|
|
@@ -21080,7 +21134,7 @@ var init_call_tool_flow = __esm({
|
|
|
21080
21134
|
this.logger.verbose(`checkToolAuthorization: using anonymous for ${appId}`);
|
|
21081
21135
|
return;
|
|
21082
21136
|
}
|
|
21083
|
-
const authUrl = this.buildProgressiveAuthUrl(appId, tool?.fullName || "");
|
|
21137
|
+
const authUrl = this.buildProgressiveAuthUrl(appId, tool?.fullName || "", authInfo, claimAuthorizedApps);
|
|
21084
21138
|
this.logger.info(`checkToolAuthorization: authorization required for app "${appId}"`);
|
|
21085
21139
|
throw new AuthorizationRequiredError({
|
|
21086
21140
|
appId,
|
|
@@ -21092,11 +21146,51 @@ var init_call_tool_flow = __esm({
|
|
|
21092
21146
|
this.logger.verbose("checkToolAuthorization:done");
|
|
21093
21147
|
}
|
|
21094
21148
|
/**
|
|
21095
|
-
* Build the URL for progressive/incremental authorization
|
|
21149
|
+
* Build the URL for progressive/incremental authorization.
|
|
21150
|
+
*
|
|
21151
|
+
* This is the ONE point in the request lifecycle where the caller's identity
|
|
21152
|
+
* is both known and verified, so it is where the incremental-authorization
|
|
21153
|
+
* ticket is minted (GHSA-2c4g-9c8x-6m8g). The ticket is what later authorizes
|
|
21154
|
+
* `/oauth/authorize` to skip the login step; `app`/`tool` are carried purely
|
|
21155
|
+
* for display and are re-read from the signed payload server-side.
|
|
21156
|
+
*
|
|
21157
|
+
* When no verified subject or signing secret is available the URL is emitted
|
|
21158
|
+
* WITHOUT a ticket — the user then completes an ordinary login, which is the
|
|
21159
|
+
* correct fail-closed outcome rather than a credential-free shortcut.
|
|
21096
21160
|
*/
|
|
21097
|
-
buildProgressiveAuthUrl(appId, toolId) {
|
|
21161
|
+
buildProgressiveAuthUrl(appId, toolId, authInfo, priorAppIds) {
|
|
21098
21162
|
const baseUrl = this.scope.fullPath || "";
|
|
21099
|
-
|
|
21163
|
+
const url = new URL(`${baseUrl}/oauth/authorize`, "http://placeholder.invalid");
|
|
21164
|
+
url.searchParams.set("app", appId);
|
|
21165
|
+
url.searchParams.set("tool", toolId);
|
|
21166
|
+
const ticket = this.mintIncrementalTicket(appId, toolId, authInfo, priorAppIds);
|
|
21167
|
+
if (ticket) {
|
|
21168
|
+
url.searchParams.set("ticket", ticket);
|
|
21169
|
+
} else {
|
|
21170
|
+
this.logger.verbose("buildProgressiveAuthUrl: no incremental ticket minted \u2014 link requires a full login");
|
|
21171
|
+
}
|
|
21172
|
+
return baseUrl.startsWith("http") ? url.toString() : `${url.pathname}${url.search}`;
|
|
21173
|
+
}
|
|
21174
|
+
/**
|
|
21175
|
+
* Mint a single-use, short-lived incremental-authorization ticket for the
|
|
21176
|
+
* verified caller. Returns undefined when the scope has no local signing
|
|
21177
|
+
* secret or the request has no verified subject.
|
|
21178
|
+
*/
|
|
21179
|
+
mintIncrementalTicket(appId, toolId, authInfo, priorAppIds) {
|
|
21180
|
+
const extraUser = authInfo?.extra?.user;
|
|
21181
|
+
const sub = extraUser?.sub ?? authInfo?.user?.sub;
|
|
21182
|
+
if (!sub) return void 0;
|
|
21183
|
+
const secret = this.scope.auth?.signingSecret;
|
|
21184
|
+
if (!secret) return void 0;
|
|
21185
|
+
return (0, import_auth17.signIncrementalAuthTicket)(
|
|
21186
|
+
{
|
|
21187
|
+
sub,
|
|
21188
|
+
appId,
|
|
21189
|
+
toolId,
|
|
21190
|
+
...priorAppIds && priorAppIds.size > 0 ? { priorAppIds: [...priorAppIds] } : {}
|
|
21191
|
+
},
|
|
21192
|
+
secret
|
|
21193
|
+
);
|
|
21100
21194
|
}
|
|
21101
21195
|
async checkEntryAuthorities() {
|
|
21102
21196
|
this.logger.verbose("checkEntryAuthorities:start");
|
|
@@ -35577,25 +35671,25 @@ var init_auth_utils = __esm({
|
|
|
35577
35671
|
});
|
|
35578
35672
|
|
|
35579
35673
|
// libs/sdk/src/auth/authorization/orchestrated.context-extension.ts
|
|
35580
|
-
var
|
|
35674
|
+
var import_auth18, orchestratedAuthContextExtension;
|
|
35581
35675
|
var init_orchestrated_context_extension = __esm({
|
|
35582
35676
|
"libs/sdk/src/auth/authorization/orchestrated.context-extension.ts"() {
|
|
35583
35677
|
"use strict";
|
|
35584
|
-
|
|
35678
|
+
import_auth18 = require("@frontmcp/auth");
|
|
35585
35679
|
orchestratedAuthContextExtension = {
|
|
35586
35680
|
property: "orchestration",
|
|
35587
|
-
token:
|
|
35681
|
+
token: import_auth18.ORCHESTRATED_AUTH_ACCESSOR,
|
|
35588
35682
|
errorMessage: "Orchestrated authorization not available. Ensure your server is configured with orchestrated mode and the user has completed authentication."
|
|
35589
35683
|
};
|
|
35590
35684
|
}
|
|
35591
35685
|
});
|
|
35592
35686
|
|
|
35593
35687
|
// libs/sdk/src/auth/cimd/index.ts
|
|
35594
|
-
var
|
|
35688
|
+
var import_auth19;
|
|
35595
35689
|
var init_cimd = __esm({
|
|
35596
35690
|
"libs/sdk/src/auth/cimd/index.ts"() {
|
|
35597
35691
|
"use strict";
|
|
35598
|
-
|
|
35692
|
+
import_auth19 = require("@frontmcp/auth");
|
|
35599
35693
|
}
|
|
35600
35694
|
});
|
|
35601
35695
|
|
|
@@ -35617,9 +35711,9 @@ function createCredentialsProviders(opts) {
|
|
|
35617
35711
|
name: "SessionCredentialVault"
|
|
35618
35712
|
};
|
|
35619
35713
|
const accessorProvider = {
|
|
35620
|
-
provide:
|
|
35714
|
+
provide: import_auth20.CREDENTIALS_ACCESSOR,
|
|
35621
35715
|
inject: () => [FRONTMCP_CONTEXT, FrontMcpLogger],
|
|
35622
|
-
useFactory: (ctx, logger) => new
|
|
35716
|
+
useFactory: (ctx, logger) => new import_auth20.CredentialsAccessorImpl({
|
|
35623
35717
|
vault,
|
|
35624
35718
|
resolveSub: () => resolveRequestSub(ctx),
|
|
35625
35719
|
signingSecret,
|
|
@@ -35632,11 +35726,11 @@ function createCredentialsProviders(opts) {
|
|
|
35632
35726
|
};
|
|
35633
35727
|
return [vaultProvider, accessorProvider];
|
|
35634
35728
|
}
|
|
35635
|
-
var
|
|
35729
|
+
var import_auth20, import_di31, SESSION_CREDENTIAL_VAULT;
|
|
35636
35730
|
var init_credentials_providers = __esm({
|
|
35637
35731
|
"libs/sdk/src/auth/credentials/credentials.providers.ts"() {
|
|
35638
35732
|
"use strict";
|
|
35639
|
-
|
|
35733
|
+
import_auth20 = require("@frontmcp/auth");
|
|
35640
35734
|
import_di31 = require("@frontmcp/di");
|
|
35641
35735
|
init_common();
|
|
35642
35736
|
init_frontmcp_context_provider();
|
|
@@ -35647,14 +35741,14 @@ var init_credentials_providers = __esm({
|
|
|
35647
35741
|
});
|
|
35648
35742
|
|
|
35649
35743
|
// libs/sdk/src/auth/credentials/credentials.context-extension.ts
|
|
35650
|
-
var
|
|
35744
|
+
var import_auth21, credentialsContextExtension;
|
|
35651
35745
|
var init_credentials_context_extension = __esm({
|
|
35652
35746
|
"libs/sdk/src/auth/credentials/credentials.context-extension.ts"() {
|
|
35653
35747
|
"use strict";
|
|
35654
|
-
|
|
35748
|
+
import_auth21 = require("@frontmcp/auth");
|
|
35655
35749
|
credentialsContextExtension = {
|
|
35656
35750
|
property: "credentials",
|
|
35657
|
-
token:
|
|
35751
|
+
token: import_auth21.CREDENTIALS_ACCESSOR,
|
|
35658
35752
|
errorMessage: "Per-session credentials are not available. Ensure the server uses auth mode 'local' (or 'remote'); credentials are persisted by a local authenticate() verifier."
|
|
35659
35753
|
};
|
|
35660
35754
|
}
|
|
@@ -35823,11 +35917,11 @@ function getConfiguredProviders(auth) {
|
|
|
35823
35917
|
function getConfiguredProviderIds(auth) {
|
|
35824
35918
|
return getConfiguredProviders(auth).map((p) => p.id);
|
|
35825
35919
|
}
|
|
35826
|
-
var
|
|
35920
|
+
var import_auth22, import_lazy_zod64, import_utils66, pkceChallengeSchema, codeChallengeMethodSchema, responseTypeSchema, safeRedirectUriSchema, oauthAuthorizeRequestSchema, anonymousAuthorizeRequestSchema, inputSchema16, stateSchema15, outputSchema15, plan15, name15, Stage15, OauthAuthorizeFlow;
|
|
35827
35921
|
var init_oauth_authorize_flow = __esm({
|
|
35828
35922
|
"libs/sdk/src/auth/flows/oauth.authorize.flow.ts"() {
|
|
35829
35923
|
"use strict";
|
|
35830
|
-
|
|
35924
|
+
import_auth22 = require("@frontmcp/auth");
|
|
35831
35925
|
import_lazy_zod64 = require("@frontmcp/lazy-zod");
|
|
35832
35926
|
import_utils66 = require("@frontmcp/utils");
|
|
35833
35927
|
init_common();
|
|
@@ -35885,6 +35979,13 @@ var init_oauth_authorize_flow = __esm({
|
|
|
35885
35979
|
targetAppId: import_lazy_zod64.z.string().optional().describe("Target app ID for incremental authorization"),
|
|
35886
35980
|
targetToolId: import_lazy_zod64.z.string().optional().describe("Target tool ID that triggered the incremental auth"),
|
|
35887
35981
|
existingSessionId: import_lazy_zod64.z.string().optional().describe("Existing session ID for incremental auth"),
|
|
35982
|
+
/**
|
|
35983
|
+
* The VERIFIED incremental-authorization ticket (GHSA-2c4g-9c8x-6m8g). Set
|
|
35984
|
+
* only when `?ticket=` carried a valid, unexpired server signature. Its `sub`
|
|
35985
|
+
* is the proven identity the expanded grant is minted for; `isIncrementalAuth`
|
|
35986
|
+
* is derived from its presence, never from a raw query parameter.
|
|
35987
|
+
*/
|
|
35988
|
+
incrementalTicket: import_lazy_zod64.z.custom().optional(),
|
|
35888
35989
|
/**
|
|
35889
35990
|
* Apps the client already holds a grant for (the prior `authorized_apps`
|
|
35890
35991
|
* claim), carried forward on an incremental authorize so the newly-minted
|
|
@@ -35898,7 +35999,7 @@ var init_oauth_authorize_flow = __esm({
|
|
|
35898
35999
|
requiresConsent: import_lazy_zod64.z.boolean().default(false).describe("Whether consent flow is enabled"),
|
|
35899
36000
|
// CIMD (Client ID Metadata Documents)
|
|
35900
36001
|
isCimdClient: import_lazy_zod64.z.boolean().default(false).describe("Whether client_id is a CIMD URL"),
|
|
35901
|
-
cimdMetadata:
|
|
36002
|
+
cimdMetadata: import_auth19.clientMetadataDocumentSchema.optional().describe("CIMD metadata document for the client")
|
|
35902
36003
|
});
|
|
35903
36004
|
outputSchema15 = import_lazy_zod64.z.union([
|
|
35904
36005
|
HttpRedirectSchema,
|
|
@@ -35937,18 +36038,21 @@ var init_oauth_authorize_flow = __esm({
|
|
|
35937
36038
|
const { request } = this.rawInput;
|
|
35938
36039
|
const rawRedirectUri = request.query["redirect_uri"];
|
|
35939
36040
|
const rawState = request.query["state"];
|
|
35940
|
-
const
|
|
35941
|
-
const
|
|
35942
|
-
const
|
|
35943
|
-
const
|
|
35944
|
-
const isIncrementalAuth = mode === "incremental" || !!targetAppId;
|
|
35945
|
-
const appsParam = request.query["apps"];
|
|
36041
|
+
const incrementalTicket = this.verifyIncrementalTicket(request.query["ticket"]);
|
|
36042
|
+
const isIncrementalAuth = incrementalTicket !== void 0;
|
|
36043
|
+
const targetAppId = incrementalTicket?.appId;
|
|
36044
|
+
const targetToolId = incrementalTicket?.toolId;
|
|
35946
36045
|
let priorAuthorizedAppIds;
|
|
35947
|
-
if (
|
|
35948
|
-
|
|
35949
|
-
|
|
35950
|
-
|
|
36046
|
+
if (incrementalTicket) {
|
|
36047
|
+
priorAuthorizedAppIds = incrementalTicket.priorAppIds && incrementalTicket.priorAppIds.length > 0 ? Array.from(new Set(incrementalTicket.priorAppIds)) : void 0;
|
|
36048
|
+
} else {
|
|
36049
|
+
const appsParam = request.query["apps"];
|
|
36050
|
+
if (appsParam) {
|
|
36051
|
+
const ids = (Array.isArray(appsParam) ? appsParam : [appsParam]).flatMap((v) => String(v).split(",")).map((v) => v.trim()).filter(Boolean);
|
|
36052
|
+
priorAuthorizedAppIds = ids.length > 0 ? Array.from(new Set(ids)) : void 0;
|
|
36053
|
+
}
|
|
35951
36054
|
}
|
|
36055
|
+
const existingSessionId = request.query["session_id"];
|
|
35952
36056
|
const isDefaultAuthProvider = !metadata.auth;
|
|
35953
36057
|
let requiresFederatedLogin = false;
|
|
35954
36058
|
if (metadata.auth && (0, import_auth10.isOrchestratedMode)(metadata.auth)) {
|
|
@@ -35974,6 +36078,7 @@ var init_oauth_authorize_flow = __esm({
|
|
|
35974
36078
|
targetToolId,
|
|
35975
36079
|
existingSessionId,
|
|
35976
36080
|
priorAuthorizedAppIds,
|
|
36081
|
+
incrementalTicket,
|
|
35977
36082
|
// Federated Login
|
|
35978
36083
|
requiresFederatedLogin,
|
|
35979
36084
|
// Consent Flow
|
|
@@ -36045,7 +36150,7 @@ var init_oauth_authorize_flow = __esm({
|
|
|
36045
36150
|
}
|
|
36046
36151
|
}
|
|
36047
36152
|
const { client_id, redirect_uri } = result.data;
|
|
36048
|
-
const cimdService = this.get(
|
|
36153
|
+
const cimdService = this.get(import_auth19.CimdService);
|
|
36049
36154
|
const isCimdClientId2 = !!cimdService?.enabled && cimdService.isCimdClientId(client_id);
|
|
36050
36155
|
const dcrError = this.checkDcrAllowlist(client_id, redirect_uri, isCimdClientId2);
|
|
36051
36156
|
if (dcrError) {
|
|
@@ -36099,6 +36204,28 @@ var init_oauth_authorize_flow = __esm({
|
|
|
36099
36204
|
}
|
|
36100
36205
|
}
|
|
36101
36206
|
}
|
|
36207
|
+
/**
|
|
36208
|
+
* Verify a client-supplied incremental-authorization ticket.
|
|
36209
|
+
*
|
|
36210
|
+
* Returns the verified payload, or `undefined` when the parameter is absent,
|
|
36211
|
+
* malformed, expired, or not signed by this server. Verification is
|
|
36212
|
+
* side-effect free — the ticket is CLAIMED (single use) later, in
|
|
36213
|
+
* {@link prepareAuthorizationRequest}, so a request that fails OAuth
|
|
36214
|
+
* validation does not burn the user's ticket.
|
|
36215
|
+
*/
|
|
36216
|
+
verifyIncrementalTicket(raw) {
|
|
36217
|
+
const token = typeof raw === "string" ? raw : Array.isArray(raw) ? raw[0] : void 0;
|
|
36218
|
+
if (typeof token !== "string" || token.length === 0) return void 0;
|
|
36219
|
+
const localAuth = this.scope.auth;
|
|
36220
|
+
const secret = localAuth?.signingSecret;
|
|
36221
|
+
if (!secret) return void 0;
|
|
36222
|
+
const payload = (0, import_auth22.verifyIncrementalAuthTicket)(token, secret);
|
|
36223
|
+
if (!payload) {
|
|
36224
|
+
this.logger.warn("Incremental auth ticket failed verification \u2014 treating as an ordinary login");
|
|
36225
|
+
return void 0;
|
|
36226
|
+
}
|
|
36227
|
+
return payload;
|
|
36228
|
+
}
|
|
36102
36229
|
async checkIfAuthorized() {
|
|
36103
36230
|
}
|
|
36104
36231
|
async prepareAuthorizationRequest() {
|
|
@@ -36110,7 +36237,8 @@ var init_oauth_authorize_flow = __esm({
|
|
|
36110
36237
|
existingSessionId,
|
|
36111
36238
|
priorAuthorizedAppIds,
|
|
36112
36239
|
requiresFederatedLogin,
|
|
36113
|
-
requiresConsent
|
|
36240
|
+
requiresConsent,
|
|
36241
|
+
incrementalTicket
|
|
36114
36242
|
} = this.state;
|
|
36115
36243
|
const { metadata } = this.scope;
|
|
36116
36244
|
if (!validatedRequest) {
|
|
@@ -36123,6 +36251,18 @@ var init_oauth_authorize_flow = __esm({
|
|
|
36123
36251
|
}
|
|
36124
36252
|
const localAuth = auth;
|
|
36125
36253
|
const store = localAuth.authorizationStore;
|
|
36254
|
+
let effectiveIncremental = isIncrementalAuth;
|
|
36255
|
+
if (incrementalTicket) {
|
|
36256
|
+
const claimed = await localAuth.claimIncrementalTicket(
|
|
36257
|
+
incrementalTicket.jti,
|
|
36258
|
+
Math.max(0, incrementalTicket.exp - Date.now())
|
|
36259
|
+
);
|
|
36260
|
+
if (!claimed) {
|
|
36261
|
+
this.logger.warn("Incremental auth ticket already used \u2014 falling back to an ordinary login");
|
|
36262
|
+
effectiveIncremental = false;
|
|
36263
|
+
this.state.set({ isIncrementalAuth: false, targetAppId: void 0, targetToolId: void 0 });
|
|
36264
|
+
}
|
|
36265
|
+
}
|
|
36126
36266
|
let federatedLogin;
|
|
36127
36267
|
if (requiresFederatedLogin) {
|
|
36128
36268
|
const apps = this.scope.apps.getApps();
|
|
@@ -36170,10 +36310,14 @@ var init_oauth_authorize_flow = __esm({
|
|
|
36170
36310
|
},
|
|
36171
36311
|
state: validatedRequest.state,
|
|
36172
36312
|
resource: validatedRequest.resource,
|
|
36173
|
-
// Progressive/Incremental Authorization Fields
|
|
36174
|
-
|
|
36175
|
-
|
|
36176
|
-
|
|
36313
|
+
// Progressive/Incremental Authorization Fields. `isIncremental` reflects a
|
|
36314
|
+
// CLAIMED, server-signed ticket; `incrementalSub` is the identity that
|
|
36315
|
+
// ticket proved, and is the only subject the callback may mint for when
|
|
36316
|
+
// it skips the credential gate.
|
|
36317
|
+
isIncremental: effectiveIncremental,
|
|
36318
|
+
incrementalSub: effectiveIncremental ? incrementalTicket?.sub : void 0,
|
|
36319
|
+
targetAppId: effectiveIncremental ? targetAppId : void 0,
|
|
36320
|
+
targetToolId: effectiveIncremental ? targetToolId : void 0,
|
|
36177
36321
|
existingSessionId,
|
|
36178
36322
|
priorAuthorizedAppIds,
|
|
36179
36323
|
// Federated Login State
|
|
@@ -36183,7 +36327,7 @@ var init_oauth_authorize_flow = __esm({
|
|
|
36183
36327
|
});
|
|
36184
36328
|
await localAuth.authorizationStore.storePendingAuthorization(pendingRecord);
|
|
36185
36329
|
this.logger.info(
|
|
36186
|
-
`Pending authorization created: ${pendingRecord.id}${
|
|
36330
|
+
`Pending authorization created: ${pendingRecord.id}${effectiveIncremental ? ` (incremental for app: ${targetAppId})` : ""}${requiresFederatedLogin ? " (federated)" : ""}${requiresConsent ? " (consent enabled)" : ""}`
|
|
36187
36331
|
);
|
|
36188
36332
|
this.state.set("pendingAuthId", pendingRecord.id);
|
|
36189
36333
|
}
|
|
@@ -36404,7 +36548,7 @@ var init_oauth_authorize_flow = __esm({
|
|
|
36404
36548
|
);
|
|
36405
36549
|
return;
|
|
36406
36550
|
}
|
|
36407
|
-
const session = (0,
|
|
36551
|
+
const session = (0, import_auth22.createFederatedAuthSession)({
|
|
36408
36552
|
pendingAuthId,
|
|
36409
36553
|
clientId: validatedRequest.client_id,
|
|
36410
36554
|
redirectUri: validatedRequest.redirect_uri,
|
|
@@ -36419,7 +36563,7 @@ var init_oauth_authorize_flow = __esm({
|
|
|
36419
36563
|
const challenge = (0, import_utils66.sha256Base64url)(verifier);
|
|
36420
36564
|
const pkce = { verifier, challenge, method: "S256" };
|
|
36421
36565
|
const providerState = `federated:${session.id}:${(0, import_utils66.randomUUID)()}`;
|
|
36422
|
-
(0,
|
|
36566
|
+
(0, import_auth22.startNextProvider)(session, pkce, providerState);
|
|
36423
36567
|
await localAuth.federatedSessionStore.store(session);
|
|
36424
36568
|
const redirectUrl = await localAuth.buildProviderAuthorizeUrl(providerId, {
|
|
36425
36569
|
state: providerState,
|
|
@@ -36520,7 +36664,7 @@ var init_oauth_authorize_flow = __esm({
|
|
|
36520
36664
|
callbackPath,
|
|
36521
36665
|
fields: login?.fields ?? {}
|
|
36522
36666
|
};
|
|
36523
|
-
return (0,
|
|
36667
|
+
return (0, import_auth22.renderLocalLoginPage)(login, ctx);
|
|
36524
36668
|
}
|
|
36525
36669
|
/**
|
|
36526
36670
|
* Render incremental authorization page for a single app using HTMX templates
|
|
@@ -36533,7 +36677,7 @@ var init_oauth_authorize_flow = __esm({
|
|
|
36533
36677
|
appName,
|
|
36534
36678
|
description: appDescription
|
|
36535
36679
|
};
|
|
36536
|
-
return (0,
|
|
36680
|
+
return (0, import_auth22.buildIncrementalAuthPage)({
|
|
36537
36681
|
app,
|
|
36538
36682
|
toolId: toolId || "unknown tool",
|
|
36539
36683
|
sessionHint: pendingAuthId,
|
|
@@ -36554,7 +36698,7 @@ var init_oauth_authorize_flow = __esm({
|
|
|
36554
36698
|
appIds: provider.appIds.filter((id) => id !== "__parent__"),
|
|
36555
36699
|
isPrimary: provider.isParentProvider
|
|
36556
36700
|
}));
|
|
36557
|
-
return (0,
|
|
36701
|
+
return (0, import_auth22.buildFederatedLoginPage)({
|
|
36558
36702
|
providers,
|
|
36559
36703
|
clientName: clientId,
|
|
36560
36704
|
pendingAuthId,
|
|
@@ -36599,8 +36743,8 @@ var init_oauth_authorize_flow = __esm({
|
|
|
36599
36743
|
<div class="error-container">
|
|
36600
36744
|
<div class="error-icon">\u26A0\uFE0F</div>
|
|
36601
36745
|
<h1>Authorization Error</h1>
|
|
36602
|
-
<p><span class="error-code">${(0,
|
|
36603
|
-
<p>${(0,
|
|
36746
|
+
<p><span class="error-code">${(0, import_auth22.escapeHtml)(error)}</span></p>
|
|
36747
|
+
<p>${(0, import_auth22.escapeHtml)(description)}</p>
|
|
36604
36748
|
</div>
|
|
36605
36749
|
</body>
|
|
36606
36750
|
</html>`;
|
|
@@ -36649,11 +36793,11 @@ function timingSafeEqualStr3(a, b) {
|
|
|
36649
36793
|
}
|
|
36650
36794
|
return diff === 0;
|
|
36651
36795
|
}
|
|
36652
|
-
var
|
|
36796
|
+
var import_auth23, import_lazy_zod65, import_utils67, inputSchema17, stateSchema16, outputSchema16, plan16, name16, Stage16, OauthCallbackFlow;
|
|
36653
36797
|
var init_oauth_callback_flow = __esm({
|
|
36654
36798
|
"libs/sdk/src/auth/flows/oauth.callback.flow.ts"() {
|
|
36655
36799
|
"use strict";
|
|
36656
|
-
|
|
36800
|
+
import_auth23 = require("@frontmcp/auth");
|
|
36657
36801
|
import_lazy_zod65 = require("@frontmcp/lazy-zod");
|
|
36658
36802
|
import_utils67 = require("@frontmcp/utils");
|
|
36659
36803
|
init_common();
|
|
@@ -36744,8 +36888,6 @@ var init_oauth_callback_flow = __esm({
|
|
|
36744
36888
|
const email = request.query["email"];
|
|
36745
36889
|
const name42 = request.query["name"];
|
|
36746
36890
|
const loginFields = this.collectLoginFields(request);
|
|
36747
|
-
const isIncremental = request.query["incremental"] === "true";
|
|
36748
|
-
const targetAppId = request.query["app_id"];
|
|
36749
36891
|
const isFederated = request.query["federated"] === "true";
|
|
36750
36892
|
const providersParam = request.query["providers"];
|
|
36751
36893
|
let selectedProviders;
|
|
@@ -36764,8 +36906,6 @@ var init_oauth_callback_flow = __esm({
|
|
|
36764
36906
|
pendingAuthId,
|
|
36765
36907
|
email,
|
|
36766
36908
|
name: name42,
|
|
36767
|
-
isIncremental,
|
|
36768
|
-
targetAppId,
|
|
36769
36909
|
isFederated,
|
|
36770
36910
|
selectedProviders,
|
|
36771
36911
|
selectedTools,
|
|
@@ -36773,9 +36913,6 @@ var init_oauth_callback_flow = __esm({
|
|
|
36773
36913
|
loginFields,
|
|
36774
36914
|
csrf
|
|
36775
36915
|
});
|
|
36776
|
-
if (isIncremental) {
|
|
36777
|
-
this.logger.info(`Incremental auth callback for app: ${targetAppId}`);
|
|
36778
|
-
}
|
|
36779
36916
|
if (isFederated) {
|
|
36780
36917
|
this.logger.info(`Federated login callback with ${selectedProviders?.length || 0} selected providers`);
|
|
36781
36918
|
}
|
|
@@ -36784,31 +36921,13 @@ var init_oauth_callback_flow = __esm({
|
|
|
36784
36921
|
}
|
|
36785
36922
|
}
|
|
36786
36923
|
async validatePendingAuth() {
|
|
36787
|
-
const {
|
|
36788
|
-
pendingAuthId,
|
|
36789
|
-
email,
|
|
36790
|
-
name: name42,
|
|
36791
|
-
isIncremental,
|
|
36792
|
-
isFederated,
|
|
36793
|
-
selectedProviders,
|
|
36794
|
-
selectedTools,
|
|
36795
|
-
consentSubmitted,
|
|
36796
|
-
loginFields
|
|
36797
|
-
} = this.state;
|
|
36924
|
+
const { pendingAuthId, email, name: name42, isFederated, selectedProviders, selectedTools, consentSubmitted, loginFields } = this.state;
|
|
36798
36925
|
if (!pendingAuthId) {
|
|
36799
36926
|
this.logger.warn("Missing pending_auth_id in callback");
|
|
36800
36927
|
this.respond(httpRespond.html(this.renderErrorPage("invalid_request", "Missing pending_auth_id parameter"), 400));
|
|
36801
36928
|
return;
|
|
36802
36929
|
}
|
|
36803
36930
|
const localAuth = this.scope.auth;
|
|
36804
|
-
const localOptions = localAuth.options;
|
|
36805
|
-
const authenticateFn = typeof localOptions.authenticate === "function" ? localOptions.authenticate : void 0;
|
|
36806
|
-
const requireEmail = localOptions.requireEmail ?? true;
|
|
36807
|
-
if (!authenticateFn && !isIncremental && !email && requireEmail) {
|
|
36808
|
-
this.logger.warn("Missing email in callback");
|
|
36809
|
-
this.respond(httpRespond.html(this.renderErrorPage("invalid_request", "Email is required"), 400));
|
|
36810
|
-
return;
|
|
36811
|
-
}
|
|
36812
36931
|
const pendingAuth = await localAuth.authorizationStore.getPendingAuthorization(pendingAuthId);
|
|
36813
36932
|
if (!pendingAuth) {
|
|
36814
36933
|
this.logger.warn(`Pending authorization not found or expired: ${pendingAuthId}`);
|
|
@@ -36820,6 +36939,20 @@ var init_oauth_callback_flow = __esm({
|
|
|
36820
36939
|
);
|
|
36821
36940
|
return;
|
|
36822
36941
|
}
|
|
36942
|
+
const isIncremental = pendingAuth.isIncremental === true && typeof pendingAuth.incrementalSub === "string";
|
|
36943
|
+
const targetAppId = isIncremental ? pendingAuth.targetAppId : void 0;
|
|
36944
|
+
this.state.set({ isIncremental, targetAppId });
|
|
36945
|
+
if (isIncremental) {
|
|
36946
|
+
this.logger.info(`Incremental auth callback for app: ${targetAppId}`);
|
|
36947
|
+
}
|
|
36948
|
+
const localOptions = localAuth.options;
|
|
36949
|
+
const authenticateFn = typeof localOptions.authenticate === "function" ? localOptions.authenticate : void 0;
|
|
36950
|
+
const requireEmail = localOptions.requireEmail ?? true;
|
|
36951
|
+
if (!authenticateFn && !isIncremental && !email && requireEmail) {
|
|
36952
|
+
this.logger.warn("Missing email in callback");
|
|
36953
|
+
this.respond(httpRespond.html(this.renderErrorPage("invalid_request", "Email is required"), 400));
|
|
36954
|
+
return;
|
|
36955
|
+
}
|
|
36823
36956
|
if (pendingAuth.authUiCsrf) {
|
|
36824
36957
|
const submitted = this.state.csrf;
|
|
36825
36958
|
if (!submitted || !timingSafeEqualStr3(pendingAuth.authUiCsrf, submitted)) {
|
|
@@ -36834,9 +36967,11 @@ var init_oauth_callback_flow = __esm({
|
|
|
36834
36967
|
return;
|
|
36835
36968
|
}
|
|
36836
36969
|
let userSub;
|
|
36837
|
-
if (
|
|
36970
|
+
if (isIncremental) {
|
|
36971
|
+
userSub = pendingAuth.incrementalSub;
|
|
36972
|
+
} else if (email) {
|
|
36838
36973
|
userSub = this.generateUserSub(email);
|
|
36839
|
-
} else if (!requireEmail
|
|
36974
|
+
} else if (!requireEmail) {
|
|
36840
36975
|
const anonymousSubject = localOptions.anonymousSubject ?? "local-operator";
|
|
36841
36976
|
userSub = this.generateUserSub(anonymousSubject);
|
|
36842
36977
|
} else {
|
|
@@ -37005,10 +37140,13 @@ var init_oauth_callback_flow = __esm({
|
|
|
37005
37140
|
originalState: pendingAuth.state,
|
|
37006
37141
|
resource: pendingAuth.resource,
|
|
37007
37142
|
userSub,
|
|
37008
|
-
// Progressive/Incremental Authorization from pending record
|
|
37009
|
-
|
|
37010
|
-
|
|
37011
|
-
|
|
37143
|
+
// Progressive/Incremental Authorization — from the pending record ONLY.
|
|
37144
|
+
// `isIncremental` was derived above from the record plus its proven
|
|
37145
|
+
// subject; OR-ing a client-supplied flag in here is what let a caller
|
|
37146
|
+
// override the server's `false` (GHSA-2c4g-9c8x-6m8g).
|
|
37147
|
+
isIncremental,
|
|
37148
|
+
targetAppId,
|
|
37149
|
+
targetToolId: isIncremental ? pendingAuth.targetToolId : void 0,
|
|
37012
37150
|
existingSessionId: pendingAuth.existingSessionId,
|
|
37013
37151
|
existingAuthorizationId: pendingAuth.existingAuthorizationId,
|
|
37014
37152
|
priorAuthorizedAppIds: pendingAuth.priorAuthorizedAppIds,
|
|
@@ -37091,7 +37229,7 @@ var init_oauth_callback_flow = __esm({
|
|
|
37091
37229
|
this.respond(httpRespond.html(this.renderErrorPage("server_error", "Authorization request incomplete"), 500));
|
|
37092
37230
|
return;
|
|
37093
37231
|
}
|
|
37094
|
-
const federatedSession = (0,
|
|
37232
|
+
const federatedSession = (0, import_auth23.createFederatedAuthSession)({
|
|
37095
37233
|
pendingAuthId: this.state.required.pendingAuthId || (0, import_utils67.randomUUID)(),
|
|
37096
37234
|
clientId,
|
|
37097
37235
|
redirectUri,
|
|
@@ -37120,7 +37258,7 @@ var init_oauth_callback_flow = __esm({
|
|
|
37120
37258
|
method: "S256"
|
|
37121
37259
|
};
|
|
37122
37260
|
const providerState = `federated:${federatedSession.id}:${(0, import_utils67.randomUUID)()}`;
|
|
37123
|
-
(0,
|
|
37261
|
+
(0, import_auth23.startNextProvider)(federatedSession, pkce, providerState);
|
|
37124
37262
|
await sessionStore.update(federatedSession);
|
|
37125
37263
|
const providerConfig = localAuth.getProviderConfig(firstProviderId);
|
|
37126
37264
|
if (!providerConfig) {
|
|
@@ -37424,7 +37562,7 @@ var init_oauth_callback_flow = __esm({
|
|
|
37424
37562
|
fields: login?.fields ?? {},
|
|
37425
37563
|
error: failure.message
|
|
37426
37564
|
};
|
|
37427
|
-
return (0,
|
|
37565
|
+
return (0, import_auth23.renderLocalLoginPage)(login, ctx, fields);
|
|
37428
37566
|
}
|
|
37429
37567
|
/**
|
|
37430
37568
|
* Render a custom `@AuthUi({ slot: 'consent' })` page when one is registered.
|
|
@@ -37520,7 +37658,7 @@ var init_oauth_callback_flow = __esm({
|
|
|
37520
37658
|
if (pendingAuth.authUiCsrf) {
|
|
37521
37659
|
hiddenFields.push({ name: "csrf", value: pendingAuth.authUiCsrf });
|
|
37522
37660
|
}
|
|
37523
|
-
return (0,
|
|
37661
|
+
return (0, import_auth23.buildToolConsentPage)({
|
|
37524
37662
|
tools: toolCards,
|
|
37525
37663
|
clientName: pendingAuth.clientId,
|
|
37526
37664
|
pendingAuthId: pendingAuth.id,
|
|
@@ -37553,8 +37691,8 @@ var init_oauth_callback_flow = __esm({
|
|
|
37553
37691
|
* Render an error page
|
|
37554
37692
|
*/
|
|
37555
37693
|
renderErrorPage(error, description) {
|
|
37556
|
-
const safeError = (0,
|
|
37557
|
-
const safeDescription = (0,
|
|
37694
|
+
const safeError = (0, import_auth23.escapeHtml)(error);
|
|
37695
|
+
const safeDescription = (0, import_auth23.escapeHtml)(description);
|
|
37558
37696
|
return `<!DOCTYPE html>
|
|
37559
37697
|
<html lang="en">
|
|
37560
37698
|
<head>
|
|
@@ -37612,6 +37750,7 @@ var init_oauth_callback_flow = __esm({
|
|
|
37612
37750
|
"pending_auth_id",
|
|
37613
37751
|
"incremental",
|
|
37614
37752
|
"app_id",
|
|
37753
|
+
"ticket",
|
|
37615
37754
|
"federated",
|
|
37616
37755
|
"providers",
|
|
37617
37756
|
"tools",
|
|
@@ -37664,11 +37803,11 @@ var init_oauth_callback_flow = __esm({
|
|
|
37664
37803
|
});
|
|
37665
37804
|
|
|
37666
37805
|
// libs/sdk/src/auth/flows/oauth.connect.flow.ts
|
|
37667
|
-
var
|
|
37806
|
+
var import_auth24, import_lazy_zod66, inputSchema18, stateSchema17, outputSchema17, plan17, name17, Stage17, RESERVED_CONNECT_PARAMS, OauthConnectFlow;
|
|
37668
37807
|
var init_oauth_connect_flow = __esm({
|
|
37669
37808
|
"libs/sdk/src/auth/flows/oauth.connect.flow.ts"() {
|
|
37670
37809
|
"use strict";
|
|
37671
|
-
|
|
37810
|
+
import_auth24 = require("@frontmcp/auth");
|
|
37672
37811
|
import_lazy_zod66 = require("@frontmcp/lazy-zod");
|
|
37673
37812
|
init_common();
|
|
37674
37813
|
inputSchema18 = httpInputSchema;
|
|
@@ -37778,7 +37917,7 @@ var init_oauth_connect_flow = __esm({
|
|
|
37778
37917
|
}
|
|
37779
37918
|
this.logger.info(`Connected ${stored} credential(s) mid-session for key "${payload.key}"`);
|
|
37780
37919
|
this.notifyResourcesUpdated(payload.key);
|
|
37781
|
-
this.respond(httpRespond.html((0,
|
|
37920
|
+
this.respond(httpRespond.html((0, import_auth24.buildConnectSuccessPage)({ key: payload.key })));
|
|
37782
37921
|
}
|
|
37783
37922
|
// ============================================
|
|
37784
37923
|
// Helpers
|
|
@@ -37786,7 +37925,7 @@ var init_oauth_connect_flow = __esm({
|
|
|
37786
37925
|
/** Verify the signed resume token against the server secret (constant-time + expiry). */
|
|
37787
37926
|
verifyToken(token, localAuth) {
|
|
37788
37927
|
const secret = new TextDecoder().decode(localAuth.secret);
|
|
37789
|
-
return (0,
|
|
37928
|
+
return (0, import_auth24.verifyCredentialResumeToken)(token, secret);
|
|
37790
37929
|
}
|
|
37791
37930
|
/** Resolve the single credential field to render from `login.fields` (first field), or a default. */
|
|
37792
37931
|
resolveConnectField(login, key) {
|
|
@@ -37802,8 +37941,8 @@ var init_oauth_connect_flow = __esm({
|
|
|
37802
37941
|
renderConnectPage(payload, login, token, error) {
|
|
37803
37942
|
const { name: name42, def } = this.resolveConnectField(login, payload.key);
|
|
37804
37943
|
const action = `${this.scope.fullPath}/oauth/connect`;
|
|
37805
|
-
const [field] = (0,
|
|
37806
|
-
return (0,
|
|
37944
|
+
const [field] = (0, import_auth24.toLoginExtraFields)({ [name42]: def });
|
|
37945
|
+
return (0, import_auth24.buildConnectPage)({
|
|
37807
37946
|
token,
|
|
37808
37947
|
field,
|
|
37809
37948
|
action,
|
|
@@ -37902,8 +38041,8 @@ var init_oauth_connect_flow = __esm({
|
|
|
37902
38041
|
return `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Connect Error</title></head>
|
|
37903
38042
|
<body style="font-family:-apple-system,sans-serif;padding:40px;text-align:center">
|
|
37904
38043
|
<h1 style="color:#e53e3e">Connect Error</h1>
|
|
37905
|
-
<p><code>${(0,
|
|
37906
|
-
<p>${(0,
|
|
38044
|
+
<p><code>${(0, import_auth24.escapeHtml)(error)}</code></p>
|
|
38045
|
+
<p>${(0, import_auth24.escapeHtml)(description)}</p>
|
|
37907
38046
|
</body></html>`;
|
|
37908
38047
|
}
|
|
37909
38048
|
};
|
|
@@ -37930,11 +38069,11 @@ var init_oauth_connect_flow = __esm({
|
|
|
37930
38069
|
});
|
|
37931
38070
|
|
|
37932
38071
|
// libs/sdk/src/auth/flows/oauth.provider-callback.flow.ts
|
|
37933
|
-
var
|
|
38072
|
+
var import_auth25, import_lazy_zod67, import_utils68, inputSchema19, stateSchema18, outputSchema18, plan18, name18, Stage18, OauthProviderCallbackFlow;
|
|
37934
38073
|
var init_oauth_provider_callback_flow = __esm({
|
|
37935
38074
|
"libs/sdk/src/auth/flows/oauth.provider-callback.flow.ts"() {
|
|
37936
38075
|
"use strict";
|
|
37937
|
-
|
|
38076
|
+
import_auth25 = require("@frontmcp/auth");
|
|
37938
38077
|
import_lazy_zod67 = require("@frontmcp/lazy-zod");
|
|
37939
38078
|
import_utils68 = require("@frontmcp/utils");
|
|
37940
38079
|
init_common();
|
|
@@ -38037,7 +38176,7 @@ var init_oauth_provider_callback_flow = __esm({
|
|
|
38037
38176
|
);
|
|
38038
38177
|
return;
|
|
38039
38178
|
}
|
|
38040
|
-
if (!(0,
|
|
38179
|
+
if (!(0, import_auth25.isSessionComplete)(session)) {
|
|
38041
38180
|
this.respond(
|
|
38042
38181
|
httpRespond.html(this.renderErrorPage("invalid_request", "Authentication is still in progress."), 400)
|
|
38043
38182
|
);
|
|
@@ -38236,7 +38375,7 @@ var init_oauth_provider_callback_flow = __esm({
|
|
|
38236
38375
|
this.logger.info(`Stored tokens for provider ${providerId} (temp auth: ${tempAuthId})`);
|
|
38237
38376
|
}
|
|
38238
38377
|
if (tokens) {
|
|
38239
|
-
(0,
|
|
38378
|
+
(0, import_auth25.completeCurrentProvider)(session, tokens, userInfo);
|
|
38240
38379
|
if (userInfo) {
|
|
38241
38380
|
if (!session.userInfo.sub && userInfo.sub) {
|
|
38242
38381
|
session.userInfo.sub = userInfo.sub;
|
|
@@ -38269,11 +38408,11 @@ var init_oauth_provider_callback_flow = __esm({
|
|
|
38269
38408
|
return;
|
|
38270
38409
|
}
|
|
38271
38410
|
const localAuth = this.getLocalAuth();
|
|
38272
|
-
if ((0,
|
|
38411
|
+
if ((0, import_auth25.isSessionComplete)(session)) {
|
|
38273
38412
|
this.logger.info("All providers authenticated, completing federated auth flow");
|
|
38274
38413
|
return this.completeFederatedAuth(session);
|
|
38275
38414
|
}
|
|
38276
|
-
const nextProviderId = (0,
|
|
38415
|
+
const nextProviderId = (0, import_auth25.getNextProvider)(session);
|
|
38277
38416
|
if (!nextProviderId) {
|
|
38278
38417
|
return this.completeFederatedAuth(session);
|
|
38279
38418
|
}
|
|
@@ -38285,7 +38424,7 @@ var init_oauth_provider_callback_flow = __esm({
|
|
|
38285
38424
|
method: "S256"
|
|
38286
38425
|
};
|
|
38287
38426
|
const providerState = `federated:${session.id}:${(0, import_utils68.randomUUID)()}`;
|
|
38288
|
-
(0,
|
|
38427
|
+
(0, import_auth25.startNextProvider)(session, pkce, providerState);
|
|
38289
38428
|
const sessionStore = localAuth.federatedSessionStore;
|
|
38290
38429
|
if (sessionStore) {
|
|
38291
38430
|
await sessionStore.update(session);
|
|
@@ -38448,7 +38587,7 @@ var init_oauth_provider_callback_flow = __esm({
|
|
|
38448
38587
|
renderConsentScreen(session, consentConfig, error, preSelectedTools) {
|
|
38449
38588
|
const callbackPath = `${this.scope.fullPath}/oauth/provider/_consent/callback`;
|
|
38450
38589
|
const { toolCards } = projectConsentTools(this.scope, consentConfig.excludedTools);
|
|
38451
|
-
return (0,
|
|
38590
|
+
return (0, import_auth25.buildToolConsentPage)({
|
|
38452
38591
|
tools: toolCards,
|
|
38453
38592
|
clientName: session.clientId,
|
|
38454
38593
|
// The pending_auth_id input is unused on the federated round-trip (we key
|
|
@@ -38485,8 +38624,8 @@ var init_oauth_provider_callback_flow = __esm({
|
|
|
38485
38624
|
* Render an error page
|
|
38486
38625
|
*/
|
|
38487
38626
|
renderErrorPage(error, description) {
|
|
38488
|
-
const safeError = (0,
|
|
38489
|
-
const safeDescription = (0,
|
|
38627
|
+
const safeError = (0, import_auth25.escapeHtml)(error);
|
|
38628
|
+
const safeDescription = (0, import_auth25.escapeHtml)(description);
|
|
38490
38629
|
return `<!DOCTYPE html>
|
|
38491
38630
|
<html lang="en">
|
|
38492
38631
|
<head>
|
|
@@ -39229,12 +39368,12 @@ var init_oauth_token_flow = __esm({
|
|
|
39229
39368
|
});
|
|
39230
39369
|
|
|
39231
39370
|
// libs/sdk/src/auth/flows/oauth.userinfo.flow.ts
|
|
39232
|
-
var import_reflect_metadata16,
|
|
39371
|
+
var import_reflect_metadata16, import_auth26, import_lazy_zod70, inputSchema22, stateSchema21, outputSchema21, plan21, name21, Stage21, OauthUserInfoFlow;
|
|
39233
39372
|
var init_oauth_userinfo_flow = __esm({
|
|
39234
39373
|
"libs/sdk/src/auth/flows/oauth.userinfo.flow.ts"() {
|
|
39235
39374
|
"use strict";
|
|
39236
39375
|
import_reflect_metadata16 = require("reflect-metadata");
|
|
39237
|
-
|
|
39376
|
+
import_auth26 = require("@frontmcp/auth");
|
|
39238
39377
|
import_lazy_zod70 = require("@frontmcp/lazy-zod");
|
|
39239
39378
|
init_common();
|
|
39240
39379
|
inputSchema22 = httpInputSchema;
|
|
@@ -39254,7 +39393,7 @@ var init_oauth_userinfo_flow = __esm({
|
|
|
39254
39393
|
async parseInput() {
|
|
39255
39394
|
const { request } = this.rawInput;
|
|
39256
39395
|
const authorizationHeader = request.headers?.["authorization"] ?? void 0;
|
|
39257
|
-
const token = (0,
|
|
39396
|
+
const token = (0, import_auth26.extractBearerToken)(authorizationHeader);
|
|
39258
39397
|
const baseUrl = getRequestBaseUrl(request, this.scope.entryPath);
|
|
39259
39398
|
this.state.set({ token, baseUrl });
|
|
39260
39399
|
}
|
|
@@ -39325,13 +39464,13 @@ var init_oauth_userinfo_flow = __esm({
|
|
|
39325
39464
|
});
|
|
39326
39465
|
|
|
39327
39466
|
// libs/sdk/src/auth/flows/session.verify.flow.ts
|
|
39328
|
-
var import_reflect_metadata17,
|
|
39467
|
+
var import_reflect_metadata17, import_auth27, import_lazy_zod71, import_utils71, inputSchema23, stateSchema22, UnauthorizedSchema, AuthorizedSchema, ForbiddenSchema, sessionVerifyOutputSchema, plan22, name22, Stage22, warnedUnpinnedAudience, SessionVerifyFlow;
|
|
39329
39468
|
var init_session_verify_flow = __esm({
|
|
39330
39469
|
"libs/sdk/src/auth/flows/session.verify.flow.ts"() {
|
|
39331
39470
|
"use strict";
|
|
39332
39471
|
init_common();
|
|
39333
39472
|
import_reflect_metadata17 = require("reflect-metadata");
|
|
39334
|
-
|
|
39473
|
+
import_auth27 = require("@frontmcp/auth");
|
|
39335
39474
|
import_lazy_zod71 = require("@frontmcp/lazy-zod");
|
|
39336
39475
|
import_utils71 = require("@frontmcp/utils");
|
|
39337
39476
|
init_notification_service();
|
|
@@ -39426,7 +39565,7 @@ var init_session_verify_flow = __esm({
|
|
|
39426
39565
|
authMode,
|
|
39427
39566
|
...platformType !== "unknown" && { platformType }
|
|
39428
39567
|
};
|
|
39429
|
-
const sessionId = (0,
|
|
39568
|
+
const sessionId = (0, import_auth27.encryptJson)(payload);
|
|
39430
39569
|
this.respond({
|
|
39431
39570
|
kind: "authorized",
|
|
39432
39571
|
authorization: {
|
|
@@ -39447,11 +39586,11 @@ var init_session_verify_flow = __esm({
|
|
|
39447
39586
|
const sessionIdQuery = request.query["sessionId"];
|
|
39448
39587
|
const sessionIdHeader = sessionIdRawHeader ?? sessionIdQuery ?? void 0;
|
|
39449
39588
|
const sessionProtocol = httpTransportHeader ? "http" : sessionIdRawHeader ? "streamable-http" : sessionIdQuery ? "sse" : void 0;
|
|
39450
|
-
const token = (0,
|
|
39589
|
+
const token = (0, import_auth27.extractBearerToken)(authorizationHeader);
|
|
39451
39590
|
const userAgent = request.headers?.["user-agent"] ?? void 0;
|
|
39452
39591
|
const prmMetadataPath = `/.well-known/oauth-protected-resource${entryPath}${routeBase}`;
|
|
39453
39592
|
const prmUrl = `${getRequestBaseUrl(request)}${prmMetadataPath}`;
|
|
39454
|
-
const prmMetadataHeader = (0,
|
|
39593
|
+
const prmMetadataHeader = (0, import_auth27.buildUnauthorizedHeader)(prmUrl);
|
|
39455
39594
|
this.logger.verbose("parseInput", {
|
|
39456
39595
|
hasAuthHeader: !!authorizationHeader,
|
|
39457
39596
|
hasToken: !!token,
|
|
@@ -39505,7 +39644,7 @@ var init_session_verify_flow = __esm({
|
|
|
39505
39644
|
});
|
|
39506
39645
|
}
|
|
39507
39646
|
async verifyIfJwt() {
|
|
39508
|
-
const jwks = this.get(
|
|
39647
|
+
const jwks = this.get(import_auth27.JwksService);
|
|
39509
39648
|
const token = this.state.token;
|
|
39510
39649
|
if (!token) {
|
|
39511
39650
|
this.logger.warn("verifyIfJwt: missing or empty bearer token, returning 401");
|
|
@@ -39515,11 +39654,11 @@ var init_session_verify_flow = __esm({
|
|
|
39515
39654
|
});
|
|
39516
39655
|
return;
|
|
39517
39656
|
}
|
|
39518
|
-
if (!(0,
|
|
39657
|
+
if (!(0, import_auth27.isJwt)(token)) {
|
|
39519
39658
|
this.logger.warn("verifyIfJwt: token is not a JWT, returning 401");
|
|
39520
39659
|
this.respond({
|
|
39521
39660
|
kind: "unauthorized",
|
|
39522
|
-
prmMetadataHeader: (0,
|
|
39661
|
+
prmMetadataHeader: (0, import_auth27.buildInvalidTokenHeader)(this.state.required.prmUrl, "Token is not a valid JWT")
|
|
39523
39662
|
});
|
|
39524
39663
|
return;
|
|
39525
39664
|
}
|
|
@@ -39564,8 +39703,8 @@ var init_session_verify_flow = __esm({
|
|
|
39564
39703
|
"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."
|
|
39565
39704
|
);
|
|
39566
39705
|
}
|
|
39567
|
-
const expectedAudiences = configuredAudience ? Array.isArray(configuredAudience) ? configuredAudience : [configuredAudience] : (0,
|
|
39568
|
-
const audResult = (0,
|
|
39706
|
+
const expectedAudiences = configuredAudience ? Array.isArray(configuredAudience) ? configuredAudience : [configuredAudience] : (0, import_auth27.deriveExpectedAudience)(this.state.required.baseUrl);
|
|
39707
|
+
const audResult = (0, import_auth27.validateAudience)(result.payload?.["aud"], {
|
|
39569
39708
|
expectedAudiences,
|
|
39570
39709
|
// By default accept tokens with no `aud` claim: many IdPs omit it, and
|
|
39571
39710
|
// rejecting them here would break existing transparent deployments.
|
|
@@ -39579,7 +39718,7 @@ var init_session_verify_flow = __esm({
|
|
|
39579
39718
|
this.logger.warn("verifyIfJwt: audience validation failed", { error: audResult.error });
|
|
39580
39719
|
this.respond({
|
|
39581
39720
|
kind: "unauthorized",
|
|
39582
|
-
prmMetadataHeader: (0,
|
|
39721
|
+
prmMetadataHeader: (0, import_auth27.buildInvalidTokenHeader)(
|
|
39583
39722
|
this.state.required.prmUrl,
|
|
39584
39723
|
audResult.error ?? "Token audience is not valid for this resource"
|
|
39585
39724
|
)
|
|
@@ -39599,7 +39738,7 @@ var init_session_verify_flow = __esm({
|
|
|
39599
39738
|
});
|
|
39600
39739
|
this.respond({
|
|
39601
39740
|
kind: "forbidden",
|
|
39602
|
-
prmMetadataHeader: (0,
|
|
39741
|
+
prmMetadataHeader: (0, import_auth27.buildInsufficientScopeHeader)(this.state.required.prmUrl, requiredScopes)
|
|
39603
39742
|
});
|
|
39604
39743
|
return;
|
|
39605
39744
|
}
|
|
@@ -39609,14 +39748,14 @@ var init_session_verify_flow = __esm({
|
|
|
39609
39748
|
this.logger.warn("verifyIfJwt: JWT verification failed", { error: result.error });
|
|
39610
39749
|
this.respond({
|
|
39611
39750
|
kind: "unauthorized",
|
|
39612
|
-
prmMetadataHeader: (0,
|
|
39751
|
+
prmMetadataHeader: (0, import_auth27.buildInvalidTokenHeader)(
|
|
39613
39752
|
this.state.required.prmUrl,
|
|
39614
39753
|
result.error ?? "Token verification failed"
|
|
39615
39754
|
)
|
|
39616
39755
|
});
|
|
39617
39756
|
}
|
|
39618
39757
|
async deriveUser() {
|
|
39619
|
-
this.state.set("user", (0,
|
|
39758
|
+
this.state.set("user", (0, import_auth27.deriveTypedUser)(this.state.required.jwtPayload ?? {}));
|
|
39620
39759
|
}
|
|
39621
39760
|
async parseSessionHeader() {
|
|
39622
39761
|
const {
|
|
@@ -39702,13 +39841,13 @@ var init_session_verify_flow = __esm({
|
|
|
39702
39841
|
});
|
|
39703
39842
|
|
|
39704
39843
|
// libs/sdk/src/auth/flows/well-known.jwks.flow.ts
|
|
39705
|
-
var import_reflect_metadata18,
|
|
39844
|
+
var import_reflect_metadata18, import_auth28, import_lazy_zod72, inputSchema24, stateSchema23, outputSchema22, plan23, name23, Stage23, WellKnownJwksFlow;
|
|
39706
39845
|
var init_well_known_jwks_flow = __esm({
|
|
39707
39846
|
"libs/sdk/src/auth/flows/well-known.jwks.flow.ts"() {
|
|
39708
39847
|
"use strict";
|
|
39709
39848
|
init_common();
|
|
39710
39849
|
import_reflect_metadata18 = require("reflect-metadata");
|
|
39711
|
-
|
|
39850
|
+
import_auth28 = require("@frontmcp/auth");
|
|
39712
39851
|
import_lazy_zod72 = require("@frontmcp/lazy-zod");
|
|
39713
39852
|
init_auth_internal_errors();
|
|
39714
39853
|
inputSchema24 = httpInputSchema;
|
|
@@ -39734,7 +39873,7 @@ var init_well_known_jwks_flow = __esm({
|
|
|
39734
39873
|
}
|
|
39735
39874
|
async collectData() {
|
|
39736
39875
|
const { isOrchestrated } = this.state.required;
|
|
39737
|
-
const jwksSvc = this.get(
|
|
39876
|
+
const jwksSvc = this.get(import_auth28.JwksService);
|
|
39738
39877
|
if (isOrchestrated) {
|
|
39739
39878
|
const keysDoc = await jwksSvc.getPublicJwks();
|
|
39740
39879
|
if (!keysDoc?.keys || !Array.isArray(keysDoc.keys)) {
|
|
@@ -40064,9 +40203,9 @@ function createSecureStoreProviders(opts) {
|
|
|
40064
40203
|
name: "SecureStoreBackend"
|
|
40065
40204
|
};
|
|
40066
40205
|
const accessorProvider = {
|
|
40067
|
-
provide:
|
|
40206
|
+
provide: import_auth29.SECURE_STORE_ACCESSOR,
|
|
40068
40207
|
inject: () => [FRONTMCP_CONTEXT, FrontMcpLogger],
|
|
40069
|
-
useFactory: (ctx, logger) => new
|
|
40208
|
+
useFactory: (ctx, logger) => new import_auth29.SecureStoreAccessorImpl({
|
|
40070
40209
|
backend,
|
|
40071
40210
|
scope,
|
|
40072
40211
|
resolveSub: () => resolveRequestSub(ctx),
|
|
@@ -40079,11 +40218,11 @@ function createSecureStoreProviders(opts) {
|
|
|
40079
40218
|
};
|
|
40080
40219
|
return [backendProvider, accessorProvider];
|
|
40081
40220
|
}
|
|
40082
|
-
var
|
|
40221
|
+
var import_auth29, import_di32, SECURE_STORE_BACKEND;
|
|
40083
40222
|
var init_secure_store_providers = __esm({
|
|
40084
40223
|
"libs/sdk/src/auth/secure-store/secure-store.providers.ts"() {
|
|
40085
40224
|
"use strict";
|
|
40086
|
-
|
|
40225
|
+
import_auth29 = require("@frontmcp/auth");
|
|
40087
40226
|
import_di32 = require("@frontmcp/di");
|
|
40088
40227
|
init_common();
|
|
40089
40228
|
init_frontmcp_context_provider();
|
|
@@ -40093,14 +40232,14 @@ var init_secure_store_providers = __esm({
|
|
|
40093
40232
|
});
|
|
40094
40233
|
|
|
40095
40234
|
// libs/sdk/src/auth/secure-store/secure-store.context-extension.ts
|
|
40096
|
-
var
|
|
40235
|
+
var import_auth30, secureStoreContextExtension;
|
|
40097
40236
|
var init_secure_store_context_extension = __esm({
|
|
40098
40237
|
"libs/sdk/src/auth/secure-store/secure-store.context-extension.ts"() {
|
|
40099
40238
|
"use strict";
|
|
40100
|
-
|
|
40239
|
+
import_auth30 = require("@frontmcp/auth");
|
|
40101
40240
|
secureStoreContextExtension = {
|
|
40102
40241
|
property: "secureStore",
|
|
40103
|
-
token:
|
|
40242
|
+
token: import_auth30.SECURE_STORE_ACCESSOR,
|
|
40104
40243
|
errorMessage: "The secure store is not available. Ensure the server uses auth mode 'local' (or 'remote'); the secure store is enabled automatically there."
|
|
40105
40244
|
};
|
|
40106
40245
|
}
|
|
@@ -40135,12 +40274,15 @@ function validateAuthorizationIssuer(received, expected) {
|
|
|
40135
40274
|
}
|
|
40136
40275
|
return { ok: true };
|
|
40137
40276
|
}
|
|
40138
|
-
|
|
40277
|
+
function isUnsupportedOperation(error) {
|
|
40278
|
+
return error instanceof import_utils73.StorageNotSupportedError;
|
|
40279
|
+
}
|
|
40280
|
+
var import_jose, import_auth31, import_utils73, defaultNoAuthSecret, RESERVED_JWT_CLAIMS, LocalPrimaryAuth;
|
|
40139
40281
|
var init_instance_local_primary_auth = __esm({
|
|
40140
40282
|
"libs/sdk/src/auth/instances/instance.local-primary-auth.ts"() {
|
|
40141
40283
|
"use strict";
|
|
40142
40284
|
import_jose = require("jose");
|
|
40143
|
-
|
|
40285
|
+
import_auth31 = require("@frontmcp/auth");
|
|
40144
40286
|
import_utils73 = require("@frontmcp/utils");
|
|
40145
40287
|
init_common();
|
|
40146
40288
|
init_auth2();
|
|
@@ -40196,17 +40338,17 @@ var init_instance_local_primary_auth = __esm({
|
|
|
40196
40338
|
this.secret = getDefaultNoAuthSecret();
|
|
40197
40339
|
}
|
|
40198
40340
|
this.tokenStorage = this.readTokenStorage(options);
|
|
40199
|
-
this.authorizationStoreImpl = new
|
|
40200
|
-
this.federatedSessionStoreImpl = new
|
|
40201
|
-
this.orchestratedTokenStoreImpl = new
|
|
40341
|
+
this.authorizationStoreImpl = new import_auth31.InMemoryAuthorizationStore();
|
|
40342
|
+
this.federatedSessionStoreImpl = new import_auth31.InMemoryFederatedAuthSessionStore();
|
|
40343
|
+
this.orchestratedTokenStoreImpl = new import_auth31.InMemoryOrchestratedTokenStore({
|
|
40202
40344
|
encryptionKey: this.secret
|
|
40203
40345
|
// Reuse JWT secret for token encryption
|
|
40204
40346
|
});
|
|
40205
|
-
this.consentStoreImpl = new
|
|
40206
|
-
this.dcrClientRegistryImpl = new
|
|
40347
|
+
this.consentStoreImpl = new import_auth31.InMemoryConsentStore();
|
|
40348
|
+
this.dcrClientRegistryImpl = new import_auth31.DcrClientRegistry(this.getDcrConfig() ?? {});
|
|
40207
40349
|
if ((0, import_auth10.isOrchestratedMode)(options)) {
|
|
40208
40350
|
const cimdConfig = options.cimd;
|
|
40209
|
-
this.cimdService = new
|
|
40351
|
+
this.cimdService = new import_auth19.CimdService(this.logger, cimdConfig);
|
|
40210
40352
|
}
|
|
40211
40353
|
this.ready = this.initialize();
|
|
40212
40354
|
}
|
|
@@ -40218,7 +40360,7 @@ var init_instance_local_primary_auth = __esm({
|
|
|
40218
40360
|
keys = [];
|
|
40219
40361
|
secret;
|
|
40220
40362
|
logger;
|
|
40221
|
-
jwks = new
|
|
40363
|
+
jwks = new import_auth31.JwksService();
|
|
40222
40364
|
cimdService;
|
|
40223
40365
|
/**
|
|
40224
40366
|
* Token storage backend selected from `options.tokenStorage`.
|
|
@@ -40249,6 +40391,17 @@ var init_instance_local_primary_auth = __esm({
|
|
|
40249
40391
|
dcrClientRegistryImpl;
|
|
40250
40392
|
/** Storage adapter backing the persistent stores (kept for disposal). */
|
|
40251
40393
|
storageAdapter;
|
|
40394
|
+
/**
|
|
40395
|
+
* Replay guard for incremental-authorization tickets (GHSA-2c4g-9c8x-6m8g).
|
|
40396
|
+
* Shares the persistent adapter when one is configured so the guard holds
|
|
40397
|
+
* across a distributed deployment; falls back to a dedicated in-memory
|
|
40398
|
+
* adapter, matching how the credential vault selects its backing store.
|
|
40399
|
+
*
|
|
40400
|
+
* Holds the in-flight PROMISE, not the resolved adapter: two concurrent
|
|
40401
|
+
* claims for the same ticket must not each build their own store, or each
|
|
40402
|
+
* would write and read back its own nonce and both would win.
|
|
40403
|
+
*/
|
|
40404
|
+
ticketReplayStorage;
|
|
40252
40405
|
/**
|
|
40253
40406
|
* Per-session encrypted credential vault (Checkpoint 3b). Backs
|
|
40254
40407
|
* `this.credentials` in tools and persists `authenticate()` credentials.
|
|
@@ -40386,19 +40539,19 @@ var init_instance_local_primary_auth = __esm({
|
|
|
40386
40539
|
* key, so upstream provider tokens stay encrypted at rest in every backend.
|
|
40387
40540
|
*/
|
|
40388
40541
|
async initializeStores() {
|
|
40389
|
-
if (!(0,
|
|
40542
|
+
if (!(0, import_auth31.isPersistentTokenStorage)(this.tokenStorage)) {
|
|
40390
40543
|
return;
|
|
40391
40544
|
}
|
|
40392
40545
|
try {
|
|
40393
|
-
const adapter = await (0,
|
|
40546
|
+
const adapter = await (0, import_auth31.createTokenStorageAdapter)(this.tokenStorage);
|
|
40394
40547
|
this.storageAdapter = adapter;
|
|
40395
|
-
this.authorizationStoreImpl = new
|
|
40396
|
-
this.federatedSessionStoreImpl = new
|
|
40397
|
-
this.orchestratedTokenStoreImpl = new
|
|
40548
|
+
this.authorizationStoreImpl = new import_auth31.StorageAuthorizationStore(adapter);
|
|
40549
|
+
this.federatedSessionStoreImpl = new import_auth31.StorageFederatedAuthSessionStore(adapter);
|
|
40550
|
+
this.orchestratedTokenStoreImpl = new import_auth31.StorageOrchestratedTokenStore(adapter, {
|
|
40398
40551
|
encryptionKey: this.secret
|
|
40399
40552
|
});
|
|
40400
|
-
this.consentStoreImpl = new
|
|
40401
|
-
const backend = (0,
|
|
40553
|
+
this.consentStoreImpl = new import_auth31.StorageConsentStore(adapter);
|
|
40554
|
+
const backend = (0, import_auth31.isSqliteTokenStorage)(this.tokenStorage) ? "sqlite" : (0, import_auth31.isRedisTokenStorage)(this.tokenStorage) ? "redis" : "unknown";
|
|
40402
40555
|
this.logger.info(`Token storage initialized with persistent backend: ${backend}`);
|
|
40403
40556
|
} catch (err) {
|
|
40404
40557
|
this.logger.error("Failed to initialize persistent token storage", err);
|
|
@@ -40416,6 +40569,81 @@ var init_instance_local_primary_auth = __esm({
|
|
|
40416
40569
|
* (`this.secret`), so resume URLs are framework-signed with the same trust
|
|
40417
40570
|
* root as the access tokens.
|
|
40418
40571
|
*/
|
|
40572
|
+
/**
|
|
40573
|
+
* The server HMAC signing key, as the string form the `signData`/`verifyData`
|
|
40574
|
+
* helpers take. These are the exact bytes the access tokens are signed with,
|
|
40575
|
+
* so framework-signed URLs share the access tokens' trust root.
|
|
40576
|
+
*/
|
|
40577
|
+
get signingSecret() {
|
|
40578
|
+
return new TextDecoder().decode(this.secret);
|
|
40579
|
+
}
|
|
40580
|
+
/**
|
|
40581
|
+
* Claim an incremental-authorization ticket, exactly once.
|
|
40582
|
+
*
|
|
40583
|
+
* A signature check proves a ticket was minted by this server for a verified
|
|
40584
|
+
* subject; it cannot prove the ticket has not already been used. The `auth_url`
|
|
40585
|
+
* carrying the ticket travels through agent transcripts and server logs, so a
|
|
40586
|
+
* leaked ticket must not stay usable for the rest of its TTL.
|
|
40587
|
+
*
|
|
40588
|
+
* The claim is race-free without a transaction: each attempt writes its OWN
|
|
40589
|
+
* nonce under `ifNotExists` and reads the key back. Only the attempt whose
|
|
40590
|
+
* nonce survives won, so concurrent replays all lose.
|
|
40591
|
+
*
|
|
40592
|
+
* @returns true when this caller claimed the ticket, false when it was already used.
|
|
40593
|
+
*/
|
|
40594
|
+
async claimIncrementalTicket(jti, ttlMs) {
|
|
40595
|
+
const key = `incremental-ticket:${jti}`;
|
|
40596
|
+
const nonce = (0, import_utils73.randomUUID)();
|
|
40597
|
+
const ttlSeconds = Math.max(1, Math.ceil(ttlMs / 1e3));
|
|
40598
|
+
let storage = await this.getTicketReplayStorage();
|
|
40599
|
+
try {
|
|
40600
|
+
await storage.set(key, nonce, { ifNotExists: true, ttlSeconds });
|
|
40601
|
+
} catch (error) {
|
|
40602
|
+
if (!isUnsupportedOperation(error)) {
|
|
40603
|
+
this.logger.warn(`Incremental ticket replay guard unavailable: ${String(error)}`);
|
|
40604
|
+
return false;
|
|
40605
|
+
}
|
|
40606
|
+
this.logger.warn(
|
|
40607
|
+
"Incremental ticket replay guard: the configured storage has no conditional write; falling back to an in-memory guard (single use holds within this instance only)."
|
|
40608
|
+
);
|
|
40609
|
+
storage = await this.useInMemoryTicketReplayStorage();
|
|
40610
|
+
try {
|
|
40611
|
+
await storage.set(key, nonce, { ifNotExists: true, ttlSeconds });
|
|
40612
|
+
} catch (fallbackError) {
|
|
40613
|
+
this.logger.warn(`Incremental ticket replay guard unavailable: ${String(fallbackError)}`);
|
|
40614
|
+
return false;
|
|
40615
|
+
}
|
|
40616
|
+
}
|
|
40617
|
+
try {
|
|
40618
|
+
return await storage.get(key) === nonce;
|
|
40619
|
+
} catch (error) {
|
|
40620
|
+
this.logger.warn(`Incremental ticket replay guard unavailable: ${String(error)}`);
|
|
40621
|
+
return false;
|
|
40622
|
+
}
|
|
40623
|
+
}
|
|
40624
|
+
getTicketReplayStorage() {
|
|
40625
|
+
if (!this.ticketReplayStorage) {
|
|
40626
|
+
if (this.storageAdapter) {
|
|
40627
|
+
this.ticketReplayStorage = Promise.resolve(this.storageAdapter);
|
|
40628
|
+
} else {
|
|
40629
|
+
this.logger.debug(
|
|
40630
|
+
"Incremental ticket replay guard is in-memory (no persistent tokenStorage configured); single use holds within this instance only."
|
|
40631
|
+
);
|
|
40632
|
+
this.ticketReplayStorage = this.useInMemoryTicketReplayStorage();
|
|
40633
|
+
}
|
|
40634
|
+
}
|
|
40635
|
+
return this.ticketReplayStorage;
|
|
40636
|
+
}
|
|
40637
|
+
/** Replace the replay guard with a fresh in-memory adapter, memoized. */
|
|
40638
|
+
useInMemoryTicketReplayStorage() {
|
|
40639
|
+
const pending = (async () => {
|
|
40640
|
+
const memory = new import_utils73.MemoryStorageAdapter();
|
|
40641
|
+
await memory.connect();
|
|
40642
|
+
return memory;
|
|
40643
|
+
})();
|
|
40644
|
+
this.ticketReplayStorage = pending;
|
|
40645
|
+
return pending;
|
|
40646
|
+
}
|
|
40419
40647
|
async initializeCredentialVault() {
|
|
40420
40648
|
if ((0, import_auth10.isPublicMode)(this.options)) {
|
|
40421
40649
|
return;
|
|
@@ -40429,12 +40657,12 @@ var init_instance_local_primary_auth = __esm({
|
|
|
40429
40657
|
storage = memory;
|
|
40430
40658
|
}
|
|
40431
40659
|
const pepper = (0, import_utils73.getEnv)("VAULT_SECRET") ?? (0, import_utils73.getEnv)("JWT_SECRET") ?? void 0;
|
|
40432
|
-
this.credentialVaultImpl = new
|
|
40660
|
+
this.credentialVaultImpl = new import_auth31.SessionCredentialVault({
|
|
40433
40661
|
storage,
|
|
40434
40662
|
pepper,
|
|
40435
40663
|
logger: this.logger.child("SessionCredentialVault")
|
|
40436
40664
|
});
|
|
40437
|
-
const signingSecret =
|
|
40665
|
+
const signingSecret = this.signingSecret;
|
|
40438
40666
|
const basePath = this.issuer;
|
|
40439
40667
|
await this.providers.addDynamicProviders(
|
|
40440
40668
|
createCredentialsProviders({
|
|
@@ -40480,7 +40708,7 @@ var init_instance_local_primary_auth = __esm({
|
|
|
40480
40708
|
const config = this.readSecureStoreConfig();
|
|
40481
40709
|
const reuseAdapter = this.storageAdapter && this.secureStoreSharesTokenStorage(config) ? this.storageAdapter : void 0;
|
|
40482
40710
|
const pepper = (0, import_utils73.getEnv)("VAULT_SECRET") ?? (0, import_utils73.getEnv)("JWT_SECRET") ?? new TextDecoder().decode(this.secret);
|
|
40483
|
-
const resolved = await (0,
|
|
40711
|
+
const resolved = await (0, import_auth31.createSecureStore)({
|
|
40484
40712
|
config,
|
|
40485
40713
|
pepper,
|
|
40486
40714
|
storage: reuseAdapter,
|
|
@@ -40507,8 +40735,8 @@ var init_instance_local_primary_auth = __esm({
|
|
|
40507
40735
|
*/
|
|
40508
40736
|
secureStoreSharesTokenStorage(config) {
|
|
40509
40737
|
if (typeof config !== "object" || config === null) return false;
|
|
40510
|
-
if ("sqlite" in config && config.sqlite) return (0,
|
|
40511
|
-
if ("redis" in config && config.redis) return (0,
|
|
40738
|
+
if ("sqlite" in config && config.sqlite) return (0, import_auth31.isSqliteTokenStorage)(this.tokenStorage);
|
|
40739
|
+
if ("redis" in config && config.redis) return (0, import_auth31.isRedisTokenStorage)(this.tokenStorage);
|
|
40512
40740
|
return false;
|
|
40513
40741
|
}
|
|
40514
40742
|
async signAnonymousJwt() {
|
|
@@ -40636,7 +40864,7 @@ var init_instance_local_primary_auth = __esm({
|
|
|
40636
40864
|
error_description: "Redirect URI does not match"
|
|
40637
40865
|
};
|
|
40638
40866
|
}
|
|
40639
|
-
if (!(0,
|
|
40867
|
+
if (!(0, import_auth31.verifyPkce)(codeVerifier, codeRecord.pkce)) {
|
|
40640
40868
|
this.logger.warn(`PKCE verification failed`);
|
|
40641
40869
|
return {
|
|
40642
40870
|
error: "invalid_grant",
|
|
@@ -40807,7 +41035,7 @@ var init_instance_local_primary_auth = __esm({
|
|
|
40807
41035
|
scope: import_di4.ProviderScope.GLOBAL,
|
|
40808
41036
|
name: "auth:jwk-service"
|
|
40809
41037
|
},
|
|
40810
|
-
provide:
|
|
41038
|
+
provide: import_auth31.JwksService
|
|
40811
41039
|
});
|
|
40812
41040
|
if (this.cimdService) {
|
|
40813
41041
|
this.providers.injectProvider({
|
|
@@ -40816,7 +41044,7 @@ var init_instance_local_primary_auth = __esm({
|
|
|
40816
41044
|
scope: import_di4.ProviderScope.GLOBAL,
|
|
40817
41045
|
name: "auth:cimd-service"
|
|
40818
41046
|
},
|
|
40819
|
-
provide:
|
|
41047
|
+
provide: import_auth19.CimdService
|
|
40820
41048
|
});
|
|
40821
41049
|
this.logger.debug("CIMD service registered");
|
|
40822
41050
|
}
|
|
@@ -41182,11 +41410,11 @@ var init_instance_local_primary_auth = __esm({
|
|
|
41182
41410
|
});
|
|
41183
41411
|
|
|
41184
41412
|
// libs/sdk/src/auth/instances/instance.remote-primary-auth.ts
|
|
41185
|
-
var
|
|
41413
|
+
var import_auth33, RemotePrimaryAuth;
|
|
41186
41414
|
var init_instance_remote_primary_auth = __esm({
|
|
41187
41415
|
"libs/sdk/src/auth/instances/instance.remote-primary-auth.ts"() {
|
|
41188
41416
|
"use strict";
|
|
41189
|
-
|
|
41417
|
+
import_auth33 = require("@frontmcp/auth");
|
|
41190
41418
|
init_common();
|
|
41191
41419
|
init_session_verify_flow();
|
|
41192
41420
|
init_well_known_jwks_flow();
|
|
@@ -41202,7 +41430,7 @@ var init_instance_remote_primary_auth = __esm({
|
|
|
41202
41430
|
scope;
|
|
41203
41431
|
providers;
|
|
41204
41432
|
ready;
|
|
41205
|
-
jwks = new
|
|
41433
|
+
jwks = new import_auth33.JwksService();
|
|
41206
41434
|
fetch(input, init) {
|
|
41207
41435
|
return fetch(input, init);
|
|
41208
41436
|
}
|
|
@@ -41220,7 +41448,7 @@ var init_instance_remote_primary_auth = __esm({
|
|
|
41220
41448
|
scope: import_di4.ProviderScope.GLOBAL,
|
|
41221
41449
|
name: "auth:jwk-service"
|
|
41222
41450
|
},
|
|
41223
|
-
provide:
|
|
41451
|
+
provide: import_auth33.JwksService
|
|
41224
41452
|
});
|
|
41225
41453
|
await this.registerAuthFlows(scope);
|
|
41226
41454
|
return Promise.resolve();
|
|
@@ -41238,12 +41466,12 @@ var init_instance_remote_primary_auth = __esm({
|
|
|
41238
41466
|
});
|
|
41239
41467
|
|
|
41240
41468
|
// libs/sdk/src/auth/auth.registry.ts
|
|
41241
|
-
var import_reflect_metadata21,
|
|
41469
|
+
var import_reflect_metadata21, import_auth34, import_di33, DEFAULT_AUTH_OPTIONS, AuthRegistry;
|
|
41242
41470
|
var init_auth_registry = __esm({
|
|
41243
41471
|
"libs/sdk/src/auth/auth.registry.ts"() {
|
|
41244
41472
|
"use strict";
|
|
41245
41473
|
import_reflect_metadata21 = require("reflect-metadata");
|
|
41246
|
-
|
|
41474
|
+
import_auth34 = require("@frontmcp/auth");
|
|
41247
41475
|
import_di33 = require("@frontmcp/di");
|
|
41248
41476
|
init_common();
|
|
41249
41477
|
init_context_extension();
|
|
@@ -41275,7 +41503,7 @@ var init_auth_registry = __esm({
|
|
|
41275
41503
|
this.logger = providers.get(FrontMcpLogger).child("AuthRegistry");
|
|
41276
41504
|
this.parsedOptions = (0, import_auth10.parseAuthOptions)(primaryInput ?? DEFAULT_AUTH_OPTIONS);
|
|
41277
41505
|
const appAuthInfos = this.extractAppAuthInfo(scope);
|
|
41278
|
-
this.detection = (0,
|
|
41506
|
+
this.detection = (0, import_auth34.detectAuthProviders)(this.parsedOptions, appAuthInfos);
|
|
41279
41507
|
this.requiresOrchestration = this.detection.requiresOrchestration;
|
|
41280
41508
|
this.logDetectionResults();
|
|
41281
41509
|
this.validateConfiguration();
|
|
@@ -44733,6 +44961,7 @@ var init_execute_workflow_tool = __esm({
|
|
|
44733
44961
|
"use strict";
|
|
44734
44962
|
import_lazy_zod81 = require("@frontmcp/lazy-zod");
|
|
44735
44963
|
init_common();
|
|
44964
|
+
init_errors();
|
|
44736
44965
|
ExecuteWorkflowTool = class extends ToolContext {
|
|
44737
44966
|
async execute(input) {
|
|
44738
44967
|
const scope = this.scope;
|
|
@@ -44744,12 +44973,13 @@ var init_execute_workflow_tool = __esm({
|
|
|
44744
44973
|
}
|
|
44745
44974
|
const workflow = workflowRegistry.findByName(input.name);
|
|
44746
44975
|
if (!workflow) {
|
|
44747
|
-
return this.fail(new
|
|
44976
|
+
return this.fail(new JobNotAuthorizedError(input.name));
|
|
44748
44977
|
}
|
|
44749
44978
|
const result = await executionManager.executeWorkflow(workflow, jobRegistry, {
|
|
44750
44979
|
background: input.background,
|
|
44751
44980
|
sessionId: this.authInfo.sessionId,
|
|
44752
44981
|
authInfo: this.authInfo,
|
|
44982
|
+
authoritiesContextBuilder: this.scope.authoritiesContextBuilder,
|
|
44753
44983
|
workflowInput: input.input
|
|
44754
44984
|
});
|
|
44755
44985
|
return result;
|
|
@@ -44774,6 +45004,55 @@ var init_execute_workflow_tool = __esm({
|
|
|
44774
45004
|
}
|
|
44775
45005
|
});
|
|
44776
45006
|
|
|
45007
|
+
// libs/sdk/src/common/utils/principal.utils.ts
|
|
45008
|
+
function toStringArray(value) {
|
|
45009
|
+
if (Array.isArray(value)) return value.filter((v) => typeof v === "string");
|
|
45010
|
+
if (typeof value === "string" && value.length > 0) return value.split(" ").filter(Boolean);
|
|
45011
|
+
return [];
|
|
45012
|
+
}
|
|
45013
|
+
function asRecord(value) {
|
|
45014
|
+
return value && typeof value === "object" ? value : void 0;
|
|
45015
|
+
}
|
|
45016
|
+
function firstPresent(sources, convert) {
|
|
45017
|
+
for (const source of sources) {
|
|
45018
|
+
if (source !== void 0 && source !== null) return convert(source);
|
|
45019
|
+
}
|
|
45020
|
+
return [];
|
|
45021
|
+
}
|
|
45022
|
+
function resolvePrincipal2(authInfo, contextBuilder) {
|
|
45023
|
+
const info = authInfo ?? {};
|
|
45024
|
+
const user = asRecord(info["user"]) ?? {};
|
|
45025
|
+
const extraAuthorization = asRecord(asRecord(info["extra"])?.["authorization"]);
|
|
45026
|
+
const authInfoClaims = asRecord(info["claims"]) ?? {};
|
|
45027
|
+
const authorizationClaims = asRecord(extraAuthorization?.["claims"]) ?? {};
|
|
45028
|
+
const claims = { ...authorizationClaims, ...authInfoClaims, ...user };
|
|
45029
|
+
const scopes = toStringArray(info["scopes"]).length ? toStringArray(info["scopes"]) : firstPresent([extraAuthorization?.["scopes"], claims["scope"], claims["scopes"]], toStringArray);
|
|
45030
|
+
if (contextBuilder) {
|
|
45031
|
+
const ctx = contextBuilder.build(info);
|
|
45032
|
+
return {
|
|
45033
|
+
sub: ctx.user.sub,
|
|
45034
|
+
roles: ctx.user.roles,
|
|
45035
|
+
permissions: ctx.user.permissions,
|
|
45036
|
+
scopes,
|
|
45037
|
+
claims: ctx.user.claims
|
|
45038
|
+
};
|
|
45039
|
+
}
|
|
45040
|
+
const roles = firstPresent([user["roles"], claims["roles"], extraAuthorization?.["scopes"]], toStringArray);
|
|
45041
|
+
const permissions = firstPresent([user["permissions"], claims["permissions"]], toStringArray);
|
|
45042
|
+
return {
|
|
45043
|
+
sub: typeof user["sub"] === "string" ? user["sub"] : "",
|
|
45044
|
+
roles,
|
|
45045
|
+
permissions,
|
|
45046
|
+
scopes,
|
|
45047
|
+
claims
|
|
45048
|
+
};
|
|
45049
|
+
}
|
|
45050
|
+
var init_principal_utils = __esm({
|
|
45051
|
+
"libs/sdk/src/common/utils/principal.utils.ts"() {
|
|
45052
|
+
"use strict";
|
|
45053
|
+
}
|
|
45054
|
+
});
|
|
45055
|
+
|
|
44777
45056
|
// libs/sdk/src/workflow/tools/get-workflow-status.tool.ts
|
|
44778
45057
|
var import_lazy_zod82, GetWorkflowStatusTool;
|
|
44779
45058
|
var init_get_workflow_status_tool = __esm({
|
|
@@ -44781,6 +45060,7 @@ var init_get_workflow_status_tool = __esm({
|
|
|
44781
45060
|
"use strict";
|
|
44782
45061
|
import_lazy_zod82 = require("@frontmcp/lazy-zod");
|
|
44783
45062
|
init_common();
|
|
45063
|
+
init_principal_utils();
|
|
44784
45064
|
GetWorkflowStatusTool = class extends ToolContext {
|
|
44785
45065
|
async execute(input) {
|
|
44786
45066
|
const scope = this.scope;
|
|
@@ -44789,7 +45069,7 @@ var init_get_workflow_status_tool = __esm({
|
|
|
44789
45069
|
return this.fail(new Error("Jobs system is not enabled"));
|
|
44790
45070
|
}
|
|
44791
45071
|
const record = await executionManager.getStatus(input.runId);
|
|
44792
|
-
if (!record) {
|
|
45072
|
+
if (!record || !this.ownsRun(record)) {
|
|
44793
45073
|
return this.fail(new Error(`Run "${input.runId}" not found`));
|
|
44794
45074
|
}
|
|
44795
45075
|
const workflowRecord = record;
|
|
@@ -44802,6 +45082,23 @@ var init_get_workflow_status_tool = __esm({
|
|
|
44802
45082
|
completedAt: record.completedAt
|
|
44803
45083
|
};
|
|
44804
45084
|
}
|
|
45085
|
+
/**
|
|
45086
|
+
* Match on the owning subject, falling back to the session for runs started
|
|
45087
|
+
* before an owner was recorded (and for anonymous/public servers, where every
|
|
45088
|
+
* caller has an empty subject and the session is the only identity there is).
|
|
45089
|
+
*
|
|
45090
|
+
* A record carrying NEITHER identity is refused. Such a run cannot be
|
|
45091
|
+
* attributed to anyone, and its inputs and results are exactly what this check
|
|
45092
|
+
* exists to protect — anyone holding the id would otherwise be able to read
|
|
45093
|
+
* it. Internal callers that must read unattributed runs should go through the
|
|
45094
|
+
* execution manager directly rather than this tool.
|
|
45095
|
+
*/
|
|
45096
|
+
ownsRun(record) {
|
|
45097
|
+
const callerSub = resolvePrincipal2(this.authInfo, this.scope.authoritiesContextBuilder).sub;
|
|
45098
|
+
if (record.ownerSub) return record.ownerSub === callerSub;
|
|
45099
|
+
if (record.sessionId) return record.sessionId === this.authInfo.sessionId;
|
|
45100
|
+
return false;
|
|
45101
|
+
}
|
|
44805
45102
|
};
|
|
44806
45103
|
GetWorkflowStatusTool = __decorateClass([
|
|
44807
45104
|
FrontMcpTool({
|
|
@@ -44829,6 +45126,53 @@ var init_get_workflow_status_tool = __esm({
|
|
|
44829
45126
|
}
|
|
44830
45127
|
});
|
|
44831
45128
|
|
|
45129
|
+
// libs/sdk/src/job/job-permission.guard.ts
|
|
45130
|
+
var JobPermissionGuard;
|
|
45131
|
+
var init_job_permission_guard = __esm({
|
|
45132
|
+
"libs/sdk/src/job/job-permission.guard.ts"() {
|
|
45133
|
+
"use strict";
|
|
45134
|
+
init_principal_utils();
|
|
45135
|
+
JobPermissionGuard = class _JobPermissionGuard {
|
|
45136
|
+
/**
|
|
45137
|
+
* Check whether the caller may perform `action`.
|
|
45138
|
+
*
|
|
45139
|
+
* @param permissions - the entry's declared rules (undefined = unrestricted).
|
|
45140
|
+
* @param action - the action being attempted.
|
|
45141
|
+
* @param authInfo - the request's AuthInfo.
|
|
45142
|
+
* @param contextBuilder - the scope's authorities context builder, when configured.
|
|
45143
|
+
*/
|
|
45144
|
+
static async check(permissions, action, authInfo, contextBuilder) {
|
|
45145
|
+
if (!permissions || permissions.length === 0) {
|
|
45146
|
+
return true;
|
|
45147
|
+
}
|
|
45148
|
+
const relevant = permissions.filter((p) => p.action === action);
|
|
45149
|
+
if (relevant.length === 0) {
|
|
45150
|
+
return true;
|
|
45151
|
+
}
|
|
45152
|
+
const principal = resolvePrincipal2(authInfo, contextBuilder);
|
|
45153
|
+
for (const perm of relevant) {
|
|
45154
|
+
const passed = await _JobPermissionGuard.checkSingle(perm, principal, authInfo);
|
|
45155
|
+
if (!passed) return false;
|
|
45156
|
+
}
|
|
45157
|
+
return true;
|
|
45158
|
+
}
|
|
45159
|
+
static async checkSingle(perm, principal, authInfo) {
|
|
45160
|
+
if (perm.roles && perm.roles.length > 0) {
|
|
45161
|
+
if (!perm.roles.some((role) => principal.roles.includes(role))) return false;
|
|
45162
|
+
}
|
|
45163
|
+
if (perm.scopes && perm.scopes.length > 0) {
|
|
45164
|
+
if (!perm.scopes.some((scope) => principal.scopes.includes(scope))) return false;
|
|
45165
|
+
}
|
|
45166
|
+
if (perm.custom) {
|
|
45167
|
+
const result = await perm.custom(authInfo ?? {});
|
|
45168
|
+
if (!result) return false;
|
|
45169
|
+
}
|
|
45170
|
+
return true;
|
|
45171
|
+
}
|
|
45172
|
+
};
|
|
45173
|
+
}
|
|
45174
|
+
});
|
|
45175
|
+
|
|
44832
45176
|
// libs/sdk/src/workflow/tools/list-workflows.tool.ts
|
|
44833
45177
|
var import_lazy_zod83, ListWorkflowsTool;
|
|
44834
45178
|
var init_list_workflows_tool = __esm({
|
|
@@ -44836,6 +45180,7 @@ var init_list_workflows_tool = __esm({
|
|
|
44836
45180
|
"use strict";
|
|
44837
45181
|
import_lazy_zod83 = require("@frontmcp/lazy-zod");
|
|
44838
45182
|
init_common();
|
|
45183
|
+
init_job_permission_guard();
|
|
44839
45184
|
ListWorkflowsTool = class extends ToolContext {
|
|
44840
45185
|
async execute(input) {
|
|
44841
45186
|
const scope = this.scope;
|
|
@@ -44847,7 +45192,11 @@ var init_list_workflows_tool = __esm({
|
|
|
44847
45192
|
tags: input.tags,
|
|
44848
45193
|
labels: input.labels
|
|
44849
45194
|
});
|
|
44850
|
-
const
|
|
45195
|
+
const visible = [];
|
|
45196
|
+
for (const workflow of workflows) {
|
|
45197
|
+
if (await this.maySee(workflow)) visible.push(workflow);
|
|
45198
|
+
}
|
|
45199
|
+
const mapped = visible.map((w) => ({
|
|
44851
45200
|
name: w.name,
|
|
44852
45201
|
description: w.metadata.description,
|
|
44853
45202
|
trigger: w.getTrigger(),
|
|
@@ -44859,6 +45208,12 @@ var init_list_workflows_tool = __esm({
|
|
|
44859
45208
|
count: mapped.length
|
|
44860
45209
|
};
|
|
44861
45210
|
}
|
|
45211
|
+
async maySee(workflow) {
|
|
45212
|
+
const permissions = workflow.metadata.permissions;
|
|
45213
|
+
const builder = this.scope.authoritiesContextBuilder;
|
|
45214
|
+
if (!await JobPermissionGuard.check(permissions, "list", this.authInfo, builder)) return false;
|
|
45215
|
+
return JobPermissionGuard.check(permissions, "execute", this.authInfo, builder);
|
|
45216
|
+
}
|
|
44862
45217
|
};
|
|
44863
45218
|
ListWorkflowsTool = __decorateClass([
|
|
44864
45219
|
FrontMcpTool({
|
|
@@ -44894,6 +45249,7 @@ var init_register_workflow_tool = __esm({
|
|
|
44894
45249
|
"use strict";
|
|
44895
45250
|
import_lazy_zod84 = require("@frontmcp/lazy-zod");
|
|
44896
45251
|
init_common();
|
|
45252
|
+
init_errors();
|
|
44897
45253
|
RegisterWorkflowTool = class extends ToolContext {
|
|
44898
45254
|
async execute(input) {
|
|
44899
45255
|
const scope = this.scope;
|
|
@@ -44901,6 +45257,9 @@ var init_register_workflow_tool = __esm({
|
|
|
44901
45257
|
if (!workflowRegistry) {
|
|
44902
45258
|
return this.fail(new Error("Workflows system is not enabled"));
|
|
44903
45259
|
}
|
|
45260
|
+
if (!this.dynamicRegistrationEnabled()) {
|
|
45261
|
+
return this.fail(new DynamicJobRegistrationDisabledError("workflow"));
|
|
45262
|
+
}
|
|
44904
45263
|
const existing = workflowRegistry.findByName(input.name);
|
|
44905
45264
|
if (existing) {
|
|
44906
45265
|
return this.fail(new Error(`Workflow "${input.name}" already exists`));
|
|
@@ -44922,6 +45281,10 @@ var init_register_workflow_tool = __esm({
|
|
|
44922
45281
|
});
|
|
44923
45282
|
return { success: true, workflowId };
|
|
44924
45283
|
}
|
|
45284
|
+
dynamicRegistrationEnabled() {
|
|
45285
|
+
const jobs = this.scope.metadata?.jobs;
|
|
45286
|
+
return jobs?.allowDynamicRegistration === true;
|
|
45287
|
+
}
|
|
44925
45288
|
};
|
|
44926
45289
|
RegisterWorkflowTool = __decorateClass([
|
|
44927
45290
|
FrontMcpTool({
|
|
@@ -44960,6 +45323,8 @@ var init_remove_workflow_tool = __esm({
|
|
|
44960
45323
|
"use strict";
|
|
44961
45324
|
import_lazy_zod85 = require("@frontmcp/lazy-zod");
|
|
44962
45325
|
init_common();
|
|
45326
|
+
init_errors();
|
|
45327
|
+
init_job_permission_guard();
|
|
44963
45328
|
RemoveWorkflowTool = class extends ToolContext {
|
|
44964
45329
|
async execute(input) {
|
|
44965
45330
|
const scope = this.scope;
|
|
@@ -44969,7 +45334,16 @@ var init_remove_workflow_tool = __esm({
|
|
|
44969
45334
|
}
|
|
44970
45335
|
const workflow = workflowRegistry.findByName(input.name);
|
|
44971
45336
|
if (!workflow) {
|
|
44972
|
-
return this.fail(new
|
|
45337
|
+
return this.fail(new JobNotAuthorizedError(input.name));
|
|
45338
|
+
}
|
|
45339
|
+
const allowed = await JobPermissionGuard.check(
|
|
45340
|
+
workflow.metadata.permissions,
|
|
45341
|
+
"delete",
|
|
45342
|
+
this.authInfo,
|
|
45343
|
+
this.scope.authoritiesContextBuilder
|
|
45344
|
+
);
|
|
45345
|
+
if (!allowed) {
|
|
45346
|
+
return this.fail(new JobNotAuthorizedError(input.name));
|
|
44973
45347
|
}
|
|
44974
45348
|
if (!workflow.isDynamic()) {
|
|
44975
45349
|
return this.fail(new Error(`Workflow "${input.name}" is not dynamic and cannot be removed`));
|
|
@@ -45579,7 +45953,10 @@ var init_job_execution_manager = __esm({
|
|
|
45579
45953
|
"libs/sdk/src/job/execution/job-execution.manager.ts"() {
|
|
45580
45954
|
"use strict";
|
|
45581
45955
|
import_utils79 = require("@frontmcp/utils");
|
|
45956
|
+
init_principal_utils();
|
|
45957
|
+
init_errors();
|
|
45582
45958
|
init_workflow_engine();
|
|
45959
|
+
init_job_permission_guard();
|
|
45583
45960
|
JobExecutionManager = class {
|
|
45584
45961
|
stateStore;
|
|
45585
45962
|
logger;
|
|
@@ -45593,6 +45970,7 @@ var init_job_execution_manager = __esm({
|
|
|
45593
45970
|
* Execute a job inline (synchronous) or in background.
|
|
45594
45971
|
*/
|
|
45595
45972
|
async executeJob(job, input, opts = {}) {
|
|
45973
|
+
await this.assertMayExecute(job.metadata.permissions, job.name, opts);
|
|
45596
45974
|
const runId = (0, import_utils79.randomUUID)();
|
|
45597
45975
|
const retryConfig = job.metadata.retry ?? {};
|
|
45598
45976
|
const maxAttempts = retryConfig.maxAttempts ?? 1;
|
|
@@ -45601,6 +45979,7 @@ var init_job_execution_manager = __esm({
|
|
|
45601
45979
|
jobId: job.metadata.id ?? job.name,
|
|
45602
45980
|
jobName: job.name,
|
|
45603
45981
|
sessionId: opts.sessionId,
|
|
45982
|
+
ownerSub: resolvePrincipal2(opts.authInfo, opts.authoritiesContextBuilder).sub || void 0,
|
|
45604
45983
|
state: "pending",
|
|
45605
45984
|
input,
|
|
45606
45985
|
startedAt: Date.now(),
|
|
@@ -45631,6 +46010,7 @@ var init_job_execution_manager = __esm({
|
|
|
45631
46010
|
* Execute a workflow inline or in background.
|
|
45632
46011
|
*/
|
|
45633
46012
|
async executeWorkflow(workflow, jobRegistry, opts = {}) {
|
|
46013
|
+
await this.assertMayExecute(workflow.metadata.permissions, workflow.name, opts);
|
|
45634
46014
|
const runId = (0, import_utils79.randomUUID)();
|
|
45635
46015
|
const runRecord = {
|
|
45636
46016
|
runId,
|
|
@@ -45638,6 +46018,7 @@ var init_job_execution_manager = __esm({
|
|
|
45638
46018
|
jobName: workflow.name,
|
|
45639
46019
|
workflowName: workflow.name,
|
|
45640
46020
|
sessionId: opts.sessionId,
|
|
46021
|
+
ownerSub: resolvePrincipal2(opts.authInfo, opts.authoritiesContextBuilder).sub || void 0,
|
|
45641
46022
|
state: "pending",
|
|
45642
46023
|
input: opts.workflowInput,
|
|
45643
46024
|
startedAt: Date.now(),
|
|
@@ -45665,6 +46046,25 @@ var init_job_execution_manager = __esm({
|
|
|
45665
46046
|
}
|
|
45666
46047
|
return this.executeWorkflowInline(workflow, jobRegistry, runId, opts);
|
|
45667
46048
|
}
|
|
46049
|
+
/**
|
|
46050
|
+
* Throw unless the caller may execute the entry.
|
|
46051
|
+
*
|
|
46052
|
+
* Uses the same error for "not authorized" as the tools use for "not found",
|
|
46053
|
+
* so a caller cannot enumerate which restricted jobs exist by comparing
|
|
46054
|
+
* responses.
|
|
46055
|
+
*/
|
|
46056
|
+
async assertMayExecute(permissions, name42, opts) {
|
|
46057
|
+
const allowed = await JobPermissionGuard.check(
|
|
46058
|
+
permissions,
|
|
46059
|
+
"execute",
|
|
46060
|
+
opts.authInfo,
|
|
46061
|
+
opts.authoritiesContextBuilder
|
|
46062
|
+
);
|
|
46063
|
+
if (!allowed) {
|
|
46064
|
+
this.logger.warn(`Execution of "${name42}" denied: caller does not satisfy its execute permissions`);
|
|
46065
|
+
throw new JobNotAuthorizedError(name42);
|
|
46066
|
+
}
|
|
46067
|
+
}
|
|
45668
46068
|
/**
|
|
45669
46069
|
* Get execution status.
|
|
45670
46070
|
*/
|
|
@@ -46690,6 +47090,7 @@ var init_execute_job_tool = __esm({
|
|
|
46690
47090
|
"use strict";
|
|
46691
47091
|
import_lazy_zod88 = require("@frontmcp/lazy-zod");
|
|
46692
47092
|
init_common();
|
|
47093
|
+
init_errors();
|
|
46693
47094
|
ExecuteJobTool = class extends ToolContext {
|
|
46694
47095
|
async execute(input) {
|
|
46695
47096
|
const scope = this.scope;
|
|
@@ -46700,12 +47101,13 @@ var init_execute_job_tool = __esm({
|
|
|
46700
47101
|
}
|
|
46701
47102
|
const job = jobRegistry.findByName(input.name);
|
|
46702
47103
|
if (!job) {
|
|
46703
|
-
return this.fail(new
|
|
47104
|
+
return this.fail(new JobNotAuthorizedError(input.name));
|
|
46704
47105
|
}
|
|
46705
47106
|
const result = await executionManager.executeJob(job, input.input ?? {}, {
|
|
46706
47107
|
background: input.background,
|
|
46707
47108
|
sessionId: this.authInfo.sessionId,
|
|
46708
|
-
authInfo: this.authInfo
|
|
47109
|
+
authInfo: this.authInfo,
|
|
47110
|
+
authoritiesContextBuilder: this.scope.authoritiesContextBuilder
|
|
46709
47111
|
});
|
|
46710
47112
|
return result;
|
|
46711
47113
|
}
|
|
@@ -46737,6 +47139,7 @@ var init_get_job_status_tool = __esm({
|
|
|
46737
47139
|
"use strict";
|
|
46738
47140
|
import_lazy_zod89 = require("@frontmcp/lazy-zod");
|
|
46739
47141
|
init_common();
|
|
47142
|
+
init_principal_utils();
|
|
46740
47143
|
init_errors();
|
|
46741
47144
|
GetJobStatusTool = class extends ToolContext {
|
|
46742
47145
|
async execute(input) {
|
|
@@ -46746,7 +47149,7 @@ var init_get_job_status_tool = __esm({
|
|
|
46746
47149
|
return this.fail(new GenericServerError("Jobs system is not enabled"));
|
|
46747
47150
|
}
|
|
46748
47151
|
const record = await executionManager.getStatus(input.runId);
|
|
46749
|
-
if (!record) {
|
|
47152
|
+
if (!record || !this.ownsRun(record)) {
|
|
46750
47153
|
return this.fail(new InvalidInputError(`Run "${input.runId}" not found`));
|
|
46751
47154
|
}
|
|
46752
47155
|
return {
|
|
@@ -46761,6 +47164,23 @@ var init_get_job_status_tool = __esm({
|
|
|
46761
47164
|
logs: record.logs
|
|
46762
47165
|
};
|
|
46763
47166
|
}
|
|
47167
|
+
/**
|
|
47168
|
+
* Match on the owning subject, falling back to the session for runs started
|
|
47169
|
+
* before an owner was recorded (and for anonymous/public servers, where every
|
|
47170
|
+
* caller has an empty subject and the session is the only identity there is).
|
|
47171
|
+
*
|
|
47172
|
+
* A record carrying NEITHER identity is refused. Such a run cannot be
|
|
47173
|
+
* attributed to anyone, and its inputs and results are exactly what this check
|
|
47174
|
+
* exists to protect — anyone holding the id would otherwise be able to read
|
|
47175
|
+
* it. Internal callers that must read unattributed runs should go through the
|
|
47176
|
+
* execution manager directly rather than this tool.
|
|
47177
|
+
*/
|
|
47178
|
+
ownsRun(record) {
|
|
47179
|
+
const callerSub = resolvePrincipal2(this.authInfo, this.scope.authoritiesContextBuilder).sub;
|
|
47180
|
+
if (record.ownerSub) return record.ownerSub === callerSub;
|
|
47181
|
+
if (record.sessionId) return record.sessionId === this.authInfo.sessionId;
|
|
47182
|
+
return false;
|
|
47183
|
+
}
|
|
46764
47184
|
};
|
|
46765
47185
|
GetJobStatusTool = __decorateClass([
|
|
46766
47186
|
FrontMcpTool({
|
|
@@ -46795,6 +47215,7 @@ var init_list_jobs_tool = __esm({
|
|
|
46795
47215
|
"use strict";
|
|
46796
47216
|
import_lazy_zod90 = require("@frontmcp/lazy-zod");
|
|
46797
47217
|
init_common();
|
|
47218
|
+
init_job_permission_guard();
|
|
46798
47219
|
ListJobsTool = class extends ToolContext {
|
|
46799
47220
|
async execute(input) {
|
|
46800
47221
|
const jobRegistry = this.scope.jobs;
|
|
@@ -46805,7 +47226,11 @@ var init_list_jobs_tool = __esm({
|
|
|
46805
47226
|
tags: input.tags,
|
|
46806
47227
|
labels: input.labels
|
|
46807
47228
|
});
|
|
46808
|
-
const
|
|
47229
|
+
const visible = [];
|
|
47230
|
+
for (const job of jobs) {
|
|
47231
|
+
if (await this.maySee(job)) visible.push(job);
|
|
47232
|
+
}
|
|
47233
|
+
const mapped = visible.map((j) => ({
|
|
46809
47234
|
name: j.name,
|
|
46810
47235
|
description: j.metadata.description,
|
|
46811
47236
|
tags: j.getTags(),
|
|
@@ -46817,6 +47242,17 @@ var init_list_jobs_tool = __esm({
|
|
|
46817
47242
|
count: mapped.length
|
|
46818
47243
|
};
|
|
46819
47244
|
}
|
|
47245
|
+
/**
|
|
47246
|
+
* A job is listable when the caller passes its `list` rules and is not
|
|
47247
|
+
* blocked from executing it. Checking `execute` too keeps the listing honest:
|
|
47248
|
+
* showing a job the caller can never run is the disclosure this closes.
|
|
47249
|
+
*/
|
|
47250
|
+
async maySee(job) {
|
|
47251
|
+
const permissions = job.metadata.permissions;
|
|
47252
|
+
const builder = this.scope.authoritiesContextBuilder;
|
|
47253
|
+
if (!await JobPermissionGuard.check(permissions, "list", this.authInfo, builder)) return false;
|
|
47254
|
+
return JobPermissionGuard.check(permissions, "execute", this.authInfo, builder);
|
|
47255
|
+
}
|
|
46820
47256
|
};
|
|
46821
47257
|
ListJobsTool = __decorateClass([
|
|
46822
47258
|
FrontMcpTool({
|
|
@@ -46852,6 +47288,7 @@ var init_register_job_tool = __esm({
|
|
|
46852
47288
|
"use strict";
|
|
46853
47289
|
import_lazy_zod91 = require("@frontmcp/lazy-zod");
|
|
46854
47290
|
init_common();
|
|
47291
|
+
init_errors();
|
|
46855
47292
|
RegisterJobTool = class extends ToolContext {
|
|
46856
47293
|
async execute(input) {
|
|
46857
47294
|
const scope = this.scope;
|
|
@@ -46859,6 +47296,9 @@ var init_register_job_tool = __esm({
|
|
|
46859
47296
|
if (!jobRegistry) {
|
|
46860
47297
|
return this.fail(new Error("Jobs system is not enabled"));
|
|
46861
47298
|
}
|
|
47299
|
+
if (!this.dynamicRegistrationEnabled()) {
|
|
47300
|
+
return this.fail(new DynamicJobRegistrationDisabledError("job"));
|
|
47301
|
+
}
|
|
46862
47302
|
const existing = jobRegistry.findByName(input.name);
|
|
46863
47303
|
if (existing) {
|
|
46864
47304
|
return this.fail(new Error(`Job "${input.name}" already exists`));
|
|
@@ -46881,6 +47321,10 @@ var init_register_job_tool = __esm({
|
|
|
46881
47321
|
});
|
|
46882
47322
|
return { success: true, jobId };
|
|
46883
47323
|
}
|
|
47324
|
+
dynamicRegistrationEnabled() {
|
|
47325
|
+
const jobs = this.scope.metadata?.jobs;
|
|
47326
|
+
return jobs?.allowDynamicRegistration === true;
|
|
47327
|
+
}
|
|
46884
47328
|
};
|
|
46885
47329
|
RegisterJobTool = __decorateClass([
|
|
46886
47330
|
FrontMcpTool({
|
|
@@ -46911,6 +47355,8 @@ var init_remove_job_tool = __esm({
|
|
|
46911
47355
|
"use strict";
|
|
46912
47356
|
import_lazy_zod92 = require("@frontmcp/lazy-zod");
|
|
46913
47357
|
init_common();
|
|
47358
|
+
init_errors();
|
|
47359
|
+
init_job_permission_guard();
|
|
46914
47360
|
RemoveJobTool = class extends ToolContext {
|
|
46915
47361
|
async execute(input) {
|
|
46916
47362
|
const scope = this.scope;
|
|
@@ -46920,7 +47366,16 @@ var init_remove_job_tool = __esm({
|
|
|
46920
47366
|
}
|
|
46921
47367
|
const job = jobRegistry.findByName(input.name);
|
|
46922
47368
|
if (!job) {
|
|
46923
|
-
return this.fail(new
|
|
47369
|
+
return this.fail(new JobNotAuthorizedError(input.name));
|
|
47370
|
+
}
|
|
47371
|
+
const allowed = await JobPermissionGuard.check(
|
|
47372
|
+
job.metadata.permissions,
|
|
47373
|
+
"delete",
|
|
47374
|
+
this.authInfo,
|
|
47375
|
+
this.scope.authoritiesContextBuilder
|
|
47376
|
+
);
|
|
47377
|
+
if (!allowed) {
|
|
47378
|
+
return this.fail(new JobNotAuthorizedError(input.name));
|
|
46924
47379
|
}
|
|
46925
47380
|
if (!job.isDynamic()) {
|
|
46926
47381
|
return this.fail(new Error(`Job "${input.name}" is not a dynamic job and cannot be removed`));
|
|
@@ -46981,14 +47436,18 @@ async function registerJobCapabilities(args) {
|
|
|
46981
47436
|
ListJobsTool,
|
|
46982
47437
|
ExecuteJobTool,
|
|
46983
47438
|
GetJobStatusTool,
|
|
46984
|
-
RegisterJobTool,
|
|
46985
47439
|
RemoveJobTool,
|
|
46986
47440
|
ListWorkflowsTool,
|
|
46987
47441
|
ExecuteWorkflowTool,
|
|
46988
47442
|
GetWorkflowStatusTool,
|
|
46989
|
-
RegisterWorkflowTool,
|
|
46990
47443
|
RemoveWorkflowTool
|
|
46991
47444
|
];
|
|
47445
|
+
if (jobsConfig.allowDynamicRegistration === true) {
|
|
47446
|
+
managementTools.push(RegisterJobTool, RegisterWorkflowTool);
|
|
47447
|
+
logger.warn(
|
|
47448
|
+
"Dynamic job registration is ENABLED: register_job/register_workflow accept a raw script and register it as an executable job."
|
|
47449
|
+
);
|
|
47450
|
+
}
|
|
46992
47451
|
logger.info(
|
|
46993
47452
|
`Jobs initialized: ${jobRegistry.getJobs().length} jobs, ${workflowRegistry.getWorkflows().length} workflows`
|
|
46994
47453
|
);
|
|
@@ -51820,6 +52279,7 @@ var init_utils2 = __esm({
|
|
|
51820
52279
|
init_caller_dir_utils();
|
|
51821
52280
|
init_decide_request_intent_utils();
|
|
51822
52281
|
init_path_utils();
|
|
52282
|
+
init_principal_utils();
|
|
51823
52283
|
init_global_config_utils();
|
|
51824
52284
|
init_primitive_filter();
|
|
51825
52285
|
init_validate_remote_url();
|
|
@@ -52767,9 +53227,24 @@ var init_redis_event_store = __esm({
|
|
|
52767
53227
|
await client.expire(streamKey, this.ttlSeconds);
|
|
52768
53228
|
return `${streamId}:${redisId}`;
|
|
52769
53229
|
}
|
|
53230
|
+
/**
|
|
53231
|
+
* Resolve the stream an event belongs to.
|
|
53232
|
+
*
|
|
53233
|
+
* Optional in the upstream `EventStore` contract, and the transport's
|
|
53234
|
+
* 409-conflict guard (refusing a second live stream for the same id) is dead
|
|
53235
|
+
* code while no store provides it (GHSA-84j6-jc92-77jm).
|
|
53236
|
+
*/
|
|
53237
|
+
async getStreamIdForEventId(eventId) {
|
|
53238
|
+
const parsed = this.parseEventId(eventId);
|
|
53239
|
+
return parsed ? parsed[0] : void 0;
|
|
53240
|
+
}
|
|
52770
53241
|
async replayEventsAfter(lastEventId, { send }) {
|
|
53242
|
+
const parsed = this.parseEventId(lastEventId);
|
|
53243
|
+
if (!parsed) {
|
|
53244
|
+
return "default-stream";
|
|
53245
|
+
}
|
|
52771
53246
|
const client = await this.getClient();
|
|
52772
|
-
const [streamId, redisId] =
|
|
53247
|
+
const [streamId, redisId] = parsed;
|
|
52773
53248
|
const streamKey = `${this.keyPrefix}${streamId}`;
|
|
52774
53249
|
const entries = await client.xrange(streamKey, `(${redisId}`, "+");
|
|
52775
53250
|
for (const [id, fields] of entries) {
|
|
@@ -52788,16 +53263,32 @@ var init_redis_event_store = __esm({
|
|
|
52788
53263
|
* Event IDs have format: `{streamId}:{redisStreamEntryId}`
|
|
52789
53264
|
* Redis stream entry IDs have format: `{timestamp}-{sequence}`
|
|
52790
53265
|
*
|
|
53266
|
+
* The entry-id half is validated because it is interpolated into the `xrange`
|
|
53267
|
+
* range argument (`(${redisId}`), where a malformed value is a protocol error
|
|
53268
|
+
* rather than data.
|
|
53269
|
+
*
|
|
53270
|
+
* The stream-id half is NOT character-filtered. `xadd`/`xrange` take the key
|
|
53271
|
+
* as an exact argument — Redis applies no glob matching there — and ioredis
|
|
53272
|
+
* length-prefixes it, so no character is special. Filtering here would also
|
|
53273
|
+
* make `storeEvent`, which accepts any `StreamId`, able to mint ids this
|
|
53274
|
+
* function then refuses, silently breaking replay for that stream. Ownership
|
|
53275
|
+
* is enforced by the session-scoped facade (GHSA-84j6-jc92-77jm), not by
|
|
53276
|
+
* guessing at the id's characters.
|
|
53277
|
+
*
|
|
53278
|
+
* @returns the pair, or `undefined` when the id is malformed.
|
|
53279
|
+
*
|
|
52791
53280
|
* @example
|
|
52792
53281
|
* 'my-stream:1234567890123-0' -> ['my-stream', '1234567890123-0']
|
|
52793
53282
|
*/
|
|
52794
53283
|
parseEventId(eventId) {
|
|
52795
|
-
const
|
|
52796
|
-
|
|
52797
|
-
|
|
52798
|
-
|
|
52799
|
-
|
|
52800
|
-
|
|
53284
|
+
const value = String(eventId);
|
|
53285
|
+
const lastDash = value.lastIndexOf("-");
|
|
53286
|
+
const separator = lastDash === -1 ? value.lastIndexOf(":") : value.lastIndexOf(":", lastDash);
|
|
53287
|
+
if (separator <= 0 || separator === value.length - 1) return void 0;
|
|
53288
|
+
const streamId = value.slice(0, separator);
|
|
53289
|
+
const redisId = value.slice(separator + 1);
|
|
53290
|
+
if (!/^\d+(-\d+)?$/.test(redisId)) return void 0;
|
|
53291
|
+
return [streamId, redisId];
|
|
52801
53292
|
}
|
|
52802
53293
|
/**
|
|
52803
53294
|
* Close the Redis client connection.
|
|
@@ -52843,6 +53334,16 @@ var init_memory_event_store = __esm({
|
|
|
52843
53334
|
}
|
|
52844
53335
|
return id;
|
|
52845
53336
|
}
|
|
53337
|
+
/**
|
|
53338
|
+
* Resolve the stream an event belongs to.
|
|
53339
|
+
*
|
|
53340
|
+
* Optional in the upstream `EventStore` contract, and the transport's
|
|
53341
|
+
* 409-conflict guard (refusing a second live stream for the same id) is dead
|
|
53342
|
+
* code while no store provides it (GHSA-84j6-jc92-77jm).
|
|
53343
|
+
*/
|
|
53344
|
+
async getStreamIdForEventId(eventId) {
|
|
53345
|
+
return this.index.get(eventId)?.streamId;
|
|
53346
|
+
}
|
|
52846
53347
|
async replayEventsAfter(lastEventId, { send }) {
|
|
52847
53348
|
const meta = this.index.get(lastEventId);
|
|
52848
53349
|
if (!meta) {
|
|
@@ -52994,6 +53495,50 @@ var init_event_store_factory = __esm({
|
|
|
52994
53495
|
}
|
|
52995
53496
|
});
|
|
52996
53497
|
|
|
53498
|
+
// libs/sdk/src/transport/event-stores/session-scoped.event-store.ts
|
|
53499
|
+
function createSessionScopedEventStore(store, sessionId) {
|
|
53500
|
+
const prefix = `${sessionId}${SCOPE_SEPARATOR}`;
|
|
53501
|
+
const scope = (streamId) => `${prefix}${streamId}`;
|
|
53502
|
+
const unscope = (streamId) => {
|
|
53503
|
+
const value = String(streamId);
|
|
53504
|
+
return value.startsWith(prefix) ? value.slice(prefix.length) : value;
|
|
53505
|
+
};
|
|
53506
|
+
const resolveOwnedStream = async (eventId) => {
|
|
53507
|
+
const lookup = store.getStreamIdForEventId;
|
|
53508
|
+
if (lookup) {
|
|
53509
|
+
const resolved = await lookup.call(store, eventId);
|
|
53510
|
+
return resolved !== void 0 && String(resolved).startsWith(prefix) ? resolved : void 0;
|
|
53511
|
+
}
|
|
53512
|
+
if (!String(eventId).startsWith(prefix)) return void 0;
|
|
53513
|
+
const value = String(eventId);
|
|
53514
|
+
const lastColon = value.lastIndexOf(":");
|
|
53515
|
+
return lastColon === -1 ? void 0 : value.slice(0, lastColon);
|
|
53516
|
+
};
|
|
53517
|
+
return {
|
|
53518
|
+
async storeEvent(streamId, message) {
|
|
53519
|
+
return store.storeEvent(scope(streamId), message);
|
|
53520
|
+
},
|
|
53521
|
+
async getStreamIdForEventId(eventId) {
|
|
53522
|
+
const owned = await resolveOwnedStream(eventId);
|
|
53523
|
+
return owned === void 0 ? void 0 : unscope(owned);
|
|
53524
|
+
},
|
|
53525
|
+
async replayEventsAfter(lastEventId, options) {
|
|
53526
|
+
const owned = await resolveOwnedStream(lastEventId);
|
|
53527
|
+
if (owned === void 0) {
|
|
53528
|
+
return "default-stream";
|
|
53529
|
+
}
|
|
53530
|
+
return unscope(await store.replayEventsAfter(lastEventId, options));
|
|
53531
|
+
}
|
|
53532
|
+
};
|
|
53533
|
+
}
|
|
53534
|
+
var SCOPE_SEPARATOR;
|
|
53535
|
+
var init_session_scoped_event_store = __esm({
|
|
53536
|
+
"libs/sdk/src/transport/event-stores/session-scoped.event-store.ts"() {
|
|
53537
|
+
"use strict";
|
|
53538
|
+
SCOPE_SEPARATOR = " ";
|
|
53539
|
+
}
|
|
53540
|
+
});
|
|
53541
|
+
|
|
52997
53542
|
// libs/sdk/src/transport/event-stores/index.ts
|
|
52998
53543
|
var init_event_stores = __esm({
|
|
52999
53544
|
"libs/sdk/src/transport/event-stores/index.ts"() {
|
|
@@ -54664,6 +55209,61 @@ var init_transport_error = __esm({
|
|
|
54664
55209
|
}
|
|
54665
55210
|
});
|
|
54666
55211
|
|
|
55212
|
+
// libs/sdk/src/server/security/host-validation.ts
|
|
55213
|
+
function normalizeHost(value) {
|
|
55214
|
+
const host = value.trim().toLowerCase();
|
|
55215
|
+
if (host.length === 0) return host;
|
|
55216
|
+
const portSeparator = host.startsWith("[") ? host.indexOf(":", host.indexOf("]")) : host.lastIndexOf(":");
|
|
55217
|
+
if (portSeparator === -1) return host;
|
|
55218
|
+
const name42 = host.slice(0, portSeparator);
|
|
55219
|
+
const port = host.slice(portSeparator + 1);
|
|
55220
|
+
if (port.length === 0 || !/^\d+$/.test(port)) return host;
|
|
55221
|
+
return DEFAULT_PORTS.has(port) ? name42 : `${name42}:${port}`;
|
|
55222
|
+
}
|
|
55223
|
+
function normalizeOrigin(value) {
|
|
55224
|
+
const origin = value.trim().toLowerCase();
|
|
55225
|
+
try {
|
|
55226
|
+
return new URL(origin).origin.toLowerCase();
|
|
55227
|
+
} catch {
|
|
55228
|
+
return origin;
|
|
55229
|
+
}
|
|
55230
|
+
}
|
|
55231
|
+
function compileHostValidation(rules) {
|
|
55232
|
+
return {
|
|
55233
|
+
hosts: rules.allowedHosts ? new Set(rules.allowedHosts.map(normalizeHost)) : void 0,
|
|
55234
|
+
origins: rules.allowedOrigins ? new Set(rules.allowedOrigins.map(normalizeOrigin)) : void 0
|
|
55235
|
+
};
|
|
55236
|
+
}
|
|
55237
|
+
function validateHostHeaders(headers, compiled) {
|
|
55238
|
+
if (compiled.hosts) {
|
|
55239
|
+
const host = headers.host;
|
|
55240
|
+
if (!host || !compiled.hosts.has(normalizeHost(host))) {
|
|
55241
|
+
return { status: 403, error: "Forbidden", message: "Invalid Host header" };
|
|
55242
|
+
}
|
|
55243
|
+
const forwardedHost = headers.forwardedHost;
|
|
55244
|
+
if (forwardedHost) {
|
|
55245
|
+
const candidates = forwardedHost.split(",").map((h2) => h2.trim()).filter(Boolean);
|
|
55246
|
+
if (candidates.some((h2) => !compiled.hosts?.has(normalizeHost(h2)))) {
|
|
55247
|
+
return { status: 403, error: "Forbidden", message: "Invalid X-Forwarded-Host header" };
|
|
55248
|
+
}
|
|
55249
|
+
}
|
|
55250
|
+
}
|
|
55251
|
+
if (compiled.origins) {
|
|
55252
|
+
const origin = headers.origin;
|
|
55253
|
+
if (origin && !compiled.origins.has(normalizeOrigin(origin))) {
|
|
55254
|
+
return { status: 403, error: "Forbidden", message: "Invalid Origin header" };
|
|
55255
|
+
}
|
|
55256
|
+
}
|
|
55257
|
+
return void 0;
|
|
55258
|
+
}
|
|
55259
|
+
var DEFAULT_PORTS;
|
|
55260
|
+
var init_host_validation = __esm({
|
|
55261
|
+
"libs/sdk/src/server/security/host-validation.ts"() {
|
|
55262
|
+
"use strict";
|
|
55263
|
+
DEFAULT_PORTS = /* @__PURE__ */ new Set(["80", "443"]);
|
|
55264
|
+
}
|
|
55265
|
+
});
|
|
55266
|
+
|
|
54667
55267
|
// libs/sdk/src/transport/adapters/base-sse-transport.ts
|
|
54668
55268
|
var import_content_type, import_raw_body, import_protocol54, import_utils93, MAXIMUM_MESSAGE_SIZE, SSEServerTransport;
|
|
54669
55269
|
var init_base_sse_transport = __esm({
|
|
@@ -54674,6 +55274,7 @@ var init_base_sse_transport = __esm({
|
|
|
54674
55274
|
import_protocol54 = require("@frontmcp/protocol");
|
|
54675
55275
|
import_utils93 = require("@frontmcp/utils");
|
|
54676
55276
|
init_transport_errors();
|
|
55277
|
+
init_host_validation();
|
|
54677
55278
|
MAXIMUM_MESSAGE_SIZE = "4mb";
|
|
54678
55279
|
SSEServerTransport = class {
|
|
54679
55280
|
/**
|
|
@@ -54697,25 +55298,31 @@ var init_base_sse_transport = __esm({
|
|
|
54697
55298
|
onmessage;
|
|
54698
55299
|
/**
|
|
54699
55300
|
* Validates request headers for DNS rebinding protection.
|
|
55301
|
+
*
|
|
55302
|
+
* Since v1.7.2 the primary defence is the adapter-level Host validation, which
|
|
55303
|
+
* runs before routing and therefore already covers `/sse` and `/message`
|
|
55304
|
+
* (GHSA-mc9g-v2cp-vfff). These per-transport options remain for a host that
|
|
55305
|
+
* constructs the transport directly; they delegate to the same rules so the
|
|
55306
|
+
* two can never disagree about what a valid Host looks like.
|
|
55307
|
+
*
|
|
54700
55308
|
* @returns Error message if validation fails, undefined if validation passes.
|
|
54701
55309
|
*/
|
|
54702
55310
|
validateRequestHeaders(req) {
|
|
54703
55311
|
if (!this._options.enableDnsRebindingProtection) {
|
|
54704
55312
|
return void 0;
|
|
54705
55313
|
}
|
|
54706
|
-
|
|
54707
|
-
|
|
54708
|
-
|
|
54709
|
-
|
|
54710
|
-
|
|
54711
|
-
|
|
54712
|
-
|
|
54713
|
-
|
|
54714
|
-
|
|
54715
|
-
|
|
54716
|
-
|
|
54717
|
-
|
|
54718
|
-
return void 0;
|
|
55314
|
+
const rejection = validateHostHeaders(
|
|
55315
|
+
{
|
|
55316
|
+
host: req.headers.host,
|
|
55317
|
+
forwardedHost: req.headers["x-forwarded-host"],
|
|
55318
|
+
origin: req.headers.origin
|
|
55319
|
+
},
|
|
55320
|
+
compileHostValidation({
|
|
55321
|
+
allowedHosts: this._options.allowedHosts?.length ? this._options.allowedHosts : void 0,
|
|
55322
|
+
allowedOrigins: this._options.allowedOrigins?.length ? this._options.allowedOrigins : void 0
|
|
55323
|
+
})
|
|
55324
|
+
);
|
|
55325
|
+
return rejection?.message;
|
|
54719
55326
|
}
|
|
54720
55327
|
/**
|
|
54721
55328
|
* Handles the initial SSE connection request.
|
|
@@ -55213,7 +55820,7 @@ var init_transport_local_adapter = __esm({
|
|
|
55213
55820
|
}
|
|
55214
55821
|
}
|
|
55215
55822
|
ensureAuthInfo(req, transport) {
|
|
55216
|
-
const { token, user, session } = req[ServerRequestTokens.auth];
|
|
55823
|
+
const { token, user, session, scopes, claims } = req[ServerRequestTokens.auth];
|
|
55217
55824
|
if (!session?.id) {
|
|
55218
55825
|
throw new UnauthorizedError(
|
|
55219
55826
|
"Session could not be reconstructed for the provided token. Re-authenticate to start a new session.",
|
|
@@ -55242,7 +55849,16 @@ var init_transport_local_adapter = __esm({
|
|
|
55242
55849
|
user,
|
|
55243
55850
|
sessionId,
|
|
55244
55851
|
sessionIdPayload: sessionPayload,
|
|
55245
|
-
scopes
|
|
55852
|
+
// Carry the scopes and claims the verified Authorization actually holds.
|
|
55853
|
+
// These used to be dropped (`scopes: []`, no claims), which left every
|
|
55854
|
+
// scope- or claim-based check downstream inert.
|
|
55855
|
+
//
|
|
55856
|
+
// Both are COPIED, not aliased. Tool code receives this object, and a
|
|
55857
|
+
// nested mutation (`authInfo.claims.roles.push('admin')`) on a shared
|
|
55858
|
+
// reference would rewrite the request's own verified authorization — which
|
|
55859
|
+
// the job/workflow permission guard then reads.
|
|
55860
|
+
scopes: Array.isArray(scopes) ? [...scopes] : [],
|
|
55861
|
+
...claims ? { claims: structuredClone(claims) } : {},
|
|
55246
55862
|
clientId: user.sub ?? "",
|
|
55247
55863
|
transport
|
|
55248
55864
|
};
|
|
@@ -55723,6 +56339,7 @@ var init_transport_streamable_http_adapter = __esm({
|
|
|
55723
56339
|
"use strict";
|
|
55724
56340
|
import_lazy_zod105 = require("@frontmcp/lazy-zod");
|
|
55725
56341
|
init_errors();
|
|
56342
|
+
init_session_scoped_event_store();
|
|
55726
56343
|
init_transport_error();
|
|
55727
56344
|
init_streamable_http_transport();
|
|
55728
56345
|
init_transport_local_adapter();
|
|
@@ -55732,7 +56349,8 @@ var init_transport_streamable_http_adapter = __esm({
|
|
|
55732
56349
|
TransportStreamableHttpAdapter = class extends LocalTransportAdapter {
|
|
55733
56350
|
createTransport(sessionId, response) {
|
|
55734
56351
|
const sessionIdGenerator = resolveSessionIdGenerator(this.key.type, sessionId);
|
|
55735
|
-
const
|
|
56352
|
+
const sharedEventStore = this.scope.eventStore;
|
|
56353
|
+
const eventStore = sharedEventStore ? createSessionScopedEventStore(sharedEventStore, sessionId) : void 0;
|
|
55736
56354
|
const isStateless = this.key.type === "stateless-http";
|
|
55737
56355
|
return new RecreateableStreamableHTTPServerTransport({
|
|
55738
56356
|
sessionIdGenerator,
|
|
@@ -56789,11 +57407,11 @@ function parseUserScopes(user) {
|
|
|
56789
57407
|
const s = user?.scope;
|
|
56790
57408
|
return typeof s === "string" ? s.split(/\s+/).filter(Boolean) : [];
|
|
56791
57409
|
}
|
|
56792
|
-
var
|
|
57410
|
+
var import_auth39, import_lazy_zod106, import_utils96, plan40, httpRequestStateSchema, name41, Stage41, HttpRequestFlow;
|
|
56793
57411
|
var init_http_request_flow = __esm({
|
|
56794
57412
|
"libs/sdk/src/scope/flows/http.request.flow.ts"() {
|
|
56795
57413
|
"use strict";
|
|
56796
|
-
|
|
57414
|
+
import_auth39 = require("@frontmcp/auth");
|
|
56797
57415
|
import_lazy_zod106 = require("@frontmcp/lazy-zod");
|
|
56798
57416
|
import_utils96 = require("@frontmcp/utils");
|
|
56799
57417
|
init_session_verify_flow();
|
|
@@ -56987,20 +57605,20 @@ var init_http_request_flow = __esm({
|
|
|
56987
57605
|
const tokenStore = auth.orchestratedTokenStore;
|
|
56988
57606
|
let providers;
|
|
56989
57607
|
try {
|
|
56990
|
-
const authorizationId = (0,
|
|
57608
|
+
const authorizationId = (0, import_auth39.deriveAuthorizationId)(token);
|
|
56991
57609
|
const providerIds = await tokenStore.getProviderIds(authorizationId);
|
|
56992
57610
|
providers = Object.fromEntries(providerIds.map((id) => [id, { id }]));
|
|
56993
57611
|
} catch (err) {
|
|
56994
57612
|
this.logger.warn(`[${this.requestId}] failed to load orchestrated providers: ${String(err)}`);
|
|
56995
57613
|
}
|
|
56996
|
-
const authorization =
|
|
57614
|
+
const authorization = import_auth39.OrchestratedAuthorization.create({
|
|
56997
57615
|
token,
|
|
56998
57616
|
user: { sub: user.sub ?? "unknown", name: user.name, email: user.email, picture: user.picture },
|
|
56999
57617
|
expiresAt: user.exp ? user.exp * 1e3 : void 0,
|
|
57000
57618
|
tokenStore,
|
|
57001
57619
|
providers
|
|
57002
57620
|
});
|
|
57003
|
-
ctx.setContextToken(
|
|
57621
|
+
ctx.setContextToken(import_auth39.ORCHESTRATED_AUTH_ACCESSOR, new import_auth39.OrchestratedAuthAccessorAdapter(authorization));
|
|
57004
57622
|
}
|
|
57005
57623
|
async router() {
|
|
57006
57624
|
try {
|
|
@@ -59611,7 +60229,13 @@ function normalizeAppScope(appItem, metadata) {
|
|
|
59611
60229
|
...metadata,
|
|
59612
60230
|
id: scopeId,
|
|
59613
60231
|
apps: [appItem],
|
|
59614
|
-
auth
|
|
60232
|
+
// An app that declares no `auth` INHERITS the server's policy. Writing
|
|
60233
|
+
// `appMetadata.auth` unconditionally set the key to `undefined`, which
|
|
60234
|
+
// overrode the spread above and dropped the server's auth entirely — so a
|
|
60235
|
+
// standalone app silently became public (GHSA-rgxj-434m-vxh3, where the
|
|
60236
|
+
// dashboard's own scope was the case in point). Declaring
|
|
60237
|
+
// `auth: { mode: 'public' }` explicitly is still honoured.
|
|
60238
|
+
auth: appMetadata.auth ?? metadata.auth,
|
|
59615
60239
|
// For a splitByApp scope, auth (and any auth.ui paths) come from `@App`, so
|
|
59616
60240
|
// anchor on the App's captured source dir. Fall back to the server's.
|
|
59617
60241
|
// `__sourceDir` lives on LocalAppMetadata only; read it defensively.
|
|
@@ -59875,6 +60499,11 @@ function createWebFetchHandler(scope, options = {}) {
|
|
|
59875
60499
|
const entryPaths = new Set((Array.isArray(rawEntry) ? rawEntry : [rawEntry]).map(normalizePath));
|
|
59876
60500
|
const cors2 = options.cors ?? mapHttpCors(httpConfig?.cors, scope);
|
|
59877
60501
|
const corsEnabled = cors2?.origin !== void 0 && cors2.origin !== false;
|
|
60502
|
+
const rebinding = httpConfig?.security?.dnsRebindingProtection;
|
|
60503
|
+
const hostValidation = rebinding?.enabled === false || !rebinding?.allowedHosts?.length && !rebinding?.allowedOrigins?.length ? void 0 : compileHostValidation({
|
|
60504
|
+
allowedHosts: rebinding.allowedHosts,
|
|
60505
|
+
allowedOrigins: rebinding.allowedOrigins
|
|
60506
|
+
});
|
|
59878
60507
|
const corsHeadersFor = (request) => {
|
|
59879
60508
|
if (!corsEnabled) return {};
|
|
59880
60509
|
const reqOrigin = request.headers.get("origin") ?? "";
|
|
@@ -59905,6 +60534,19 @@ function createWebFetchHandler(scope, options = {}) {
|
|
|
59905
60534
|
};
|
|
59906
60535
|
return async function handle(request, ctx, env) {
|
|
59907
60536
|
const url = new URL(request.url);
|
|
60537
|
+
if (hostValidation) {
|
|
60538
|
+
const rejection = validateHostHeaders(
|
|
60539
|
+
{
|
|
60540
|
+
host: request.headers.get("host") ?? url.host,
|
|
60541
|
+
forwardedHost: request.headers.get("x-forwarded-host") ?? void 0,
|
|
60542
|
+
origin: request.headers.get("origin") ?? void 0
|
|
60543
|
+
},
|
|
60544
|
+
hostValidation
|
|
60545
|
+
);
|
|
60546
|
+
if (rejection) {
|
|
60547
|
+
return Response.json({ error: rejection.error, message: rejection.message }, { status: rejection.status });
|
|
60548
|
+
}
|
|
60549
|
+
}
|
|
59908
60550
|
if (corsEnabled && request.method === "OPTIONS") {
|
|
59909
60551
|
return new Response(null, { status: 204, headers: corsHeadersFor(request) });
|
|
59910
60552
|
}
|
|
@@ -60038,6 +60680,7 @@ var init_web_fetch_handler = __esm({
|
|
|
60038
60680
|
init_server_tokens();
|
|
60039
60681
|
init_path_utils();
|
|
60040
60682
|
init_errors();
|
|
60683
|
+
init_host_validation();
|
|
60041
60684
|
init_web_response_renderer();
|
|
60042
60685
|
}
|
|
60043
60686
|
});
|
|
@@ -60190,39 +60833,22 @@ function createHostValidationMiddleware(options) {
|
|
|
60190
60833
|
if (!options.enabled) {
|
|
60191
60834
|
return (_req, _res, next) => next();
|
|
60192
60835
|
}
|
|
60193
|
-
const
|
|
60194
|
-
|
|
60836
|
+
const compiled = compileHostValidation({
|
|
60837
|
+
allowedHosts: options.allowedHosts,
|
|
60838
|
+
allowedOrigins: options.allowedOrigins
|
|
60839
|
+
});
|
|
60195
60840
|
return (req, res, next) => {
|
|
60196
|
-
|
|
60197
|
-
|
|
60198
|
-
|
|
60199
|
-
|
|
60200
|
-
|
|
60201
|
-
|
|
60202
|
-
|
|
60203
|
-
|
|
60204
|
-
|
|
60205
|
-
|
|
60206
|
-
|
|
60207
|
-
const candidates = forwardedHost.split(",").map((h2) => h2.trim()).filter(Boolean);
|
|
60208
|
-
if (candidates.some((h2) => !allowedHostsSet.has(h2))) {
|
|
60209
|
-
res.status(403).json({
|
|
60210
|
-
error: "Forbidden",
|
|
60211
|
-
message: "Invalid X-Forwarded-Host header"
|
|
60212
|
-
});
|
|
60213
|
-
return;
|
|
60214
|
-
}
|
|
60215
|
-
}
|
|
60216
|
-
}
|
|
60217
|
-
if (allowedOriginsSet) {
|
|
60218
|
-
const origin = req.headers?.["origin"];
|
|
60219
|
-
if (origin && !allowedOriginsSet.has(origin)) {
|
|
60220
|
-
res.status(403).json({
|
|
60221
|
-
error: "Forbidden",
|
|
60222
|
-
message: "Invalid Origin header"
|
|
60223
|
-
});
|
|
60224
|
-
return;
|
|
60225
|
-
}
|
|
60841
|
+
const rejection = validateHostHeaders(
|
|
60842
|
+
{
|
|
60843
|
+
host: req.headers?.["host"],
|
|
60844
|
+
forwardedHost: req.headers?.["x-forwarded-host"],
|
|
60845
|
+
origin: req.headers?.["origin"]
|
|
60846
|
+
},
|
|
60847
|
+
compiled
|
|
60848
|
+
);
|
|
60849
|
+
if (rejection) {
|
|
60850
|
+
res.status(rejection.status).json({ error: rejection.error, message: rejection.message });
|
|
60851
|
+
return;
|
|
60226
60852
|
}
|
|
60227
60853
|
next();
|
|
60228
60854
|
};
|
|
@@ -60230,6 +60856,82 @@ function createHostValidationMiddleware(options) {
|
|
|
60230
60856
|
var init_host_validation_middleware = __esm({
|
|
60231
60857
|
"libs/sdk/src/server/middleware/host-validation.middleware.ts"() {
|
|
60232
60858
|
"use strict";
|
|
60859
|
+
init_host_validation();
|
|
60860
|
+
}
|
|
60861
|
+
});
|
|
60862
|
+
|
|
60863
|
+
// libs/sdk/src/server/security/resolve-allowed-hosts.ts
|
|
60864
|
+
function canonicalizeAddress(address) {
|
|
60865
|
+
const bare = address.trim().toLowerCase().replace(/^\[|\]$/g, "");
|
|
60866
|
+
if (!bare.includes(":")) return bare;
|
|
60867
|
+
try {
|
|
60868
|
+
return new URL(`http://[${bare}]`).hostname.replace(/^\[|\]$/g, "");
|
|
60869
|
+
} catch {
|
|
60870
|
+
return bare;
|
|
60871
|
+
}
|
|
60872
|
+
}
|
|
60873
|
+
function isLoopbackAddress(address) {
|
|
60874
|
+
if (!address) return false;
|
|
60875
|
+
const normalized = canonicalizeAddress(address);
|
|
60876
|
+
return normalized === "::1" || normalized === "localhost" || normalized.startsWith("127.") || // IPv4-mapped IPv6 loopback, which `URL` renders as `::ffff:7f00:1`.
|
|
60877
|
+
normalized === "::ffff:7f00:1" || normalized.startsWith("::ffff:127.");
|
|
60878
|
+
}
|
|
60879
|
+
function withAndWithoutPort(host, port) {
|
|
60880
|
+
return port ? [host, `${host}:${port}`] : [host];
|
|
60881
|
+
}
|
|
60882
|
+
function hostFromUrl(url) {
|
|
60883
|
+
if (!url) return void 0;
|
|
60884
|
+
try {
|
|
60885
|
+
return new URL(url).host || void 0;
|
|
60886
|
+
} catch {
|
|
60887
|
+
return void 0;
|
|
60888
|
+
}
|
|
60889
|
+
}
|
|
60890
|
+
function isRoutableBind(address) {
|
|
60891
|
+
if (!address) return false;
|
|
60892
|
+
return !isLoopbackAddress(address);
|
|
60893
|
+
}
|
|
60894
|
+
function deriveAllowedHosts(input) {
|
|
60895
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
60896
|
+
if (input.socketPath) return [];
|
|
60897
|
+
for (const name42 of LOOPBACK_NAMES) {
|
|
60898
|
+
for (const value of withAndWithoutPort(name42, input.port)) hosts.add(value);
|
|
60899
|
+
}
|
|
60900
|
+
const bindAddress = input.bindAddress?.trim();
|
|
60901
|
+
if (bindAddress && !isLoopbackAddress(bindAddress) && bindAddress !== "0.0.0.0" && bindAddress !== "::") {
|
|
60902
|
+
const literal = bindAddress.includes(":") && !bindAddress.startsWith("[") ? `[${bindAddress}]` : bindAddress;
|
|
60903
|
+
for (const value of withAndWithoutPort(literal, input.port)) hosts.add(value);
|
|
60904
|
+
}
|
|
60905
|
+
const issuerHost = hostFromUrl(input.issuer);
|
|
60906
|
+
if (issuerHost) {
|
|
60907
|
+
hosts.add(issuerHost);
|
|
60908
|
+
const [name42] = issuerHost.split(":");
|
|
60909
|
+
if (!issuerHost.includes(":") && input.port) hosts.add(`${name42}:${input.port}`);
|
|
60910
|
+
}
|
|
60911
|
+
for (const extra of input.extraHosts ?? []) {
|
|
60912
|
+
const trimmed = extra.trim();
|
|
60913
|
+
if (trimmed) hosts.add(trimmed);
|
|
60914
|
+
}
|
|
60915
|
+
return [...hosts];
|
|
60916
|
+
}
|
|
60917
|
+
function shouldEnforceDerivedHosts(input) {
|
|
60918
|
+
if (input.socketPath) return false;
|
|
60919
|
+
const hasExplicitName = hostFromUrl(input.issuer) !== void 0 || (input.extraHosts ?? []).some((h2) => h2.trim());
|
|
60920
|
+
if (hasExplicitName) return true;
|
|
60921
|
+
if (input.port === void 0) return false;
|
|
60922
|
+
return !isRoutableBind(input.bindAddress);
|
|
60923
|
+
}
|
|
60924
|
+
function allowedHostsFromEnv(env = process.env) {
|
|
60925
|
+
const raw = env["FRONTMCP_ALLOWED_HOSTS"]?.trim();
|
|
60926
|
+
if (!raw) return void 0;
|
|
60927
|
+
const hosts = raw.split(",").map((h2) => h2.trim()).filter(Boolean);
|
|
60928
|
+
return hosts.length > 0 ? hosts : void 0;
|
|
60929
|
+
}
|
|
60930
|
+
var LOOPBACK_NAMES;
|
|
60931
|
+
var init_resolve_allowed_hosts = __esm({
|
|
60932
|
+
"libs/sdk/src/server/security/resolve-allowed-hosts.ts"() {
|
|
60933
|
+
"use strict";
|
|
60934
|
+
LOOPBACK_NAMES = ["localhost", "127.0.0.1", "[::1]"];
|
|
60233
60935
|
}
|
|
60234
60936
|
});
|
|
60235
60937
|
|
|
@@ -60255,12 +60957,17 @@ var init_express_host_adapter = __esm({
|
|
|
60255
60957
|
import_utils98 = require("@frontmcp/utils");
|
|
60256
60958
|
init_mcp_error();
|
|
60257
60959
|
init_host_validation_middleware();
|
|
60960
|
+
init_resolve_allowed_hosts();
|
|
60258
60961
|
init_base_host_adapter();
|
|
60259
60962
|
DEFAULT_EXPRESS_BODY_LIMIT = "4mb";
|
|
60260
60963
|
ExpressHostAdapter = class extends HostServerAdapter {
|
|
60261
60964
|
app = (0, import_express.default)();
|
|
60262
60965
|
router = import_express.default.Router();
|
|
60263
60966
|
prepared = false;
|
|
60967
|
+
/** Active host-validation middleware, or undefined while nothing is enforced. */
|
|
60968
|
+
hostValidation;
|
|
60969
|
+
/** Builds the derived allow-list, once this process commits to listening. */
|
|
60970
|
+
deriveHostValidation;
|
|
60264
60971
|
constructor(options) {
|
|
60265
60972
|
super();
|
|
60266
60973
|
const corsOptions = options?.cors;
|
|
@@ -60274,6 +60981,7 @@ var init_express_host_adapter = __esm({
|
|
|
60274
60981
|
})
|
|
60275
60982
|
);
|
|
60276
60983
|
}
|
|
60984
|
+
this.installHostValidation(options);
|
|
60277
60985
|
const jsonLimit = options?.bodyLimit ?? DEFAULT_EXPRESS_BODY_LIMIT;
|
|
60278
60986
|
const formLimit = options?.urlencodedLimit ?? jsonLimit;
|
|
60279
60987
|
const headerContains = (req, needle) => (req.headers["content-type"] ?? "").toLowerCase().includes(needle);
|
|
@@ -60294,23 +61002,6 @@ var init_express_host_adapter = __esm({
|
|
|
60294
61002
|
}
|
|
60295
61003
|
next(err);
|
|
60296
61004
|
});
|
|
60297
|
-
const securityOpts = options?.security;
|
|
60298
|
-
if (securityOpts?.dnsRebindingProtection?.enabled || securityOpts?.strict) {
|
|
60299
|
-
const allowedHosts = securityOpts.dnsRebindingProtection?.allowedHosts;
|
|
60300
|
-
const allowedOrigins = securityOpts.dnsRebindingProtection?.allowedOrigins;
|
|
60301
|
-
const effectiveHosts = allowedHosts ?? (securityOpts.strict ? ["localhost", "127.0.0.1"] : void 0);
|
|
60302
|
-
if (!effectiveHosts?.length && !allowedOrigins?.length) {
|
|
60303
|
-
throw new Error(
|
|
60304
|
-
"security.dnsRebindingProtection is enabled but no allowedHosts or allowedOrigins are configured. Provide at least one allowedHosts entry or disable dnsRebindingProtection."
|
|
60305
|
-
);
|
|
60306
|
-
}
|
|
60307
|
-
const hostValidation = createHostValidationMiddleware({
|
|
60308
|
-
enabled: true,
|
|
60309
|
-
allowedHosts: effectiveHosts,
|
|
60310
|
-
allowedOrigins
|
|
60311
|
-
});
|
|
60312
|
-
this.app.use(hostValidation);
|
|
60313
|
-
}
|
|
60314
61005
|
this.app.use((req, res, next) => {
|
|
60315
61006
|
if (corsEnabled) {
|
|
60316
61007
|
res.setHeader("Access-Control-Expose-Headers", "WWW-Authenticate, Mcp-Session-Id");
|
|
@@ -60320,6 +61011,64 @@ var init_express_host_adapter = __esm({
|
|
|
60320
61011
|
next();
|
|
60321
61012
|
});
|
|
60322
61013
|
}
|
|
61014
|
+
/**
|
|
61015
|
+
* Install DNS-rebinding protection.
|
|
61016
|
+
*
|
|
61017
|
+
* ON by default since v1.7.2 (BC-035). `allowedHosts` falls back to the names
|
|
61018
|
+
* this process actually answers to — the loopback aliases with and without
|
|
61019
|
+
* the bound port, the bound NIC address, and the configured issuer host.
|
|
61020
|
+
*
|
|
61021
|
+
* Escape hatches, in precedence order: explicit
|
|
61022
|
+
* `security.dnsRebindingProtection.allowedHosts`, the
|
|
61023
|
+
* `FRONTMCP_ALLOWED_HOSTS` env var, or `enabled: false` to turn it off.
|
|
61024
|
+
*/
|
|
61025
|
+
installHostValidation(options) {
|
|
61026
|
+
const protection = options?.security?.dnsRebindingProtection;
|
|
61027
|
+
if (protection?.enabled === false) return;
|
|
61028
|
+
const allowedOrigins = protection?.allowedOrigins;
|
|
61029
|
+
const explicitHosts = protection?.allowedHosts ?? allowedHostsFromEnv();
|
|
61030
|
+
if (explicitHosts?.length || allowedOrigins?.length) {
|
|
61031
|
+
this.hostValidation = createHostValidationMiddleware({
|
|
61032
|
+
enabled: true,
|
|
61033
|
+
allowedHosts: explicitHosts?.length ? explicitHosts : void 0,
|
|
61034
|
+
allowedOrigins
|
|
61035
|
+
});
|
|
61036
|
+
} else {
|
|
61037
|
+
this.deriveHostValidation = (portOrSocketPath, bindAddress) => {
|
|
61038
|
+
const isSocket = typeof portOrSocketPath === "string";
|
|
61039
|
+
const derivation = {
|
|
61040
|
+
// `start()` binds `bindAddress ?? '0.0.0.0'`; mirror that exactly.
|
|
61041
|
+
bindAddress: isSocket ? void 0 : bindAddress ?? "0.0.0.0",
|
|
61042
|
+
port: isSocket ? void 0 : portOrSocketPath,
|
|
61043
|
+
socketPath: isSocket ? portOrSocketPath : void 0,
|
|
61044
|
+
issuer: options?.listen?.issuer
|
|
61045
|
+
};
|
|
61046
|
+
if (!shouldEnforceDerivedHosts(derivation)) {
|
|
61047
|
+
console.warn(
|
|
61048
|
+
`[frontmcp] DNS-rebinding protection is not enforcing a Host allow-list: the server binds a routable address (${derivation.bindAddress ?? "unknown"}) and no allowed hosts are configured. Set security.dnsRebindingProtection.allowedHosts (or FRONTMCP_ALLOWED_HOSTS) to your public hostname(s).`
|
|
61049
|
+
);
|
|
61050
|
+
return void 0;
|
|
61051
|
+
}
|
|
61052
|
+
const allowedHosts = deriveAllowedHosts(derivation);
|
|
61053
|
+
if (!allowedHosts.length) return void 0;
|
|
61054
|
+
return createHostValidationMiddleware({ enabled: true, allowedHosts });
|
|
61055
|
+
};
|
|
61056
|
+
}
|
|
61057
|
+
this.app.use(((req, res, next) => {
|
|
61058
|
+
const validate = this.hostValidation;
|
|
61059
|
+
if (!validate) return next();
|
|
61060
|
+
return validate(req, res, next);
|
|
61061
|
+
}));
|
|
61062
|
+
}
|
|
61063
|
+
/**
|
|
61064
|
+
* Enable the DERIVED host allow-list. Called from `start()` only — see
|
|
61065
|
+
* `installHostValidation`.
|
|
61066
|
+
*/
|
|
61067
|
+
enableDerivedHostValidation(portOrSocketPath, bindAddress) {
|
|
61068
|
+
if (this.hostValidation || !this.deriveHostValidation) return;
|
|
61069
|
+
this.hostValidation = this.deriveHostValidation(portOrSocketPath, bindAddress);
|
|
61070
|
+
this.deriveHostValidation = void 0;
|
|
61071
|
+
}
|
|
60323
61072
|
registerRoute(method, path3, handler) {
|
|
60324
61073
|
this.router[method.toLowerCase()](path3, this.enhancedHandler(handler));
|
|
60325
61074
|
}
|
|
@@ -60352,6 +61101,7 @@ var init_express_host_adapter = __esm({
|
|
|
60352
61101
|
return this.app;
|
|
60353
61102
|
}
|
|
60354
61103
|
async start(portOrSocketPath, bindAddress) {
|
|
61104
|
+
this.enableDerivedHostValidation(portOrSocketPath, bindAddress);
|
|
60355
61105
|
this.prepare();
|
|
60356
61106
|
const server = http.createServer(this.app);
|
|
60357
61107
|
server.requestTimeout = 0;
|
|
@@ -63241,6 +63991,7 @@ __export(index_exports, {
|
|
|
63241
63991
|
DynamicAdapter: () => DynamicAdapter,
|
|
63242
63992
|
DynamicAdapterNameError: () => DynamicAdapterNameError,
|
|
63243
63993
|
DynamicJobDirectExecutionError: () => DynamicJobDirectExecutionError,
|
|
63994
|
+
DynamicJobRegistrationDisabledError: () => DynamicJobRegistrationDisabledError,
|
|
63244
63995
|
DynamicPlugin: () => DynamicPlugin,
|
|
63245
63996
|
ELICITATION_FALLBACK_JSON_SCHEMA: () => ELICITATION_FALLBACK_JSON_SCHEMA,
|
|
63246
63997
|
ESM_SH_BASE_URL: () => ESM_SH_BASE_URL,
|
|
@@ -63364,7 +64115,7 @@ __export(index_exports, {
|
|
|
63364
64115
|
HttpTextSchema: () => HttpTextSchema,
|
|
63365
64116
|
HttpWebResponseSchema: () => HttpWebResponseSchema,
|
|
63366
64117
|
ImageOutputSchema: () => ImageOutputSchema,
|
|
63367
|
-
InMemoryOrchestratedTokenStore: () =>
|
|
64118
|
+
InMemoryOrchestratedTokenStore: () => import_auth41.InMemoryOrchestratedTokenStore,
|
|
63368
64119
|
InMemoryStoreRequiredError: () => import_auth11.InMemoryStoreRequiredError,
|
|
63369
64120
|
InProcessTaskRunner: () => InProcessTaskRunner,
|
|
63370
64121
|
InputRequiredSignal: () => InputRequiredSignal,
|
|
@@ -63395,6 +64146,7 @@ __export(index_exports, {
|
|
|
63395
64146
|
JobExecutionManager: () => JobExecutionManager,
|
|
63396
64147
|
JobInstance: () => JobInstance,
|
|
63397
64148
|
JobKind: () => JobKind,
|
|
64149
|
+
JobNotAuthorizedError: () => JobNotAuthorizedError,
|
|
63398
64150
|
JobPermissionGuard: () => JobPermissionGuard,
|
|
63399
64151
|
JobRegistry: () => JobRegistry,
|
|
63400
64152
|
LLM_ADAPTER: () => LLM_ADAPTER,
|
|
@@ -63912,6 +64664,7 @@ __export(index_exports, {
|
|
|
63912
64664
|
resolveNameConflicts: () => resolveNameConflicts,
|
|
63913
64665
|
resolveOutputSchemaMode: () => resolveOutputSchemaMode,
|
|
63914
64666
|
resolvePendingTC39HooksForClass: () => resolvePendingTC39HooksForClass,
|
|
64667
|
+
resolvePrincipal: () => resolvePrincipal2,
|
|
63915
64668
|
resolveRegistryToken: () => resolveRegistryToken,
|
|
63916
64669
|
resolveSchemaDescriptionFormat: () => resolveSchemaDescriptionFormat,
|
|
63917
64670
|
resolveToolVisibility: () => resolveToolVisibility,
|
|
@@ -64004,49 +64757,7 @@ init_job_registry();
|
|
|
64004
64757
|
init_job_instance();
|
|
64005
64758
|
init_job_events();
|
|
64006
64759
|
init_job_utils();
|
|
64007
|
-
|
|
64008
|
-
// libs/sdk/src/job/job-permission.guard.ts
|
|
64009
|
-
var JobPermissionGuard = class _JobPermissionGuard {
|
|
64010
|
-
/**
|
|
64011
|
-
* Check if the given auth info is allowed to perform the action.
|
|
64012
|
-
* Returns true if no permissions are defined (permissive by default).
|
|
64013
|
-
*/
|
|
64014
|
-
static async check(permissions, action, authInfo) {
|
|
64015
|
-
if (!permissions || permissions.length === 0) {
|
|
64016
|
-
return true;
|
|
64017
|
-
}
|
|
64018
|
-
const relevant = permissions.filter((p) => p.action === action);
|
|
64019
|
-
if (relevant.length === 0) {
|
|
64020
|
-
return true;
|
|
64021
|
-
}
|
|
64022
|
-
for (const perm of relevant) {
|
|
64023
|
-
const passed = await _JobPermissionGuard.checkSingle(perm, authInfo);
|
|
64024
|
-
if (!passed) return false;
|
|
64025
|
-
}
|
|
64026
|
-
return true;
|
|
64027
|
-
}
|
|
64028
|
-
static async checkSingle(perm, authInfo) {
|
|
64029
|
-
if (perm.roles && perm.roles.length > 0) {
|
|
64030
|
-
const userRoles = authInfo["roles"];
|
|
64031
|
-
if (!Array.isArray(userRoles)) return false;
|
|
64032
|
-
const hasRole = perm.roles.some((r) => userRoles.includes(r));
|
|
64033
|
-
if (!hasRole) return false;
|
|
64034
|
-
}
|
|
64035
|
-
if (perm.scopes && perm.scopes.length > 0) {
|
|
64036
|
-
const userScopes = authInfo["scopes"] ?? authInfo["scope"];
|
|
64037
|
-
const scopeArr = typeof userScopes === "string" ? userScopes.split(" ") : Array.isArray(userScopes) ? userScopes : [];
|
|
64038
|
-
const hasScope = perm.scopes.some((s) => scopeArr.includes(s));
|
|
64039
|
-
if (!hasScope) return false;
|
|
64040
|
-
}
|
|
64041
|
-
if (perm.custom) {
|
|
64042
|
-
const result = await perm.custom(authInfo);
|
|
64043
|
-
if (!result) return false;
|
|
64044
|
-
}
|
|
64045
|
-
return true;
|
|
64046
|
-
}
|
|
64047
|
-
};
|
|
64048
|
-
|
|
64049
|
-
// libs/sdk/src/job/index.ts
|
|
64760
|
+
init_job_permission_guard();
|
|
64050
64761
|
init_job_scope_helper();
|
|
64051
64762
|
|
|
64052
64763
|
// libs/sdk/src/job/execution/index.ts
|
|
@@ -64342,7 +65053,7 @@ init_mcp_20260728();
|
|
|
64342
65053
|
init_mcp_stateless_client_adapter();
|
|
64343
65054
|
var import_protocol57 = require("@frontmcp/protocol");
|
|
64344
65055
|
init_ext_apps2();
|
|
64345
|
-
var
|
|
65056
|
+
var import_auth41 = require("@frontmcp/auth");
|
|
64346
65057
|
(function suppressZodExpressWarning() {
|
|
64347
65058
|
if (typeof process === "undefined" || typeof process.emitWarning !== "function") return;
|
|
64348
65059
|
const originalEmitWarning = process.emitWarning.bind(process);
|
|
@@ -64449,6 +65160,7 @@ var ChannelListHook = FlowHooksOf("channels:list");
|
|
|
64449
65160
|
DynamicAdapter,
|
|
64450
65161
|
DynamicAdapterNameError,
|
|
64451
65162
|
DynamicJobDirectExecutionError,
|
|
65163
|
+
DynamicJobRegistrationDisabledError,
|
|
64452
65164
|
DynamicPlugin,
|
|
64453
65165
|
ELICITATION_FALLBACK_JSON_SCHEMA,
|
|
64454
65166
|
ESM_SH_BASE_URL,
|
|
@@ -64603,6 +65315,7 @@ var ChannelListHook = FlowHooksOf("channels:list");
|
|
|
64603
65315
|
JobExecutionManager,
|
|
64604
65316
|
JobInstance,
|
|
64605
65317
|
JobKind,
|
|
65318
|
+
JobNotAuthorizedError,
|
|
64606
65319
|
JobPermissionGuard,
|
|
64607
65320
|
JobRegistry,
|
|
64608
65321
|
LLM_ADAPTER,
|
|
@@ -65119,6 +65832,7 @@ var ChannelListHook = FlowHooksOf("channels:list");
|
|
|
65119
65832
|
resolveNameConflicts,
|
|
65120
65833
|
resolveOutputSchemaMode,
|
|
65121
65834
|
resolvePendingTC39HooksForClass,
|
|
65835
|
+
resolvePrincipal,
|
|
65122
65836
|
resolveRegistryToken,
|
|
65123
65837
|
resolveSchemaDescriptionFormat,
|
|
65124
65838
|
resolveToolVisibility,
|