@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
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import {
|
|
3
|
+
ATLASSIAN_PROVIDER_DOMAIN,
|
|
4
|
+
AtlassianConnectionMetadata,
|
|
5
|
+
AtlassianDisconnectRequest,
|
|
6
|
+
AtlassianLifecycleActionRequest,
|
|
7
|
+
AtlassianOAuthStartRequest,
|
|
8
|
+
AtlassianOAuthStartResponse,
|
|
9
|
+
} from "@opengeni/contracts/atlassian";
|
|
10
|
+
import {
|
|
11
|
+
API_INTEGRATION_OAUTH_CREDENTIAL_ROLE,
|
|
12
|
+
ApiIntegrationOAuthStartRequest,
|
|
3
13
|
ConnectionResponse,
|
|
4
14
|
CreateConnectionRequest,
|
|
15
|
+
FIKEN_CREDENTIAL_LABEL,
|
|
16
|
+
FIKEN_CREDENTIAL_ROLE,
|
|
17
|
+
FIKEN_PROVIDER_DOMAIN,
|
|
18
|
+
FikenInstallRequest,
|
|
19
|
+
FikenOAuthStartRequest,
|
|
20
|
+
FikenOAuthStartResponse,
|
|
5
21
|
IntegrationClientMetadata,
|
|
22
|
+
ListSlackInstallationBindingsResponse,
|
|
6
23
|
ListConnectionsResponse,
|
|
7
24
|
OpenGeniSlackBotInstallRequest,
|
|
8
25
|
OpenGeniSlackBotInstallStart,
|
|
@@ -23,28 +40,33 @@ import {
|
|
|
23
40
|
GoogleDriveOAuthStartResponse,
|
|
24
41
|
} from "@opengeni/contracts/google-drive";
|
|
25
42
|
import {
|
|
43
|
+
fikenConnectionMetadata,
|
|
26
44
|
hasPermission,
|
|
45
|
+
hasReservedFikenMetadata,
|
|
27
46
|
hasReservedOpenGeniSlackBotMetadata,
|
|
47
|
+
isFikenConnection,
|
|
28
48
|
isOpenGeniSlackBotConnection,
|
|
29
49
|
openGeniSlackBotMetadata,
|
|
30
50
|
requireAccessGrant,
|
|
31
51
|
requireAccessGrantAuthorization,
|
|
32
52
|
requireEnvironmentEncryption,
|
|
53
|
+
resolveFikenDefaultCompanySlug,
|
|
33
54
|
} from "@opengeni/core";
|
|
34
55
|
import {
|
|
35
56
|
consumeIntegrationOAuthStateNonce,
|
|
36
57
|
createConnection,
|
|
37
|
-
createConnectionWithSlackBotSuccessAudit,
|
|
38
58
|
encryptEnvironmentValue,
|
|
39
59
|
getConnectionMetadata,
|
|
40
60
|
getWorkspaceGrant,
|
|
41
61
|
listConnectionsMetadata,
|
|
62
|
+
listSlackInstallationBindings,
|
|
63
|
+
persistSlackBotInstallationWithSuccessAudit,
|
|
42
64
|
recordSlackBotInstallCallbackFailure,
|
|
43
65
|
revokeConnection,
|
|
44
66
|
revokeConnectionWithSlackBotSuccessAudit,
|
|
45
67
|
SlackBotLifecycleSuccessAuditError,
|
|
68
|
+
SlackInstallationBindingConflictError,
|
|
46
69
|
updateConnection,
|
|
47
|
-
updateConnectionWithSlackBotSuccessAudit,
|
|
48
70
|
updateSlackBotDocumentDestination,
|
|
49
71
|
type SlackBotInstallCallbackFailureReason,
|
|
50
72
|
type SlackBotInstallCallbackFailureStage,
|
|
@@ -60,6 +82,18 @@ import {
|
|
|
60
82
|
startGoogleDriveOAuth,
|
|
61
83
|
transitionGoogleDriveLifecycle,
|
|
62
84
|
} from "../integrations/google-drive";
|
|
85
|
+
import {
|
|
86
|
+
completeApiIntegrationProviderOAuth,
|
|
87
|
+
startApiIntegrationProviderOAuth,
|
|
88
|
+
} from "../integrations/provider-oauth";
|
|
89
|
+
import {
|
|
90
|
+
browseAtlassianSources,
|
|
91
|
+
completeAtlassianOAuthCallback,
|
|
92
|
+
disconnectAtlassian,
|
|
93
|
+
saveAtlassianSources,
|
|
94
|
+
startAtlassianOAuth,
|
|
95
|
+
transitionAtlassianLifecycle,
|
|
96
|
+
} from "../integrations/atlassian";
|
|
63
97
|
import {
|
|
64
98
|
completeMcpOAuthCallback,
|
|
65
99
|
integrationBaseUrl,
|
|
@@ -71,6 +105,12 @@ import {
|
|
|
71
105
|
SlackBotCredentialVerificationError,
|
|
72
106
|
verifyOpenGeniSlackBotCredential,
|
|
73
107
|
} from "../integrations/slack-bot";
|
|
108
|
+
import {
|
|
109
|
+
completeFikenOAuthCallback,
|
|
110
|
+
fikenCredentialBundle,
|
|
111
|
+
startFikenOAuth,
|
|
112
|
+
verifyFikenApiToken,
|
|
113
|
+
} from "../integrations/fiken";
|
|
74
114
|
import {
|
|
75
115
|
OPENGENI_SLACK_BOT_CREDENTIAL_LABEL,
|
|
76
116
|
OPENGENI_SLACK_BOT_CREDENTIAL_ROLE,
|
|
@@ -109,16 +149,32 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
109
149
|
);
|
|
110
150
|
});
|
|
111
151
|
|
|
152
|
+
app.get("/v1/workspaces/:workspaceId/connections/slack-bot/bindings", async (c) => {
|
|
153
|
+
const workspaceId = c.req.param("workspaceId");
|
|
154
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "connections:read");
|
|
155
|
+
return c.json(
|
|
156
|
+
ListSlackInstallationBindingsResponse.parse({
|
|
157
|
+
bindings: await listSlackInstallationBindings(db, {
|
|
158
|
+
accountId: grant.accountId,
|
|
159
|
+
workspaceId,
|
|
160
|
+
}),
|
|
161
|
+
}),
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
|
|
112
165
|
app.post("/v1/workspaces/:workspaceId/connections", async (c) => {
|
|
113
166
|
const workspaceId = c.req.param("workspaceId");
|
|
114
167
|
const grant = await requireAccessGrant(c, deps, workspaceId, "connections:write");
|
|
115
168
|
const payload = CreateConnectionRequest.parse(await c.req.json());
|
|
116
169
|
assertNotReservedSlackBotMetadata(payload.metadata);
|
|
170
|
+
assertNotReservedFikenMetadata(payload.metadata);
|
|
171
|
+
assertNotReservedApiIntegrationOAuthMetadata(payload.metadata);
|
|
117
172
|
const key = requireEnvironmentEncryption(settings);
|
|
118
173
|
const subjectId = createConnectionSubjectId(payload, grant.subjectId);
|
|
119
174
|
const providerDomain = canonicalProviderDomain(payload.providerDomain);
|
|
120
175
|
assertNotDirectPersonalSlackOAuth(providerDomain, payload.kind);
|
|
121
176
|
assertNotDirectGoogleDriveOAuth(providerDomain, payload.kind, payload.metadata);
|
|
177
|
+
assertNotDirectAtlassianOAuth(providerDomain, payload.kind, payload.metadata);
|
|
122
178
|
const connection = await createConnection(db, {
|
|
123
179
|
accountId: grant.accountId,
|
|
124
180
|
workspaceId,
|
|
@@ -266,6 +322,120 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
266
322
|
}
|
|
267
323
|
});
|
|
268
324
|
|
|
325
|
+
// Verified paste-a-token install for the first-party Fiken connector. The
|
|
326
|
+
// token is validated against Fiken (and its accessible companies discovered)
|
|
327
|
+
// before it enters encrypted storage. Workspace-owned only.
|
|
328
|
+
app.post("/v1/workspaces/:workspaceId/connections/fiken/install", async (c) => {
|
|
329
|
+
assertIntegrationsEnabled();
|
|
330
|
+
const workspaceId = c.req.param("workspaceId");
|
|
331
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "connections:write");
|
|
332
|
+
const payload = FikenInstallRequest.parse(await c.req.json());
|
|
333
|
+
const key = requireEnvironmentEncryption(settings);
|
|
334
|
+
const existing = payload.connectionId
|
|
335
|
+
? await getConnectionMetadata(db, workspaceId, payload.connectionId, null)
|
|
336
|
+
: null;
|
|
337
|
+
if (payload.connectionId && !existing) {
|
|
338
|
+
throw new HTTPException(404, { message: "connection not found" });
|
|
339
|
+
}
|
|
340
|
+
if (existing && !isFikenConnection(existing)) {
|
|
341
|
+
throw new HTTPException(422, { message: "connectionId is not a Fiken connection" });
|
|
342
|
+
}
|
|
343
|
+
const verified = await verifyFikenApiToken(payload.apiToken, deps.fikenFetch ?? fetch);
|
|
344
|
+
const previousDefault = existing
|
|
345
|
+
? (fikenConnectionMetadata(existing.metadata)?.defaultCompanySlug ?? null)
|
|
346
|
+
: null;
|
|
347
|
+
const defaultCompanySlug = resolveFikenDefaultCompanySlug({
|
|
348
|
+
requested: payload.defaultCompanySlug ?? null,
|
|
349
|
+
previous: previousDefault,
|
|
350
|
+
companies: verified.companies,
|
|
351
|
+
});
|
|
352
|
+
if (payload.defaultCompanySlug && defaultCompanySlug !== payload.defaultCompanySlug) {
|
|
353
|
+
throw new HTTPException(422, {
|
|
354
|
+
message: `defaultCompanySlug is not among the companies this token can access: ${verified.companies
|
|
355
|
+
.map((company) => company.slug)
|
|
356
|
+
.join(", ")}`,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
const metadata = {
|
|
360
|
+
credentialRole: FIKEN_CREDENTIAL_ROLE,
|
|
361
|
+
credentialLabel: FIKEN_CREDENTIAL_LABEL,
|
|
362
|
+
companies: verified.companies,
|
|
363
|
+
defaultCompanySlug,
|
|
364
|
+
verifiedAt: new Date().toISOString(),
|
|
365
|
+
};
|
|
366
|
+
const credentialEncrypted = encryptCredentialBundle(
|
|
367
|
+
key,
|
|
368
|
+
fikenCredentialBundle(payload.apiToken),
|
|
369
|
+
);
|
|
370
|
+
if (existing) {
|
|
371
|
+
// Rewrites the whole credential identity: a token pasted over an OAuth
|
|
372
|
+
// row must also flip kind and clear the OAuth expiry, or the broker
|
|
373
|
+
// keeps treating the api_key bundle as a refreshable oauth2 credential.
|
|
374
|
+
const updated = await updateConnection(db, {
|
|
375
|
+
workspaceId,
|
|
376
|
+
connectionId: existing.id,
|
|
377
|
+
visibleToSubjectId: null,
|
|
378
|
+
expectedVersion: existing.version,
|
|
379
|
+
kind: "api_key",
|
|
380
|
+
status: "active",
|
|
381
|
+
credentialEncrypted,
|
|
382
|
+
grantedScopes: [],
|
|
383
|
+
expiresAt: null,
|
|
384
|
+
metadata,
|
|
385
|
+
updatedBySubjectId: grant.subjectId,
|
|
386
|
+
});
|
|
387
|
+
if (!updated) {
|
|
388
|
+
throw new HTTPException(409, { message: "the Fiken connection changed; retry" });
|
|
389
|
+
}
|
|
390
|
+
return c.json(ConnectionResponse.parse({ connection: updated }));
|
|
391
|
+
}
|
|
392
|
+
const connection = await createConnection(db, {
|
|
393
|
+
accountId: grant.accountId,
|
|
394
|
+
workspaceId,
|
|
395
|
+
subjectId: null,
|
|
396
|
+
providerDomain: FIKEN_PROVIDER_DOMAIN,
|
|
397
|
+
kind: "api_key",
|
|
398
|
+
credentialEncrypted,
|
|
399
|
+
grantedScopes: [],
|
|
400
|
+
expiresAt: null,
|
|
401
|
+
metadata,
|
|
402
|
+
createdBySubjectId: grant.subjectId,
|
|
403
|
+
});
|
|
404
|
+
return c.json(ConnectionResponse.parse({ connection }), 201);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// Fiken OAuth (registered app) start. Both Fiken lanes produce the same
|
|
408
|
+
// workspace-owned connection shape; this one refreshes through the broker.
|
|
409
|
+
app.post("/v1/workspaces/:workspaceId/connections/fiken/oauth/start", async (c) => {
|
|
410
|
+
assertIntegrationsEnabled();
|
|
411
|
+
const workspaceId = c.req.param("workspaceId");
|
|
412
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "connections:write");
|
|
413
|
+
const payload = FikenOAuthStartRequest.parse(await c.req.json());
|
|
414
|
+
requireEnvironmentEncryption(settings);
|
|
415
|
+
return c.json(
|
|
416
|
+
FikenOAuthStartResponse.parse(
|
|
417
|
+
await startFikenOAuth(deps, {
|
|
418
|
+
accountId: grant.accountId,
|
|
419
|
+
workspaceId,
|
|
420
|
+
subjectId: grant.subjectId,
|
|
421
|
+
requestUrl: c.req.url,
|
|
422
|
+
payload,
|
|
423
|
+
}),
|
|
424
|
+
),
|
|
425
|
+
);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
app.get("/v1/integrations/fiken/callback", async (c) => {
|
|
429
|
+
assertIntegrationsEnabled();
|
|
430
|
+
const result = await completeFikenOAuthCallback(deps, {
|
|
431
|
+
...(c.req.query("code") ? { code: c.req.query("code") } : {}),
|
|
432
|
+
...(c.req.query("state") ? { state: c.req.query("state") } : {}),
|
|
433
|
+
...(c.req.query("error") ? { error: c.req.query("error") } : {}),
|
|
434
|
+
requestUrl: c.req.url,
|
|
435
|
+
});
|
|
436
|
+
return c.redirect(result.redirectTo, 302);
|
|
437
|
+
});
|
|
438
|
+
|
|
269
439
|
app.post("/v1/workspaces/:workspaceId/connections/google-drive/install", async (c) => {
|
|
270
440
|
assertIntegrationsEnabled();
|
|
271
441
|
const workspaceId = c.req.param("workspaceId");
|
|
@@ -287,9 +457,30 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
287
457
|
);
|
|
288
458
|
});
|
|
289
459
|
|
|
290
|
-
app.
|
|
460
|
+
app.post("/v1/workspaces/:workspaceId/connections/atlassian/install", async (c) => {
|
|
461
|
+
assertIntegrationsEnabled();
|
|
462
|
+
const workspaceId = c.req.param("workspaceId");
|
|
463
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "connections:write");
|
|
464
|
+
const parsed = AtlassianOAuthStartRequest.safeParse(await c.req.json());
|
|
465
|
+
if (!parsed.success) {
|
|
466
|
+
throw new HTTPException(400, { message: "invalid Atlassian install request" });
|
|
467
|
+
}
|
|
468
|
+
return c.json(
|
|
469
|
+
AtlassianOAuthStartResponse.parse(
|
|
470
|
+
await startAtlassianOAuth(deps, {
|
|
471
|
+
accountId: grant.accountId,
|
|
472
|
+
workspaceId,
|
|
473
|
+
subjectId: grant.subjectId,
|
|
474
|
+
requestUrl: c.req.url,
|
|
475
|
+
payload: parsed.data,
|
|
476
|
+
}),
|
|
477
|
+
),
|
|
478
|
+
);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
app.get("/v1/integrations/atlassian/callback", async (c) => {
|
|
291
482
|
assertIntegrationsEnabled();
|
|
292
|
-
const result = await
|
|
483
|
+
const result = await completeAtlassianOAuthCallback(deps, {
|
|
293
484
|
...(c.req.query("code") ? { code: c.req.query("code") } : {}),
|
|
294
485
|
...(c.req.query("state") ? { state: c.req.query("state") } : {}),
|
|
295
486
|
...(c.req.query("error") ? { error: c.req.query("error") } : {}),
|
|
@@ -298,6 +489,80 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
298
489
|
return c.redirect(result.redirectTo, 302);
|
|
299
490
|
});
|
|
300
491
|
|
|
492
|
+
app.patch(
|
|
493
|
+
"/v1/workspaces/:workspaceId/connections/atlassian/:connectionId/lifecycle",
|
|
494
|
+
async (c) => {
|
|
495
|
+
assertIntegrationsEnabled();
|
|
496
|
+
const workspaceId = c.req.param("workspaceId");
|
|
497
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "connections:write");
|
|
498
|
+
const parsed = AtlassianLifecycleActionRequest.safeParse(await c.req.json());
|
|
499
|
+
if (!parsed.success) {
|
|
500
|
+
throw new HTTPException(400, { message: "invalid Atlassian lifecycle request" });
|
|
501
|
+
}
|
|
502
|
+
return c.json(
|
|
503
|
+
ConnectionResponse.parse({
|
|
504
|
+
connection: await transitionAtlassianLifecycle(deps, {
|
|
505
|
+
workspaceId,
|
|
506
|
+
subjectId: grant.subjectId,
|
|
507
|
+
connectionId: c.req.param("connectionId"),
|
|
508
|
+
payload: parsed.data,
|
|
509
|
+
}),
|
|
510
|
+
}),
|
|
511
|
+
);
|
|
512
|
+
},
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
app.get("/v1/workspaces/:workspaceId/connections/atlassian/:connectionId/browse", async (c) => {
|
|
516
|
+
assertIntegrationsEnabled();
|
|
517
|
+
const workspaceId = c.req.param("workspaceId");
|
|
518
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "connections:read");
|
|
519
|
+
return c.json(
|
|
520
|
+
await browseAtlassianSources(deps, {
|
|
521
|
+
workspaceId,
|
|
522
|
+
subjectId: grant.subjectId,
|
|
523
|
+
connectionId: c.req.param("connectionId"),
|
|
524
|
+
}),
|
|
525
|
+
);
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
app.post("/v1/workspaces/:workspaceId/connections/atlassian/:connectionId/source", async (c) => {
|
|
529
|
+
assertIntegrationsEnabled();
|
|
530
|
+
const workspaceId = c.req.param("workspaceId");
|
|
531
|
+
const authorization = await requireAccessGrantAuthorization(
|
|
532
|
+
c,
|
|
533
|
+
deps,
|
|
534
|
+
workspaceId,
|
|
535
|
+
"connections:write",
|
|
536
|
+
);
|
|
537
|
+
const { grant } = authorization;
|
|
538
|
+
const connection = await saveAtlassianSources(deps, {
|
|
539
|
+
accountId: grant.accountId,
|
|
540
|
+
workspaceId,
|
|
541
|
+
subjectId: grant.subjectId,
|
|
542
|
+
grant,
|
|
543
|
+
connectionId: c.req.param("connectionId"),
|
|
544
|
+
payload: await c.req.json(),
|
|
545
|
+
canManageOrganizationDestination:
|
|
546
|
+
authorization.accountGrant?.permissions.includes("account:admin") === true,
|
|
547
|
+
canManageWorkspaceDestination: hasPermission(grant.permissions, "workspace:admin"),
|
|
548
|
+
canManagePersonalDestination:
|
|
549
|
+
authorization.contextIntegrity && authorization.authenticatedSubjectId === grant.subjectId,
|
|
550
|
+
});
|
|
551
|
+
return c.json(ConnectionResponse.parse({ connection }));
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
app.get("/v1/integrations/google-drive/callback", async (c) => {
|
|
555
|
+
assertIntegrationsEnabled();
|
|
556
|
+
const input = {
|
|
557
|
+
...(c.req.query("code") ? { code: c.req.query("code") } : {}),
|
|
558
|
+
...(c.req.query("state") ? { state: c.req.query("state") } : {}),
|
|
559
|
+
...(c.req.query("error") ? { error: c.req.query("error") } : {}),
|
|
560
|
+
requestUrl: c.req.url,
|
|
561
|
+
};
|
|
562
|
+
const result = await completeGoogleDriveOAuthCallback(deps, input);
|
|
563
|
+
return c.redirect(result.redirectTo, 302);
|
|
564
|
+
});
|
|
565
|
+
|
|
301
566
|
app.patch(
|
|
302
567
|
"/v1/workspaces/:workspaceId/connections/google-drive/:connectionId/lifecycle",
|
|
303
568
|
async (c) => {
|
|
@@ -355,6 +620,7 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
355
620
|
accountId: grant.accountId,
|
|
356
621
|
workspaceId,
|
|
357
622
|
subjectId: grant.subjectId,
|
|
623
|
+
grant,
|
|
358
624
|
connectionId: c.req.param("connectionId"),
|
|
359
625
|
payload: await c.req.json(),
|
|
360
626
|
canManageOrganizationDestination:
|
|
@@ -394,6 +660,8 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
394
660
|
const { grant } = authorization;
|
|
395
661
|
const payload = UpdateConnectionRequest.parse(await c.req.json());
|
|
396
662
|
assertNotReservedSlackBotMetadata(payload.metadata);
|
|
663
|
+
assertNotReservedFikenMetadata(payload.metadata);
|
|
664
|
+
assertNotReservedApiIntegrationOAuthMetadata(payload.metadata);
|
|
397
665
|
const existing = await getConnectionMetadata(
|
|
398
666
|
db,
|
|
399
667
|
workspaceId,
|
|
@@ -470,6 +738,16 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
470
738
|
message: "use the dedicated Google Drive reconnect or source-selection flow",
|
|
471
739
|
});
|
|
472
740
|
}
|
|
741
|
+
if (existing?.metadata.credentialRole === API_INTEGRATION_OAUTH_CREDENTIAL_ROLE) {
|
|
742
|
+
throw new HTTPException(422, {
|
|
743
|
+
message: "use the dedicated Integration provider OAuth flow to update this connection",
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
if (existing && AtlassianConnectionMetadata.safeParse(existing.metadata).success) {
|
|
747
|
+
throw new HTTPException(422, {
|
|
748
|
+
message: "use the dedicated Atlassian reconnect or source-selection flow",
|
|
749
|
+
});
|
|
750
|
+
}
|
|
473
751
|
if (existing) {
|
|
474
752
|
const providerDomain = canonicalProviderDomain(
|
|
475
753
|
payload.providerDomain ?? existing.providerDomain,
|
|
@@ -477,6 +755,7 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
477
755
|
const kind = payload.kind ?? existing.kind;
|
|
478
756
|
assertNotDirectPersonalSlackOAuth(providerDomain, kind);
|
|
479
757
|
assertNotDirectGoogleDriveOAuth(providerDomain, kind, payload.metadata ?? existing.metadata);
|
|
758
|
+
assertNotDirectAtlassianOAuth(providerDomain, kind, payload.metadata ?? existing.metadata);
|
|
480
759
|
}
|
|
481
760
|
// Status is not a free-form field: revocation goes through DELETE, and the
|
|
482
761
|
// broker owns needs_reauth/error. Reactivating a connection is only
|
|
@@ -538,8 +817,14 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
538
817
|
existing.providerDomain === GOOGLE_DRIVE_PROVIDER_DOMAIN &&
|
|
539
818
|
existing.kind === "oauth2" &&
|
|
540
819
|
GoogleDriveConnectionMetadata.safeParse(existing.metadata).success;
|
|
820
|
+
const isAtlassian =
|
|
821
|
+
existing.subjectId === grant.subjectId &&
|
|
822
|
+
existing.providerDomain === ATLASSIAN_PROVIDER_DOMAIN &&
|
|
823
|
+
existing.kind === "oauth2" &&
|
|
824
|
+
AtlassianConnectionMetadata.safeParse(existing.metadata).success;
|
|
825
|
+
const disconnectPayload = await c.req.json().catch(() => null);
|
|
541
826
|
const googleDriveDisconnect = isGoogleDrive
|
|
542
|
-
? GoogleDriveDisconnectRequest.safeParse(
|
|
827
|
+
? GoogleDriveDisconnectRequest.safeParse(disconnectPayload)
|
|
543
828
|
: null;
|
|
544
829
|
if (googleDriveDisconnect && !googleDriveDisconnect.success) {
|
|
545
830
|
throw new HTTPException(400, {
|
|
@@ -548,7 +833,16 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
548
833
|
"invalid Google Drive disconnect request",
|
|
549
834
|
});
|
|
550
835
|
}
|
|
551
|
-
|
|
836
|
+
const atlassianDisconnect = isAtlassian
|
|
837
|
+
? AtlassianDisconnectRequest.safeParse(disconnectPayload)
|
|
838
|
+
: null;
|
|
839
|
+
if (atlassianDisconnect && !atlassianDisconnect.success) {
|
|
840
|
+
throw new HTTPException(400, {
|
|
841
|
+
message:
|
|
842
|
+
atlassianDisconnect.error.issues[0]?.message ?? "invalid Atlassian disconnect request",
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
if (existing.status === "revoked" && !isGoogleDrive && !isAtlassian) {
|
|
552
846
|
return c.json(ConnectionResponse.parse({ connection: existing }));
|
|
553
847
|
}
|
|
554
848
|
const connection = isGoogleDrive
|
|
@@ -558,18 +852,31 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
558
852
|
connection: existing,
|
|
559
853
|
payload: googleDriveDisconnect!.data,
|
|
560
854
|
})
|
|
561
|
-
:
|
|
562
|
-
? await
|
|
563
|
-
accountId: grant.accountId,
|
|
855
|
+
: isAtlassian
|
|
856
|
+
? await disconnectAtlassian(deps, {
|
|
564
857
|
workspaceId,
|
|
565
858
|
subjectId: grant.subjectId,
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
credentialRole: OPENGENI_SLACK_BOT_CREDENTIAL_ROLE,
|
|
569
|
-
credentialLabel: OPENGENI_SLACK_BOT_CREDENTIAL_LABEL,
|
|
570
|
-
slackTeamId: openGeniSlackBotMetadata(existing.metadata)!.slackTeamId,
|
|
859
|
+
connection: existing,
|
|
860
|
+
payload: atlassianDisconnect!.data,
|
|
571
861
|
})
|
|
572
|
-
:
|
|
862
|
+
: isOpenGeniSlackBotConnection(existing)
|
|
863
|
+
? await revokeConnectionWithSlackBotSuccessAudit(db, {
|
|
864
|
+
accountId: grant.accountId,
|
|
865
|
+
workspaceId,
|
|
866
|
+
subjectId: grant.subjectId,
|
|
867
|
+
connectionId,
|
|
868
|
+
expectedVersion: existing.version,
|
|
869
|
+
credentialRole: OPENGENI_SLACK_BOT_CREDENTIAL_ROLE,
|
|
870
|
+
credentialLabel: OPENGENI_SLACK_BOT_CREDENTIAL_LABEL,
|
|
871
|
+
slackTeamId: openGeniSlackBotMetadata(existing.metadata)!.slackTeamId,
|
|
872
|
+
})
|
|
873
|
+
: await revokeConnection(
|
|
874
|
+
db,
|
|
875
|
+
workspaceId,
|
|
876
|
+
connectionId,
|
|
877
|
+
grant.subjectId,
|
|
878
|
+
existing.version,
|
|
879
|
+
);
|
|
573
880
|
if (!connection) {
|
|
574
881
|
throw new HTTPException(409, { message: "connection changed during disconnect; try again" });
|
|
575
882
|
}
|
|
@@ -600,6 +907,29 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
600
907
|
return c.json(OAuthStartResponse.parse(result));
|
|
601
908
|
});
|
|
602
909
|
|
|
910
|
+
app.post("/v1/workspaces/:workspaceId/integrations/oauth/start", async (c) => {
|
|
911
|
+
assertIntegrationsEnabled();
|
|
912
|
+
const workspaceId = c.req.param("workspaceId");
|
|
913
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "connections:write");
|
|
914
|
+
const parsed = ApiIntegrationOAuthStartRequest.safeParse(await c.req.json());
|
|
915
|
+
if (!parsed.success) {
|
|
916
|
+
throw new HTTPException(400, {
|
|
917
|
+
message: parsed.error.issues[0]?.message ?? "invalid Integration OAuth start request",
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
return c.json(
|
|
921
|
+
OAuthStartResponse.parse(
|
|
922
|
+
await startApiIntegrationProviderOAuth(deps, {
|
|
923
|
+
accountId: grant.accountId,
|
|
924
|
+
workspaceId,
|
|
925
|
+
subjectId: grant.subjectId,
|
|
926
|
+
requestUrl: c.req.url,
|
|
927
|
+
payload: parsed.data,
|
|
928
|
+
}),
|
|
929
|
+
),
|
|
930
|
+
);
|
|
931
|
+
});
|
|
932
|
+
|
|
603
933
|
app.get("/v1/integrations/oauth/callback", async (c) => {
|
|
604
934
|
assertIntegrationsEnabled();
|
|
605
935
|
const result = await completeMcpOAuthCallback(
|
|
@@ -613,6 +943,17 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
613
943
|
return c.redirect(result.redirectTo, 302);
|
|
614
944
|
});
|
|
615
945
|
|
|
946
|
+
app.get("/v1/integrations/provider-oauth/callback", async (c) => {
|
|
947
|
+
assertIntegrationsEnabled();
|
|
948
|
+
const result = await completeApiIntegrationProviderOAuth(deps, {
|
|
949
|
+
...(c.req.query("code") ? { code: c.req.query("code") } : {}),
|
|
950
|
+
...(c.req.query("state") ? { state: c.req.query("state") } : {}),
|
|
951
|
+
...(c.req.query("error") ? { error: c.req.query("error") } : {}),
|
|
952
|
+
requestUrl: c.req.url,
|
|
953
|
+
});
|
|
954
|
+
return c.redirect(result.redirectTo, 302);
|
|
955
|
+
});
|
|
956
|
+
|
|
616
957
|
app.get("/v1/integrations/oauth/client-metadata.json", (c) => {
|
|
617
958
|
const baseUrl = integrationBaseUrl(settings.publicBaseUrl, c.req.url);
|
|
618
959
|
const metadataUrl = `${baseUrl}/v1/integrations/oauth/client-metadata.json`;
|
|
@@ -670,14 +1011,9 @@ async function persistOpenGeniSlackBotConnection(input: {
|
|
|
670
1011
|
"principal_validation",
|
|
671
1012
|
);
|
|
672
1013
|
}
|
|
673
|
-
const
|
|
674
|
-
requestedExisting
|
|
675
|
-
|
|
676
|
-
db,
|
|
677
|
-
input.state.workspaceId,
|
|
678
|
-
input.verified.metadata,
|
|
679
|
-
));
|
|
680
|
-
const existingMetadata = existing ? openGeniSlackBotMetadata(existing.metadata) : null;
|
|
1014
|
+
const existingMetadata = requestedExisting
|
|
1015
|
+
? openGeniSlackBotMetadata(requestedExisting.metadata)
|
|
1016
|
+
: null;
|
|
681
1017
|
if (existingMetadata && existingMetadata.slackTeamId !== input.verified.metadata.slackTeamId) {
|
|
682
1018
|
throw new SlackInstallCallbackError(
|
|
683
1019
|
409,
|
|
@@ -699,83 +1035,36 @@ async function persistOpenGeniSlackBotConnection(input: {
|
|
|
699
1035
|
);
|
|
700
1036
|
}
|
|
701
1037
|
const verifiedInstallAt = new Date(input.verified.metadata.verifiedAt);
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
updatedBySubjectId: input.state.subjectId,
|
|
729
|
-
},
|
|
730
|
-
})
|
|
731
|
-
: await createConnectionWithSlackBotSuccessAudit(db, {
|
|
732
|
-
...lifecycleAudit,
|
|
733
|
-
connection: {
|
|
734
|
-
accountId: input.state.accountId,
|
|
735
|
-
workspaceId: input.state.workspaceId,
|
|
736
|
-
subjectId: null,
|
|
737
|
-
providerDomain: "slack.com",
|
|
738
|
-
kind: "app_install",
|
|
739
|
-
credentialEncrypted,
|
|
740
|
-
grantedScopes: input.verified.grantedScopes,
|
|
741
|
-
expiresAt: null,
|
|
742
|
-
verifiedInstallAt,
|
|
743
|
-
verifiedInstallVersion: 1,
|
|
744
|
-
metadata: input.verified.metadata,
|
|
745
|
-
createdBySubjectId: input.state.subjectId,
|
|
746
|
-
},
|
|
747
|
-
});
|
|
748
|
-
if (!connection) {
|
|
749
|
-
throw new SlackInstallCallbackError(
|
|
750
|
-
409,
|
|
751
|
-
"connection_conflict",
|
|
752
|
-
"Slack bot connection changed during reinstall; start again",
|
|
753
|
-
"principal_validation",
|
|
754
|
-
);
|
|
755
|
-
}
|
|
756
|
-
return connection;
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
async function findMatchingOpenGeniSlackBotConnection(
|
|
760
|
-
db: ApiRouteDeps["db"],
|
|
761
|
-
workspaceId: string,
|
|
762
|
-
verified: Awaited<ReturnType<typeof verifyOpenGeniSlackBotCredential>>["metadata"],
|
|
763
|
-
) {
|
|
764
|
-
const connections = await listConnectionsMetadata(db, workspaceId, null);
|
|
765
|
-
return (
|
|
766
|
-
connections.find((connection) => {
|
|
767
|
-
const metadata = openGeniSlackBotMetadata(connection.metadata);
|
|
768
|
-
return (
|
|
769
|
-
connection.subjectId === null &&
|
|
770
|
-
connection.providerDomain === "slack.com" &&
|
|
771
|
-
connection.kind === "app_install" &&
|
|
772
|
-
metadata?.credentialRole === OPENGENI_SLACK_BOT_CREDENTIAL_ROLE &&
|
|
773
|
-
metadata.slackTeamId === verified.slackTeamId &&
|
|
774
|
-
metadata.botId === verified.botId &&
|
|
775
|
-
metadata.botUserId === verified.botUserId
|
|
1038
|
+
try {
|
|
1039
|
+
return await persistSlackBotInstallationWithSuccessAudit(db, {
|
|
1040
|
+
accountId: input.state.accountId,
|
|
1041
|
+
workspaceId: input.state.workspaceId,
|
|
1042
|
+
subjectId: input.state.subjectId,
|
|
1043
|
+
credentialRole: OPENGENI_SLACK_BOT_CREDENTIAL_ROLE,
|
|
1044
|
+
credentialLabel: OPENGENI_SLACK_BOT_CREDENTIAL_LABEL,
|
|
1045
|
+
slackTeamId: input.verified.metadata.slackTeamId,
|
|
1046
|
+
...(input.state.connectionId
|
|
1047
|
+
? {
|
|
1048
|
+
requestedConnectionId: input.state.connectionId,
|
|
1049
|
+
requestedConnectionVersion: input.state.connectionVersion,
|
|
1050
|
+
}
|
|
1051
|
+
: {}),
|
|
1052
|
+
credentialEncrypted,
|
|
1053
|
+
grantedScopes: input.verified.grantedScopes,
|
|
1054
|
+
verifiedInstallAt,
|
|
1055
|
+
metadata: input.verified.metadata,
|
|
1056
|
+
});
|
|
1057
|
+
} catch (error) {
|
|
1058
|
+
if (error instanceof SlackInstallationBindingConflictError) {
|
|
1059
|
+
throw new SlackInstallCallbackError(
|
|
1060
|
+
409,
|
|
1061
|
+
"connection_conflict",
|
|
1062
|
+
error.message,
|
|
1063
|
+
"principal_validation",
|
|
776
1064
|
);
|
|
777
|
-
}
|
|
778
|
-
|
|
1065
|
+
}
|
|
1066
|
+
throw error;
|
|
1067
|
+
}
|
|
779
1068
|
}
|
|
780
1069
|
|
|
781
1070
|
function requireOpenGeniSlackOAuthSettings(settings: ApiRouteDeps["settings"]): {
|
|
@@ -952,6 +1241,21 @@ function assertNotDirectGoogleDriveOAuth(
|
|
|
952
1241
|
}
|
|
953
1242
|
}
|
|
954
1243
|
|
|
1244
|
+
function assertNotDirectAtlassianOAuth(
|
|
1245
|
+
providerDomain: string,
|
|
1246
|
+
kind: string,
|
|
1247
|
+
metadata: Record<string, unknown> | undefined,
|
|
1248
|
+
): void {
|
|
1249
|
+
if (
|
|
1250
|
+
(providerDomain === ATLASSIAN_PROVIDER_DOMAIN && kind === "oauth2") ||
|
|
1251
|
+
AtlassianConnectionMetadata.safeParse(metadata).success
|
|
1252
|
+
) {
|
|
1253
|
+
throw new HTTPException(422, {
|
|
1254
|
+
message: "Atlassian credentials must use the dedicated OAuth connection flow",
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
|
|
955
1259
|
function assertNotReservedSlackBotMetadata(metadata: Record<string, unknown> | undefined): void {
|
|
956
1260
|
if (hasReservedOpenGeniSlackBotMetadata(metadata)) {
|
|
957
1261
|
throw new HTTPException(422, {
|
|
@@ -960,6 +1264,24 @@ function assertNotReservedSlackBotMetadata(metadata: Record<string, unknown> | u
|
|
|
960
1264
|
}
|
|
961
1265
|
}
|
|
962
1266
|
|
|
1267
|
+
function assertNotReservedFikenMetadata(metadata: Record<string, unknown> | undefined): void {
|
|
1268
|
+
if (hasReservedFikenMetadata(metadata)) {
|
|
1269
|
+
throw new HTTPException(422, {
|
|
1270
|
+
message: "Fiken connection metadata is reserved for the verified Fiken connect flows",
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
function assertNotReservedApiIntegrationOAuthMetadata(
|
|
1276
|
+
metadata: Record<string, unknown> | undefined,
|
|
1277
|
+
): void {
|
|
1278
|
+
if (metadata?.credentialRole === API_INTEGRATION_OAUTH_CREDENTIAL_ROLE) {
|
|
1279
|
+
throw new HTTPException(422, {
|
|
1280
|
+
message: "API Integration OAuth metadata is reserved for the dedicated provider flow",
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
963
1285
|
function writableSubjectId(
|
|
964
1286
|
requested: string | null | undefined,
|
|
965
1287
|
grantSubjectId: string,
|