@opengeni/api-router 3.0.0-canary.0 → 3.0.1-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-7IAWEYBH.js → chunk-XZGTKYOA.js} +3626 -2525
- package/dist/{chunk-7IAWEYBH.js.map → chunk-XZGTKYOA.js.map} +1 -1
- package/dist/index.js +1 -1
- package/dist/integrations/fiken.d.ts +3 -1
- package/dist/integrations/github-lens-connect.d.ts +2 -2
- package/dist/integrations/integration-commit-authority.d.ts +13 -0
- package/dist/integrations/oauth-client.d.ts +118 -1
- package/dist/integrations/slack-interactions.d.ts +1 -1
- package/dist/mcp/first-party-tool-permissions.d.ts +4 -0
- package/dist/mcp/scheduled-task-view.d.ts +3 -3
- package/dist/routes/api-integrations.d.ts +1 -0
- package/dist/routes/organization-integration-policy.d.ts +3 -0
- package/package.json +19 -19
- package/src/app.ts +7 -2
- package/src/integrations/atlassian.ts +135 -65
- package/src/integrations/fiken.ts +57 -35
- package/src/integrations/github-app-connect.ts +34 -15
- package/src/integrations/google-drive.ts +295 -124
- package/src/integrations/integration-commit-authority.ts +69 -0
- package/src/integrations/oauth-client.ts +220 -65
- package/src/integrations/personal-github.ts +55 -31
- package/src/integrations/provider-oauth.ts +54 -31
- package/src/integrations/social-oauth.ts +83 -56
- package/src/mcp/first-party-tool-permissions.ts +1 -0
- package/src/mcp/knowledge-entries.ts +23 -6
- package/src/routes/api-integrations.ts +116 -4
- package/src/routes/billing.ts +52 -0
- package/src/routes/capabilities.ts +51 -1
- package/src/routes/connect.ts +161 -57
- package/src/routes/connections.ts +430 -305
- package/src/routes/github.ts +48 -19
- package/src/routes/integration-facets.ts +66 -25
- package/src/routes/knowledge.ts +48 -1
- package/src/routes/organization-integration-policy.ts +113 -0
- package/src/routes/plugins.ts +12 -2
- package/src/routes/pr-review-github.ts +60 -30
- package/src/routes/pr-review.ts +69 -45
- package/src/routes/social.ts +24 -13
|
@@ -1,7 +1,17 @@
|
|
|
1
|
+
import {
|
|
2
|
+
withOrganizationIntegrationAcquisition,
|
|
3
|
+
withOrganizationIntegrationPolicyFence,
|
|
4
|
+
} from "@opengeni/db/organization-integration-policy";
|
|
5
|
+
import {
|
|
6
|
+
claimOAuthAcquisition,
|
|
7
|
+
finishOAuthAcquisition,
|
|
8
|
+
integrationSourceSelectionRequiresAcquisition,
|
|
9
|
+
} from "./oauth-client";
|
|
1
10
|
import {
|
|
2
11
|
scheduledTaskKnowledgeSource,
|
|
3
12
|
requireScheduledTaskKnowledgeSource,
|
|
4
13
|
knowledgeSourceAgentConfig,
|
|
14
|
+
assertOrganizationIntegrationAllowed,
|
|
5
15
|
} from "@opengeni/contracts";
|
|
6
16
|
import { createHash, randomBytes } from "node:crypto";
|
|
7
17
|
import { ExternalActorContinuation } from "@opengeni/contracts/external-identities";
|
|
@@ -48,6 +58,7 @@ import {
|
|
|
48
58
|
} from "@opengeni/contracts";
|
|
49
59
|
import {
|
|
50
60
|
GOOGLE_DRIVE_INTEGRATION_DEFINITION,
|
|
61
|
+
CORE_INTEGRATION_DEFINITIONS,
|
|
51
62
|
integrationDefinitionProviderDomain,
|
|
52
63
|
} from "@opengeni/capabilities";
|
|
53
64
|
import {
|
|
@@ -68,10 +79,10 @@ import {
|
|
|
68
79
|
ConnectionDisconnectGenerationError,
|
|
69
80
|
ConnectionDisconnectIdempotencyError,
|
|
70
81
|
consumeIntegrationOAuthStateNonce,
|
|
71
|
-
claimConnectOperation,
|
|
72
82
|
finishConnectOperation,
|
|
73
83
|
getConnectAttempt,
|
|
74
84
|
type Database,
|
|
85
|
+
listInstalledApiIntegrations,
|
|
75
86
|
createConnection,
|
|
76
87
|
decryptEnvironmentValue,
|
|
77
88
|
disconnectConnectionIdempotently,
|
|
@@ -254,6 +265,7 @@ export async function startGoogleDriveOAuth(
|
|
|
254
265
|
externalContinuation?: ExternalActorContinuation;
|
|
255
266
|
},
|
|
256
267
|
): Promise<GoogleDriveOAuthStartResponse> {
|
|
268
|
+
await withOrganizationIntegrationAcquisition(deps.db, input, ["google-drive"], async () => {});
|
|
257
269
|
const google = requireGoogleDriveSettings(deps.settings);
|
|
258
270
|
const existing = input.payload.connectionId
|
|
259
271
|
? await getConnectionMetadata(
|
|
@@ -364,12 +376,18 @@ export async function completeGoogleDriveOAuthCallback(
|
|
|
364
376
|
operationId: `oauth:${state.nonce}`,
|
|
365
377
|
inputDigest: createHash("sha256").update(input.state!).digest("hex"),
|
|
366
378
|
};
|
|
367
|
-
const claim = await
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
379
|
+
const claim = await claimOAuthAcquisition(
|
|
380
|
+
deps.db,
|
|
381
|
+
state,
|
|
382
|
+
{
|
|
383
|
+
...operation,
|
|
384
|
+
expectedRevision: stored.attempt.revision,
|
|
385
|
+
authorize: (tx, _attempt, origin) =>
|
|
386
|
+
requireConnectOwnerAuthority(tx, state!, "connections:write", origin),
|
|
387
|
+
},
|
|
388
|
+
"google-drive",
|
|
389
|
+
Boolean(input.code && !input.error),
|
|
390
|
+
);
|
|
373
391
|
if (claim.status === "replayed") return { redirectTo: exactReturnUrl, exactReturn: true };
|
|
374
392
|
}
|
|
375
393
|
await requireGoogleDriveCallbackGrant(deps, state);
|
|
@@ -409,6 +427,7 @@ export async function completeGoogleDriveOAuthCallback(
|
|
|
409
427
|
if (!input.code) {
|
|
410
428
|
throw new GoogleDriveCallbackError("missing_code");
|
|
411
429
|
}
|
|
430
|
+
await withOrganizationIntegrationAcquisition(deps.db, state, ["google-drive"], async () => {});
|
|
412
431
|
|
|
413
432
|
const google = requireGoogleDriveSettings(deps.settings);
|
|
414
433
|
const key = requireEnvironmentEncryption(deps.settings);
|
|
@@ -562,36 +581,46 @@ export async function completeGoogleDriveOAuthCallback(
|
|
|
562
581
|
});
|
|
563
582
|
};
|
|
564
583
|
if (operation) {
|
|
565
|
-
await
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
584
|
+
await finishOAuthAcquisition(
|
|
585
|
+
deps.db,
|
|
586
|
+
acceptedState,
|
|
587
|
+
{
|
|
588
|
+
...operation,
|
|
589
|
+
authorize: (tx, _attempt, origin) =>
|
|
590
|
+
requireConnectOwnerAuthority(tx, acceptedState, "connections:write", origin),
|
|
591
|
+
commit: async (tx, current) => {
|
|
592
|
+
const connection = await persist(tx);
|
|
593
|
+
if (!connection) throw new GoogleDriveCallbackError("connection_conflict");
|
|
594
|
+
return {
|
|
595
|
+
...current,
|
|
596
|
+
revision: current.revision + 1,
|
|
597
|
+
state: "complete",
|
|
598
|
+
credentialsCommitted: true,
|
|
599
|
+
nextAction: { type: "none" },
|
|
600
|
+
account: {
|
|
601
|
+
id: connection.id,
|
|
602
|
+
version: connection.version,
|
|
603
|
+
providerId: current.providerId,
|
|
604
|
+
label: identity.emailAddress ?? identity.displayName,
|
|
605
|
+
ownership: "personal",
|
|
606
|
+
status: "connected",
|
|
607
|
+
},
|
|
608
|
+
};
|
|
609
|
+
},
|
|
587
610
|
},
|
|
588
|
-
|
|
611
|
+
"google-drive",
|
|
612
|
+
);
|
|
589
613
|
return { redirectTo: exactReturnUrl!, exactReturn: true };
|
|
590
614
|
}
|
|
591
|
-
const connection = await
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
615
|
+
const connection = await withOrganizationIntegrationAcquisition(
|
|
616
|
+
deps.db,
|
|
617
|
+
acceptedState,
|
|
618
|
+
["google-drive"],
|
|
619
|
+
async (tx) => {
|
|
620
|
+
await requireGoogleDriveCallbackGrant({ ...deps, db: tx }, acceptedState);
|
|
621
|
+
return persist(tx);
|
|
622
|
+
},
|
|
623
|
+
);
|
|
595
624
|
if (!connection) {
|
|
596
625
|
throw new GoogleDriveCallbackError("connection_conflict");
|
|
597
626
|
}
|
|
@@ -682,19 +711,45 @@ export async function transitionGoogleDriveLifecycle(
|
|
|
682
711
|
);
|
|
683
712
|
}
|
|
684
713
|
|
|
685
|
-
const
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
714
|
+
const persist = (tx: Database) =>
|
|
715
|
+
transitionConnectionState(tx, {
|
|
716
|
+
workspaceId: input.workspaceId,
|
|
717
|
+
connectionId: existing.id,
|
|
718
|
+
visibleToSubjectId: input.subjectId,
|
|
719
|
+
expectedVersion: existing.version,
|
|
720
|
+
status: "active",
|
|
721
|
+
metadata: GoogleDriveConnectionMetadata.parse({
|
|
722
|
+
...metadata,
|
|
723
|
+
lifecycle: googleDriveLifecycle(targetState),
|
|
724
|
+
}),
|
|
725
|
+
lastError: null,
|
|
726
|
+
updatedBySubjectId: input.subjectId,
|
|
727
|
+
});
|
|
728
|
+
const updated =
|
|
729
|
+
input.payload.action === "resume"
|
|
730
|
+
? await withOrganizationIntegrationPolicyFence(
|
|
731
|
+
deps.db,
|
|
732
|
+
{ accountId: existing.accountId, workspaceId: input.workspaceId },
|
|
733
|
+
async (tx, policy) => {
|
|
734
|
+
const current = await getConnectionMetadata(
|
|
735
|
+
tx,
|
|
736
|
+
input.workspaceId,
|
|
737
|
+
input.connectionId,
|
|
738
|
+
input.subjectId,
|
|
739
|
+
);
|
|
740
|
+
if (
|
|
741
|
+
current?.status === "active" &&
|
|
742
|
+
effectiveGoogleDriveLifecycle(
|
|
743
|
+
current,
|
|
744
|
+
requireGoogleDriveConnection(current, input.subjectId),
|
|
745
|
+
).state === "active"
|
|
746
|
+
)
|
|
747
|
+
return current;
|
|
748
|
+
assertOrganizationIntegrationAllowed(policy, "google-drive");
|
|
749
|
+
return persist(tx);
|
|
750
|
+
},
|
|
751
|
+
)
|
|
752
|
+
: await persist(deps.db);
|
|
698
753
|
if (!updated) {
|
|
699
754
|
const converged = await getConnectionMetadata(
|
|
700
755
|
deps.db,
|
|
@@ -972,58 +1027,141 @@ export async function saveGoogleDriveFacetSource(
|
|
|
972
1027
|
}
|
|
973
1028
|
const requestedDestination = bindGoogleDriveDocumentDestination(input, payload);
|
|
974
1029
|
const requestedConfig = googleDriveFacetConfig(requestedSources, requestedDestination, payload);
|
|
975
|
-
const
|
|
1030
|
+
const admitted = await withOrganizationIntegrationPolicyFence(
|
|
976
1031
|
deps.db,
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
idempotencyKey: payload.idempotencyKey,
|
|
985
|
-
kind: "configure",
|
|
986
|
-
expectedRequestDigest: (result) => {
|
|
987
|
-
const displayName = integrationFacetReceiptDisplayName(result);
|
|
988
|
-
if (displayName === null) return "invalid-integration-facet-receipt";
|
|
989
|
-
return integrationFacetConfigureRequestDigest({
|
|
1032
|
+
input,
|
|
1033
|
+
async (tx, policy) => {
|
|
1034
|
+
const replayed =
|
|
1035
|
+
await replayCompletedIntegrationFacetOperation<IntegrationFacetMutationResult>(tx, {
|
|
1036
|
+
accountId: input.accountId,
|
|
1037
|
+
workspaceId: input.workspaceId,
|
|
1038
|
+
subjectId: input.subjectId,
|
|
990
1039
|
capabilityId: input.capabilityId,
|
|
991
1040
|
instanceKey: input.instanceKey,
|
|
992
1041
|
facetKey: input.facetKey,
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1042
|
+
idempotencyKey: payload.idempotencyKey,
|
|
1043
|
+
kind: "configure",
|
|
1044
|
+
expectedRequestDigest: (result) => {
|
|
1045
|
+
const displayName = integrationFacetReceiptDisplayName(result);
|
|
1046
|
+
if (displayName === null) return "invalid-integration-facet-receipt";
|
|
1047
|
+
return integrationFacetConfigureRequestDigest({
|
|
1048
|
+
capabilityId: input.capabilityId,
|
|
1049
|
+
instanceKey: input.instanceKey,
|
|
1050
|
+
facetKey: input.facetKey,
|
|
1051
|
+
displayName,
|
|
1052
|
+
config: requestedConfig,
|
|
1053
|
+
...(payload.expectedVersion !== undefined
|
|
1054
|
+
? { expectedVersion: payload.expectedVersion }
|
|
1055
|
+
: {}),
|
|
1056
|
+
});
|
|
1057
|
+
},
|
|
998
1058
|
});
|
|
999
|
-
}
|
|
1059
|
+
if (replayed) return { replayed: IntegrationFacetMutationResult.parse(replayed) };
|
|
1060
|
+
const context = await requireGoogleDriveIntegrationFacet({ ...deps, db: tx }, input);
|
|
1061
|
+
googleDriveDocumentDestination(input, payload);
|
|
1062
|
+
const acquiring = googleDriveFacetSelectionRequiresAcquisition(context, requestedConfig);
|
|
1063
|
+
if (acquiring) {
|
|
1064
|
+
const installed = (
|
|
1065
|
+
await listInstalledApiIntegrations(tx, input.workspaceId, input.subjectId)
|
|
1066
|
+
).find(
|
|
1067
|
+
(candidate) =>
|
|
1068
|
+
candidate.capabilityId === input.capabilityId &&
|
|
1069
|
+
candidate.instanceKey === input.instanceKey,
|
|
1070
|
+
);
|
|
1071
|
+
assertOrganizationIntegrationAllowed(
|
|
1072
|
+
policy,
|
|
1073
|
+
installed
|
|
1074
|
+
? installedGoogleDriveFacetKey({
|
|
1075
|
+
apiProtocol: installed.protocol,
|
|
1076
|
+
definitionId: installed.definitionId,
|
|
1077
|
+
definitionProvenance: installed.definitionProvenance,
|
|
1078
|
+
})
|
|
1079
|
+
: null,
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
1082
|
+
return { context, acquiring };
|
|
1000
1083
|
},
|
|
1001
1084
|
);
|
|
1002
|
-
if (replayed) return
|
|
1003
|
-
const context =
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
workspaceId: input.workspaceId,
|
|
1007
|
-
subjectId: input.subjectId,
|
|
1008
|
-
connectionId: context.connection.id,
|
|
1009
|
-
sources: requestedSources,
|
|
1010
|
-
});
|
|
1011
|
-
return IntegrationFacetMutationResult.parse(
|
|
1012
|
-
await configureIntegrationFacet(deps.db, {
|
|
1013
|
-
accountId: input.accountId,
|
|
1085
|
+
if (admitted.replayed) return admitted.replayed;
|
|
1086
|
+
const context = admitted.context!;
|
|
1087
|
+
if (admitted.acquiring)
|
|
1088
|
+
await verifyGoogleDriveSources(deps, {
|
|
1014
1089
|
workspaceId: input.workspaceId,
|
|
1015
1090
|
subjectId: input.subjectId,
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
:
|
|
1025
|
-
|
|
1026
|
-
|
|
1091
|
+
connectionId: context.connection.id,
|
|
1092
|
+
sources: requestedSources,
|
|
1093
|
+
});
|
|
1094
|
+
return IntegrationFacetMutationResult.parse(
|
|
1095
|
+
await withOrganizationIntegrationPolicyFence(deps.db, input, (tx, policy) =>
|
|
1096
|
+
configureIntegrationFacet(tx, {
|
|
1097
|
+
accountId: input.accountId,
|
|
1098
|
+
workspaceId: input.workspaceId,
|
|
1099
|
+
subjectId: input.subjectId,
|
|
1100
|
+
capabilityId: input.capabilityId,
|
|
1101
|
+
instanceKey: input.instanceKey,
|
|
1102
|
+
facetKey: input.facetKey,
|
|
1103
|
+
displayName:
|
|
1104
|
+
context.facet.binding?.displayName ?? `${input.instanceKey} — Google Drive content`,
|
|
1105
|
+
config: requestedConfig,
|
|
1106
|
+
...(payload.expectedVersion !== undefined
|
|
1107
|
+
? { expectedVersion: payload.expectedVersion }
|
|
1108
|
+
: {}),
|
|
1109
|
+
idempotencyKey: payload.idempotencyKey,
|
|
1110
|
+
beforeAcquire: async (locked, installed) => {
|
|
1111
|
+
const current = await requireGoogleDriveIntegrationFacet({ ...deps, db: locked }, input);
|
|
1112
|
+
if (googleDriveFacetSelectionRequiresAcquisition(current, requestedConfig)) {
|
|
1113
|
+
assertOrganizationIntegrationAllowed(policy, installedGoogleDriveFacetKey(installed));
|
|
1114
|
+
if (!admitted.acquiring)
|
|
1115
|
+
throw new HTTPException(409, {
|
|
1116
|
+
message: "Google Drive source binding changed; verify the selection again",
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
},
|
|
1120
|
+
}),
|
|
1121
|
+
),
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
/** This is installed server-authored version provenance, not a facet's mutable
|
|
1126
|
+
* capabilities.provider, URL, domain, or client-authored credential metadata. */
|
|
1127
|
+
function installedGoogleDriveFacetKey(context: {
|
|
1128
|
+
apiProtocol: string | null;
|
|
1129
|
+
definitionId: string | null;
|
|
1130
|
+
definitionProvenance: string | null;
|
|
1131
|
+
}): string | null {
|
|
1132
|
+
if (context.apiProtocol !== "openapi" && context.apiProtocol !== "graphql") return null;
|
|
1133
|
+
if (context.definitionProvenance === "workspace") return `custom:${context.apiProtocol}`;
|
|
1134
|
+
return context.definitionProvenance === "curated" &&
|
|
1135
|
+
CORE_INTEGRATION_DEFINITIONS.some((definition) => definition.id === context.definitionId)
|
|
1136
|
+
? context.definitionId
|
|
1137
|
+
: null;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
function googleDriveFacetSelectionRequiresAcquisition(
|
|
1141
|
+
context: Awaited<ReturnType<typeof requireGoogleDriveIntegrationFacet>>,
|
|
1142
|
+
requested: ReturnType<typeof googleDriveFacetConfig>,
|
|
1143
|
+
): boolean {
|
|
1144
|
+
const binding = context.facet.binding;
|
|
1145
|
+
if (
|
|
1146
|
+
!binding ||
|
|
1147
|
+
binding.status !== "active" ||
|
|
1148
|
+
!binding.directlyOwned ||
|
|
1149
|
+
binding.connectionId !== context.connection.id
|
|
1150
|
+
)
|
|
1151
|
+
return true;
|
|
1152
|
+
const previous = GoogleDriveKnowledgeSourceConfig.safeParse(binding.config);
|
|
1153
|
+
if (!previous.success) return true;
|
|
1154
|
+
const selection = (config: ReturnType<typeof googleDriveFacetConfig>) =>
|
|
1155
|
+
config.sources.map((source) => ({
|
|
1156
|
+
...source,
|
|
1157
|
+
destination: config.destination,
|
|
1158
|
+
syncCadence: config.syncCadence,
|
|
1159
|
+
syncEnabled: true,
|
|
1160
|
+
readPolicy: config.readPolicy,
|
|
1161
|
+
}));
|
|
1162
|
+
return integrationSourceSelectionRequiresAcquisition(
|
|
1163
|
+
selection(previous.data),
|
|
1164
|
+
selection(requested),
|
|
1027
1165
|
);
|
|
1028
1166
|
}
|
|
1029
1167
|
|
|
@@ -1065,14 +1203,34 @@ export async function saveGoogleDriveSource(
|
|
|
1065
1203
|
if (existing.accountId !== input.accountId || existing.workspaceId !== input.workspaceId) {
|
|
1066
1204
|
throw new HTTPException(403, { message: "Google Drive connection authority mismatch" });
|
|
1067
1205
|
}
|
|
1068
|
-
await requireGoogleDriveSourceConnection(
|
|
1206
|
+
const existingMetadata = await requireGoogleDriveSourceConnection(
|
|
1207
|
+
deps,
|
|
1208
|
+
existing,
|
|
1209
|
+
input.subjectId,
|
|
1210
|
+
);
|
|
1069
1211
|
const documentDestination = googleDriveDocumentDestination(input, payload);
|
|
1070
|
-
const
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1212
|
+
const requestedSelection = payload.sources.map((source) => ({
|
|
1213
|
+
...source,
|
|
1214
|
+
destination: documentDestination,
|
|
1215
|
+
syncCadence: payload.syncCadence,
|
|
1216
|
+
syncEnabled: payload.syncEnabled,
|
|
1217
|
+
readPolicy: payload.readPolicy,
|
|
1218
|
+
}));
|
|
1219
|
+
const acquiring = integrationSourceSelectionRequiresAcquisition(
|
|
1220
|
+
existingMetadata.selectedSources ??
|
|
1221
|
+
(existingMetadata.selectedSource ? [existingMetadata.selectedSource] : []),
|
|
1222
|
+
requestedSelection,
|
|
1223
|
+
);
|
|
1224
|
+
if (acquiring)
|
|
1225
|
+
await withOrganizationIntegrationAcquisition(deps.db, input, ["google-drive"], async () => {});
|
|
1226
|
+
const verifiedSources = acquiring
|
|
1227
|
+
? await verifyGoogleDriveSources(deps, {
|
|
1228
|
+
workspaceId: input.workspaceId,
|
|
1229
|
+
subjectId: input.subjectId,
|
|
1230
|
+
connectionId: input.connectionId,
|
|
1231
|
+
sources: payload.sources,
|
|
1232
|
+
})
|
|
1233
|
+
: payload.sources;
|
|
1076
1234
|
const latest =
|
|
1077
1235
|
(await getConnectionMetadata(
|
|
1078
1236
|
deps.db,
|
|
@@ -1084,33 +1242,46 @@ export async function saveGoogleDriveSource(
|
|
|
1084
1242
|
const previousSources =
|
|
1085
1243
|
latestMetadata.selectedSources ??
|
|
1086
1244
|
(latestMetadata.selectedSource ? [latestMetadata.selectedSource] : []);
|
|
1087
|
-
const updated = await
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1245
|
+
const updated = await withOrganizationIntegrationPolicyFence(
|
|
1246
|
+
deps.db,
|
|
1247
|
+
input,
|
|
1248
|
+
async (tx, policy) => {
|
|
1249
|
+
if (integrationSourceSelectionRequiresAcquisition(previousSources, requestedSelection)) {
|
|
1250
|
+
assertOrganizationIntegrationAllowed(policy, "google-drive");
|
|
1251
|
+
if (!acquiring)
|
|
1252
|
+
throw new HTTPException(409, {
|
|
1253
|
+
message: "Google Drive sources changed; verify the selection again",
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
return transitionConnectionState(tx, {
|
|
1257
|
+
workspaceId: input.workspaceId,
|
|
1258
|
+
connectionId: latest.id,
|
|
1259
|
+
visibleToSubjectId: input.subjectId,
|
|
1260
|
+
expectedVersion: latest.version,
|
|
1261
|
+
metadata: GoogleDriveConnectionMetadata.parse({
|
|
1262
|
+
...latestMetadata,
|
|
1263
|
+
documentDestination,
|
|
1264
|
+
selectedSource: null,
|
|
1265
|
+
selectedSources: verifiedSources.map((verified) => {
|
|
1266
|
+
const previous = previousSources.find((source) => source.id === verified.id);
|
|
1267
|
+
return {
|
|
1268
|
+
id: verified.id,
|
|
1269
|
+
name: verified.name,
|
|
1270
|
+
mimeType: verified.mimeType,
|
|
1271
|
+
driveId: verified.driveId,
|
|
1272
|
+
destination: documentDestination,
|
|
1273
|
+
syncCadence: payload.syncCadence,
|
|
1274
|
+
syncEnabled: payload.syncEnabled,
|
|
1275
|
+
configGeneration: (previous?.configGeneration ?? 0) + 1,
|
|
1276
|
+
readPolicy: payload.readPolicy,
|
|
1277
|
+
selectedAt: new Date().toISOString(),
|
|
1278
|
+
};
|
|
1279
|
+
}),
|
|
1280
|
+
}),
|
|
1281
|
+
updatedBySubjectId: input.subjectId,
|
|
1282
|
+
});
|
|
1283
|
+
},
|
|
1284
|
+
);
|
|
1114
1285
|
if (!updated) {
|
|
1115
1286
|
throw new HTTPException(409, { message: "Google Drive connection changed; try again" });
|
|
1116
1287
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { verifyDelegatedAccessToken, type AccessGrant, type Permission } from "@opengeni/contracts";
|
|
2
|
+
import { resolveFirstPartyDelegationSecret, type Settings } from "@opengeni/config";
|
|
3
|
+
import { HTTPException } from "hono/http-exception";
|
|
4
|
+
import {
|
|
5
|
+
externalActorContinuationForAuthorization,
|
|
6
|
+
requireConnectOwnerAuthority,
|
|
7
|
+
hasPermission,
|
|
8
|
+
type AccessGrantAuthorization,
|
|
9
|
+
} from "@opengeni/core";
|
|
10
|
+
import type { Database } from "@opengeni/db";
|
|
11
|
+
|
|
12
|
+
export type IntegrationCommitGrant = AccessGrant & {
|
|
13
|
+
authorizeCommit: (tx: Database) => Promise<void>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** Keep server-verified provenance alongside the grant across provider work.
|
|
17
|
+
* Call only inside the policy transaction, before persistence locks. */
|
|
18
|
+
export async function integrationCommitGrant(
|
|
19
|
+
authorization: AccessGrantAuthorization,
|
|
20
|
+
permissions: readonly Permission[],
|
|
21
|
+
request: { settings: Settings; authorizationHeader?: string | undefined },
|
|
22
|
+
): Promise<IntegrationCommitGrant> {
|
|
23
|
+
const grant = structuredClone(authorization.grant);
|
|
24
|
+
const continuation = externalActorContinuationForAuthorization(authorization);
|
|
25
|
+
const state = {
|
|
26
|
+
accountId: grant.accountId,
|
|
27
|
+
workspaceId: grant.workspaceId,
|
|
28
|
+
subjectId: grant.subjectId,
|
|
29
|
+
personalOwnerVerified: authorization.canonicalManagedHumanSession,
|
|
30
|
+
...(continuation ? { externalContinuation: structuredClone(continuation) } : {}),
|
|
31
|
+
};
|
|
32
|
+
const required = [...permissions];
|
|
33
|
+
const token = request.authorizationHeader?.startsWith("Bearer ")
|
|
34
|
+
? request.authorizationHeader.slice(7)
|
|
35
|
+
: null;
|
|
36
|
+
const secret = resolveFirstPartyDelegationSecret(request.settings);
|
|
37
|
+
const signed = token && secret ? await verifyDelegatedAccessToken(secret, token) : null;
|
|
38
|
+
if (signed) {
|
|
39
|
+
const matches = (value: NonNullable<typeof signed>) =>
|
|
40
|
+
value.accountId === state.accountId &&
|
|
41
|
+
value.workspaceId === state.workspaceId &&
|
|
42
|
+
value.subjectId === state.subjectId &&
|
|
43
|
+
required.every((permission) => hasPermission(value.permissions, permission));
|
|
44
|
+
if (!matches(signed))
|
|
45
|
+
throw new HTTPException(403, { message: "Integration bearer authority mismatch" });
|
|
46
|
+
return {
|
|
47
|
+
...grant,
|
|
48
|
+
authorizeCommit: async (tx) => {
|
|
49
|
+
const currentSecret = resolveFirstPartyDelegationSecret(request.settings);
|
|
50
|
+
const current =
|
|
51
|
+
currentSecret && token ? await verifyDelegatedAccessToken(currentSecret, token) : null;
|
|
52
|
+
if (!current || !matches(current))
|
|
53
|
+
throw new HTTPException(403, {
|
|
54
|
+
message: "Integration bearer authority expired or changed",
|
|
55
|
+
});
|
|
56
|
+
// A signed external continuation still has live host/key/link authority.
|
|
57
|
+
if (continuation)
|
|
58
|
+
for (const permission of required)
|
|
59
|
+
await requireConnectOwnerAuthority(tx, state, permission);
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
...grant,
|
|
65
|
+
authorizeCommit: async (tx) => {
|
|
66
|
+
for (const permission of required) await requireConnectOwnerAuthority(tx, state, permission);
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|