@opengeni/api-router 2.11.3 → 2.12.3-canary.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.js +1 -1
- package/dist/{chunk-5X3XM6CE.js → chunk-4AQMKVQM.js} +17727 -14427
- package/dist/chunk-4AQMKVQM.js.map +1 -0
- package/dist/connection-ownership.d.ts +5 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/integrations/atlassian.d.ts +4 -0
- package/dist/integrations/connect-authority.d.ts +1 -0
- package/dist/integrations/connect-callback-return.d.ts +6 -0
- package/dist/integrations/fiken.d.ts +7 -0
- package/dist/integrations/github-app-connect.d.ts +47 -0
- package/dist/integrations/github-installation-proof.d.ts +3 -0
- package/dist/integrations/github-lens-connect.d.ts +153 -0
- package/dist/integrations/google-drive.d.ts +4 -0
- package/dist/integrations/oauth-client.d.ts +4 -0
- package/dist/integrations/oauth-return-path.d.ts +1 -0
- package/dist/integrations/personal-github.d.ts +2 -0
- package/dist/integrations/provider-oauth.d.ts +11 -1
- package/dist/integrations/slack-install.d.ts +20 -0
- package/dist/integrations/social-oauth.d.ts +2 -0
- package/dist/mcp/scheduled-task-view.d.ts +3 -3
- package/dist/routes/api-integrations.d.ts +14 -0
- package/dist/routes/connect.d.ts +5 -0
- package/dist/routes/external-identity-links.d.ts +6 -0
- package/dist/routes/host-mcp-bindings.d.ts +5 -0
- package/dist/sandbox/rematerialize.d.ts +1 -1
- package/dist/workspace-tool-gateway.d.ts +1 -0
- package/package.json +19 -19
- package/src/app.ts +22 -2
- package/src/connection-ownership.ts +14 -1
- package/src/index.ts +2 -0
- package/src/integrations/atlassian.ts +143 -39
- package/src/integrations/connect-authority.ts +2 -0
- package/src/integrations/connect-callback-return.ts +86 -0
- package/src/integrations/fiken.ts +222 -40
- package/src/integrations/github-app-connect.ts +389 -0
- package/src/integrations/github-installation-proof.ts +60 -0
- package/src/integrations/github-lens-connect.ts +97 -0
- package/src/integrations/google-drive.ts +152 -47
- package/src/integrations/oauth-client.ts +180 -84
- package/src/integrations/oauth-return-path.ts +18 -0
- package/src/integrations/personal-github.ts +146 -52
- package/src/integrations/provider-oauth.ts +132 -21
- package/src/integrations/slack-install.ts +83 -0
- package/src/integrations/social-oauth.ts +142 -1
- package/src/mcp/documents.ts +1 -1
- package/src/mcp/server.ts +64 -17
- package/src/routes/api-integrations.ts +74 -64
- package/src/routes/connect.ts +1869 -0
- package/src/routes/connections.ts +263 -221
- package/src/routes/documents.ts +2 -2
- package/src/routes/external-identity-links.ts +200 -0
- package/src/routes/github.ts +39 -64
- package/src/routes/host-mcp-bindings.ts +122 -0
- package/src/routes/organization-memberships.ts +23 -0
- package/src/routes/organization-sessions.ts +2 -2
- package/src/routes/personal-github.ts +8 -1
- package/src/routes/pr-review-github.ts +30 -3
- package/src/routes/scheduled-tasks.ts +29 -4
- package/src/routes/sessions.ts +61 -32
- package/src/routes/social.ts +5 -1
- package/src/routes/supergrok.ts +90 -3
- package/src/routes/workspaces.ts +16 -0
- package/src/sandbox/rematerialize.ts +33 -1
- package/src/workspace-tool-gateway.ts +136 -24
- package/dist/chunk-5X3XM6CE.js.map +0 -1
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
import { safeReturnPath } from "./oauth-return-path";
|
|
1
2
|
import type { ApiRouteDeps } from "@opengeni/core";
|
|
2
3
|
import type { Settings } from "@opengeni/config";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { ExternalActorContinuation } from "@opengeni/contracts/external-identities";
|
|
6
|
+
import { requireConnectOwnerAuthority } from "@opengeni/core";
|
|
3
7
|
import {
|
|
4
8
|
FIKEN_CREDENTIAL_LABEL,
|
|
5
9
|
FIKEN_CREDENTIAL_ROLE,
|
|
6
10
|
FIKEN_PROVIDER_DOMAIN,
|
|
7
11
|
FikenOAuthStartResponse,
|
|
12
|
+
FikenInstallRequest,
|
|
8
13
|
type AccessGrant,
|
|
9
14
|
type ConnectionMetadata,
|
|
10
15
|
type FikenCompanySummary,
|
|
@@ -13,7 +18,6 @@ import {
|
|
|
13
18
|
} from "@opengeni/contracts";
|
|
14
19
|
import {
|
|
15
20
|
fikenConnectionMetadata,
|
|
16
|
-
hasPermission,
|
|
17
21
|
isFikenConnection,
|
|
18
22
|
preferredFikenConnection,
|
|
19
23
|
requireEnvironmentEncryption,
|
|
@@ -24,8 +28,11 @@ import {
|
|
|
24
28
|
consumeIntegrationOAuthStateNonce,
|
|
25
29
|
createConnection,
|
|
26
30
|
encryptEnvironmentValue,
|
|
31
|
+
decryptEnvironmentValue,
|
|
32
|
+
getConnectAttempt,
|
|
33
|
+
claimConnectOperation,
|
|
34
|
+
finishConnectOperation,
|
|
27
35
|
getConnectionMetadata,
|
|
28
|
-
getWorkspaceGrant,
|
|
29
36
|
listConnectionsMetadata,
|
|
30
37
|
loadConnectionCredentialForBroker,
|
|
31
38
|
recordAuditEvent,
|
|
@@ -55,6 +62,84 @@ const MAX_DRAFT_LINES = 100;
|
|
|
55
62
|
// Fiken company slugs as issued by Fiken: lowercase alphanumerics and dashes.
|
|
56
63
|
const COMPANY_SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{0,127}$/;
|
|
57
64
|
|
|
65
|
+
/** Shared native/embedded token adapter. Provider verification happens before
|
|
66
|
+
* persistence; the caller reauthorizes inside its durable commit transaction. */
|
|
67
|
+
export async function prepareFikenTokenInstall(
|
|
68
|
+
deps: ApiRouteDeps,
|
|
69
|
+
grant: Pick<AccessGrant, "accountId" | "workspaceId" | "subjectId">,
|
|
70
|
+
raw: unknown,
|
|
71
|
+
expectedVersion?: number,
|
|
72
|
+
): Promise<(tx: Database) => Promise<ConnectionMetadata>> {
|
|
73
|
+
const payload = FikenInstallRequest.parse(raw);
|
|
74
|
+
const key = requireEnvironmentEncryption(deps.settings);
|
|
75
|
+
const existing = payload.connectionId
|
|
76
|
+
? await getConnectionMetadata(deps.db, grant.workspaceId, payload.connectionId, null)
|
|
77
|
+
: null;
|
|
78
|
+
if (payload.connectionId && !existing)
|
|
79
|
+
throw new HTTPException(404, { message: "connection not found" });
|
|
80
|
+
if (existing && !isFikenConnection(existing))
|
|
81
|
+
throw new HTTPException(422, { message: "connectionId is not a Fiken connection" });
|
|
82
|
+
if (expectedVersion !== undefined && existing?.version !== expectedVersion)
|
|
83
|
+
throw new HTTPException(409, { message: "The selected Fiken account changed" });
|
|
84
|
+
const verified = await verifyFikenApiToken(payload.apiToken, deps.fikenFetch ?? fetch);
|
|
85
|
+
const defaultCompanySlug = resolveFikenDefaultCompanySlug({
|
|
86
|
+
requested: payload.defaultCompanySlug ?? null,
|
|
87
|
+
previous: existing
|
|
88
|
+
? (fikenConnectionMetadata(existing.metadata)?.defaultCompanySlug ?? null)
|
|
89
|
+
: null,
|
|
90
|
+
companies: verified.companies,
|
|
91
|
+
});
|
|
92
|
+
if (payload.defaultCompanySlug && defaultCompanySlug !== payload.defaultCompanySlug)
|
|
93
|
+
throw new HTTPException(422, {
|
|
94
|
+
message: "The selected company is not accessible with this token",
|
|
95
|
+
});
|
|
96
|
+
const metadata = {
|
|
97
|
+
credentialRole: FIKEN_CREDENTIAL_ROLE,
|
|
98
|
+
credentialLabel: FIKEN_CREDENTIAL_LABEL,
|
|
99
|
+
companies: verified.companies,
|
|
100
|
+
defaultCompanySlug,
|
|
101
|
+
verifiedAt: new Date().toISOString(),
|
|
102
|
+
};
|
|
103
|
+
const credentialEncrypted = encryptEnvironmentValue(
|
|
104
|
+
key,
|
|
105
|
+
JSON.stringify(fikenCredentialBundle(payload.apiToken)),
|
|
106
|
+
);
|
|
107
|
+
return async (tx) => {
|
|
108
|
+
if (existing) {
|
|
109
|
+
const updated = await updateConnection(tx, {
|
|
110
|
+
workspaceId: grant.workspaceId,
|
|
111
|
+
connectionId: existing.id,
|
|
112
|
+
visibleToSubjectId: null,
|
|
113
|
+
expectedVersion: existing.version,
|
|
114
|
+
kind: "api_key",
|
|
115
|
+
status: "active",
|
|
116
|
+
credentialEncrypted,
|
|
117
|
+
grantedScopes: [],
|
|
118
|
+
expiresAt: null,
|
|
119
|
+
metadata,
|
|
120
|
+
updatedBySubjectId: grant.subjectId,
|
|
121
|
+
});
|
|
122
|
+
if (!updated)
|
|
123
|
+
throw new HTTPException(409, {
|
|
124
|
+
message: "The Fiken connection changed; reload before retrying",
|
|
125
|
+
});
|
|
126
|
+
return updated;
|
|
127
|
+
}
|
|
128
|
+
return createConnection(tx, {
|
|
129
|
+
accountId: grant.accountId,
|
|
130
|
+
workspaceId: grant.workspaceId,
|
|
131
|
+
subjectId: null,
|
|
132
|
+
providerDomain: FIKEN_PROVIDER_DOMAIN,
|
|
133
|
+
kind: "api_key",
|
|
134
|
+
credentialEncrypted,
|
|
135
|
+
grantedScopes: [],
|
|
136
|
+
expiresAt: null,
|
|
137
|
+
metadata,
|
|
138
|
+
createdBySubjectId: grant.subjectId,
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
58
143
|
export type FikenOperation =
|
|
59
144
|
| "companies.list"
|
|
60
145
|
| "contacts.list"
|
|
@@ -978,6 +1063,8 @@ export class FikenOAuthCallbackError extends Error {
|
|
|
978
1063
|
}
|
|
979
1064
|
|
|
980
1065
|
type FikenOAuthState = {
|
|
1066
|
+
externalContinuation?: ExternalActorContinuation;
|
|
1067
|
+
connectAttemptId?: string;
|
|
981
1068
|
accountId: string;
|
|
982
1069
|
workspaceId: string;
|
|
983
1070
|
subjectId: string;
|
|
@@ -1011,6 +1098,8 @@ export async function startFikenOAuth(
|
|
|
1011
1098
|
subjectId: string;
|
|
1012
1099
|
requestUrl: string;
|
|
1013
1100
|
payload: FikenOAuthStartRequest;
|
|
1101
|
+
externalContinuation?: ExternalActorContinuation;
|
|
1102
|
+
connectAttemptId?: string;
|
|
1014
1103
|
},
|
|
1015
1104
|
): Promise<FikenOAuthStartResponse> {
|
|
1016
1105
|
const fiken = requireFikenOAuthSettings(deps.settings);
|
|
@@ -1028,7 +1117,18 @@ export async function startFikenOAuth(
|
|
|
1028
1117
|
accountId: input.accountId,
|
|
1029
1118
|
workspaceId: input.workspaceId,
|
|
1030
1119
|
subjectId: input.subjectId,
|
|
1031
|
-
returnPath:
|
|
1120
|
+
returnPath: safeReturnPath(
|
|
1121
|
+
input.payload.returnPath ?? `/workspaces/${input.workspaceId}/capabilities`,
|
|
1122
|
+
),
|
|
1123
|
+
...(input.connectAttemptId ? { connectAttemptId: input.connectAttemptId } : {}),
|
|
1124
|
+
...(input.externalContinuation
|
|
1125
|
+
? {
|
|
1126
|
+
encryptedExternalContinuation: encryptEnvironmentValue(
|
|
1127
|
+
requireEnvironmentEncryption(deps.settings),
|
|
1128
|
+
JSON.stringify(ExternalActorContinuation.parse(input.externalContinuation)),
|
|
1129
|
+
),
|
|
1130
|
+
}
|
|
1131
|
+
: {}),
|
|
1032
1132
|
...(existing ? { connectionId: existing.id, connectionVersion: existing.version } : {}),
|
|
1033
1133
|
});
|
|
1034
1134
|
const authorizationUrl = new URL(FIKEN_AUTHORIZE_URL);
|
|
@@ -1050,12 +1150,32 @@ export async function completeFikenOAuthCallback(
|
|
|
1050
1150
|
error?: string | undefined;
|
|
1051
1151
|
requestUrl: string;
|
|
1052
1152
|
},
|
|
1053
|
-
): Promise<{ redirectTo: string }> {
|
|
1153
|
+
): Promise<{ redirectTo: string; exactReturn?: boolean }> {
|
|
1054
1154
|
const baseUrl = integrationBaseUrl(deps.settings.publicBaseUrl, input.requestUrl);
|
|
1055
1155
|
const returnBaseUrl = deps.settings.webBaseUrl?.replace(/\/+$/, "") ?? baseUrl;
|
|
1056
1156
|
let state: FikenOAuthState | null = null;
|
|
1157
|
+
let exactReturnUrl: string | undefined;
|
|
1158
|
+
let operation: { attemptId: string; operationId: string; inputDigest: string } | undefined;
|
|
1057
1159
|
try {
|
|
1058
1160
|
state = readFikenOAuthState(input.state, deps.settings);
|
|
1161
|
+
if (state.connectAttemptId) {
|
|
1162
|
+
const stored = await getConnectAttempt(deps.db, state, state.connectAttemptId);
|
|
1163
|
+
if (stored.attempt.providerId !== "fiken-oauth" || stored.attempt.ownership !== "workspace")
|
|
1164
|
+
throw new FikenOAuthCallbackError("invalid_state");
|
|
1165
|
+
exactReturnUrl = stored.returnUrl;
|
|
1166
|
+
operation = {
|
|
1167
|
+
attemptId: state.connectAttemptId,
|
|
1168
|
+
operationId: `oauth:${state.nonce}`,
|
|
1169
|
+
inputDigest: createHash("sha256").update(input.state!).digest("hex"),
|
|
1170
|
+
};
|
|
1171
|
+
const claim = await claimConnectOperation(deps.db, state, {
|
|
1172
|
+
...operation,
|
|
1173
|
+
expectedRevision: stored.attempt.revision,
|
|
1174
|
+
authorize: (tx, _attempt, origin) =>
|
|
1175
|
+
requireConnectOwnerAuthority(tx, state!, "connections:write", origin),
|
|
1176
|
+
});
|
|
1177
|
+
if (claim.status === "replayed") return { redirectTo: exactReturnUrl, exactReturn: true };
|
|
1178
|
+
}
|
|
1059
1179
|
await requireFikenCallbackGrant(deps.db, state);
|
|
1060
1180
|
const consumed = await consumeIntegrationOAuthStateNonce(deps.db, {
|
|
1061
1181
|
accountId: state.accountId,
|
|
@@ -1068,6 +1188,27 @@ export async function completeFikenOAuthCallback(
|
|
|
1068
1188
|
if (!consumed) {
|
|
1069
1189
|
throw new FikenOAuthCallbackError("state_replayed");
|
|
1070
1190
|
}
|
|
1191
|
+
// A proven pre-exchange rejection is terminal, not an uncertain provider
|
|
1192
|
+
// effect. Persist its receipt so repeated browser callbacks cannot retry it.
|
|
1193
|
+
if (operation && (input.error || !input.code)) {
|
|
1194
|
+
await finishConnectOperation(deps.db, state, {
|
|
1195
|
+
...operation,
|
|
1196
|
+
authorize: (tx, _attempt, origin) =>
|
|
1197
|
+
requireConnectOwnerAuthority(tx, state!, "connections:write", origin),
|
|
1198
|
+
commit: async (_tx, current) => ({
|
|
1199
|
+
...current,
|
|
1200
|
+
revision: current.revision + 1,
|
|
1201
|
+
state: input.error === "access_denied" ? "cancelled" : "failed",
|
|
1202
|
+
nextAction: { type: "none" },
|
|
1203
|
+
error: {
|
|
1204
|
+
code: input.error ? "provider_denied" : "missing_code",
|
|
1205
|
+
message: "Authorization was not completed. Start a new connection attempt.",
|
|
1206
|
+
retryable: false,
|
|
1207
|
+
},
|
|
1208
|
+
}),
|
|
1209
|
+
});
|
|
1210
|
+
return { redirectTo: exactReturnUrl!, exactReturn: true };
|
|
1211
|
+
}
|
|
1071
1212
|
if (input.error) {
|
|
1072
1213
|
throw new FikenOAuthCallbackError(
|
|
1073
1214
|
input.error === "access_denied" ? "provider_denied" : "provider_error",
|
|
@@ -1132,34 +1273,66 @@ export async function completeFikenOAuthCallback(
|
|
|
1132
1273
|
defaultCompanySlug,
|
|
1133
1274
|
verifiedAt: new Date().toISOString(),
|
|
1134
1275
|
};
|
|
1135
|
-
const
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1276
|
+
const persist = (tx: Database) =>
|
|
1277
|
+
existing
|
|
1278
|
+
? updateConnection(tx, {
|
|
1279
|
+
workspaceId: state!.workspaceId,
|
|
1280
|
+
connectionId: existing.id,
|
|
1281
|
+
visibleToSubjectId: null,
|
|
1282
|
+
expectedVersion: existing.version,
|
|
1283
|
+
kind: "oauth2",
|
|
1284
|
+
status: "active",
|
|
1285
|
+
credentialEncrypted,
|
|
1286
|
+
expiresAt: token.expiresAt,
|
|
1287
|
+
metadata,
|
|
1288
|
+
updatedBySubjectId: state!.subjectId,
|
|
1289
|
+
})
|
|
1290
|
+
: createConnection(tx, {
|
|
1291
|
+
accountId: state!.accountId,
|
|
1292
|
+
workspaceId: state!.workspaceId,
|
|
1293
|
+
// Workspace-owned by design, like the pasted-token install: the
|
|
1294
|
+
// first-party fiken tools resolve only workspace connections until
|
|
1295
|
+
// the delegation-snapshot lane exists for personal ownership.
|
|
1296
|
+
subjectId: null,
|
|
1297
|
+
providerDomain: FIKEN_PROVIDER_DOMAIN,
|
|
1298
|
+
kind: "oauth2",
|
|
1299
|
+
credentialEncrypted,
|
|
1300
|
+
grantedScopes: [],
|
|
1301
|
+
expiresAt: token.expiresAt,
|
|
1302
|
+
metadata,
|
|
1303
|
+
createdBySubjectId: state!.subjectId,
|
|
1304
|
+
});
|
|
1305
|
+
if (operation) {
|
|
1306
|
+
await finishConnectOperation(deps.db, state, {
|
|
1307
|
+
...operation,
|
|
1308
|
+
authorize: (tx, _attempt, origin) =>
|
|
1309
|
+
requireConnectOwnerAuthority(tx, state!, "connections:write", origin),
|
|
1310
|
+
commit: async (tx, current) => {
|
|
1311
|
+
const connection = await persist(tx);
|
|
1312
|
+
if (!connection) throw new FikenOAuthCallbackError("connection_conflict");
|
|
1313
|
+
return {
|
|
1314
|
+
...current,
|
|
1315
|
+
revision: current.revision + 1,
|
|
1316
|
+
state: "complete",
|
|
1317
|
+
credentialsCommitted: true,
|
|
1318
|
+
nextAction: { type: "none" },
|
|
1319
|
+
account: {
|
|
1320
|
+
id: connection.id,
|
|
1321
|
+
version: connection.version,
|
|
1322
|
+
providerId: "fiken-oauth",
|
|
1323
|
+
label: "Fiken",
|
|
1324
|
+
ownership: "workspace",
|
|
1325
|
+
status: "connected",
|
|
1326
|
+
},
|
|
1327
|
+
};
|
|
1328
|
+
},
|
|
1329
|
+
});
|
|
1330
|
+
return { redirectTo: exactReturnUrl!, exactReturn: true };
|
|
1331
|
+
}
|
|
1332
|
+
const connection = await deps.db.transaction(async (tx) => {
|
|
1333
|
+
await requireFikenCallbackGrant(tx, state!);
|
|
1334
|
+
return persist(tx);
|
|
1335
|
+
});
|
|
1163
1336
|
if (!connection) {
|
|
1164
1337
|
throw new FikenOAuthCallbackError("connection_conflict");
|
|
1165
1338
|
}
|
|
@@ -1167,6 +1340,7 @@ export async function completeFikenOAuthCallback(
|
|
|
1167
1340
|
redirectTo: fikenReturnUrl(returnBaseUrl, state.returnPath, "connected", connection.id),
|
|
1168
1341
|
};
|
|
1169
1342
|
} catch (error) {
|
|
1343
|
+
if (exactReturnUrl) return { redirectTo: exactReturnUrl, exactReturn: true };
|
|
1170
1344
|
return {
|
|
1171
1345
|
redirectTo: fikenReturnUrl(
|
|
1172
1346
|
returnBaseUrl,
|
|
@@ -1270,6 +1444,21 @@ function readFikenOAuthState(raw: string | undefined, settings: Settings): Fiken
|
|
|
1270
1444
|
workspaceId: required(payload.workspaceId),
|
|
1271
1445
|
subjectId: required(payload.subjectId),
|
|
1272
1446
|
returnPath: required(payload.returnPath),
|
|
1447
|
+
...(typeof payload.connectAttemptId === "string"
|
|
1448
|
+
? { connectAttemptId: required(payload.connectAttemptId) }
|
|
1449
|
+
: {}),
|
|
1450
|
+
...(typeof payload.encryptedExternalContinuation === "string"
|
|
1451
|
+
? {
|
|
1452
|
+
externalContinuation: ExternalActorContinuation.parse(
|
|
1453
|
+
JSON.parse(
|
|
1454
|
+
decryptEnvironmentValue(
|
|
1455
|
+
requireEnvironmentEncryption(settings),
|
|
1456
|
+
payload.encryptedExternalContinuation,
|
|
1457
|
+
),
|
|
1458
|
+
),
|
|
1459
|
+
),
|
|
1460
|
+
}
|
|
1461
|
+
: {}),
|
|
1273
1462
|
...(typeof payload.connectionId === "string" ? { connectionId: payload.connectionId } : {}),
|
|
1274
1463
|
...(typeof payload.connectionVersion === "number"
|
|
1275
1464
|
? { connectionVersion: payload.connectionVersion }
|
|
@@ -1280,14 +1469,7 @@ function readFikenOAuthState(raw: string | undefined, settings: Settings): Fiken
|
|
|
1280
1469
|
}
|
|
1281
1470
|
|
|
1282
1471
|
async function requireFikenCallbackGrant(db: Database, state: FikenOAuthState): Promise<void> {
|
|
1283
|
-
|
|
1284
|
-
if (
|
|
1285
|
-
!grant ||
|
|
1286
|
-
grant.accountId !== state.accountId ||
|
|
1287
|
-
!hasPermission(grant.permissions, "connections:write")
|
|
1288
|
-
) {
|
|
1289
|
-
throw new FikenOAuthCallbackError("permission_lost");
|
|
1290
|
-
}
|
|
1472
|
+
await requireConnectOwnerAuthority(db, state);
|
|
1291
1473
|
}
|
|
1292
1474
|
|
|
1293
1475
|
function fikenReturnUrl(
|