@opengeni/api-router 0.30.3 → 1.0.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/access-grant-rls.d.ts +3 -0
- package/dist/admission-trace.d.ts +8 -0
- package/dist/api-websocket.d.ts +18 -0
- package/dist/app.d.ts +50 -2
- package/dist/app.js +12 -1
- package/dist/artifact-catalog-cursor.d.ts +27 -0
- package/dist/auth/managed-auth-attempt-context.d.ts +10 -0
- package/dist/auth/managed-auth-rate-limits.d.ts +167 -0
- package/dist/auth/managed-auth-session-adapter.d.ts +6 -0
- package/dist/auth/managed-auth.d.ts +102 -3
- package/dist/auth/managed-email.d.ts +29 -0
- package/dist/auth/managed-sign-in-notifications.d.ts +4 -0
- package/dist/auth/new-signups-gate.d.ts +23 -0
- package/dist/auth/organization-user-setup.d.ts +80 -0
- package/dist/auth/signup-funnel-metrics.d.ts +44 -0
- package/dist/browser-controller-authority.d.ts +1 -61
- package/dist/browser-ephemeral.d.ts +5 -0
- package/dist/browser-state-authority.d.ts +1 -27
- package/dist/chunk-6TZ5LG3O.js +123948 -0
- package/dist/chunk-6TZ5LG3O.js.map +1 -0
- package/dist/chunk-OGQDXVXY.js +18 -0
- package/dist/chunk-OGQDXVXY.js.map +1 -0
- package/dist/claude-subscription-account-conflicts.d.ts +1 -0
- package/dist/claude-subscription-account-usage.d.ts +39 -0
- package/dist/claude-subscription-oauth.d.ts +25 -0
- package/dist/claude-subscription-profile.d.ts +9 -0
- package/dist/claude-subscription-usage.d.ts +44 -0
- package/dist/claude-workspace-connection.d.ts +15 -0
- package/dist/codemode.d.ts +32 -1
- package/dist/company-brain-okf.d.ts +24 -0
- package/dist/connected-machine-computer-access.d.ts +11 -0
- package/dist/connection-ownership.d.ts +54 -0
- package/dist/controller-data-plane.d.ts +32 -0
- package/dist/editable-artifact-source-session.d.ts +3 -0
- package/dist/editable-artifact-websocket.d.ts +10 -18
- package/dist/editable-artifact-workspace-files.d.ts +1 -1
- package/dist/fatal-process-boundary.d.ts +25 -0
- package/dist/github-access.d.ts +31 -2
- package/dist/http/acting-person.d.ts +24 -0
- package/dist/http/api-contract-compat.d.ts +19 -0
- package/dist/http/api-error.d.ts +27 -1
- package/dist/http/cors.d.ts +17 -0
- package/dist/http/deprecation.d.ts +42 -0
- package/dist/http/interaction-control-error.d.ts +13 -0
- package/dist/http/keyed-admission.d.ts +12 -0
- package/dist/http/local-browser-boundary.d.ts +72 -0
- package/dist/http/metrics-listener.d.ts +22 -0
- package/dist/http/path-identifier.d.ts +17 -0
- package/dist/http/registered-route-label.d.ts +16 -0
- package/dist/http/request-body.d.ts +27 -0
- package/dist/http/request-source.d.ts +52 -0
- package/dist/http/sse.d.ts +17 -6
- package/dist/http/unmatched-route.d.ts +18 -0
- package/dist/http/user-content.d.ts +36 -0
- package/dist/http/websocket-upgrade-dispatch.d.ts +18 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.js +1091 -162
- package/dist/index.js.map +1 -1
- package/dist/integration-endpoint-test.d.ts +20 -0
- package/dist/integrations/api-integrations.d.ts +2 -2
- package/dist/integrations/atlassian.d.ts +29 -3
- 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 +16 -2
- 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 +116 -0
- package/dist/integrations/github-skill-source.d.ts +1 -5
- package/dist/integrations/google-drive.d.ts +24 -0
- package/dist/integrations/integration-commit-authority.d.ts +13 -0
- package/dist/integrations/marketplace-plugin.d.ts +2691 -0
- package/dist/integrations/oauth-client.d.ts +178 -18
- package/dist/integrations/oauth-profiles.d.ts +222 -0
- package/dist/integrations/oauth-return-path.d.ts +1 -0
- package/dist/integrations/personal-github-repositories.d.ts +84 -0
- package/dist/integrations/personal-github.d.ts +42 -0
- package/dist/integrations/pr-review-provider.d.ts +22 -0
- package/dist/integrations/provider-oauth.d.ts +19 -1
- package/dist/integrations/slack-app-home.d.ts +24 -0
- package/dist/integrations/slack-bot.d.ts +367 -12
- package/dist/integrations/slack-file-upload-flow.d.ts +62 -0
- package/dist/integrations/slack-install.d.ts +20 -0
- package/dist/integrations/slack-interactions.d.ts +107 -9
- package/dist/integrations/slack-mrkdwn.d.ts +41 -0
- package/dist/integrations/slack-routing.d.ts +120 -0
- package/dist/integrations/slack-session-defaults.d.ts +58 -0
- package/dist/integrations/slack-task-file-upload.d.ts +36 -0
- package/dist/integrations/social-oauth.d.ts +7 -0
- package/dist/interaction-agent-access.d.ts +15 -0
- package/dist/interaction-frame-proxy.d.ts +68 -0
- package/dist/interaction-metrics.d.ts +2 -0
- package/dist/mcp/action-catalog-types.d.ts +9 -0
- package/dist/mcp/action-catalog.gen.d.ts +2 -0
- package/dist/mcp/capability-account-readiness.d.ts +7 -0
- package/dist/mcp/child-read-evidence.d.ts +9 -0
- package/dist/mcp/command-read.d.ts +24 -0
- package/dist/mcp/company-profile-agent-admin.d.ts +44 -0
- package/dist/mcp/contract-input.d.ts +11 -0
- package/dist/mcp/documents.d.ts +7 -5
- package/dist/mcp/files.d.ts +2 -2
- package/dist/mcp/first-party-tool-permissions.d.ts +650 -0
- package/dist/mcp/knowledge-entries.d.ts +5 -0
- package/dist/mcp/orchestration-failure-diagnostic.d.ts +43 -0
- package/dist/mcp/receipts.d.ts +9 -0
- package/dist/mcp/request-abort.d.ts +19 -0
- package/dist/mcp/scheduled-task-input.d.ts +1189 -0
- package/dist/mcp/scheduled-task-view.d.ts +22 -4
- package/dist/mcp/server.d.ts +255 -3
- package/dist/mcp/session-event-view.d.ts +94 -0
- package/dist/mcp/session-view.d.ts +66 -2
- package/dist/mcp/session-wait.d.ts +174 -0
- package/dist/mcp-oauth.d.ts +52 -0
- package/dist/model-catalog.d.ts +11 -31
- package/dist/observability.d.ts +3 -1
- package/dist/organization-mcp.d.ts +52 -0
- package/dist/organization-recovery-notifications.d.ts +41 -0
- package/dist/routes/analytics-consent.d.ts +26 -0
- package/dist/routes/api-integrations.d.ts +25 -0
- package/dist/routes/api-keys.d.ts +17 -0
- package/dist/routes/artifact-catalog.d.ts +6 -0
- package/dist/routes/automations.d.ts +19 -0
- package/dist/routes/billing.d.ts +49 -0
- package/dist/routes/browser-sessions.d.ts +59 -2
- package/dist/routes/claude-subscription-accounts.d.ts +3 -0
- package/dist/routes/claude-subscription-oauth.d.ts +3 -0
- package/dist/routes/client-errors.d.ts +146 -0
- package/dist/routes/codex.d.ts +76 -2
- package/dist/routes/company-brain.d.ts +3 -0
- package/dist/routes/company-profile.d.ts +2 -1
- package/dist/routes/computer-sessions.d.ts +14 -0
- package/dist/routes/connect.d.ts +5 -0
- package/dist/routes/editable-artifacts.d.ts +2 -0
- package/dist/routes/external-identity-links.d.ts +6 -0
- package/dist/routes/feedback.d.ts +3 -0
- package/dist/routes/github-browser-pages.d.ts +12 -0
- package/dist/routes/github.d.ts +2 -0
- package/dist/routes/insights-response-cache.d.ts +10 -0
- package/dist/routes/insights-usage.d.ts +12 -0
- package/dist/routes/insights.d.ts +30 -1
- package/dist/routes/knowledge.d.ts +3 -0
- package/dist/routes/managed-auth-session-sets.d.ts +26 -0
- package/dist/routes/managed-onboarding.d.ts +29 -0
- package/dist/routes/managed-sign-in-methods.d.ts +6 -0
- package/dist/routes/model-connection-access.d.ts +3 -0
- package/dist/routes/organization-integration-policy.d.ts +3 -0
- package/dist/routes/organization-integrations.d.ts +6 -0
- package/dist/routes/organization-mcp-connections.d.ts +3 -0
- package/dist/routes/organization-model-providers.d.ts +3 -0
- package/dist/routes/organization-recovery.d.ts +19 -0
- package/dist/routes/organization-service-accounts.d.ts +3 -0
- package/dist/routes/organization-sessions.d.ts +26 -0
- package/dist/routes/personal-github-git-broker.d.ts +29 -0
- package/dist/routes/personal-github.d.ts +3 -0
- package/dist/routes/pr-review-github.d.ts +3 -0
- package/dist/routes/pr-review.d.ts +3 -0
- package/dist/routes/session-artifact-associations.d.ts +4 -0
- package/dist/routes/session-history-imports.d.ts +9 -0
- package/dist/routes/sessions.d.ts +44 -6
- package/dist/routes/skill-content.d.ts +4 -0
- package/dist/routes/skill-install-authority.d.ts +6 -0
- package/dist/routes/subscription-account-pools.d.ts +34 -0
- package/dist/routes/subscription-pool-access.d.ts +16 -0
- package/dist/routes/supergrok.d.ts +6 -1
- package/dist/routes/usage-allowances.d.ts +12 -0
- package/dist/routes/user-resource-authorities.d.ts +3 -0
- package/dist/routes/workspace-artifacts.d.ts +2 -1
- package/dist/routes/workspace-capture.d.ts +10 -2
- package/dist/routes/workspace-integrations.d.ts +35 -0
- package/dist/routes/workspace-learning.d.ts +3 -0
- package/dist/routes/workspace-model-providers.d.ts +4 -0
- package/dist/routes/workspaces.d.ts +29 -0
- package/dist/sandbox/auth-callout.d.ts +8 -0
- package/dist/sandbox/channel-a.d.ts +19 -1
- package/dist/sandbox/connection-authority.d.ts +3 -0
- package/dist/sandbox/enrollment.d.ts +9 -1
- package/dist/sandbox/machines.d.ts +5 -2
- package/dist/sandbox/metrics-ingestion.d.ts +28 -20
- package/dist/sandbox/rematerialize.d.ts +2 -0
- package/dist/sandbox/session-attach-variable-sets.d.ts +30 -0
- package/dist/sandbox/viewer.d.ts +26 -1
- package/dist/sandbox-file-artifacts.d.ts +50 -0
- package/dist/scheduled-task-deletion.d.ts +13 -0
- package/dist/session-create-observability.d.ts +5 -0
- package/dist/session-event-view-MPS2OZD3.js +292 -0
- package/dist/session-event-view-MPS2OZD3.js.map +1 -0
- package/dist/site-session-origin.d.ts +13 -0
- package/dist/site-uploads.d.ts +41 -0
- package/dist/slack-reaction-files.d.ts +1 -1
- package/dist/temporal-schedule-cleanup.d.ts +1 -0
- package/dist/temporal-schedule-sync.d.ts +26 -0
- package/dist/transcription/providers/xai-subscription.d.ts +1 -0
- package/dist/transcription/service.d.ts +2 -1
- package/dist/work-discovery-observability.d.ts +33 -0
- package/dist/workspace-artifact-content.d.ts +28 -0
- package/dist/workspace-artifact-provenance.d.ts +7 -0
- package/dist/workspace-delete-observability.d.ts +10 -0
- package/dist/workspace-deletion.d.ts +6 -0
- package/dist/workspace-state-projection.d.ts +3 -5
- package/dist/workspace-tool-gateway-observability.d.ts +17 -0
- package/dist/workspace-tool-gateway.d.ts +123 -0
- package/dist/workspace-webhook-dispatch.d.ts +26 -0
- package/package.json +23 -19
- package/src/access-grant-rls.ts +75 -0
- package/src/admission-trace.ts +14 -0
- package/src/api-websocket.ts +23 -0
- package/src/app.ts +1967 -252
- package/src/artifact-catalog-cursor.ts +60 -0
- package/src/auth/managed-auth-attempt-context.ts +70 -0
- package/src/auth/managed-auth-rate-limits.ts +252 -0
- package/src/auth/managed-auth-session-adapter.ts +222 -0
- package/src/auth/managed-auth.ts +536 -60
- package/src/auth/managed-email.ts +174 -0
- package/src/auth/managed-sign-in-notifications.ts +81 -0
- package/src/auth/new-signups-gate.ts +49 -0
- package/src/auth/organization-user-setup.ts +307 -0
- package/src/auth/signup-funnel-metrics.ts +164 -0
- package/src/browser-controller-authority.ts +1 -186
- package/src/browser-ephemeral.ts +39 -0
- package/src/browser-state-authority.ts +1 -236
- package/src/claude-subscription-account-conflicts.ts +12 -0
- package/src/claude-subscription-account-usage.ts +48 -0
- package/src/claude-subscription-oauth.ts +362 -0
- package/src/claude-subscription-profile.ts +73 -0
- package/src/claude-subscription-usage.ts +97 -0
- package/src/claude-workspace-connection.ts +82 -0
- package/src/codemode.ts +194 -30
- package/src/codex-realtime.ts +8 -2
- package/src/company-brain-okf.ts +340 -0
- package/src/connected-machine-computer-access.ts +33 -0
- package/src/connection-ownership.ts +199 -0
- package/src/controller-data-plane.ts +81 -0
- package/src/editable-artifact-native-kernel.ts +26 -15
- package/src/editable-artifact-office-import.ts +28 -3
- package/src/editable-artifact-production.ts +23 -7
- package/src/editable-artifact-source-session.ts +123 -0
- package/src/editable-artifact-websocket.ts +27 -21
- package/src/editable-artifact-workspace-files.ts +31 -6
- package/src/fatal-process-boundary.ts +246 -0
- package/src/gateway-realtime.ts +1 -1
- package/src/github-access.ts +147 -5
- package/src/http/acting-person.ts +71 -0
- package/src/http/api-contract-compat.ts +49 -0
- package/src/http/api-error.ts +115 -1
- package/src/http/auth.ts +48 -0
- package/src/http/cors.ts +67 -0
- package/src/http/deprecation.ts +118 -0
- package/src/http/interaction-control-error.ts +164 -0
- package/src/http/keyed-admission.ts +29 -0
- package/src/http/local-browser-boundary.ts +343 -0
- package/src/http/metrics-listener.ts +57 -0
- package/src/http/path-identifier.ts +54 -0
- package/src/http/registered-route-label.ts +34 -0
- package/src/http/request-body.ts +127 -0
- package/src/http/request-source.ts +178 -0
- package/src/http/sse.ts +486 -132
- package/src/http/unmatched-route.ts +60 -0
- package/src/http/user-content.ts +158 -0
- package/src/http/websocket-upgrade-dispatch.ts +29 -0
- package/src/index.ts +157 -52
- package/src/integration-endpoint-test.ts +291 -0
- package/src/integrations/api-integrations.ts +98 -11
- package/src/integrations/atlassian.ts +474 -136
- package/src/integrations/connect-authority.ts +2 -0
- package/src/integrations/connect-callback-return.ts +86 -0
- package/src/integrations/fiken.ts +354 -64
- package/src/integrations/github-app-connect.ts +435 -0
- package/src/integrations/github-installation-proof.ts +60 -0
- package/src/integrations/github-lens-connect.ts +76 -0
- package/src/integrations/github-skill-source.ts +2 -142
- package/src/integrations/google-drive.ts +717 -225
- package/src/integrations/integration-commit-authority.ts +69 -0
- package/src/integrations/marketplace-plugin.ts +48 -0
- package/src/integrations/oauth-client.ts +1356 -459
- package/src/integrations/oauth-profiles.ts +537 -0
- package/src/integrations/oauth-return-path.ts +18 -0
- package/src/integrations/personal-github-repositories.ts +674 -0
- package/src/integrations/personal-github.ts +825 -0
- package/src/integrations/pr-review-provider.ts +246 -0
- package/src/integrations/provider-oauth.ts +250 -22
- package/src/integrations/slack-app-home.ts +300 -0
- package/src/integrations/slack-bot.ts +963 -239
- package/src/integrations/slack-file-upload-flow.ts +147 -0
- package/src/integrations/slack-install.ts +83 -0
- package/src/integrations/slack-interactions.ts +3167 -474
- package/src/integrations/slack-mrkdwn.ts +193 -0
- package/src/integrations/slack-routing.ts +337 -0
- package/src/integrations/slack-session-defaults.ts +224 -0
- package/src/integrations/slack-task-file-upload.ts +323 -0
- package/src/integrations/social-oauth.ts +213 -15
- package/src/interaction-agent-access.ts +48 -0
- package/src/interaction-frame-proxy.ts +409 -0
- package/src/interaction-metrics.ts +30 -0
- package/src/mcp/action-catalog-types.ts +9 -0
- package/src/mcp/action-catalog.gen.ts +6696 -0
- package/src/mcp/capability-account-readiness.ts +17 -0
- package/src/mcp/child-read-evidence.ts +19 -0
- package/src/mcp/command-read.ts +109 -0
- package/src/mcp/company-profile-agent-admin.ts +198 -0
- package/src/mcp/contract-input.ts +141 -0
- package/src/mcp/documents.ts +65 -377
- package/src/mcp/editable-artifacts.ts +12 -6
- package/src/mcp/files.ts +80 -43
- package/src/mcp/first-party-tool-permissions.ts +305 -0
- package/src/mcp/knowledge-entries.ts +322 -0
- package/src/mcp/orchestration-failure-diagnostic.ts +43 -0
- package/src/mcp/receipts.ts +34 -0
- package/src/mcp/request-abort.ts +44 -0
- package/src/mcp/scheduled-task-input.ts +76 -0
- package/src/mcp/scheduled-task-view.ts +44 -3
- package/src/mcp/server.ts +3030 -1267
- package/src/mcp/session-event-view.ts +383 -0
- package/src/mcp/session-view.ts +84 -3
- package/src/mcp/session-wait.ts +665 -0
- package/src/mcp-oauth.ts +1123 -0
- package/src/model-catalog.ts +91 -333
- package/src/observability.ts +15 -1
- package/src/organization-mcp.ts +397 -0
- package/src/organization-recovery-notifications.ts +103 -0
- package/src/routes/analytics-consent.ts +123 -0
- package/src/routes/api-integrations.ts +364 -74
- package/src/routes/api-keys.ts +336 -10
- package/src/routes/artifact-catalog.ts +279 -0
- package/src/routes/automations.ts +768 -0
- package/src/routes/billing.ts +461 -46
- package/src/routes/browser-sessions.ts +780 -112
- package/src/routes/canonical-human-identities.ts +90 -14
- package/src/routes/capabilities.ts +107 -6
- package/src/routes/channels.ts +20 -3
- package/src/routes/claude-subscription-accounts.ts +334 -0
- package/src/routes/claude-subscription-oauth.ts +142 -0
- package/src/routes/client-errors.ts +362 -0
- package/src/routes/codex.ts +833 -116
- package/src/routes/company-brain.ts +322 -0
- package/src/routes/company-profile.ts +65 -1
- package/src/routes/computer-sessions.ts +557 -106
- package/src/routes/connect.ts +1938 -0
- package/src/routes/connections.ts +1039 -349
- package/src/routes/documents.ts +488 -287
- package/src/routes/editable-artifacts.ts +79 -9
- package/src/routes/enrollments.ts +135 -39
- package/src/routes/environments.ts +154 -58
- package/src/routes/external-identity-links.ts +201 -0
- package/src/routes/feedback.ts +113 -0
- package/src/routes/files.ts +267 -33
- package/src/routes/github-browser-pages.ts +162 -0
- package/src/routes/github.ts +446 -144
- package/src/routes/insights-response-cache.ts +83 -0
- package/src/routes/insights-usage.ts +345 -0
- package/src/routes/insights.ts +153 -19
- package/src/routes/install.ts +133 -38
- package/src/routes/integration-facets.ts +107 -33
- package/src/routes/interaction-resources.ts +30 -3
- package/src/routes/knowledge.ts +488 -0
- package/src/routes/machines.ts +313 -15
- package/src/routes/managed-auth-session-sets.ts +1218 -0
- package/src/routes/managed-onboarding.ts +337 -0
- package/src/routes/managed-sign-in-methods.ts +433 -0
- package/src/routes/memory-slack-publications.ts +3 -2
- package/src/routes/model-connection-access.ts +208 -0
- package/src/routes/organization-integration-policy.ts +116 -0
- package/src/routes/organization-integrations.ts +326 -0
- package/src/routes/organization-mcp-connections.ts +178 -0
- package/src/routes/organization-memberships.ts +1019 -27
- package/src/routes/organization-model-providers.ts +293 -0
- package/src/routes/organization-recovery.ts +325 -0
- package/src/routes/organization-service-accounts.ts +128 -0
- package/src/routes/organization-sessions.ts +350 -0
- package/src/routes/personal-github-git-broker.ts +785 -0
- package/src/routes/personal-github.ts +370 -0
- package/src/routes/plugins.ts +251 -22
- package/src/routes/pr-review-github.ts +881 -0
- package/src/routes/pr-review.ts +622 -0
- package/src/routes/preference-registry.ts +12 -93
- package/src/routes/rigs.ts +167 -50
- package/src/routes/scheduled-tasks.ts +346 -41
- package/src/routes/session-artifact-associations.ts +87 -0
- package/src/routes/session-history-imports.ts +280 -0
- package/src/routes/sessions.ts +2306 -264
- package/src/routes/skill-content.ts +297 -0
- package/src/routes/skill-install-authority.ts +64 -0
- package/src/routes/skills.ts +62 -9
- package/src/routes/social.ts +46 -18
- package/src/routes/subscription-account-pools.ts +436 -0
- package/src/routes/subscription-pool-access.ts +126 -0
- package/src/routes/supergrok.ts +219 -303
- package/src/routes/transcription-recordings.ts +38 -3
- package/src/routes/transcriptions.ts +4 -0
- package/src/routes/usage-allowances.ts +378 -0
- package/src/routes/user-resource-authorities.ts +138 -0
- package/src/routes/video-generation.ts +14 -10
- package/src/routes/workspace-artifacts.ts +275 -69
- package/src/routes/workspace-capture.ts +21 -0
- package/src/routes/workspace-integrations.ts +576 -0
- package/src/routes/workspace-learning.ts +159 -0
- package/src/routes/workspace-model-providers.ts +148 -0
- package/src/routes/workspace-state.ts +38 -25
- package/src/routes/workspaces.ts +1004 -127
- package/src/sandbox/auth-callout.ts +96 -19
- package/src/sandbox/channel-a.ts +323 -24
- package/src/sandbox/connection-authority.ts +3 -0
- package/src/sandbox/enrollment.ts +81 -6
- package/src/sandbox/machines.ts +218 -76
- package/src/sandbox/metrics-ingestion.ts +411 -62
- package/src/sandbox/rematerialize.ts +121 -19
- package/src/sandbox/session-attach-variable-sets.ts +131 -0
- package/src/sandbox/viewer.ts +280 -25
- package/src/sandbox-file-artifacts.ts +413 -0
- package/src/scheduled-task-deletion.ts +138 -0
- package/src/session-create-observability.ts +46 -0
- package/src/session-realtime-context.ts +6 -3
- package/src/site-session-origin.ts +48 -0
- package/src/site-uploads.ts +224 -0
- package/src/slack-reaction-files.ts +16 -5
- package/src/temporal-schedule-cleanup.ts +13 -0
- package/src/temporal-schedule-sync.ts +74 -0
- package/src/transcription/providers/codex-subscription.ts +2 -0
- package/src/transcription/providers/openai.ts +1 -0
- package/src/transcription/providers/xai-subscription.ts +49 -3
- package/src/transcription/segmenter.ts +8 -2
- package/src/transcription/service.ts +43 -6
- package/src/work-discovery-observability.ts +121 -0
- package/src/workspace-artifact-content.ts +144 -0
- package/src/workspace-artifact-provenance.ts +104 -0
- package/src/workspace-delete-observability.ts +75 -0
- package/src/workspace-deletion.ts +73 -0
- package/src/workspace-state-projection.ts +18 -148
- package/src/workspace-tool-gateway-observability.ts +100 -0
- package/src/workspace-tool-gateway.ts +870 -0
- package/src/workspace-webhook-dispatch.ts +190 -0
- package/src/xai-subscription-auth.ts +9 -2
- package/dist/chunk-WCXHI3FF.js +0 -49920
- package/dist/chunk-WCXHI3FF.js.map +0 -1
- package/dist/routes/packs.d.ts +0 -3
- package/src/routes/packs.ts +0 -729
|
@@ -1,8 +1,30 @@
|
|
|
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";
|
|
10
|
+
import {
|
|
11
|
+
scheduledTaskKnowledgeSource,
|
|
12
|
+
requireScheduledTaskKnowledgeSource,
|
|
13
|
+
knowledgeSourceAgentConfig,
|
|
14
|
+
assertOrganizationIntegrationAllowed,
|
|
15
|
+
} from "@opengeni/contracts";
|
|
1
16
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
-
import
|
|
17
|
+
import { ExternalActorContinuation } from "@opengeni/contracts/external-identities";
|
|
18
|
+
import { requireConnectOwnerAuthority } from "./connect-authority";
|
|
19
|
+
import {
|
|
20
|
+
configuredGoogleDriveSyncLimits,
|
|
21
|
+
googleDriveOAuthCallbackUrl,
|
|
22
|
+
type Settings,
|
|
23
|
+
} from "@opengeni/config";
|
|
3
24
|
import type { AccessGrant, ScheduledTask, ScheduledTaskScheduleSpec } from "@opengeni/contracts";
|
|
4
25
|
import {
|
|
5
26
|
bindConnectorDocumentDestination,
|
|
27
|
+
type ConnectorDocumentDestination,
|
|
6
28
|
type ConnectorDocumentDestinationSelection,
|
|
7
29
|
} from "@opengeni/contracts/connector-destinations";
|
|
8
30
|
import {
|
|
@@ -36,12 +58,12 @@ import {
|
|
|
36
58
|
} from "@opengeni/contracts";
|
|
37
59
|
import {
|
|
38
60
|
GOOGLE_DRIVE_INTEGRATION_DEFINITION,
|
|
61
|
+
CORE_INTEGRATION_DEFINITIONS,
|
|
39
62
|
integrationDefinitionProviderDomain,
|
|
40
63
|
} from "@opengeni/capabilities";
|
|
41
64
|
import {
|
|
42
65
|
captureScheduledTaskRestoreState,
|
|
43
66
|
createValidatedScheduledTask,
|
|
44
|
-
hasPermission,
|
|
45
67
|
manualScheduledTaskTriggerWorkflowId,
|
|
46
68
|
requireEnvironmentEncryption,
|
|
47
69
|
syncCreatedScheduledTask,
|
|
@@ -51,11 +73,16 @@ import type { ApiRouteDeps } from "@opengeni/core";
|
|
|
51
73
|
import {
|
|
52
74
|
buildConnectionTokenResolver,
|
|
53
75
|
configureIntegrationFacet,
|
|
76
|
+
integrationFacetConfigureRequestDigest,
|
|
54
77
|
appendKnowledgeSourceAclVersion,
|
|
55
78
|
deauthorizeKnowledgeSourceRetrieval,
|
|
56
79
|
ConnectionDisconnectGenerationError,
|
|
57
80
|
ConnectionDisconnectIdempotencyError,
|
|
58
81
|
consumeIntegrationOAuthStateNonce,
|
|
82
|
+
finishConnectOperation,
|
|
83
|
+
getConnectAttempt,
|
|
84
|
+
type Database,
|
|
85
|
+
listInstalledApiIntegrations,
|
|
59
86
|
createConnection,
|
|
60
87
|
decryptEnvironmentValue,
|
|
61
88
|
disconnectConnectionIdempotently,
|
|
@@ -64,15 +91,16 @@ import {
|
|
|
64
91
|
getConnectionMetadata,
|
|
65
92
|
getKnowledgeSourceByExternalIdentityForSyncAuthority,
|
|
66
93
|
getKnowledgeSourceForSyncAuthority,
|
|
67
|
-
getWorkspaceGrant,
|
|
68
94
|
listKnowledgeSourceSyncTasksForConnection,
|
|
69
95
|
loadConnectionCredentialForBroker,
|
|
70
96
|
listIntegrationInstanceFacets,
|
|
97
|
+
replayCompletedIntegrationFacetOperation,
|
|
71
98
|
transitionConnectionState,
|
|
72
99
|
updateConnection,
|
|
73
100
|
updateScheduledTask,
|
|
74
101
|
upsertKnowledgeProvider,
|
|
75
102
|
upsertKnowledgeSource,
|
|
103
|
+
withWorkspaceSubjectRls,
|
|
76
104
|
recordKnowledgeLifecycleEvent,
|
|
77
105
|
type PermanentConnectionRefreshFailure,
|
|
78
106
|
} from "@opengeni/db";
|
|
@@ -80,8 +108,15 @@ import { googleDriveKnowledgeSourceIdentity } from "@opengeni/documents/google-d
|
|
|
80
108
|
import { createSignedState, readSignedState } from "@opengeni/github";
|
|
81
109
|
import { readResponseJsonBounded, type FetchLike } from "@opengeni/network";
|
|
82
110
|
import { HTTPException } from "hono/http-exception";
|
|
111
|
+
import {
|
|
112
|
+
personalOnlyConnectionPrincipalMessage,
|
|
113
|
+
personalOwnerStateAccepted,
|
|
114
|
+
personalOwnerVerifiedInState,
|
|
115
|
+
PERSONAL_OWNER_VERIFIED_STATE_CLAIM,
|
|
116
|
+
} from "../connection-ownership";
|
|
83
117
|
import {
|
|
84
118
|
integrationBaseUrl,
|
|
119
|
+
oauthStateFailureReturn,
|
|
85
120
|
oauthStateTtlMs,
|
|
86
121
|
requireIntegrationsStateSecret,
|
|
87
122
|
} from "./oauth-client";
|
|
@@ -94,6 +129,17 @@ const GOOGLE_RESPONSE_MAX_BYTES = 2 * 1024 * 1024;
|
|
|
94
129
|
const GOOGLE_REQUEST_TIMEOUT_MS = 10_000;
|
|
95
130
|
const GOOGLE_DRIVE_PAGE_SIZE = 100;
|
|
96
131
|
const GOOGLE_DRIVE_RETURN_PATH = (workspaceId: string) => `/workspaces/${workspaceId}/capabilities`;
|
|
132
|
+
/**
|
|
133
|
+
* Flow discriminator for this connector's signed OAuth state.
|
|
134
|
+
*
|
|
135
|
+
* This state carries no `ownership` field and no provider identity - it is
|
|
136
|
+
* personal-only by construction - so the `personalOwnerVerified` claim would
|
|
137
|
+
* otherwise be its sole ownership gate. Its return path is also byte-identical
|
|
138
|
+
* to the MCP OAuth start's, which signs that path from caller input, so a
|
|
139
|
+
* caller's own MCP state could be presented here. Requiring an exact flow kind
|
|
140
|
+
* binds a state to the flow that minted it.
|
|
141
|
+
*/
|
|
142
|
+
const GOOGLE_DRIVE_OAUTH_STATE_KIND = "google_drive_oauth";
|
|
97
143
|
const GOOGLE_DRIVE_RECONSENT_ERROR_CODES = new Set([
|
|
98
144
|
"appNotAuthorizedToFile",
|
|
99
145
|
"authError",
|
|
@@ -102,9 +148,13 @@ const GOOGLE_DRIVE_RECONSENT_ERROR_CODES = new Set([
|
|
|
102
148
|
]);
|
|
103
149
|
|
|
104
150
|
type GoogleDriveOAuthState = {
|
|
151
|
+
connectAttemptId?: string;
|
|
152
|
+
externalContinuation?: ExternalActorContinuation;
|
|
105
153
|
accountId: string;
|
|
106
154
|
workspaceId: string;
|
|
107
155
|
subjectId: string;
|
|
156
|
+
/** Signed proof that a live managed human started this personal-only flow. */
|
|
157
|
+
personalOwnerVerified: boolean;
|
|
108
158
|
returnPath: string;
|
|
109
159
|
encryptedPkceVerifier: string;
|
|
110
160
|
capability: "source_read" | "publish";
|
|
@@ -155,7 +205,7 @@ export async function wakeGoogleDriveSourcesFromWorkspaceEvent(
|
|
|
155
205
|
);
|
|
156
206
|
let triggered = 0;
|
|
157
207
|
for (const task of tasks) {
|
|
158
|
-
if (task
|
|
208
|
+
if (!scheduledTaskKnowledgeSource(task) || task.status !== "active") continue;
|
|
159
209
|
const externalSourceId =
|
|
160
210
|
typeof task.metadata.externalSourceId === "string" ? task.metadata.externalSourceId : null;
|
|
161
211
|
const selectedSource = externalSourceId ? selectedById.get(externalSourceId) : null;
|
|
@@ -212,8 +262,11 @@ export async function startGoogleDriveOAuth(
|
|
|
212
262
|
subjectId: string;
|
|
213
263
|
requestUrl: string;
|
|
214
264
|
payload: GoogleDriveOAuthStartRequest;
|
|
265
|
+
connectAttemptId?: string;
|
|
266
|
+
externalContinuation?: ExternalActorContinuation;
|
|
215
267
|
},
|
|
216
268
|
): Promise<GoogleDriveOAuthStartResponse> {
|
|
269
|
+
await withOrganizationIntegrationAcquisition(deps.db, input, ["google-drive"], async () => {});
|
|
217
270
|
const google = requireGoogleDriveSettings(deps.settings);
|
|
218
271
|
const existing = input.payload.connectionId
|
|
219
272
|
? await getConnectionMetadata(
|
|
@@ -237,12 +290,24 @@ export async function startGoogleDriveOAuth(
|
|
|
237
290
|
|
|
238
291
|
const key = requireEnvironmentEncryption(deps.settings);
|
|
239
292
|
const verifier = randomBytes(48).toString("base64url");
|
|
240
|
-
const
|
|
241
|
-
const redirectUri = `${baseUrl}/v1/integrations/google-drive/callback`;
|
|
293
|
+
const redirectUri = requireGoogleDriveOAuthCallbackUrl(deps.settings);
|
|
242
294
|
const state = createSignedState(requireIntegrationsStateSecret(deps.settings), {
|
|
243
295
|
accountId: input.accountId,
|
|
244
296
|
workspaceId: input.workspaceId,
|
|
245
297
|
subjectId: input.subjectId,
|
|
298
|
+
kind: GOOGLE_DRIVE_OAUTH_STATE_KIND,
|
|
299
|
+
...(input.connectAttemptId ? { connectAttemptId: input.connectAttemptId } : {}),
|
|
300
|
+
...(input.externalContinuation
|
|
301
|
+
? {
|
|
302
|
+
encryptedExternalContinuation: encryptEnvironmentValue(
|
|
303
|
+
key,
|
|
304
|
+
JSON.stringify(ExternalActorContinuation.parse(input.externalContinuation)),
|
|
305
|
+
),
|
|
306
|
+
}
|
|
307
|
+
: {}),
|
|
308
|
+
// The route admits only a managed human, so reaching here is the proof; the
|
|
309
|
+
// callback has no live principal and enforces exactly this claim.
|
|
310
|
+
[PERSONAL_OWNER_VERIFIED_STATE_CLAIM]: true,
|
|
246
311
|
returnPath: GOOGLE_DRIVE_RETURN_PATH(input.workspaceId),
|
|
247
312
|
encryptedPkceVerifier: encryptEnvironmentValue(key, verifier),
|
|
248
313
|
capability: input.payload.capability,
|
|
@@ -289,12 +354,43 @@ export async function completeGoogleDriveOAuthCallback(
|
|
|
289
354
|
pickedFileIds?: string | undefined;
|
|
290
355
|
requestUrl: string;
|
|
291
356
|
},
|
|
292
|
-
): Promise<{ redirectTo: string }> {
|
|
357
|
+
): Promise<{ redirectTo: string; exactReturn?: boolean }> {
|
|
358
|
+
const redirectUri = requireGoogleDriveOAuthCallbackUrl(deps.settings);
|
|
293
359
|
const baseUrl = integrationBaseUrl(deps.settings.publicBaseUrl, input.requestUrl);
|
|
294
360
|
const returnBaseUrl = deps.settings.webBaseUrl?.replace(/\/+$/, "") ?? baseUrl;
|
|
295
361
|
let state: GoogleDriveOAuthState | null = null;
|
|
362
|
+
let exactReturnUrl: string | undefined;
|
|
363
|
+
let operation: { attemptId: string; operationId: string; inputDigest: string } | undefined;
|
|
296
364
|
try {
|
|
297
365
|
state = readGoogleDriveOAuthState(input.state, deps.settings);
|
|
366
|
+
if (state.connectAttemptId) {
|
|
367
|
+
const stored = await getConnectAttempt(deps.db, state, state.connectAttemptId);
|
|
368
|
+
if (
|
|
369
|
+
stored.attempt.providerId !==
|
|
370
|
+
(state.capability === "publish" ? "google-drive-publish" : "google-drive-knowledge") ||
|
|
371
|
+
stored.attempt.ownership !== "personal"
|
|
372
|
+
)
|
|
373
|
+
throw new GoogleDriveCallbackError("connection_conflict");
|
|
374
|
+
exactReturnUrl = stored.returnUrl;
|
|
375
|
+
operation = {
|
|
376
|
+
attemptId: state.connectAttemptId,
|
|
377
|
+
operationId: `oauth:${state.nonce}`,
|
|
378
|
+
inputDigest: createHash("sha256").update(input.state!).digest("hex"),
|
|
379
|
+
};
|
|
380
|
+
const claim = await claimOAuthAcquisition(
|
|
381
|
+
deps.db,
|
|
382
|
+
state,
|
|
383
|
+
{
|
|
384
|
+
...operation,
|
|
385
|
+
expectedRevision: stored.attempt.revision,
|
|
386
|
+
authorize: (tx, _attempt, origin) =>
|
|
387
|
+
requireConnectOwnerAuthority(tx, state!, "connections:write", origin),
|
|
388
|
+
},
|
|
389
|
+
"google-drive",
|
|
390
|
+
Boolean(input.code && !input.error),
|
|
391
|
+
);
|
|
392
|
+
if (claim.status === "replayed") return { redirectTo: exactReturnUrl, exactReturn: true };
|
|
393
|
+
}
|
|
298
394
|
await requireGoogleDriveCallbackGrant(deps, state);
|
|
299
395
|
const consumed = await consumeIntegrationOAuthStateNonce(deps.db, {
|
|
300
396
|
accountId: state.accountId,
|
|
@@ -305,7 +401,26 @@ export async function completeGoogleDriveOAuthCallback(
|
|
|
305
401
|
now: new Date(),
|
|
306
402
|
});
|
|
307
403
|
if (!consumed) {
|
|
308
|
-
throw new
|
|
404
|
+
throw new GoogleDriveCallbackError("state_replayed");
|
|
405
|
+
}
|
|
406
|
+
if (operation && (input.error || !input.code)) {
|
|
407
|
+
await finishConnectOperation(deps.db, state, {
|
|
408
|
+
...operation,
|
|
409
|
+
authorize: (tx, _attempt, origin) =>
|
|
410
|
+
requireConnectOwnerAuthority(tx, state!, "connections:write", origin),
|
|
411
|
+
commit: async (_tx, current) => ({
|
|
412
|
+
...current,
|
|
413
|
+
revision: current.revision + 1,
|
|
414
|
+
state: input.error === "access_denied" ? "cancelled" : "failed",
|
|
415
|
+
nextAction: { type: "none" },
|
|
416
|
+
error: {
|
|
417
|
+
code: input.error ? "provider_denied" : "missing_code",
|
|
418
|
+
message: "Authorization was not completed. Start a new connection attempt.",
|
|
419
|
+
retryable: false,
|
|
420
|
+
},
|
|
421
|
+
}),
|
|
422
|
+
});
|
|
423
|
+
return { redirectTo: exactReturnUrl!, exactReturn: true };
|
|
309
424
|
}
|
|
310
425
|
if (input.error) {
|
|
311
426
|
throw new GoogleDriveCallbackError("provider_denied");
|
|
@@ -313,11 +428,11 @@ export async function completeGoogleDriveOAuthCallback(
|
|
|
313
428
|
if (!input.code) {
|
|
314
429
|
throw new GoogleDriveCallbackError("missing_code");
|
|
315
430
|
}
|
|
431
|
+
await withOrganizationIntegrationAcquisition(deps.db, state, ["google-drive"], async () => {});
|
|
316
432
|
|
|
317
433
|
const google = requireGoogleDriveSettings(deps.settings);
|
|
318
434
|
const key = requireEnvironmentEncryption(deps.settings);
|
|
319
435
|
const verifier = decryptEnvironmentValue(key, state.encryptedPkceVerifier);
|
|
320
|
-
const redirectUri = `${baseUrl}/v1/integrations/google-drive/callback`;
|
|
321
436
|
const fetchImpl = deps.googleDriveFetch ?? fetch;
|
|
322
437
|
const token = await exchangeGoogleAuthorizationCode(
|
|
323
438
|
{
|
|
@@ -372,16 +487,6 @@ export async function completeGoogleDriveOAuthCallback(
|
|
|
372
487
|
: previousMetadata?.outputDestination;
|
|
373
488
|
if (state.capability === "publish") {
|
|
374
489
|
if (!existing) throw new GoogleDriveCallbackError("connection_conflict");
|
|
375
|
-
await ensureConnectorActionPolicyDefault(deps.db, {
|
|
376
|
-
accountId: state.accountId,
|
|
377
|
-
workspaceId: state.workspaceId,
|
|
378
|
-
subjectId: state.subjectId,
|
|
379
|
-
connectionId: existing.id,
|
|
380
|
-
serverId: GOOGLE_DRIVE_PUBLICATION_SERVER_ID,
|
|
381
|
-
toolName: GOOGLE_DRIVE_PUBLICATION_TOOL_NAME,
|
|
382
|
-
actionName: GOOGLE_DRIVE_PUBLICATION_CREATE_ACTION,
|
|
383
|
-
policy: "ask",
|
|
384
|
-
});
|
|
385
490
|
}
|
|
386
491
|
let refreshToken = token.refreshToken;
|
|
387
492
|
if (!refreshToken && existing) {
|
|
@@ -431,34 +536,92 @@ export async function completeGoogleDriveOAuthCallback(
|
|
|
431
536
|
? { selectedSources: [previousMetadata.selectedSource] }
|
|
432
537
|
: {}),
|
|
433
538
|
});
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
providerDomain: GOOGLE_DRIVE_PROVIDER_DOMAIN,
|
|
442
|
-
kind: "oauth2",
|
|
443
|
-
status: "active",
|
|
444
|
-
credentialEncrypted,
|
|
445
|
-
grantedScopes,
|
|
446
|
-
expiresAt: token.expiresAt,
|
|
447
|
-
metadata,
|
|
448
|
-
updatedBySubjectId: state.subjectId,
|
|
449
|
-
})
|
|
450
|
-
: await createConnection(deps.db, {
|
|
539
|
+
const acceptedState = state;
|
|
540
|
+
const persist = async (tx: Database) => {
|
|
541
|
+
// Capture the validated immutable callback state for delayed persistence.
|
|
542
|
+
// eslint-disable-next-line no-shadow
|
|
543
|
+
const state = acceptedState;
|
|
544
|
+
if (state.capability === "publish" && existing)
|
|
545
|
+
await ensureConnectorActionPolicyDefault(tx, {
|
|
451
546
|
accountId: state.accountId,
|
|
452
547
|
workspaceId: state.workspaceId,
|
|
453
548
|
subjectId: state.subjectId,
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
metadata,
|
|
460
|
-
createdBySubjectId: state.subjectId,
|
|
549
|
+
connectionId: existing.id,
|
|
550
|
+
serverId: GOOGLE_DRIVE_PUBLICATION_SERVER_ID,
|
|
551
|
+
toolName: GOOGLE_DRIVE_PUBLICATION_TOOL_NAME,
|
|
552
|
+
actionName: GOOGLE_DRIVE_PUBLICATION_CREATE_ACTION,
|
|
553
|
+
policy: "ask",
|
|
461
554
|
});
|
|
555
|
+
return existing
|
|
556
|
+
? updateConnection(tx, {
|
|
557
|
+
workspaceId: state.workspaceId,
|
|
558
|
+
connectionId: existing.id,
|
|
559
|
+
visibleToSubjectId: state.subjectId,
|
|
560
|
+
expectedVersion: existing.version,
|
|
561
|
+
subjectId: state.subjectId,
|
|
562
|
+
providerDomain: GOOGLE_DRIVE_PROVIDER_DOMAIN,
|
|
563
|
+
kind: "oauth2",
|
|
564
|
+
status: "active",
|
|
565
|
+
credentialEncrypted,
|
|
566
|
+
grantedScopes,
|
|
567
|
+
expiresAt: token.expiresAt,
|
|
568
|
+
metadata,
|
|
569
|
+
updatedBySubjectId: state.subjectId,
|
|
570
|
+
})
|
|
571
|
+
: createConnection(tx, {
|
|
572
|
+
accountId: state.accountId,
|
|
573
|
+
workspaceId: state.workspaceId,
|
|
574
|
+
subjectId: state.subjectId,
|
|
575
|
+
providerDomain: GOOGLE_DRIVE_PROVIDER_DOMAIN,
|
|
576
|
+
kind: "oauth2",
|
|
577
|
+
credentialEncrypted,
|
|
578
|
+
grantedScopes,
|
|
579
|
+
expiresAt: token.expiresAt,
|
|
580
|
+
metadata,
|
|
581
|
+
createdBySubjectId: state.subjectId,
|
|
582
|
+
});
|
|
583
|
+
};
|
|
584
|
+
if (operation) {
|
|
585
|
+
await finishOAuthAcquisition(
|
|
586
|
+
deps.db,
|
|
587
|
+
acceptedState,
|
|
588
|
+
{
|
|
589
|
+
...operation,
|
|
590
|
+
authorize: (tx, _attempt, origin) =>
|
|
591
|
+
requireConnectOwnerAuthority(tx, acceptedState, "connections:write", origin),
|
|
592
|
+
commit: async (tx, current) => {
|
|
593
|
+
const connection = await persist(tx);
|
|
594
|
+
if (!connection) throw new GoogleDriveCallbackError("connection_conflict");
|
|
595
|
+
return {
|
|
596
|
+
...current,
|
|
597
|
+
revision: current.revision + 1,
|
|
598
|
+
state: "complete",
|
|
599
|
+
credentialsCommitted: true,
|
|
600
|
+
nextAction: { type: "none" },
|
|
601
|
+
account: {
|
|
602
|
+
id: connection.id,
|
|
603
|
+
version: connection.version,
|
|
604
|
+
providerId: current.providerId,
|
|
605
|
+
label: identity.emailAddress ?? identity.displayName,
|
|
606
|
+
ownership: "personal",
|
|
607
|
+
status: "connected",
|
|
608
|
+
},
|
|
609
|
+
};
|
|
610
|
+
},
|
|
611
|
+
},
|
|
612
|
+
"google-drive",
|
|
613
|
+
);
|
|
614
|
+
return { redirectTo: exactReturnUrl!, exactReturn: true };
|
|
615
|
+
}
|
|
616
|
+
const connection = await withOrganizationIntegrationAcquisition(
|
|
617
|
+
deps.db,
|
|
618
|
+
acceptedState,
|
|
619
|
+
["google-drive"],
|
|
620
|
+
async (tx) => {
|
|
621
|
+
await requireGoogleDriveCallbackGrant({ ...deps, db: tx }, acceptedState);
|
|
622
|
+
return persist(tx);
|
|
623
|
+
},
|
|
624
|
+
);
|
|
462
625
|
if (!connection) {
|
|
463
626
|
throw new GoogleDriveCallbackError("connection_conflict");
|
|
464
627
|
}
|
|
@@ -466,12 +629,16 @@ export async function completeGoogleDriveOAuthCallback(
|
|
|
466
629
|
redirectTo: googleDriveReturnUrl(returnBaseUrl, state.returnPath, "connected", connection.id),
|
|
467
630
|
};
|
|
468
631
|
} catch (error) {
|
|
632
|
+
if (exactReturnUrl) return { redirectTo: exactReturnUrl, exactReturn: true };
|
|
633
|
+
// Reading the state is the first step, so no state means the link itself
|
|
634
|
+
// was unusable: say whether it expired rather than blame configuration.
|
|
635
|
+
const failure = state ? null : oauthStateFailureReturn(deps.settings, input.state);
|
|
469
636
|
return {
|
|
470
637
|
redirectTo: googleDriveReturnUrl(
|
|
471
638
|
returnBaseUrl,
|
|
472
|
-
state?.returnPath ??
|
|
639
|
+
state?.returnPath ?? failure!.returnPath,
|
|
473
640
|
"error",
|
|
474
|
-
googleDriveErrorReason(error),
|
|
641
|
+
failure?.reason ?? googleDriveErrorReason(error),
|
|
475
642
|
),
|
|
476
643
|
};
|
|
477
644
|
}
|
|
@@ -548,19 +715,45 @@ export async function transitionGoogleDriveLifecycle(
|
|
|
548
715
|
);
|
|
549
716
|
}
|
|
550
717
|
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
718
|
+
const persist = (tx: Database) =>
|
|
719
|
+
transitionConnectionState(tx, {
|
|
720
|
+
workspaceId: input.workspaceId,
|
|
721
|
+
connectionId: existing.id,
|
|
722
|
+
visibleToSubjectId: input.subjectId,
|
|
723
|
+
expectedVersion: existing.version,
|
|
724
|
+
status: "active",
|
|
725
|
+
metadata: GoogleDriveConnectionMetadata.parse({
|
|
726
|
+
...metadata,
|
|
727
|
+
lifecycle: googleDriveLifecycle(targetState),
|
|
728
|
+
}),
|
|
729
|
+
lastError: null,
|
|
730
|
+
updatedBySubjectId: input.subjectId,
|
|
731
|
+
});
|
|
732
|
+
const updated =
|
|
733
|
+
input.payload.action === "resume"
|
|
734
|
+
? await withOrganizationIntegrationPolicyFence(
|
|
735
|
+
deps.db,
|
|
736
|
+
{ accountId: existing.accountId, workspaceId: input.workspaceId },
|
|
737
|
+
async (tx, policy) => {
|
|
738
|
+
const current = await getConnectionMetadata(
|
|
739
|
+
tx,
|
|
740
|
+
input.workspaceId,
|
|
741
|
+
input.connectionId,
|
|
742
|
+
input.subjectId,
|
|
743
|
+
);
|
|
744
|
+
if (
|
|
745
|
+
current?.status === "active" &&
|
|
746
|
+
effectiveGoogleDriveLifecycle(
|
|
747
|
+
current,
|
|
748
|
+
requireGoogleDriveConnection(current, input.subjectId),
|
|
749
|
+
).state === "active"
|
|
750
|
+
)
|
|
751
|
+
return current;
|
|
752
|
+
assertOrganizationIntegrationAllowed(policy, "google-drive");
|
|
753
|
+
return persist(tx);
|
|
754
|
+
},
|
|
755
|
+
)
|
|
756
|
+
: await persist(deps.db);
|
|
564
757
|
if (!updated) {
|
|
565
758
|
const converged = await getConnectionMetadata(
|
|
566
759
|
deps.db,
|
|
@@ -679,20 +872,20 @@ async function deauthorizeGoogleDriveConnectionSources(
|
|
|
679
872
|
connection.workspaceId,
|
|
680
873
|
connection.id,
|
|
681
874
|
async (task) => {
|
|
682
|
-
if (task.
|
|
875
|
+
if (requireScheduledTaskKnowledgeSource(task).initiatingSubjectId !== subjectId) return;
|
|
683
876
|
const resolved = await getKnowledgeSourceForSyncAuthority(deps.db, {
|
|
684
877
|
accountId: task.accountId,
|
|
685
878
|
workspaceId: task.workspaceId,
|
|
686
|
-
sourceId: task.
|
|
879
|
+
sourceId: requireScheduledTaskKnowledgeSource(task).sourceId,
|
|
687
880
|
initiatingSubjectId: subjectId,
|
|
688
881
|
});
|
|
689
882
|
if (!resolved || resolved.source.lifecycleState !== "active") return;
|
|
690
883
|
await deauthorizeKnowledgeSourceRetrieval(deps.db, {
|
|
691
884
|
accountId: task.accountId,
|
|
692
885
|
workspaceId: task.workspaceId,
|
|
693
|
-
sourceId: task.
|
|
694
|
-
audience: task.
|
|
695
|
-
operationId: `google-drive-deauthorize:${connection.id}:${authorityVersion}:${task.
|
|
886
|
+
sourceId: requireScheduledTaskKnowledgeSource(task).sourceId,
|
|
887
|
+
audience: requireScheduledTaskKnowledgeSource(task).destination,
|
|
888
|
+
operationId: `google-drive-deauthorize:${connection.id}:${authorityVersion}:${requireScheduledTaskKnowledgeSource(task).sourceId}:${reasonCode}`,
|
|
696
889
|
reasonCode,
|
|
697
890
|
actor: {
|
|
698
891
|
kind: "human",
|
|
@@ -708,7 +901,7 @@ async function forEachGoogleDriveConnectionTask(
|
|
|
708
901
|
deps: ApiRouteDeps,
|
|
709
902
|
workspaceId: string,
|
|
710
903
|
connectionId: string,
|
|
711
|
-
fn: (task: ScheduledTask
|
|
904
|
+
fn: (task: ScheduledTask) => Promise<void>,
|
|
712
905
|
): Promise<void> {
|
|
713
906
|
const tasks = await listKnowledgeSourceSyncTasksForConnection(deps.db, workspaceId, connectionId);
|
|
714
907
|
for (const task of tasks) {
|
|
@@ -829,63 +1022,160 @@ export async function saveGoogleDriveFacetSource(
|
|
|
829
1022
|
throw new HTTPException(400, { message: "invalid Google Drive source selection" });
|
|
830
1023
|
}
|
|
831
1024
|
const payload = parsedPayload.data;
|
|
832
|
-
const
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
const
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
:
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
1025
|
+
const requestedSources = payload.sources.map((source) => ({
|
|
1026
|
+
...source,
|
|
1027
|
+
id: validDriveId(source.id, "source.id"),
|
|
1028
|
+
}));
|
|
1029
|
+
if (new Set(requestedSources.map((source) => source.id)).size !== requestedSources.length) {
|
|
1030
|
+
throw new HTTPException(400, { message: "Google Drive sources must be unique" });
|
|
1031
|
+
}
|
|
1032
|
+
const requestedDestination = bindGoogleDriveDocumentDestination(input, payload);
|
|
1033
|
+
const requestedConfig = googleDriveFacetConfig(requestedSources, requestedDestination, payload);
|
|
1034
|
+
const admitted = await withOrganizationIntegrationPolicyFence(
|
|
1035
|
+
deps.db,
|
|
1036
|
+
input,
|
|
1037
|
+
async (tx, policy) => {
|
|
1038
|
+
const replayed =
|
|
1039
|
+
await replayCompletedIntegrationFacetOperation<IntegrationFacetMutationResult>(tx, {
|
|
1040
|
+
accountId: input.accountId,
|
|
1041
|
+
workspaceId: input.workspaceId,
|
|
1042
|
+
subjectId: input.subjectId,
|
|
1043
|
+
capabilityId: input.capabilityId,
|
|
1044
|
+
instanceKey: input.instanceKey,
|
|
1045
|
+
facetKey: input.facetKey,
|
|
1046
|
+
idempotencyKey: payload.idempotencyKey,
|
|
1047
|
+
kind: "configure",
|
|
1048
|
+
expectedRequestDigest: (result) => {
|
|
1049
|
+
const displayName = integrationFacetReceiptDisplayName(result);
|
|
1050
|
+
if (displayName === null) return "invalid-integration-facet-receipt";
|
|
1051
|
+
return integrationFacetConfigureRequestDigest({
|
|
1052
|
+
capabilityId: input.capabilityId,
|
|
1053
|
+
instanceKey: input.instanceKey,
|
|
1054
|
+
facetKey: input.facetKey,
|
|
1055
|
+
displayName,
|
|
1056
|
+
config: requestedConfig,
|
|
1057
|
+
...(payload.expectedVersion !== undefined
|
|
1058
|
+
? { expectedVersion: payload.expectedVersion }
|
|
1059
|
+
: {}),
|
|
1060
|
+
});
|
|
1061
|
+
},
|
|
1062
|
+
});
|
|
1063
|
+
if (replayed) return { replayed: IntegrationFacetMutationResult.parse(replayed) };
|
|
1064
|
+
const context = await requireGoogleDriveIntegrationFacet({ ...deps, db: tx }, input);
|
|
1065
|
+
googleDriveDocumentDestination(input, payload);
|
|
1066
|
+
const acquiring = googleDriveFacetSelectionRequiresAcquisition(context, requestedConfig);
|
|
1067
|
+
if (acquiring) {
|
|
1068
|
+
const installed = (
|
|
1069
|
+
await listInstalledApiIntegrations(tx, input.workspaceId, input.subjectId)
|
|
1070
|
+
).find(
|
|
1071
|
+
(candidate) =>
|
|
1072
|
+
candidate.capabilityId === input.capabilityId &&
|
|
1073
|
+
candidate.instanceKey === input.instanceKey,
|
|
1074
|
+
);
|
|
1075
|
+
assertOrganizationIntegrationAllowed(
|
|
1076
|
+
policy,
|
|
1077
|
+
installed
|
|
1078
|
+
? installedGoogleDriveFacetKey({
|
|
1079
|
+
apiProtocol: installed.protocol,
|
|
1080
|
+
definitionId: installed.definitionId,
|
|
1081
|
+
definitionProvenance: installed.definitionProvenance,
|
|
1082
|
+
})
|
|
1083
|
+
: null,
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
return { context, acquiring };
|
|
866
1087
|
},
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
await
|
|
872
|
-
accountId: input.accountId,
|
|
1088
|
+
);
|
|
1089
|
+
if (admitted.replayed) return admitted.replayed;
|
|
1090
|
+
const context = admitted.context!;
|
|
1091
|
+
if (admitted.acquiring)
|
|
1092
|
+
await verifyGoogleDriveSources(deps, {
|
|
873
1093
|
workspaceId: input.workspaceId,
|
|
874
1094
|
subjectId: input.subjectId,
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
:
|
|
884
|
-
|
|
885
|
-
|
|
1095
|
+
connectionId: context.connection.id,
|
|
1096
|
+
sources: requestedSources,
|
|
1097
|
+
});
|
|
1098
|
+
return IntegrationFacetMutationResult.parse(
|
|
1099
|
+
await withOrganizationIntegrationPolicyFence(deps.db, input, (tx, policy) =>
|
|
1100
|
+
configureIntegrationFacet(tx, {
|
|
1101
|
+
accountId: input.accountId,
|
|
1102
|
+
workspaceId: input.workspaceId,
|
|
1103
|
+
subjectId: input.subjectId,
|
|
1104
|
+
capabilityId: input.capabilityId,
|
|
1105
|
+
instanceKey: input.instanceKey,
|
|
1106
|
+
facetKey: input.facetKey,
|
|
1107
|
+
displayName:
|
|
1108
|
+
context.facet.binding?.displayName ?? `${input.instanceKey} — Google Drive content`,
|
|
1109
|
+
config: requestedConfig,
|
|
1110
|
+
...(payload.expectedVersion !== undefined
|
|
1111
|
+
? { expectedVersion: payload.expectedVersion }
|
|
1112
|
+
: {}),
|
|
1113
|
+
idempotencyKey: payload.idempotencyKey,
|
|
1114
|
+
beforeAcquire: async (locked, installed) => {
|
|
1115
|
+
const current = await requireGoogleDriveIntegrationFacet({ ...deps, db: locked }, input);
|
|
1116
|
+
if (googleDriveFacetSelectionRequiresAcquisition(current, requestedConfig)) {
|
|
1117
|
+
assertOrganizationIntegrationAllowed(policy, installedGoogleDriveFacetKey(installed));
|
|
1118
|
+
if (!admitted.acquiring)
|
|
1119
|
+
throw new HTTPException(409, {
|
|
1120
|
+
message: "Google Drive source binding changed; verify the selection again",
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
},
|
|
1124
|
+
}),
|
|
1125
|
+
),
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
/** This is installed server-authored version provenance, not a facet's mutable
|
|
1130
|
+
* capabilities.provider, URL, domain, or client-authored credential metadata. */
|
|
1131
|
+
function installedGoogleDriveFacetKey(context: {
|
|
1132
|
+
apiProtocol: string | null;
|
|
1133
|
+
definitionId: string | null;
|
|
1134
|
+
definitionProvenance: string | null;
|
|
1135
|
+
}): string | null {
|
|
1136
|
+
if (context.apiProtocol !== "openapi" && context.apiProtocol !== "graphql") return null;
|
|
1137
|
+
if (context.definitionProvenance === "workspace") return `custom:${context.apiProtocol}`;
|
|
1138
|
+
return context.definitionProvenance === "curated" &&
|
|
1139
|
+
CORE_INTEGRATION_DEFINITIONS.some((definition) => definition.id === context.definitionId)
|
|
1140
|
+
? context.definitionId
|
|
1141
|
+
: null;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
function googleDriveFacetSelectionRequiresAcquisition(
|
|
1145
|
+
context: Awaited<ReturnType<typeof requireGoogleDriveIntegrationFacet>>,
|
|
1146
|
+
requested: ReturnType<typeof googleDriveFacetConfig>,
|
|
1147
|
+
): boolean {
|
|
1148
|
+
const binding = context.facet.binding;
|
|
1149
|
+
if (
|
|
1150
|
+
!binding ||
|
|
1151
|
+
binding.status !== "active" ||
|
|
1152
|
+
!binding.directlyOwned ||
|
|
1153
|
+
binding.connectionId !== context.connection.id
|
|
1154
|
+
)
|
|
1155
|
+
return true;
|
|
1156
|
+
const previous = GoogleDriveKnowledgeSourceConfig.safeParse(binding.config);
|
|
1157
|
+
if (!previous.success) return true;
|
|
1158
|
+
const selection = (config: ReturnType<typeof googleDriveFacetConfig>) =>
|
|
1159
|
+
config.sources.map((source) => ({
|
|
1160
|
+
...source,
|
|
1161
|
+
destination: config.destination,
|
|
1162
|
+
syncCadence: config.syncCadence,
|
|
1163
|
+
syncEnabled: true,
|
|
1164
|
+
readPolicy: config.readPolicy,
|
|
1165
|
+
}));
|
|
1166
|
+
return integrationSourceSelectionRequiresAcquisition(
|
|
1167
|
+
selection(previous.data),
|
|
1168
|
+
selection(requested),
|
|
886
1169
|
);
|
|
887
1170
|
}
|
|
888
1171
|
|
|
1172
|
+
function integrationFacetReceiptDisplayName(result: Record<string, unknown>): string | null {
|
|
1173
|
+
const binding = result.binding;
|
|
1174
|
+
if (!binding || typeof binding !== "object" || Array.isArray(binding)) return null;
|
|
1175
|
+
const bindingRecord = binding as Record<string, unknown>;
|
|
1176
|
+
return typeof bindingRecord.displayName === "string" ? bindingRecord.displayName : null;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
889
1179
|
export async function saveGoogleDriveSource(
|
|
890
1180
|
deps: ApiRouteDeps,
|
|
891
1181
|
input: {
|
|
@@ -917,14 +1207,34 @@ export async function saveGoogleDriveSource(
|
|
|
917
1207
|
if (existing.accountId !== input.accountId || existing.workspaceId !== input.workspaceId) {
|
|
918
1208
|
throw new HTTPException(403, { message: "Google Drive connection authority mismatch" });
|
|
919
1209
|
}
|
|
920
|
-
await requireGoogleDriveSourceConnection(
|
|
1210
|
+
const existingMetadata = await requireGoogleDriveSourceConnection(
|
|
1211
|
+
deps,
|
|
1212
|
+
existing,
|
|
1213
|
+
input.subjectId,
|
|
1214
|
+
);
|
|
921
1215
|
const documentDestination = googleDriveDocumentDestination(input, payload);
|
|
922
|
-
const
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
1216
|
+
const requestedSelection = payload.sources.map((source) => ({
|
|
1217
|
+
...source,
|
|
1218
|
+
destination: documentDestination,
|
|
1219
|
+
syncCadence: payload.syncCadence,
|
|
1220
|
+
syncEnabled: payload.syncEnabled,
|
|
1221
|
+
readPolicy: payload.readPolicy,
|
|
1222
|
+
}));
|
|
1223
|
+
const acquiring = integrationSourceSelectionRequiresAcquisition(
|
|
1224
|
+
existingMetadata.selectedSources ??
|
|
1225
|
+
(existingMetadata.selectedSource ? [existingMetadata.selectedSource] : []),
|
|
1226
|
+
requestedSelection,
|
|
1227
|
+
);
|
|
1228
|
+
if (acquiring)
|
|
1229
|
+
await withOrganizationIntegrationAcquisition(deps.db, input, ["google-drive"], async () => {});
|
|
1230
|
+
const verifiedSources = acquiring
|
|
1231
|
+
? await verifyGoogleDriveSources(deps, {
|
|
1232
|
+
workspaceId: input.workspaceId,
|
|
1233
|
+
subjectId: input.subjectId,
|
|
1234
|
+
connectionId: input.connectionId,
|
|
1235
|
+
sources: payload.sources,
|
|
1236
|
+
})
|
|
1237
|
+
: payload.sources;
|
|
928
1238
|
const latest =
|
|
929
1239
|
(await getConnectionMetadata(
|
|
930
1240
|
deps.db,
|
|
@@ -936,33 +1246,46 @@ export async function saveGoogleDriveSource(
|
|
|
936
1246
|
const previousSources =
|
|
937
1247
|
latestMetadata.selectedSources ??
|
|
938
1248
|
(latestMetadata.selectedSource ? [latestMetadata.selectedSource] : []);
|
|
939
|
-
const updated = await
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1249
|
+
const updated = await withOrganizationIntegrationPolicyFence(
|
|
1250
|
+
deps.db,
|
|
1251
|
+
input,
|
|
1252
|
+
async (tx, policy) => {
|
|
1253
|
+
if (integrationSourceSelectionRequiresAcquisition(previousSources, requestedSelection)) {
|
|
1254
|
+
assertOrganizationIntegrationAllowed(policy, "google-drive");
|
|
1255
|
+
if (!acquiring)
|
|
1256
|
+
throw new HTTPException(409, {
|
|
1257
|
+
message: "Google Drive sources changed; verify the selection again",
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
return transitionConnectionState(tx, {
|
|
1261
|
+
workspaceId: input.workspaceId,
|
|
1262
|
+
connectionId: latest.id,
|
|
1263
|
+
visibleToSubjectId: input.subjectId,
|
|
1264
|
+
expectedVersion: latest.version,
|
|
1265
|
+
metadata: GoogleDriveConnectionMetadata.parse({
|
|
1266
|
+
...latestMetadata,
|
|
1267
|
+
documentDestination,
|
|
1268
|
+
selectedSource: null,
|
|
1269
|
+
selectedSources: verifiedSources.map((verified) => {
|
|
1270
|
+
const previous = previousSources.find((source) => source.id === verified.id);
|
|
1271
|
+
return {
|
|
1272
|
+
id: verified.id,
|
|
1273
|
+
name: verified.name,
|
|
1274
|
+
mimeType: verified.mimeType,
|
|
1275
|
+
driveId: verified.driveId,
|
|
1276
|
+
destination: documentDestination,
|
|
1277
|
+
syncCadence: payload.syncCadence,
|
|
1278
|
+
syncEnabled: payload.syncEnabled,
|
|
1279
|
+
configGeneration: (previous?.configGeneration ?? 0) + 1,
|
|
1280
|
+
readPolicy: payload.readPolicy,
|
|
1281
|
+
selectedAt: new Date().toISOString(),
|
|
1282
|
+
};
|
|
1283
|
+
}),
|
|
1284
|
+
}),
|
|
1285
|
+
updatedBySubjectId: input.subjectId,
|
|
1286
|
+
});
|
|
1287
|
+
},
|
|
1288
|
+
);
|
|
966
1289
|
if (!updated) {
|
|
967
1290
|
throw new HTTPException(409, { message: "Google Drive connection changed; try again" });
|
|
968
1291
|
}
|
|
@@ -1108,6 +1431,24 @@ function googleDriveDocumentDestination(
|
|
|
1108
1431
|
if (destinationSelection.authorityKind === "personal" && !input.canManagePersonalDestination) {
|
|
1109
1432
|
throw new HTTPException(403, { message: "personal destination requires the exact actor" });
|
|
1110
1433
|
}
|
|
1434
|
+
return bindGoogleDriveDocumentDestination(input, payload);
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
function bindGoogleDriveDocumentDestination(
|
|
1438
|
+
input: {
|
|
1439
|
+
accountId: string;
|
|
1440
|
+
workspaceId: string;
|
|
1441
|
+
subjectId: string;
|
|
1442
|
+
},
|
|
1443
|
+
payload: {
|
|
1444
|
+
destination?: ConnectorDocumentDestinationSelection | undefined;
|
|
1445
|
+
targetScope?: "user" | "workspace" | "organization" | undefined;
|
|
1446
|
+
},
|
|
1447
|
+
): ConnectorDocumentDestination {
|
|
1448
|
+
const destinationSelection: ConnectorDocumentDestinationSelection = payload.destination ?? {
|
|
1449
|
+
authorityKind: "workspace",
|
|
1450
|
+
collectionId: null,
|
|
1451
|
+
};
|
|
1111
1452
|
return bindConnectorDocumentDestination(destinationSelection, {
|
|
1112
1453
|
accountId: input.accountId,
|
|
1113
1454
|
workspaceId: input.workspaceId,
|
|
@@ -1115,6 +1456,51 @@ function googleDriveDocumentDestination(
|
|
|
1115
1456
|
});
|
|
1116
1457
|
}
|
|
1117
1458
|
|
|
1459
|
+
function googleDriveFacetConfig(
|
|
1460
|
+
sources: ReadonlyArray<{
|
|
1461
|
+
id: string;
|
|
1462
|
+
name: string;
|
|
1463
|
+
mimeType: string;
|
|
1464
|
+
driveId: string | null;
|
|
1465
|
+
}>,
|
|
1466
|
+
documentDestination: ConnectorDocumentDestination,
|
|
1467
|
+
payload: {
|
|
1468
|
+
syncCadence: "manual" | "hourly" | "daily";
|
|
1469
|
+
readPolicy: "allow" | "ask" | "block";
|
|
1470
|
+
},
|
|
1471
|
+
) {
|
|
1472
|
+
return GoogleDriveKnowledgeSourceConfig.parse({
|
|
1473
|
+
sources: sources.map((source) => ({
|
|
1474
|
+
id: source.id,
|
|
1475
|
+
name: source.name,
|
|
1476
|
+
mimeType: source.mimeType,
|
|
1477
|
+
...(source.driveId ? { driveId: source.driveId } : {}),
|
|
1478
|
+
sourceKind:
|
|
1479
|
+
source.id === "root"
|
|
1480
|
+
? "my_drive"
|
|
1481
|
+
: source.driveId === source.id
|
|
1482
|
+
? "shared_drive"
|
|
1483
|
+
: "folder",
|
|
1484
|
+
includeDescendants: true,
|
|
1485
|
+
})),
|
|
1486
|
+
destination: {
|
|
1487
|
+
authorityKind: documentDestination.authorityKind,
|
|
1488
|
+
authorityAccountId: documentDestination.authorityAccountId,
|
|
1489
|
+
...(documentDestination.authorityWorkspaceId
|
|
1490
|
+
? { authorityWorkspaceId: documentDestination.authorityWorkspaceId }
|
|
1491
|
+
: {}),
|
|
1492
|
+
...(documentDestination.authoritySubjectId
|
|
1493
|
+
? { authoritySubjectId: documentDestination.authoritySubjectId }
|
|
1494
|
+
: {}),
|
|
1495
|
+
...(documentDestination.collectionId
|
|
1496
|
+
? { collectionId: documentDestination.collectionId }
|
|
1497
|
+
: {}),
|
|
1498
|
+
},
|
|
1499
|
+
syncCadence: payload.syncCadence,
|
|
1500
|
+
readPolicy: payload.readPolicy,
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1118
1504
|
async function verifyGoogleDriveSources(
|
|
1119
1505
|
deps: ApiRouteDeps,
|
|
1120
1506
|
input: {
|
|
@@ -1187,11 +1573,11 @@ async function materializeGoogleDriveKnowledgeSchedules(
|
|
|
1187
1573
|
let source = null as Awaited<ReturnType<typeof upsertKnowledgeSource>> | null;
|
|
1188
1574
|
let existingTask = null as (typeof connectionTasks)[number] | null;
|
|
1189
1575
|
for (const task of connectionTasks) {
|
|
1190
|
-
if (task
|
|
1576
|
+
if (!scheduledTaskKnowledgeSource(task)) continue;
|
|
1191
1577
|
const resolved = await getKnowledgeSourceForSyncAuthority(deps.db, {
|
|
1192
1578
|
accountId: input.accountId,
|
|
1193
1579
|
workspaceId: input.workspaceId,
|
|
1194
|
-
sourceId: task.
|
|
1580
|
+
sourceId: requireScheduledTaskKnowledgeSource(task).sourceId,
|
|
1195
1581
|
initiatingSubjectId: input.subjectId,
|
|
1196
1582
|
});
|
|
1197
1583
|
if (resolved?.source.externalSourceId !== identity.externalSourceId) continue;
|
|
@@ -1282,13 +1668,8 @@ async function materializeGoogleDriveKnowledgeSchedules(
|
|
|
1282
1668
|
kind: input.connection.kind,
|
|
1283
1669
|
},
|
|
1284
1670
|
limits: {
|
|
1285
|
-
|
|
1286
|
-
maxBytes: 500_000_000,
|
|
1287
|
-
maxFileBytes: 100_000_000,
|
|
1288
|
-
maxProviderRequests: 1_000,
|
|
1289
|
-
maxElapsedSeconds: 300,
|
|
1671
|
+
...configuredGoogleDriveSyncLimits(deps.settings),
|
|
1290
1672
|
maxConcurrency: 4,
|
|
1291
|
-
maxFailureDetails: 25,
|
|
1292
1673
|
},
|
|
1293
1674
|
};
|
|
1294
1675
|
const schedule = googleDriveSchedule(selectedSource.syncCadence);
|
|
@@ -1298,7 +1679,10 @@ async function materializeGoogleDriveKnowledgeSchedules(
|
|
|
1298
1679
|
task = await updateScheduledTask(deps.db, input.workspaceId, existingTask.id, {
|
|
1299
1680
|
name: `Sync Google Drive: ${selectedSource.name}`,
|
|
1300
1681
|
overlapPolicy: "buffer_one",
|
|
1301
|
-
action,
|
|
1682
|
+
action: { kind: "agent_turn" },
|
|
1683
|
+
agentConfig: knowledgeSourceAgentConfig(action, existingTask.agentConfig),
|
|
1684
|
+
refreshPersonalResourceAuthority: true,
|
|
1685
|
+
authorityUpdatedBy: { kind: "subject", subjectId: input.subjectId },
|
|
1302
1686
|
metadata: {
|
|
1303
1687
|
...existingTask.metadata,
|
|
1304
1688
|
connectorKind: "google_drive",
|
|
@@ -1330,15 +1714,11 @@ async function materializeGoogleDriveKnowledgeSchedules(
|
|
|
1330
1714
|
status: "active",
|
|
1331
1715
|
schedule,
|
|
1332
1716
|
overlapPolicy: "buffer_one",
|
|
1333
|
-
action,
|
|
1717
|
+
action: { kind: "agent_turn" },
|
|
1334
1718
|
runMode: "new_session_per_run",
|
|
1335
1719
|
targetSessionId: null,
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
resources: [],
|
|
1339
|
-
tools: [],
|
|
1340
|
-
metadata: {},
|
|
1341
|
-
},
|
|
1720
|
+
connectionAccounts: [],
|
|
1721
|
+
agentConfig: knowledgeSourceAgentConfig(action),
|
|
1342
1722
|
variableSetId: null,
|
|
1343
1723
|
environmentId: null,
|
|
1344
1724
|
rigId: null,
|
|
@@ -1363,11 +1743,11 @@ async function materializeGoogleDriveKnowledgeSchedules(
|
|
|
1363
1743
|
}
|
|
1364
1744
|
|
|
1365
1745
|
for (const task of connectionTasks) {
|
|
1366
|
-
if (task
|
|
1746
|
+
if (!scheduledTaskKnowledgeSource(task)) continue;
|
|
1367
1747
|
const resolved = await getKnowledgeSourceForSyncAuthority(deps.db, {
|
|
1368
1748
|
accountId: input.accountId,
|
|
1369
1749
|
workspaceId: input.workspaceId,
|
|
1370
|
-
sourceId: task.
|
|
1750
|
+
sourceId: requireScheduledTaskKnowledgeSource(task).sourceId,
|
|
1371
1751
|
initiatingSubjectId: input.subjectId,
|
|
1372
1752
|
});
|
|
1373
1753
|
if (!resolved || enabledIds.has(resolved.source.externalSourceId)) continue;
|
|
@@ -1406,15 +1786,15 @@ async function materializeGoogleDriveKnowledgeSchedules(
|
|
|
1406
1786
|
}
|
|
1407
1787
|
}
|
|
1408
1788
|
|
|
1409
|
-
export async function
|
|
1789
|
+
export async function preflightKnowledgeSourceScheduleAuthorization(
|
|
1410
1790
|
deps: ApiRouteDeps,
|
|
1411
1791
|
input: { task: ScheduledTask; subjectId: string },
|
|
1412
1792
|
): Promise<void> {
|
|
1413
1793
|
const { task } = input;
|
|
1414
|
-
if (task
|
|
1794
|
+
if (!scheduledTaskKnowledgeSource(task)) return;
|
|
1415
1795
|
if (
|
|
1416
|
-
task.
|
|
1417
|
-
task.
|
|
1796
|
+
requireScheduledTaskKnowledgeSource(task).initiatingSubjectId !== input.subjectId ||
|
|
1797
|
+
requireScheduledTaskKnowledgeSource(task).connection.ownerSubjectId !== input.subjectId
|
|
1418
1798
|
) {
|
|
1419
1799
|
throw new HTTPException(403, {
|
|
1420
1800
|
message: "knowledge source schedule requires the exact initiating subject",
|
|
@@ -1430,65 +1810,138 @@ export async function revokeKnowledgeSourceScheduleAuthorization(
|
|
|
1430
1810
|
if (!externalSourceId) {
|
|
1431
1811
|
throw new HTTPException(409, { message: "knowledge source schedule identity is incomplete" });
|
|
1432
1812
|
}
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1813
|
+
// A deselected/disconnected source still owns a retained task that must be
|
|
1814
|
+
// deletable. The row-locked task revision below freezes this descriptor;
|
|
1815
|
+
// cleanup mutates only its exact accepted generations.
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
export async function revokeKnowledgeSourceScheduleAuthorization(
|
|
1819
|
+
deps: ApiRouteDeps,
|
|
1820
|
+
input: { task: ScheduledTask; subjectId: string },
|
|
1821
|
+
): Promise<void> {
|
|
1822
|
+
const { task } = input;
|
|
1823
|
+
if (!scheduledTaskKnowledgeSource(task)) return;
|
|
1824
|
+
if (
|
|
1825
|
+
requireScheduledTaskKnowledgeSource(task).initiatingSubjectId !== input.subjectId ||
|
|
1826
|
+
requireScheduledTaskKnowledgeSource(task).connection.ownerSubjectId !== input.subjectId
|
|
1827
|
+
) {
|
|
1828
|
+
throw new HTTPException(403, {
|
|
1829
|
+
message: "knowledge source schedule requires the exact initiating subject",
|
|
1442
1830
|
});
|
|
1443
1831
|
}
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
const selected = sources.find((source) => source.id === externalSourceId);
|
|
1448
|
-
let revocationVersion = connection.version;
|
|
1449
|
-
if (selected?.syncEnabled) {
|
|
1450
|
-
const updated = await transitionConnectionState(deps.db, {
|
|
1451
|
-
workspaceId: task.workspaceId,
|
|
1452
|
-
connectionId: connection.id,
|
|
1453
|
-
visibleToSubjectId: input.subjectId,
|
|
1454
|
-
expectedVersion: connection.version,
|
|
1455
|
-
metadata: GoogleDriveConnectionMetadata.parse({
|
|
1456
|
-
...metadata,
|
|
1457
|
-
selectedSource: null,
|
|
1458
|
-
selectedSources: sources.map((source) =>
|
|
1459
|
-
source.id === externalSourceId
|
|
1460
|
-
? {
|
|
1461
|
-
...source,
|
|
1462
|
-
syncEnabled: false,
|
|
1463
|
-
configGeneration: source.configGeneration + 1,
|
|
1464
|
-
}
|
|
1465
|
-
: source,
|
|
1466
|
-
),
|
|
1467
|
-
}),
|
|
1468
|
-
updatedBySubjectId: input.subjectId,
|
|
1832
|
+
if (task.metadata.connectorKind !== "google_drive") {
|
|
1833
|
+
throw new HTTPException(409, {
|
|
1834
|
+
message: "knowledge source schedule connector cannot be durably disabled",
|
|
1469
1835
|
});
|
|
1470
|
-
if (!updated) {
|
|
1471
|
-
throw new HTTPException(409, {
|
|
1472
|
-
message: "knowledge source connection changed; retry schedule deletion",
|
|
1473
|
-
});
|
|
1474
|
-
}
|
|
1475
|
-
revocationVersion = updated.version;
|
|
1476
1836
|
}
|
|
1477
|
-
const
|
|
1837
|
+
const externalSourceId =
|
|
1838
|
+
typeof task.metadata.externalSourceId === "string" ? task.metadata.externalSourceId.trim() : "";
|
|
1839
|
+
if (!externalSourceId) {
|
|
1840
|
+
throw new HTTPException(409, { message: "knowledge source schedule identity is incomplete" });
|
|
1841
|
+
}
|
|
1842
|
+
await cleanupKnowledgeSourceScheduleAuthorization(deps, {
|
|
1843
|
+
taskId: task.id,
|
|
1478
1844
|
accountId: task.accountId,
|
|
1479
1845
|
workspaceId: task.workspaceId,
|
|
1480
|
-
|
|
1846
|
+
connectionId: requireScheduledTaskKnowledgeSource(task).connection.connectionId,
|
|
1847
|
+
connectionVersion: requireScheduledTaskKnowledgeSource(task).connection.connectionVersion,
|
|
1848
|
+
sourceId: requireScheduledTaskKnowledgeSource(task).sourceId,
|
|
1849
|
+
sourceLifecycleGeneration: requireScheduledTaskKnowledgeSource(task).sourceLifecycleGeneration,
|
|
1850
|
+
sourceConfigGeneration: requireScheduledTaskKnowledgeSource(task).sourceConfigGeneration,
|
|
1851
|
+
externalSourceId,
|
|
1852
|
+
subjectId: input.subjectId,
|
|
1853
|
+
});
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
export async function cleanupKnowledgeSourceScheduleAuthorization(
|
|
1857
|
+
deps: ApiRouteDeps,
|
|
1858
|
+
input: {
|
|
1859
|
+
taskId: string;
|
|
1860
|
+
accountId: string;
|
|
1861
|
+
workspaceId: string;
|
|
1862
|
+
connectionId: string;
|
|
1863
|
+
connectionVersion: number;
|
|
1864
|
+
sourceId: string;
|
|
1865
|
+
sourceLifecycleGeneration: number;
|
|
1866
|
+
sourceConfigGeneration: number;
|
|
1867
|
+
externalSourceId: string;
|
|
1868
|
+
subjectId: string;
|
|
1869
|
+
},
|
|
1870
|
+
): Promise<void> {
|
|
1871
|
+
const resolvedBeforeLock = await getKnowledgeSourceForSyncAuthority(deps.db, {
|
|
1872
|
+
accountId: input.accountId,
|
|
1873
|
+
workspaceId: input.workspaceId,
|
|
1874
|
+
sourceId: input.sourceId,
|
|
1481
1875
|
initiatingSubjectId: input.subjectId,
|
|
1482
1876
|
});
|
|
1483
|
-
if (
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1877
|
+
if (
|
|
1878
|
+
resolvedBeforeLock?.source.lifecycleState !== "active" ||
|
|
1879
|
+
resolvedBeforeLock.source.lifecycleGeneration !== input.sourceLifecycleGeneration
|
|
1880
|
+
) {
|
|
1881
|
+
return;
|
|
1882
|
+
}
|
|
1883
|
+
await withWorkspaceSubjectRls(deps.db, input.workspaceId, input.subjectId, async (tx) => {
|
|
1884
|
+
const connection = await getConnectionMetadata(
|
|
1885
|
+
tx,
|
|
1886
|
+
input.workspaceId,
|
|
1887
|
+
input.connectionId,
|
|
1888
|
+
input.subjectId,
|
|
1889
|
+
);
|
|
1890
|
+
let revocationVersion = input.connectionVersion;
|
|
1891
|
+
if (connection) {
|
|
1892
|
+
const metadata = requireGoogleDriveConnection(connection, input.subjectId);
|
|
1893
|
+
const sources =
|
|
1894
|
+
metadata.selectedSources ?? (metadata.selectedSource ? [metadata.selectedSource] : []);
|
|
1895
|
+
revocationVersion = connection.version;
|
|
1896
|
+
const updated = await transitionConnectionState(tx, {
|
|
1897
|
+
workspaceId: input.workspaceId,
|
|
1898
|
+
connectionId: connection.id,
|
|
1899
|
+
visibleToSubjectId: input.subjectId,
|
|
1900
|
+
expectedVersion: connection.version,
|
|
1901
|
+
metadata: GoogleDriveConnectionMetadata.parse({
|
|
1902
|
+
...metadata,
|
|
1903
|
+
selectedSource: null,
|
|
1904
|
+
selectedSources: sources.map((source) =>
|
|
1905
|
+
source.id === input.externalSourceId
|
|
1906
|
+
? {
|
|
1907
|
+
...source,
|
|
1908
|
+
syncEnabled: false,
|
|
1909
|
+
configGeneration: source.configGeneration + 1,
|
|
1910
|
+
}
|
|
1911
|
+
: source,
|
|
1912
|
+
),
|
|
1913
|
+
}),
|
|
1914
|
+
updatedBySubjectId: input.subjectId,
|
|
1915
|
+
});
|
|
1916
|
+
if (!updated) {
|
|
1917
|
+
throw new HTTPException(409, {
|
|
1918
|
+
message: "knowledge source connection changed; retry schedule deletion",
|
|
1919
|
+
});
|
|
1920
|
+
}
|
|
1921
|
+
revocationVersion = updated.version;
|
|
1922
|
+
}
|
|
1923
|
+
const resolved = await getKnowledgeSourceForSyncAuthority(tx, {
|
|
1924
|
+
accountId: input.accountId,
|
|
1925
|
+
workspaceId: input.workspaceId,
|
|
1926
|
+
sourceId: input.sourceId,
|
|
1927
|
+
initiatingSubjectId: input.subjectId,
|
|
1928
|
+
});
|
|
1929
|
+
if (
|
|
1930
|
+
resolved?.source.lifecycleState !== "active" ||
|
|
1931
|
+
resolved.source.lifecycleGeneration !== input.sourceLifecycleGeneration
|
|
1932
|
+
) {
|
|
1933
|
+
throw new HTTPException(409, {
|
|
1934
|
+
message: "knowledge source changed; retry schedule deletion",
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1937
|
+
await recordKnowledgeLifecycleEvent(tx, {
|
|
1938
|
+
accountId: input.accountId,
|
|
1939
|
+
workspaceId: input.workspaceId,
|
|
1487
1940
|
targetKind: "source",
|
|
1488
1941
|
targetId: resolved.source.id,
|
|
1489
1942
|
eventType: "deleted",
|
|
1490
1943
|
expectedGeneration: resolved.source.lifecycleGeneration,
|
|
1491
|
-
operationId: `knowledge-schedule-delete:${
|
|
1944
|
+
operationId: `knowledge-schedule-delete:${input.taskId}:${revocationVersion}`,
|
|
1492
1945
|
reasonCode: "schedule_deleted",
|
|
1493
1946
|
actor: {
|
|
1494
1947
|
kind: "human",
|
|
@@ -1496,7 +1949,7 @@ export async function revokeKnowledgeSourceScheduleAuthorization(
|
|
|
1496
1949
|
initiatingHumanSubjectId: input.subjectId,
|
|
1497
1950
|
},
|
|
1498
1951
|
});
|
|
1499
|
-
}
|
|
1952
|
+
});
|
|
1500
1953
|
}
|
|
1501
1954
|
|
|
1502
1955
|
function knowledgeSourceScheduleControl(task: ScheduledTask): {
|
|
@@ -1614,6 +2067,16 @@ function requireGoogleDriveSettings(settings: Settings): {
|
|
|
1614
2067
|
return { clientId, clientSecret };
|
|
1615
2068
|
}
|
|
1616
2069
|
|
|
2070
|
+
function requireGoogleDriveOAuthCallbackUrl(settings: Settings): string {
|
|
2071
|
+
const callbackUrl = googleDriveOAuthCallbackUrl(settings.publicBaseUrl);
|
|
2072
|
+
if (!callbackUrl) {
|
|
2073
|
+
throw new HTTPException(503, {
|
|
2074
|
+
message: "Google Drive requires a canonical OPENGENI_PUBLIC_BASE_URL origin",
|
|
2075
|
+
});
|
|
2076
|
+
}
|
|
2077
|
+
return callbackUrl;
|
|
2078
|
+
}
|
|
2079
|
+
|
|
1617
2080
|
function requireGoogleDriveConnection(connection: GoogleDriveConnectionRecord, subjectId: string) {
|
|
1618
2081
|
const parsed = GoogleDriveConnectionMetadata.safeParse(connection.metadata);
|
|
1619
2082
|
if (
|
|
@@ -1799,6 +2262,13 @@ function readGoogleDriveOAuthState(
|
|
|
1799
2262
|
if (iat === undefined || now < iat || now - iat > oauthStateTtlMs / 1000) {
|
|
1800
2263
|
throw new HTTPException(400, { message: "invalid or expired Google Drive OAuth state" });
|
|
1801
2264
|
}
|
|
2265
|
+
// Reject a state minted by any other flow before reading anything else. A
|
|
2266
|
+
// pre-cutover state carries no kind and is refused; that is the same bounded
|
|
2267
|
+
// `oauthStateTtlMs` window the personal-owner claim already fails closed on,
|
|
2268
|
+
// so this adds no new breakage.
|
|
2269
|
+
if (payload.kind !== GOOGLE_DRIVE_OAUTH_STATE_KIND) {
|
|
2270
|
+
throw new HTTPException(400, { message: "invalid Google Drive OAuth state" });
|
|
2271
|
+
}
|
|
1802
2272
|
const accountId = requiredString(payload.accountId, "state.accountId");
|
|
1803
2273
|
const workspaceId = requiredString(payload.workspaceId, "state.workspaceId");
|
|
1804
2274
|
const subjectId = requiredString(payload.subjectId, "state.subjectId");
|
|
@@ -1823,6 +2293,22 @@ function readGoogleDriveOAuthState(
|
|
|
1823
2293
|
accountId,
|
|
1824
2294
|
workspaceId,
|
|
1825
2295
|
subjectId,
|
|
2296
|
+
personalOwnerVerified: personalOwnerVerifiedInState(payload),
|
|
2297
|
+
...(typeof payload.connectAttemptId === "string"
|
|
2298
|
+
? { connectAttemptId: payload.connectAttemptId }
|
|
2299
|
+
: {}),
|
|
2300
|
+
...(typeof payload.encryptedExternalContinuation === "string"
|
|
2301
|
+
? {
|
|
2302
|
+
externalContinuation: ExternalActorContinuation.parse(
|
|
2303
|
+
JSON.parse(
|
|
2304
|
+
decryptEnvironmentValue(
|
|
2305
|
+
requireEnvironmentEncryption(settings),
|
|
2306
|
+
payload.encryptedExternalContinuation,
|
|
2307
|
+
),
|
|
2308
|
+
),
|
|
2309
|
+
),
|
|
2310
|
+
}
|
|
2311
|
+
: {}),
|
|
1826
2312
|
returnPath,
|
|
1827
2313
|
encryptedPkceVerifier: requiredString(
|
|
1828
2314
|
payload.encryptedPkceVerifier,
|
|
@@ -1839,16 +2325,22 @@ async function requireGoogleDriveCallbackGrant(
|
|
|
1839
2325
|
deps: ApiRouteDeps,
|
|
1840
2326
|
state: GoogleDriveOAuthState,
|
|
1841
2327
|
): Promise<void> {
|
|
1842
|
-
|
|
2328
|
+
// This connector is personal-only by construction: its start request carries
|
|
2329
|
+
// no ownership and the callback always writes `subjectId: state.subjectId`.
|
|
2330
|
+
// A state minted before the start-side principal fence existed carries no
|
|
2331
|
+
// `personalOwnerVerified` claim and must not land a personal Connection.
|
|
1843
2332
|
if (
|
|
1844
|
-
!
|
|
1845
|
-
|
|
1846
|
-
|
|
2333
|
+
!personalOwnerStateAccepted({
|
|
2334
|
+
ownership: "personal",
|
|
2335
|
+
subjectId: state.subjectId,
|
|
2336
|
+
personalOwnerVerified: state.personalOwnerVerified,
|
|
2337
|
+
})
|
|
1847
2338
|
) {
|
|
1848
|
-
throw new HTTPException(
|
|
1849
|
-
message: "Google Drive
|
|
2339
|
+
throw new HTTPException(422, {
|
|
2340
|
+
message: personalOnlyConnectionPrincipalMessage("Google Drive"),
|
|
1850
2341
|
});
|
|
1851
2342
|
}
|
|
2343
|
+
await requireConnectOwnerAuthority(deps.db, state, "connections:write");
|
|
1852
2344
|
}
|
|
1853
2345
|
|
|
1854
2346
|
async function exchangeGoogleAuthorizationCode(
|