@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
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type { Settings } from "@opengeni/config";
|
|
2
|
+
import type {
|
|
3
|
+
ManagedEmailDeliveryResult,
|
|
4
|
+
ManagedEmailMessage,
|
|
5
|
+
ManagedEmailTransport,
|
|
6
|
+
} from "@opengeni/core";
|
|
7
|
+
import { createHmac } from "node:crypto";
|
|
8
|
+
import { Resend } from "resend";
|
|
9
|
+
|
|
10
|
+
const CAPTURE_MAX_MESSAGES = 250;
|
|
11
|
+
const CAPTURE_TTL_MS = 15 * 60 * 1000;
|
|
12
|
+
|
|
13
|
+
export type CapturedManagedEmail = ManagedEmailMessage & {
|
|
14
|
+
capturedAt: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Process-local test/development transport. Captures are count/TTL bounded and
|
|
19
|
+
* one-time readable; there is deliberately no route, disk, database, or log
|
|
20
|
+
* projection for message bodies or setup bearers.
|
|
21
|
+
*/
|
|
22
|
+
export class InMemoryManagedEmailTransport implements ManagedEmailTransport {
|
|
23
|
+
private readonly messages: CapturedManagedEmail[] = [];
|
|
24
|
+
readonly sender: string;
|
|
25
|
+
readonly idempotency: ManagedEmailTransport["idempotency"];
|
|
26
|
+
|
|
27
|
+
constructor(
|
|
28
|
+
private readonly options: {
|
|
29
|
+
maxMessages?: number;
|
|
30
|
+
ttlMs?: number;
|
|
31
|
+
now?: () => number;
|
|
32
|
+
sender?: string;
|
|
33
|
+
idempotency?: ManagedEmailTransport["idempotency"];
|
|
34
|
+
} = {},
|
|
35
|
+
) {
|
|
36
|
+
this.sender = options.sender ?? "Opengeni <auth@mail.opengeni.ai>";
|
|
37
|
+
this.idempotency = options.idempotency ?? {
|
|
38
|
+
scope: "opengeni-in-memory-v1",
|
|
39
|
+
retentionSeconds: 86_400,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async send(message: ManagedEmailMessage): Promise<ManagedEmailDeliveryResult> {
|
|
44
|
+
this.prune();
|
|
45
|
+
const maxMessages = this.options.maxMessages ?? CAPTURE_MAX_MESSAGES;
|
|
46
|
+
if (this.messages.length >= maxMessages) this.messages.splice(0, 1);
|
|
47
|
+
this.messages.push({
|
|
48
|
+
...message,
|
|
49
|
+
capturedAt: new Date(this.now()).toISOString(),
|
|
50
|
+
});
|
|
51
|
+
return { status: "sent", providerMessageId: null };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
take(predicate: (message: CapturedManagedEmail) => boolean): CapturedManagedEmail | null {
|
|
55
|
+
this.prune();
|
|
56
|
+
const index = this.messages.findIndex(predicate);
|
|
57
|
+
if (index < 0) return null;
|
|
58
|
+
return this.messages.splice(index, 1)[0] ?? null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
size(): number {
|
|
62
|
+
this.prune();
|
|
63
|
+
return this.messages.length;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private prune(): void {
|
|
67
|
+
const cutoff = this.now() - (this.options.ttlMs ?? CAPTURE_TTL_MS);
|
|
68
|
+
while (this.messages[0] && Date.parse(this.messages[0].capturedAt) <= cutoff) {
|
|
69
|
+
this.messages.shift();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private now(): number {
|
|
74
|
+
return this.options.now?.() ?? Date.now();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
class UnconfiguredManagedEmailTransport implements ManagedEmailTransport {
|
|
79
|
+
readonly idempotency = {
|
|
80
|
+
scope: "opengeni-unconfigured-v1",
|
|
81
|
+
retentionSeconds: 0,
|
|
82
|
+
} as const;
|
|
83
|
+
|
|
84
|
+
constructor(readonly sender: string) {}
|
|
85
|
+
|
|
86
|
+
async send(): Promise<ManagedEmailDeliveryResult> {
|
|
87
|
+
return { status: "failed", errorClass: "provider_not_configured" };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
class ResendManagedEmailTransport implements ManagedEmailTransport {
|
|
92
|
+
private readonly client: Resend;
|
|
93
|
+
readonly idempotency: ManagedEmailTransport["idempotency"];
|
|
94
|
+
|
|
95
|
+
constructor(
|
|
96
|
+
apiKey: string,
|
|
97
|
+
readonly sender: string,
|
|
98
|
+
scopeSecret: string,
|
|
99
|
+
) {
|
|
100
|
+
this.client = new Resend(apiKey);
|
|
101
|
+
this.idempotency = {
|
|
102
|
+
// The keyed digest binds this delivery to the Resend account without
|
|
103
|
+
// persisting an API-key derivative that can be tested offline.
|
|
104
|
+
scope: `resend-v1-24h:${createHmac("sha256", scopeSecret).update(apiKey).digest("hex")}`,
|
|
105
|
+
retentionSeconds: 86_400,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async send(message: ManagedEmailMessage): Promise<ManagedEmailDeliveryResult> {
|
|
110
|
+
if (message.from !== this.sender) {
|
|
111
|
+
return { status: "failed", errorClass: "sender_changed" };
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const result = await this.client.emails.send(
|
|
115
|
+
{
|
|
116
|
+
from: message.from,
|
|
117
|
+
to: message.to,
|
|
118
|
+
subject: message.subject,
|
|
119
|
+
text: message.text,
|
|
120
|
+
html: message.html,
|
|
121
|
+
},
|
|
122
|
+
message.idempotencyKey ? { idempotencyKey: message.idempotencyKey } : undefined,
|
|
123
|
+
);
|
|
124
|
+
if (!result.error) {
|
|
125
|
+
return { status: "sent", providerMessageId: result.data?.id ?? null };
|
|
126
|
+
}
|
|
127
|
+
const statusCode = "statusCode" in result.error ? Number(result.error.statusCode) : NaN;
|
|
128
|
+
return clearProviderRefusal(statusCode)
|
|
129
|
+
? { status: "failed", errorClass: boundedErrorClass(result.error.name, "provider_refused") }
|
|
130
|
+
: {
|
|
131
|
+
status: "outcome_unknown",
|
|
132
|
+
errorClass: boundedErrorClass(result.error.name, "provider_ambiguous"),
|
|
133
|
+
};
|
|
134
|
+
} catch (error) {
|
|
135
|
+
return {
|
|
136
|
+
status: "outcome_unknown",
|
|
137
|
+
errorClass: boundedErrorClass(
|
|
138
|
+
error instanceof Error ? error.name : "transport_error",
|
|
139
|
+
"transport_error",
|
|
140
|
+
),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function createManagedEmailTransport(settings: Settings): ManagedEmailTransport {
|
|
147
|
+
if (settings.resendApiKey) {
|
|
148
|
+
if (!settings.betterAuthSecret) {
|
|
149
|
+
throw new Error("OPENGENI_BETTER_AUTH_SECRET is required for managed email delivery");
|
|
150
|
+
}
|
|
151
|
+
return new ResendManagedEmailTransport(
|
|
152
|
+
settings.resendApiKey,
|
|
153
|
+
settings.emailFrom,
|
|
154
|
+
settings.betterAuthSecret,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
if (settings.environment === "local" || settings.environment === "test") {
|
|
158
|
+
return new InMemoryManagedEmailTransport({ sender: settings.emailFrom });
|
|
159
|
+
}
|
|
160
|
+
return new UnconfiguredManagedEmailTransport(settings.emailFrom);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function clearProviderRefusal(statusCode: number): boolean {
|
|
164
|
+
return statusCode >= 400 && statusCode < 500 && ![408, 409, 425, 429].includes(statusCode);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function boundedErrorClass(value: string, fallback: string): string {
|
|
168
|
+
const normalized = value
|
|
169
|
+
.trim()
|
|
170
|
+
.toLowerCase()
|
|
171
|
+
.replaceAll(/[^a-z0-9_-]+/g, "_")
|
|
172
|
+
.slice(0, 64);
|
|
173
|
+
return normalized || fallback;
|
|
174
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { ManagedEmailTransport } from "@opengeni/core";
|
|
2
|
+
import type { Database } from "@opengeni/db";
|
|
3
|
+
import { sql } from "drizzle-orm";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
const Claim = z.object({
|
|
7
|
+
operationId: z.string().uuid(),
|
|
8
|
+
leaseId: z.string().uuid(),
|
|
9
|
+
email: z.string().email(),
|
|
10
|
+
provider: z.enum(["password", "google", "github"]),
|
|
11
|
+
sender: z.string(),
|
|
12
|
+
});
|
|
13
|
+
const started = new WeakSet<object>();
|
|
14
|
+
|
|
15
|
+
export async function deliverManagedSignInNotification(
|
|
16
|
+
db: Database,
|
|
17
|
+
transport: ManagedEmailTransport,
|
|
18
|
+
operationId: string | null = null,
|
|
19
|
+
): Promise<"sent" | "failed" | "outcome_unknown"> {
|
|
20
|
+
try {
|
|
21
|
+
const result = await db.execute(
|
|
22
|
+
sql`select claim_managed_sign_in_notification(${operationId}::uuid,${transport.sender},${transport.idempotency.scope},${transport.idempotency.retentionSeconds}::integer) as claim`,
|
|
23
|
+
);
|
|
24
|
+
const row = (Array.isArray(result) ? result : result.rows)[0] as { claim: unknown } | undefined;
|
|
25
|
+
if (!row?.claim) return "outcome_unknown";
|
|
26
|
+
const claim = Claim.parse(row.claim);
|
|
27
|
+
let status: "sent" | "failed" | "outcome_unknown" = "outcome_unknown";
|
|
28
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
29
|
+
try {
|
|
30
|
+
const response = await Promise.race([
|
|
31
|
+
transport.send({
|
|
32
|
+
kind: "sign_in_method_changed",
|
|
33
|
+
from: claim.sender,
|
|
34
|
+
to: claim.email,
|
|
35
|
+
idempotencyKey: `sign-in-method:${claim.operationId}`,
|
|
36
|
+
subject: "Your Opengeni sign-in methods changed",
|
|
37
|
+
text: `Your Opengeni ${claim.provider} sign-in method changed. If this was not you, reset your password and contact your administrator.`,
|
|
38
|
+
html: `<p>Your Opengeni ${claim.provider} sign-in method changed. If this was not you, reset your password and contact your administrator.</p>`,
|
|
39
|
+
}),
|
|
40
|
+
new Promise<{ status: "outcome_unknown" }>((resolve) => {
|
|
41
|
+
timer = setTimeout(() => resolve({ status: "outcome_unknown" }), 5_000);
|
|
42
|
+
}),
|
|
43
|
+
]);
|
|
44
|
+
status = response.status;
|
|
45
|
+
} catch {
|
|
46
|
+
/* Retain the durable unknown claim for idempotent retry. */
|
|
47
|
+
} finally {
|
|
48
|
+
if (timer) clearTimeout(timer);
|
|
49
|
+
}
|
|
50
|
+
await db.execute(
|
|
51
|
+
sql`select settle_managed_sign_in_notification(${claim.operationId}::uuid,${claim.leaseId}::uuid,${status})`,
|
|
52
|
+
);
|
|
53
|
+
return status;
|
|
54
|
+
} catch {
|
|
55
|
+
// Security state is already committed. A failed claim/ack must never be
|
|
56
|
+
// reported as a failed method change; its durable obligation remains live.
|
|
57
|
+
return "outcome_unknown";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function startManagedSignInNotificationDelivery(
|
|
62
|
+
db: Database,
|
|
63
|
+
transport: ManagedEmailTransport,
|
|
64
|
+
): void {
|
|
65
|
+
if (started.has(db as object)) return;
|
|
66
|
+
started.add(db as object);
|
|
67
|
+
let active = false;
|
|
68
|
+
const drain = async () => {
|
|
69
|
+
if (active) return;
|
|
70
|
+
active = true;
|
|
71
|
+
try {
|
|
72
|
+
for (let count = 0; count < 10; count++)
|
|
73
|
+
if ((await deliverManagedSignInNotification(db, transport)) === "outcome_unknown") break;
|
|
74
|
+
} finally {
|
|
75
|
+
active = false;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const timer = setInterval(() => void drain(), 60_000);
|
|
79
|
+
(timer as ReturnType<typeof setInterval> & { unref?: () => void }).unref?.();
|
|
80
|
+
void drain();
|
|
81
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Settings } from "@opengeni/config";
|
|
2
|
+
import { readManagedAuthNewSignupsSwitch, type Database } from "@opengeni/db";
|
|
3
|
+
import type { Observability } from "@opengeni/observability";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Decides whether a new managed account may be created right now.
|
|
7
|
+
*
|
|
8
|
+
* `OPENGENI_MANAGED_AUTH_NEW_SIGNUPS_ENABLED` is the deployment ceiling (read at
|
|
9
|
+
* startup). While it allows sign-ups, the newest revision of the operator
|
|
10
|
+
* runtime switch (migration 0585) decides, read on every call so a flip
|
|
11
|
+
* applies to the next request on every API replica without a restart.
|
|
12
|
+
*
|
|
13
|
+
* Fail-safe: if the read fails, the last value this process observed wins;
|
|
14
|
+
* before any successful read, the deployment ceiling wins. A missing revision
|
|
15
|
+
* (pre-0585 schema) also follows the ceiling.
|
|
16
|
+
*/
|
|
17
|
+
export type ManagedAuthNewSignupsGate = {
|
|
18
|
+
signupsOpen(): Promise<boolean>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function createManagedAuthNewSignupsGate(input: {
|
|
22
|
+
db: Database;
|
|
23
|
+
settings: Pick<Settings, "managedAuthNewSignupsEnabled">;
|
|
24
|
+
observability?: Pick<Observability, "warn"> | undefined;
|
|
25
|
+
}): ManagedAuthNewSignupsGate {
|
|
26
|
+
const ceiling = input.settings.managedAuthNewSignupsEnabled !== false;
|
|
27
|
+
let lastKnown: boolean | null = null;
|
|
28
|
+
return {
|
|
29
|
+
async signupsOpen() {
|
|
30
|
+
if (!ceiling) return false;
|
|
31
|
+
try {
|
|
32
|
+
const current = await readManagedAuthNewSignupsSwitch(input.db);
|
|
33
|
+
lastKnown = current ? current.signupsEnabled : true;
|
|
34
|
+
return lastKnown;
|
|
35
|
+
} catch (error) {
|
|
36
|
+
try {
|
|
37
|
+
input.observability?.warn("Managed auth new signups switch read failed", {
|
|
38
|
+
dependency: "managed_auth_new_signups_switch",
|
|
39
|
+
fallback: lastKnown === null ? "deployment_ceiling" : "last_known",
|
|
40
|
+
errorClass: error instanceof Error ? error.name : "UnknownError",
|
|
41
|
+
});
|
|
42
|
+
} catch {
|
|
43
|
+
// An observer failure must not decide a sign-up.
|
|
44
|
+
}
|
|
45
|
+
return lastKnown ?? ceiling;
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import type { Settings } from "@opengeni/config";
|
|
2
|
+
import type { ManagedEmailTransport } from "@opengeni/core";
|
|
3
|
+
|
|
4
|
+
const encoder = new TextEncoder();
|
|
5
|
+
export type OrganizationUserSetupEmailTokenTransport = "fragment" | "query";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Prove that the stable invited-user setup bearer can be constructed before an
|
|
9
|
+
* invitation commits. Provider availability is intentionally outside this
|
|
10
|
+
* precondition: the durable delivery journal records a failed or ambiguous
|
|
11
|
+
* transport outcome after the invitation exists.
|
|
12
|
+
*/
|
|
13
|
+
export function assertOrganizationUserSetupDeliveryConfigured(
|
|
14
|
+
settings: Settings,
|
|
15
|
+
transport: ManagedEmailTransport,
|
|
16
|
+
): void {
|
|
17
|
+
assertOrganizationUserSetupQueryTransportConfigured(settings);
|
|
18
|
+
requiredSetupSecret(settings);
|
|
19
|
+
requiredPublicBaseUrl(settings);
|
|
20
|
+
assertManagedEmailTransportMetadata(transport);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Enforce the query-bearing edge proof for env and embedded settings alike. */
|
|
24
|
+
export function assertOrganizationUserSetupQueryTransportConfigured(
|
|
25
|
+
settings: Pick<
|
|
26
|
+
Settings,
|
|
27
|
+
| "organizationUserSetupEmailTokenTransport"
|
|
28
|
+
| "organizationUserSetupQueryEdgeSanitizationConfirmed"
|
|
29
|
+
>,
|
|
30
|
+
): void {
|
|
31
|
+
if (
|
|
32
|
+
settings.organizationUserSetupEmailTokenTransport === "query" &&
|
|
33
|
+
!settings.organizationUserSetupQueryEdgeSanitizationConfirmed
|
|
34
|
+
) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
"OPENGENI_ORGANIZATION_USER_SETUP_QUERY_EDGE_SANITIZATION_CONFIRMED=true is required when OPENGENI_ORGANIZATION_USER_SETUP_EMAIL_TOKEN_TRANSPORT=query",
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Reject an invalid embedded-provider contract before any durable boundary. */
|
|
42
|
+
export function assertManagedEmailTransportMetadata(transport: ManagedEmailTransport): void {
|
|
43
|
+
if (
|
|
44
|
+
transport.sender.trim() !== transport.sender ||
|
|
45
|
+
encoder.encode(transport.sender).byteLength < 3 ||
|
|
46
|
+
encoder.encode(transport.sender).byteLength > 320
|
|
47
|
+
) {
|
|
48
|
+
throw new Error("Managed email sender is invalid");
|
|
49
|
+
}
|
|
50
|
+
const { scope, retentionSeconds } = transport.idempotency;
|
|
51
|
+
if (
|
|
52
|
+
scope.trim() !== scope ||
|
|
53
|
+
!/^[a-z0-9][a-z0-9:._-]*$/.test(scope) ||
|
|
54
|
+
encoder.encode(scope).byteLength > 200 ||
|
|
55
|
+
!Number.isInteger(retentionSeconds) ||
|
|
56
|
+
retentionSeconds < 0 ||
|
|
57
|
+
retentionSeconds > 31_536_000
|
|
58
|
+
) {
|
|
59
|
+
throw new Error("Managed email idempotency contract is invalid");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function deriveOrganizationUserSetupToken(
|
|
64
|
+
settings: Settings,
|
|
65
|
+
input: {
|
|
66
|
+
invitationId: string;
|
|
67
|
+
deliveryId: string;
|
|
68
|
+
transport?: OrganizationUserSetupEmailTokenTransport;
|
|
69
|
+
},
|
|
70
|
+
): Promise<{ token: string; digest: string; url: string }> {
|
|
71
|
+
const secret = requiredSetupSecret(settings);
|
|
72
|
+
const key = await crypto.subtle.importKey(
|
|
73
|
+
"raw",
|
|
74
|
+
encoder.encode(secret),
|
|
75
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
76
|
+
false,
|
|
77
|
+
["sign"],
|
|
78
|
+
);
|
|
79
|
+
const signature = await crypto.subtle.sign(
|
|
80
|
+
"HMAC",
|
|
81
|
+
key,
|
|
82
|
+
encoder.encode(
|
|
83
|
+
`opengeni:organization-user-setup-delivery:v1:${input.deliveryId}:${input.invitationId}`,
|
|
84
|
+
),
|
|
85
|
+
);
|
|
86
|
+
const token = base64Url(new Uint8Array(signature));
|
|
87
|
+
const digest = await sha256Hex(token);
|
|
88
|
+
const url = new URL("/setup-account", requiredPublicBaseUrl(settings));
|
|
89
|
+
if ((input.transport ?? settings.organizationUserSetupEmailTokenTransport) === "query") {
|
|
90
|
+
// Email security gateways routinely wrap links and may discard URL
|
|
91
|
+
// fragments. Enable this only after the compatibility web rollout; the
|
|
92
|
+
// browser's first executable head bootstrap scrubs query authority without
|
|
93
|
+
// redirecting or allowing a query-bearing subresource Referer.
|
|
94
|
+
url.searchParams.set("token", token);
|
|
95
|
+
} else {
|
|
96
|
+
url.hash = new URLSearchParams({ token }).toString();
|
|
97
|
+
}
|
|
98
|
+
return { token, digest, url: url.toString() };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export type OrganizationUserSetupEmailSnapshot = {
|
|
102
|
+
senderEmail: string;
|
|
103
|
+
recipientEmail: string;
|
|
104
|
+
recipientName: string | null;
|
|
105
|
+
organizationName: string;
|
|
106
|
+
organizationRole: "owner" | "admin" | "member";
|
|
107
|
+
sharedWorkspaceAccess: Array<{
|
|
108
|
+
workspaceId: string;
|
|
109
|
+
workspaceName: string;
|
|
110
|
+
role: "viewer" | "member" | "admin";
|
|
111
|
+
}>;
|
|
112
|
+
setupUrl: string;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export function renderOrganizationUserSetupEmail(input: OrganizationUserSetupEmailSnapshot): {
|
|
116
|
+
from: string;
|
|
117
|
+
to: string;
|
|
118
|
+
subject: string;
|
|
119
|
+
text: string;
|
|
120
|
+
html: string;
|
|
121
|
+
} {
|
|
122
|
+
const greeting = input.recipientName ? `Hi ${input.recipientName},` : "Hello,";
|
|
123
|
+
const role = titleCase(input.organizationRole);
|
|
124
|
+
const workspaceSummary =
|
|
125
|
+
input.sharedWorkspaceAccess.length === 0
|
|
126
|
+
? "No shared workspaces are assigned yet."
|
|
127
|
+
: `Shared workspace access:\n${input.sharedWorkspaceAccess
|
|
128
|
+
.map((workspace) => `- ${workspace.workspaceName}: ${titleCase(workspace.role)}`)
|
|
129
|
+
.join("\n")}`;
|
|
130
|
+
const workspaceHtml =
|
|
131
|
+
input.sharedWorkspaceAccess.length === 0
|
|
132
|
+
? "<p>No shared workspaces are assigned yet.</p>"
|
|
133
|
+
: `<p>Shared workspace access:</p><ul>${input.sharedWorkspaceAccess
|
|
134
|
+
.map(
|
|
135
|
+
(workspace) =>
|
|
136
|
+
`<li>${escapeHtml(workspace.workspaceName)}: ${escapeHtml(titleCase(workspace.role))}</li>`,
|
|
137
|
+
)
|
|
138
|
+
.join("")}</ul>`;
|
|
139
|
+
return {
|
|
140
|
+
from: input.senderEmail,
|
|
141
|
+
to: input.recipientEmail,
|
|
142
|
+
subject: `You're invited to join ${input.organizationName} on Opengeni`,
|
|
143
|
+
text: `${greeting}\n\nYou've been invited to join ${input.organizationName} on Opengeni as ${role}.\n\n${workspaceSummary}\n\nThis invitation grants only the organization role and shared workspace access listed above. It never shares anyone's Personal workspace.\n\nAccept invitation to ${input.organizationName}: ${input.setupUrl}\n\nYou'll sign in or create an account before joining. Already use Opengeni? Open this invitation and choose Sign in as ${input.recipientEmail}. Opengeni will show the invitation immediately after you sign in.`,
|
|
144
|
+
html: `<p>${escapeHtml(greeting)}</p><p>You've been invited to join <strong>${escapeHtml(input.organizationName)}</strong> on Opengeni as ${escapeHtml(role)}.</p>${workspaceHtml}<p>This invitation grants only the organization role and shared workspace access listed above. It never shares anyone's Personal workspace.</p><p><a href="${escapeHtml(input.setupUrl)}">Accept invitation to ${escapeHtml(input.organizationName)}</a></p><p>You'll sign in or create an account before joining. Already use Opengeni? Open this invitation and choose <strong>Sign in as ${escapeHtml(input.recipientEmail)}</strong>. Opengeni will show the invitation immediately after you sign in.</p>`,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export async function organizationUserSetupPayloadDigest(input: {
|
|
149
|
+
from: string;
|
|
150
|
+
to: string;
|
|
151
|
+
subject: string;
|
|
152
|
+
text: string;
|
|
153
|
+
html: string;
|
|
154
|
+
providerIdempotencyScope: string;
|
|
155
|
+
}): Promise<string> {
|
|
156
|
+
return await sha256Hex(
|
|
157
|
+
JSON.stringify({
|
|
158
|
+
version: 2,
|
|
159
|
+
providerIdempotencyScope: input.providerIdempotencyScope,
|
|
160
|
+
from: input.from,
|
|
161
|
+
to: input.to,
|
|
162
|
+
subject: input.subject,
|
|
163
|
+
text: input.text,
|
|
164
|
+
html: input.html,
|
|
165
|
+
}),
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export async function resolveOrganizationUserSetupDeliveryEmail(
|
|
170
|
+
settings: Settings,
|
|
171
|
+
input: {
|
|
172
|
+
invitationId: string;
|
|
173
|
+
deliveryId: string;
|
|
174
|
+
senderEmail: string;
|
|
175
|
+
recipientEmail: string;
|
|
176
|
+
recipientName: string | null;
|
|
177
|
+
organizationName: string;
|
|
178
|
+
organizationRole: "owner" | "admin" | "member";
|
|
179
|
+
sharedWorkspaceAccess: OrganizationUserSetupEmailSnapshot["sharedWorkspaceAccess"];
|
|
180
|
+
providerIdempotencyScope: string;
|
|
181
|
+
frozenTransport: OrganizationUserSetupEmailTokenTransport | null;
|
|
182
|
+
frozenPayloadDigest: string | null;
|
|
183
|
+
},
|
|
184
|
+
): Promise<{
|
|
185
|
+
transport: OrganizationUserSetupEmailTokenTransport;
|
|
186
|
+
token: string;
|
|
187
|
+
tokenDigest: string;
|
|
188
|
+
payloadDigest: string;
|
|
189
|
+
message: ReturnType<typeof renderOrganizationUserSetupEmail>;
|
|
190
|
+
}> {
|
|
191
|
+
const candidates: OrganizationUserSetupEmailTokenTransport[] = input.frozenTransport
|
|
192
|
+
? [input.frozenTransport]
|
|
193
|
+
: input.frozenPayloadDigest
|
|
194
|
+
? ["fragment", "query"]
|
|
195
|
+
: [settings.organizationUserSetupEmailTokenTransport];
|
|
196
|
+
for (const transport of candidates) {
|
|
197
|
+
const setup = await deriveOrganizationUserSetupToken(settings, {
|
|
198
|
+
invitationId: input.invitationId,
|
|
199
|
+
deliveryId: input.deliveryId,
|
|
200
|
+
transport,
|
|
201
|
+
});
|
|
202
|
+
const message = renderOrganizationUserSetupEmail({
|
|
203
|
+
senderEmail: input.senderEmail,
|
|
204
|
+
recipientEmail: input.recipientEmail,
|
|
205
|
+
recipientName: input.recipientName,
|
|
206
|
+
organizationName: input.organizationName,
|
|
207
|
+
organizationRole: input.organizationRole,
|
|
208
|
+
sharedWorkspaceAccess: input.sharedWorkspaceAccess,
|
|
209
|
+
setupUrl: setup.url,
|
|
210
|
+
});
|
|
211
|
+
const payloadDigest = await organizationUserSetupPayloadDigest({
|
|
212
|
+
...message,
|
|
213
|
+
providerIdempotencyScope: input.providerIdempotencyScope,
|
|
214
|
+
});
|
|
215
|
+
if (input.frozenPayloadDigest && payloadDigest !== input.frozenPayloadDigest) continue;
|
|
216
|
+
return {
|
|
217
|
+
transport,
|
|
218
|
+
token: setup.token,
|
|
219
|
+
tokenDigest: setup.digest,
|
|
220
|
+
payloadDigest,
|
|
221
|
+
message,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
throw new Error("Organization setup delivery transport does not match its frozen payload");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export async function organizationUserSetupRequestFingerprint(
|
|
228
|
+
settings: Settings,
|
|
229
|
+
input: { tokenDigest: string; name: string; password: string },
|
|
230
|
+
): Promise<string> {
|
|
231
|
+
const key = await crypto.subtle.importKey(
|
|
232
|
+
"raw",
|
|
233
|
+
encoder.encode(requiredSetupSecret(settings)),
|
|
234
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
235
|
+
false,
|
|
236
|
+
["sign"],
|
|
237
|
+
);
|
|
238
|
+
const signature = await crypto.subtle.sign(
|
|
239
|
+
"HMAC",
|
|
240
|
+
key,
|
|
241
|
+
encoder.encode(
|
|
242
|
+
JSON.stringify({
|
|
243
|
+
tokenDigest: input.tokenDigest,
|
|
244
|
+
name: input.name,
|
|
245
|
+
password: input.password,
|
|
246
|
+
}),
|
|
247
|
+
),
|
|
248
|
+
);
|
|
249
|
+
return hex(new Uint8Array(signature));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export async function selfServiceOrganizationSetupRequestFingerprint(input: {
|
|
253
|
+
authUserId: string;
|
|
254
|
+
organizationName: string;
|
|
255
|
+
}): Promise<string> {
|
|
256
|
+
const actorSubjectId = `user:${input.authUserId}`;
|
|
257
|
+
const organizationNameBytes = encoder.encode(input.organizationName);
|
|
258
|
+
return await sha256Hex(
|
|
259
|
+
`opengeni:self-service-organization:v1:${actorSubjectId}:${organizationNameBytes.byteLength}:${input.organizationName}`,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export async function organizationUserSetupTokenDigest(token: string): Promise<string> {
|
|
264
|
+
return await sha256Hex(token);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function requiredSetupSecret(settings: Settings): string {
|
|
268
|
+
if (!settings.betterAuthSecret) {
|
|
269
|
+
throw new Error("OPENGENI_BETTER_AUTH_SECRET is required for organization user setup");
|
|
270
|
+
}
|
|
271
|
+
return settings.betterAuthSecret;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function requiredPublicBaseUrl(settings: Settings): string {
|
|
275
|
+
if (!settings.publicBaseUrl) {
|
|
276
|
+
throw new Error("OPENGENI_PUBLIC_BASE_URL is required for organization user setup");
|
|
277
|
+
}
|
|
278
|
+
return settings.publicBaseUrl;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async function sha256Hex(value: string): Promise<string> {
|
|
282
|
+
const digest = await crypto.subtle.digest("SHA-256", encoder.encode(value));
|
|
283
|
+
return hex(new Uint8Array(digest));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function hex(bytes: Uint8Array): string {
|
|
287
|
+
return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function base64Url(bytes: Uint8Array): string {
|
|
291
|
+
let binary = "";
|
|
292
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
293
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function titleCase(value: string): string {
|
|
297
|
+
return `${value.slice(0, 1).toUpperCase()}${value.slice(1)}`;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function escapeHtml(value: string): string {
|
|
301
|
+
return value
|
|
302
|
+
.replaceAll("&", "&")
|
|
303
|
+
.replaceAll("<", "<")
|
|
304
|
+
.replaceAll(">", ">")
|
|
305
|
+
.replaceAll('"', """)
|
|
306
|
+
.replaceAll("'", "'");
|
|
307
|
+
}
|