@opengeni/api-router 0.23.1 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.d.ts +3 -2
- package/dist/app.js +5 -3
- package/dist/auth/canonical-human-session-admission.d.ts +27 -0
- package/dist/browser-auth-broker.d.ts +20 -0
- package/dist/browser-controller-authority.d.ts +61 -0
- package/dist/browser-network-route.d.ts +8 -0
- package/dist/browser-state-authority.d.ts +27 -0
- package/dist/{chunk-T4T2PGU4.js → chunk-2PQMSRCB.js} +37891 -17463
- package/dist/chunk-2PQMSRCB.js.map +1 -0
- package/dist/codemode.d.ts +23 -0
- package/dist/controller-data-plane.d.ts +12 -0
- package/dist/editable-artifact-live-hints.d.ts +11 -0
- package/dist/editable-artifact-native-kernel.d.ts +37 -0
- package/dist/editable-artifact-office-import.d.ts +22 -0
- package/dist/editable-artifact-production.d.ts +29 -0
- package/dist/editable-artifact-websocket.d.ts +49 -0
- package/dist/editable-artifact-workspace-files.d.ts +23 -0
- package/dist/github-browser-flow.d.ts +6 -0
- package/dist/http/auth.d.ts +1 -1
- package/dist/http/cors.d.ts +1 -0
- package/dist/http/sse.d.ts +18 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1796 -22
- package/dist/index.js.map +1 -1
- package/dist/integrations/api-integrations.d.ts +24 -0
- package/dist/integrations/atlassian.d.ts +176 -0
- package/dist/integrations/fiken.d.ts +233 -0
- package/dist/integrations/github-skill-source.d.ts +5 -0
- package/dist/integrations/google-drive.d.ts +85 -0
- package/dist/integrations/oauth-client.d.ts +22 -1
- package/dist/integrations/provider-oauth.d.ts +17 -0
- package/dist/integrations/slack-bot.d.ts +175 -1
- package/dist/integrations/slack-interactions.d.ts +24 -6
- package/dist/integrations/social-api.d.ts +2 -1
- package/dist/interaction-holder-heartbeat.d.ts +17 -0
- package/dist/interaction-metrics.d.ts +11 -0
- package/dist/mcp/editable-artifact-query-schema.d.ts +4 -0
- package/dist/mcp/editable-artifacts.d.ts +13 -0
- package/dist/mcp/scheduled-task-view.d.ts +168 -0
- package/dist/mcp/server.d.ts +16 -4
- package/dist/memory-slack-delivery.d.ts +9 -0
- package/dist/model-catalog.d.ts +1 -0
- package/dist/routes/api-integrations.d.ts +8 -0
- package/dist/routes/browser-identities.d.ts +5 -0
- package/dist/routes/browser-sessions.d.ts +6 -0
- package/dist/routes/canonical-human-identities.d.ts +3 -0
- package/dist/routes/channels.d.ts +3 -0
- package/dist/routes/company-profile.d.ts +3 -0
- package/dist/routes/computer-sessions.d.ts +6 -0
- package/dist/routes/editable-artifacts.d.ts +44 -0
- package/dist/routes/integration-facets.d.ts +3 -0
- package/dist/routes/interaction-resources.d.ts +5 -0
- package/dist/routes/memory-slack-publications.d.ts +6 -0
- package/dist/routes/organization-memberships.d.ts +3 -0
- package/dist/routes/plugins.d.ts +8 -0
- package/dist/routes/sessions.d.ts +18 -2
- package/dist/routes/skills.d.ts +6 -0
- package/dist/routes/slack-task-policy.d.ts +3 -0
- package/dist/routes/supergrok.d.ts +3 -0
- package/dist/routes/video-generation.d.ts +3 -0
- package/dist/sandbox/channel-a.d.ts +62 -2
- package/dist/sandbox/machines.d.ts +2 -2
- package/dist/sandbox/metrics-ingestion.d.ts +6 -1
- package/dist/sandbox/viewer.d.ts +7 -3
- package/dist/slack-reaction-files.d.ts +27 -0
- package/dist/temporal-schedule-cleanup.d.ts +26 -0
- package/dist/transcription/providers/xai-subscription.d.ts +8 -0
- package/dist/xai-realtime.d.ts +26 -0
- package/dist/xai-subscription-auth.d.ts +23 -0
- package/package.json +20 -14
- package/src/app.ts +604 -64
- package/src/auth/canonical-human-session-admission.ts +116 -0
- package/src/auth/managed-auth.ts +101 -0
- package/src/browser-auth-broker.ts +155 -0
- package/src/browser-controller-authority.ts +186 -0
- package/src/browser-network-route.ts +34 -0
- package/src/browser-state-authority.ts +236 -0
- package/src/codemode.ts +186 -0
- package/src/controller-data-plane.ts +35 -0
- package/src/editable-artifact-live-hints.ts +64 -0
- package/src/editable-artifact-native-kernel.ts +659 -0
- package/src/editable-artifact-office-import.ts +230 -0
- package/src/editable-artifact-production.ts +419 -0
- package/src/editable-artifact-websocket.ts +311 -0
- package/src/editable-artifact-workspace-files.ts +186 -0
- package/src/github-browser-flow.ts +35 -6
- package/src/http/auth.ts +29 -9
- package/src/http/cors.ts +3 -0
- package/src/http/sse.ts +229 -5
- package/src/index.ts +119 -20
- package/src/integrations/api-integrations.ts +365 -0
- package/src/integrations/atlassian.ts +1621 -0
- package/src/integrations/fiken.ts +1230 -0
- package/src/integrations/github-skill-source.ts +142 -0
- package/src/integrations/google-drive.ts +1005 -64
- package/src/integrations/oauth-client.ts +123 -33
- package/src/integrations/provider-oauth.ts +756 -0
- package/src/integrations/slack-bot.ts +669 -11
- package/src/integrations/slack-interactions.ts +2063 -93
- package/src/integrations/social-api.ts +11 -0
- package/src/interaction-holder-heartbeat.ts +95 -0
- package/src/interaction-metrics.ts +159 -0
- package/src/mcp/documents.ts +146 -6
- package/src/mcp/editable-artifact-query-schema.ts +236 -0
- package/src/mcp/editable-artifacts.ts +448 -0
- package/src/mcp/scheduled-task-view.ts +34 -0
- package/src/mcp/server.ts +1756 -355
- package/src/memory-slack-delivery.ts +209 -0
- package/src/model-catalog.ts +19 -6
- package/src/routes/api-integrations.ts +410 -0
- package/src/routes/browser-identities.ts +160 -0
- package/src/routes/browser-sessions.ts +4482 -0
- package/src/routes/canonical-human-identities.ts +156 -0
- package/src/routes/channels.ts +90 -0
- package/src/routes/company-profile.ts +255 -0
- package/src/routes/computer-sessions.ts +1602 -0
- package/src/routes/connections.ts +421 -99
- package/src/routes/documents.ts +22 -3
- package/src/routes/editable-artifacts.ts +1159 -0
- package/src/routes/files.ts +106 -4
- package/src/routes/github.ts +18 -2
- package/src/routes/install.ts +7 -1
- package/src/routes/integration-facets.ts +258 -0
- package/src/routes/interaction-resources.ts +595 -0
- package/src/routes/memory-slack-publications.ts +216 -0
- package/src/routes/organization-memberships.ts +53 -0
- package/src/routes/packs.ts +437 -7
- package/src/routes/plugins.ts +752 -0
- package/src/routes/rigs.ts +77 -20
- package/src/routes/scheduled-tasks.ts +67 -40
- package/src/routes/sessions.ts +830 -278
- package/src/routes/skills.ts +256 -0
- package/src/routes/slack-task-policy.ts +115 -0
- package/src/routes/supergrok.ts +717 -0
- package/src/routes/transcription-recordings.ts +9 -2
- package/src/routes/transcriptions.ts +2 -1
- package/src/routes/video-generation.ts +162 -0
- package/src/routes/workspaces.ts +68 -28
- package/src/sandbox/channel-a.ts +750 -116
- package/src/sandbox/machines.ts +5 -5
- package/src/sandbox/metrics-ingestion.ts +121 -3
- package/src/sandbox/rematerialize.ts +35 -47
- package/src/sandbox/viewer.ts +39 -12
- package/src/slack-reaction-files.ts +181 -0
- package/src/temporal-schedule-cleanup.ts +135 -0
- package/src/transcription/providers/xai-subscription.ts +110 -0
- package/src/transcription/service.ts +17 -2
- package/src/xai-realtime.ts +216 -0
- package/src/xai-subscription-auth.ts +132 -0
- package/dist/chunk-T4T2PGU4.js.map +0 -1
- package/dist/mcp/toolspace.d.ts +0 -71
- package/src/mcp/toolspace.ts +0 -1190
|
@@ -45,8 +45,16 @@ import { canonicalProviderDomain } from "./provider-domain";
|
|
|
45
45
|
|
|
46
46
|
export const oauthStateTtlMs = 10 * 60 * 1000;
|
|
47
47
|
export const OFFICIAL_SLACK_MCP_URL = OPENGENI_PERSONAL_SLACK_MCP_URL;
|
|
48
|
+
export const OFFICIAL_GMAIL_MCP_URL = "https://gmailmcp.googleapis.com/mcp/v1";
|
|
49
|
+
export const OFFICIAL_GMAIL_MCP_SCOPES = [
|
|
50
|
+
"https://www.googleapis.com/auth/gmail.readonly",
|
|
51
|
+
"https://www.googleapis.com/auth/gmail.compose",
|
|
52
|
+
"https://www.googleapis.com/auth/gmail.modify",
|
|
53
|
+
] as const;
|
|
48
54
|
const SLACK_OAUTH_ORIGIN = "https://slack.com";
|
|
49
55
|
const SLACK_MCP_ORIGIN = "https://mcp.slack.com";
|
|
56
|
+
const GOOGLE_OAUTH_ISSUER_ORIGIN = "https://accounts.google.com";
|
|
57
|
+
const GOOGLE_TOKEN_ORIGIN = "https://oauth2.googleapis.com";
|
|
50
58
|
export { OAUTH_MAX_RESPONSE_BYTES } from "@opengeni/network";
|
|
51
59
|
|
|
52
60
|
type OAuthClientDeps = {
|
|
@@ -120,6 +128,7 @@ type OAuthStatePayload = {
|
|
|
120
128
|
issuer: string;
|
|
121
129
|
clientRegistrationMethod: OAuthClientRegistration["method"];
|
|
122
130
|
tokenEndpointAuthMethod: OAuthClientRegistration["tokenEndpointAuthMethod"];
|
|
131
|
+
resourceParameterSupported: boolean;
|
|
123
132
|
encryptedClientSecret?: string;
|
|
124
133
|
returnPath: string;
|
|
125
134
|
connectionId?: string;
|
|
@@ -296,21 +305,15 @@ async function startMcpOAuthWithinDeadline(
|
|
|
296
305
|
): Promise<OAuthStartResponse> {
|
|
297
306
|
const { db, settings } = deps;
|
|
298
307
|
const mcpUrl = canonicalMcpResource(context.payload.mcpUrl ?? context.payload.resource);
|
|
308
|
+
const officialGmailResource = mcpUrl === OFFICIAL_GMAIL_MCP_URL;
|
|
299
309
|
const officialSlackResource = mcpUrl === OFFICIAL_SLACK_MCP_URL;
|
|
300
310
|
const providerDomain = officialSlackResource
|
|
301
311
|
? "slack.com"
|
|
302
312
|
: canonicalProviderDomain(context.payload.providerDomain ?? new URL(mcpUrl).hostname);
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
message:
|
|
308
|
-
"Slack's hosted MCP connection is personal; use the OpenGeni Slack bot installation for workspace access",
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
const requestedOwnership: ConnectionOwnership = personalSlack
|
|
312
|
-
? "personal"
|
|
313
|
-
: (context.payload.ownership ?? "workspace");
|
|
313
|
+
const hostedSlackMcp = officialSlackResource || providerDomain === "slack.com";
|
|
314
|
+
assertHostedSlackMcpOAuthStart(settings, context.payload, mcpUrl, hostedSlackMcp);
|
|
315
|
+
const requestedOwnership: ConnectionOwnership = context.payload.ownership ?? "workspace";
|
|
316
|
+
assertOfficialGmailPersonalOwnership(mcpUrl, requestedOwnership);
|
|
314
317
|
const returnPath = safeReturnPath(context.payload.returnPath ?? "/integrations");
|
|
315
318
|
const baseUrl = integrationBaseUrl(settings.publicBaseUrl, context.requestUrl);
|
|
316
319
|
const redirectUri = `${baseUrl}/v1/integrations/oauth/callback`;
|
|
@@ -321,7 +324,8 @@ async function startMcpOAuthWithinDeadline(
|
|
|
321
324
|
subjectId: context.subjectId,
|
|
322
325
|
providerDomain,
|
|
323
326
|
mcpUrl,
|
|
324
|
-
|
|
327
|
+
hostedSlackMcp,
|
|
328
|
+
exactMcpBinding: hostedSlackMcp || officialGmailResource,
|
|
325
329
|
connectionId: context.payload.connectionId,
|
|
326
330
|
requestedOwnership: context.payload.ownership,
|
|
327
331
|
newConnectionOwnership: requestedOwnership,
|
|
@@ -335,16 +339,29 @@ async function startMcpOAuthWithinDeadline(
|
|
|
335
339
|
: requestedOwnership;
|
|
336
340
|
|
|
337
341
|
const discovery = await discoverMcpOAuth(mcpUrl, settings, deadline);
|
|
338
|
-
if (
|
|
342
|
+
if (hostedSlackMcp && !isLocalTestEnvironment(settings.environment)) {
|
|
339
343
|
assertSlackAuthorizationServer(discovery.as);
|
|
340
344
|
}
|
|
345
|
+
const googleAuthorizationServer = hasGoogleAuthorizationServerIdentity(discovery.as);
|
|
346
|
+
if (officialGmailResource || googleAuthorizationServer) {
|
|
347
|
+
if (!officialGmailResource || providerDomain !== "gmailmcp.googleapis.com") {
|
|
348
|
+
throw new HTTPException(422, {
|
|
349
|
+
message: `Google OAuth is allowed only for ${OFFICIAL_GMAIL_MCP_URL}`,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
assertGoogleAuthorizationServer(discovery.as);
|
|
353
|
+
}
|
|
354
|
+
const resourceParameterSupported = !officialGmailResource;
|
|
341
355
|
const resource = discovery.prm.resource ? canonicalOAuthResource(discovery.prm.resource) : mcpUrl;
|
|
342
356
|
const verifier = randomPkceVerifier();
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
357
|
+
// The Gmail PRM advertises broader grants, including full-mail access. The
|
|
358
|
+
// reviewed connector never lets a caller widen the capability contract.
|
|
359
|
+
const authorizeScopes = chooseMcpAuthorizeScopes({
|
|
360
|
+
mcpUrl,
|
|
361
|
+
requested: context.payload.requestedScopes,
|
|
362
|
+
challenged: discovery.challenge.scope,
|
|
363
|
+
supported: discovery.prm.scopesSupported,
|
|
364
|
+
});
|
|
348
365
|
const client = await deadline.run("client_registration", (signal) =>
|
|
349
366
|
registerOAuthClient(
|
|
350
367
|
db,
|
|
@@ -366,7 +383,9 @@ async function startMcpOAuthWithinDeadline(
|
|
|
366
383
|
providerDomain,
|
|
367
384
|
mcpUrl,
|
|
368
385
|
resource,
|
|
369
|
-
requestedScopes:
|
|
386
|
+
requestedScopes: officialGmailResource
|
|
387
|
+
? [...OFFICIAL_GMAIL_MCP_SCOPES]
|
|
388
|
+
: uniqueStrings(context.payload.requestedScopes ?? []),
|
|
370
389
|
authorizeScopes,
|
|
371
390
|
encryptedPkceVerifier: encryptEnvironmentValue(key, verifier),
|
|
372
391
|
clientId: client.clientId,
|
|
@@ -375,6 +394,7 @@ async function startMcpOAuthWithinDeadline(
|
|
|
375
394
|
issuer: client.issuer,
|
|
376
395
|
clientRegistrationMethod: client.method,
|
|
377
396
|
tokenEndpointAuthMethod: client.tokenEndpointAuthMethod,
|
|
397
|
+
resourceParameterSupported,
|
|
378
398
|
...(client.method === "manual" && client.clientSecret
|
|
379
399
|
? {
|
|
380
400
|
encryptedClientSecret: encryptEnvironmentValue(key, client.clientSecret),
|
|
@@ -392,6 +412,7 @@ async function startMcpOAuthWithinDeadline(
|
|
|
392
412
|
resource,
|
|
393
413
|
verifier,
|
|
394
414
|
scopes: authorizeScopes,
|
|
415
|
+
resourceParameterSupported,
|
|
395
416
|
});
|
|
396
417
|
return OAuthStartResponse.parse({
|
|
397
418
|
state,
|
|
@@ -400,6 +421,18 @@ async function startMcpOAuthWithinDeadline(
|
|
|
400
421
|
});
|
|
401
422
|
}
|
|
402
423
|
|
|
424
|
+
export function assertOfficialGmailPersonalOwnership(
|
|
425
|
+
mcpUrl: string,
|
|
426
|
+
ownership: ConnectionOwnership,
|
|
427
|
+
): void {
|
|
428
|
+
if (mcpUrl === OFFICIAL_GMAIL_MCP_URL && ownership !== "personal") {
|
|
429
|
+
throw new HTTPException(422, {
|
|
430
|
+
message:
|
|
431
|
+
"Gmail connections are personal only; each workspace member must connect their own mailbox",
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
403
436
|
export async function completeMcpOAuthCallback(
|
|
404
437
|
deps: OAuthClientDeps,
|
|
405
438
|
input: {
|
|
@@ -445,6 +478,9 @@ async function completeMcpOAuthCallbackWithinDeadline(
|
|
|
445
478
|
}
|
|
446
479
|
try {
|
|
447
480
|
state = readOAuthState(input.state, settings);
|
|
481
|
+
// Fence state minted by an older deployment too: a rolling update must not
|
|
482
|
+
// let a still-valid workspace-owned Gmail callback persist shared authority.
|
|
483
|
+
assertOfficialGmailPersonalOwnership(state.mcpUrl, state.ownership);
|
|
448
484
|
if (!input.code) {
|
|
449
485
|
return {
|
|
450
486
|
redirectTo: callbackReturnPath(state.returnPath, "error", {
|
|
@@ -505,6 +541,7 @@ async function completeMcpOAuthCallbackWithinDeadline(
|
|
|
505
541
|
resource: state.resource,
|
|
506
542
|
tokenEndpoint: state.tokenEndpoint,
|
|
507
543
|
client,
|
|
544
|
+
resourceParameterSupported: state.resourceParameterSupported,
|
|
508
545
|
signal,
|
|
509
546
|
}),
|
|
510
547
|
);
|
|
@@ -619,13 +656,13 @@ async function requireOAuthCallbackGrant(db: Database, state: OAuthStatePayload)
|
|
|
619
656
|
}
|
|
620
657
|
}
|
|
621
658
|
|
|
622
|
-
function
|
|
659
|
+
function assertHostedSlackMcpOAuthStart(
|
|
623
660
|
settings: Settings,
|
|
624
661
|
payload: OAuthStartRequest,
|
|
625
662
|
mcpUrl: string,
|
|
626
|
-
|
|
663
|
+
hostedSlackMcp: boolean,
|
|
627
664
|
): void {
|
|
628
|
-
if (!
|
|
665
|
+
if (!hostedSlackMcp) return;
|
|
629
666
|
if (payload.oauthClient) {
|
|
630
667
|
throw new HTTPException(422, {
|
|
631
668
|
message: "Slack OAuth client credentials are deployment-managed",
|
|
@@ -638,13 +675,12 @@ function assertPersonalSlackOAuthStart(
|
|
|
638
675
|
}
|
|
639
676
|
if (!isLocalTestEnvironment(settings.environment) && mcpUrl !== OFFICIAL_SLACK_MCP_URL) {
|
|
640
677
|
throw new HTTPException(422, {
|
|
641
|
-
message: `
|
|
678
|
+
message: `Slack MCP OAuth must use ${OFFICIAL_SLACK_MCP_URL}`,
|
|
642
679
|
});
|
|
643
680
|
}
|
|
644
681
|
if (!settings.slackClientId?.trim() || !settings.slackClientSecret?.trim()) {
|
|
645
682
|
throw new HTTPException(503, {
|
|
646
|
-
message:
|
|
647
|
-
"personal Slack OAuth requires OPENGENI_SLACK_CLIENT_ID and OPENGENI_SLACK_CLIENT_SECRET",
|
|
683
|
+
message: "Slack MCP OAuth requires OPENGENI_SLACK_CLIENT_ID and OPENGENI_SLACK_CLIENT_SECRET",
|
|
648
684
|
});
|
|
649
685
|
}
|
|
650
686
|
}
|
|
@@ -664,6 +700,27 @@ export function assertSlackAuthorizationServer(as: AuthorizationServerMetadata):
|
|
|
664
700
|
}
|
|
665
701
|
}
|
|
666
702
|
|
|
703
|
+
function hasGoogleAuthorizationServerIdentity(as: AuthorizationServerMetadata): boolean {
|
|
704
|
+
return [as.issuer, as.authorizationServer].some(
|
|
705
|
+
(value) => new URL(value).origin === GOOGLE_OAUTH_ISSUER_ORIGIN,
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
export function assertGoogleAuthorizationServer(as: AuthorizationServerMetadata): void {
|
|
710
|
+
const issuerOrigins = [as.issuer, as.authorizationServer].map((value) => new URL(value).origin);
|
|
711
|
+
const authorizationOrigin = new URL(as.authorizationEndpoint).origin;
|
|
712
|
+
const tokenOrigin = new URL(as.tokenEndpoint).origin;
|
|
713
|
+
if (
|
|
714
|
+
issuerOrigins.some((origin) => origin !== GOOGLE_OAUTH_ISSUER_ORIGIN) ||
|
|
715
|
+
authorizationOrigin !== GOOGLE_OAUTH_ISSUER_ORIGIN ||
|
|
716
|
+
tokenOrigin !== GOOGLE_TOKEN_ORIGIN
|
|
717
|
+
) {
|
|
718
|
+
throw new HTTPException(422, {
|
|
719
|
+
message: "Gmail MCP authorization metadata did not remain bound to Google",
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
667
724
|
async function discoverMcpOAuth(
|
|
668
725
|
resource: string,
|
|
669
726
|
settings: Settings,
|
|
@@ -1153,7 +1210,8 @@ async function existingOAuthConnectionForStart(
|
|
|
1153
1210
|
subjectId: string;
|
|
1154
1211
|
providerDomain: string;
|
|
1155
1212
|
mcpUrl: string;
|
|
1156
|
-
|
|
1213
|
+
hostedSlackMcp: boolean;
|
|
1214
|
+
exactMcpBinding: boolean;
|
|
1157
1215
|
connectionId?: string | undefined;
|
|
1158
1216
|
requestedOwnership?: ConnectionOwnership | undefined;
|
|
1159
1217
|
newConnectionOwnership: ConnectionOwnership;
|
|
@@ -1176,7 +1234,7 @@ async function existingOAuthConnectionForStart(
|
|
|
1176
1234
|
});
|
|
1177
1235
|
}
|
|
1178
1236
|
return connection.providerDomain === input.providerDomain &&
|
|
1179
|
-
(!input.
|
|
1237
|
+
(!input.exactMcpBinding || connection.metadata.mcpUrl === input.mcpUrl)
|
|
1180
1238
|
? connection
|
|
1181
1239
|
: null;
|
|
1182
1240
|
}
|
|
@@ -1187,9 +1245,9 @@ async function existingOAuthConnectionForStart(
|
|
|
1187
1245
|
connection.subjectId === ownerSubjectId &&
|
|
1188
1246
|
connection.kind === "oauth2" &&
|
|
1189
1247
|
connection.providerDomain === input.providerDomain &&
|
|
1190
|
-
(!input.
|
|
1248
|
+
(!input.exactMcpBinding || connection.metadata.mcpUrl === input.mcpUrl),
|
|
1191
1249
|
);
|
|
1192
|
-
if (input.
|
|
1250
|
+
if (input.hostedSlackMcp && input.newConnectionOwnership === "personal") {
|
|
1193
1251
|
return selectCanonicalPersonalSlackConnection(matching);
|
|
1194
1252
|
}
|
|
1195
1253
|
return matching.find((connection) => connection.status === "active") ?? null;
|
|
@@ -1204,7 +1262,7 @@ function ownershipForConnection(
|
|
|
1204
1262
|
throw new HTTPException(404, { message: "connection not found" });
|
|
1205
1263
|
}
|
|
1206
1264
|
|
|
1207
|
-
function buildAuthorizationUrl(input: {
|
|
1265
|
+
export function buildAuthorizationUrl(input: {
|
|
1208
1266
|
endpoint: string;
|
|
1209
1267
|
settings: Settings;
|
|
1210
1268
|
clientId: string;
|
|
@@ -1213,6 +1271,7 @@ function buildAuthorizationUrl(input: {
|
|
|
1213
1271
|
resource: string;
|
|
1214
1272
|
verifier: string;
|
|
1215
1273
|
scopes: string[];
|
|
1274
|
+
resourceParameterSupported: boolean;
|
|
1216
1275
|
}): string {
|
|
1217
1276
|
const endpoint = oauthEndpointUrl(input.endpoint, input.settings, "OAuth authorization endpoint");
|
|
1218
1277
|
const url = new URL(endpoint);
|
|
@@ -1220,7 +1279,16 @@ function buildAuthorizationUrl(input: {
|
|
|
1220
1279
|
url.searchParams.set("client_id", input.clientId);
|
|
1221
1280
|
url.searchParams.set("redirect_uri", input.redirectUri);
|
|
1222
1281
|
url.searchParams.set("state", input.state);
|
|
1223
|
-
|
|
1282
|
+
if (input.resourceParameterSupported) {
|
|
1283
|
+
url.searchParams.set("resource", input.resource);
|
|
1284
|
+
} else {
|
|
1285
|
+
// Google's OAuth endpoints do not implement RFC 8707's `resource`
|
|
1286
|
+
// parameter. Explicit offline consent is required to obtain the refresh
|
|
1287
|
+
// token used by the durable connection broker.
|
|
1288
|
+
url.searchParams.set("access_type", "offline");
|
|
1289
|
+
url.searchParams.set("include_granted_scopes", "true");
|
|
1290
|
+
url.searchParams.set("prompt", "consent");
|
|
1291
|
+
}
|
|
1224
1292
|
url.searchParams.set("code_challenge_method", "S256");
|
|
1225
1293
|
url.searchParams.set("code_challenge", pkceChallenge(input.verifier));
|
|
1226
1294
|
if (input.scopes.length > 0) {
|
|
@@ -1277,6 +1345,9 @@ function readOAuthState(state: string, settings: Settings): OAuthStatePayload {
|
|
|
1277
1345
|
),
|
|
1278
1346
|
clientRegistrationMethod: registrationMethod(payload.clientRegistrationMethod),
|
|
1279
1347
|
tokenEndpointAuthMethod: tokenAuthMethod(stringValue(payload.tokenEndpointAuthMethod), false),
|
|
1348
|
+
// States minted before provider-specific compatibility was introduced used
|
|
1349
|
+
// the RFC 8707 resource parameter.
|
|
1350
|
+
resourceParameterSupported: payload.resourceParameterSupported !== false,
|
|
1280
1351
|
...(stringValue(payload.encryptedClientSecret)
|
|
1281
1352
|
? { encryptedClientSecret: stringValue(payload.encryptedClientSecret)! }
|
|
1282
1353
|
: {}),
|
|
@@ -1379,6 +1450,7 @@ async function exchangeAuthorizationCode(
|
|
|
1379
1450
|
verifier: string;
|
|
1380
1451
|
redirectUri: string;
|
|
1381
1452
|
resource: string;
|
|
1453
|
+
resourceParameterSupported: boolean;
|
|
1382
1454
|
tokenEndpoint: string;
|
|
1383
1455
|
client: OAuthClientRegistration;
|
|
1384
1456
|
signal: AbortSignal;
|
|
@@ -1389,7 +1461,9 @@ async function exchangeAuthorizationCode(
|
|
|
1389
1461
|
body.set("code", input.code);
|
|
1390
1462
|
body.set("redirect_uri", input.redirectUri);
|
|
1391
1463
|
body.set("code_verifier", input.verifier);
|
|
1392
|
-
|
|
1464
|
+
if (input.resourceParameterSupported) {
|
|
1465
|
+
body.set("resource", input.resource);
|
|
1466
|
+
}
|
|
1393
1467
|
const headers: Record<string, string> = {
|
|
1394
1468
|
"content-type": "application/x-www-form-urlencoded",
|
|
1395
1469
|
accept: "application/json",
|
|
@@ -1512,7 +1586,11 @@ function oauthCallbackFailureReason(stage: OAuthCallbackStage, error: unknown):
|
|
|
1512
1586
|
function isDatabaseStatementTimeout(error: unknown): boolean {
|
|
1513
1587
|
let current = error;
|
|
1514
1588
|
for (let depth = 0; depth < 4 && current && typeof current === "object"; depth += 1) {
|
|
1515
|
-
const candidate = current as {
|
|
1589
|
+
const candidate = current as {
|
|
1590
|
+
code?: unknown;
|
|
1591
|
+
message?: unknown;
|
|
1592
|
+
cause?: unknown;
|
|
1593
|
+
};
|
|
1516
1594
|
if (
|
|
1517
1595
|
candidate.code === "57014" ||
|
|
1518
1596
|
(typeof candidate.message === "string" &&
|
|
@@ -1727,6 +1805,7 @@ function credentialBundle(
|
|
|
1727
1805
|
token_type: token.tokenType,
|
|
1728
1806
|
...(token.expiresAt ? { expires_at: token.expiresAt.toISOString() } : {}),
|
|
1729
1807
|
resource: state.resource,
|
|
1808
|
+
resource_parameter_supported: state.resourceParameterSupported,
|
|
1730
1809
|
mcp_url: state.mcpUrl,
|
|
1731
1810
|
...(token.scopeText
|
|
1732
1811
|
? { scope: token.scopeText }
|
|
@@ -1976,6 +2055,17 @@ function chooseAuthorizeScopes(
|
|
|
1976
2055
|
return uniqueStrings(supported);
|
|
1977
2056
|
}
|
|
1978
2057
|
|
|
2058
|
+
export function chooseMcpAuthorizeScopes(input: {
|
|
2059
|
+
mcpUrl: string;
|
|
2060
|
+
requested: string[] | undefined;
|
|
2061
|
+
challenged: string[] | undefined;
|
|
2062
|
+
supported: string[];
|
|
2063
|
+
}): string[] {
|
|
2064
|
+
return input.mcpUrl === OFFICIAL_GMAIL_MCP_URL
|
|
2065
|
+
? [...OFFICIAL_GMAIL_MCP_SCOPES]
|
|
2066
|
+
: chooseAuthorizeScopes(input.requested, input.challenged, input.supported);
|
|
2067
|
+
}
|
|
2068
|
+
|
|
1979
2069
|
function grantedScopes(scopeText: string | undefined, fallback: string[]): string[] {
|
|
1980
2070
|
if (scopeText) {
|
|
1981
2071
|
return uniqueStrings(scopeText.split(/\s+/).filter(Boolean));
|