@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
package/src/routes/github.ts
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
|
+
GitHubActionPoliciesResponse,
|
|
3
|
+
GitHubActionPolicyActorState,
|
|
2
4
|
GitHubAppManifestCreate,
|
|
5
|
+
OrganizationIntegrationDeniedError,
|
|
6
|
+
UpdateGitHubActionPolicyRequest,
|
|
3
7
|
type AccessGrant,
|
|
4
8
|
type GitHubInstallationBindingCandidate,
|
|
5
|
-
type GitHubInstallationBindingProof,
|
|
6
9
|
} from "@opengeni/contracts";
|
|
10
|
+
import { PersonalGitHubConnectionMetadata } from "@opengeni/contracts/personal-github";
|
|
11
|
+
import { withOrganizationIntegrationAcquisition } from "@opengeni/db/organization-integration-policy";
|
|
12
|
+
import {
|
|
13
|
+
ListGitHubRepositoryBranchesQuery,
|
|
14
|
+
VerifyPublicGitHubRepositoryRefRequest,
|
|
15
|
+
} from "@opengeni/contracts/github-repository-contracts";
|
|
16
|
+
import { parseCanonicalGitHubRepositoryUrl } from "@opengeni/contracts/github-repository";
|
|
7
17
|
import {
|
|
8
18
|
bindAuthorizedGitHubInstallationRepositories,
|
|
9
19
|
deleteGitHubInstallationBinding,
|
|
10
20
|
GitHubInstallationAuthorityCommitError,
|
|
21
|
+
listGitHubInstallationAccessForWorkspace,
|
|
11
22
|
} from "@opengeni/db";
|
|
12
23
|
import {
|
|
13
24
|
authorizeGitHubInstallationBinding,
|
|
@@ -19,19 +30,32 @@ import {
|
|
|
19
30
|
GitHubAppApiError,
|
|
20
31
|
GitHubAppConfigurationError,
|
|
21
32
|
GitHubInstallationAuthorityError,
|
|
33
|
+
GitHubPublicRepositoryVerificationError,
|
|
22
34
|
githubAppMissingSettings,
|
|
23
35
|
githubOAuthAuthorizeUrl,
|
|
36
|
+
inspectSignedState,
|
|
24
37
|
organizationAppManifestUrl,
|
|
25
38
|
personalAppManifestUrl,
|
|
26
39
|
readSignedState,
|
|
27
40
|
stateMaxAgeSeconds,
|
|
41
|
+
verifyPublicGitHubRepositoryRef,
|
|
28
42
|
type GitHubSignedStatePayload,
|
|
29
43
|
verifySignedState,
|
|
30
44
|
} from "@opengeni/github";
|
|
31
|
-
import type { Context, Hono } from "hono";
|
|
45
|
+
import type { Context, Hono, MiddlewareHandler } from "hono";
|
|
32
46
|
import { deleteCookie, setCookie } from "hono/cookie";
|
|
33
47
|
import { HTTPException } from "hono/http-exception";
|
|
34
|
-
import {
|
|
48
|
+
import type { ContentfulStatusCode } from "hono/utils/http-status";
|
|
49
|
+
import {
|
|
50
|
+
githubAppActionPolicyActor,
|
|
51
|
+
hasPermission,
|
|
52
|
+
listGitHubActionPolicyActors,
|
|
53
|
+
personalGitHubActionPolicyActor,
|
|
54
|
+
requireAccessGrant,
|
|
55
|
+
requireAccessGrantAuthorization,
|
|
56
|
+
externalActorContinuationForAuthorization,
|
|
57
|
+
updateGitHubActionPolicyGroup,
|
|
58
|
+
} from "@opengeni/core";
|
|
35
59
|
import type { ApiRouteDeps } from "@opengeni/core";
|
|
36
60
|
import {
|
|
37
61
|
continuedGitHubBrowserGrantClaims,
|
|
@@ -42,20 +66,103 @@ import {
|
|
|
42
66
|
} from "../github-browser-flow";
|
|
43
67
|
import {
|
|
44
68
|
githubBindingStatus,
|
|
69
|
+
GitHubRepositoryBranchAuthorityError,
|
|
45
70
|
listWorkspaceGitHubInstallationBindings,
|
|
71
|
+
listWorkspaceGitHubRepositoryBranches,
|
|
46
72
|
listWorkspaceGitHubRepositories,
|
|
47
73
|
} from "../github-access";
|
|
74
|
+
import {
|
|
75
|
+
assertPersonalConnectionOwnerPrincipal,
|
|
76
|
+
requireLegacyOAuthActor,
|
|
77
|
+
} from "../connection-ownership";
|
|
78
|
+
import { workspaceIntegrationsPathForUntrusted } from "../integrations/oauth-client";
|
|
79
|
+
import { listPersonalGitHubConnections } from "../integrations/personal-github";
|
|
80
|
+
import {
|
|
81
|
+
integrationCommitGrant,
|
|
82
|
+
type IntegrationCommitGrant,
|
|
83
|
+
} from "../integrations/integration-commit-authority";
|
|
84
|
+
import {
|
|
85
|
+
isConsistentGitHubBindingCandidates,
|
|
86
|
+
isConsistentGitHubBindingProof,
|
|
87
|
+
} from "../integrations/github-installation-proof";
|
|
88
|
+
import {
|
|
89
|
+
githubConnectFailureHtml,
|
|
90
|
+
githubInstallationChooserHtml,
|
|
91
|
+
githubSetupPendingHtml,
|
|
92
|
+
githubSetupSuccessHtml,
|
|
93
|
+
githubSuccessHtml,
|
|
94
|
+
type GitHubConnectFailure,
|
|
95
|
+
} from "./github-browser-pages";
|
|
96
|
+
import {
|
|
97
|
+
completeGitHubAppConnect,
|
|
98
|
+
isGitHubAppConnectState,
|
|
99
|
+
} from "../integrations/github-app-connect";
|
|
100
|
+
import { parseRequestJson } from "../http/request-body";
|
|
48
101
|
|
|
49
102
|
const githubStateCookie = "opengeni_github_state";
|
|
50
103
|
const githubBindingStateMaxAgeSeconds = 10 * 60;
|
|
51
104
|
const legacyInstallationChooserDisabledMessage =
|
|
52
105
|
"The legacy repository-admin GitHub installation chooser is disabled; use the GitHub owner-consent connect flow";
|
|
106
|
+
/**
|
|
107
|
+
* GitHub routes a browser navigates to (not the JSON API). A page-load link
|
|
108
|
+
* opened after it expired, GitHub's Cancel button, or a non-owner all land on
|
|
109
|
+
* one of these, so their failures render a readable page instead of JSON.
|
|
110
|
+
*/
|
|
111
|
+
const GITHUB_BROWSER_ROUTES = [
|
|
112
|
+
"/v1/workspaces/:workspaceId/github/connect",
|
|
113
|
+
"/v1/workspaces/:workspaceId/github/installations/select",
|
|
114
|
+
"/v1/workspaces/:workspaceId/github/installations/:installationId/configure",
|
|
115
|
+
"/v1/github/app-manifest/callback",
|
|
116
|
+
"/v1/github/setup",
|
|
117
|
+
"/v1/github/install/callback",
|
|
118
|
+
"/v1/github/oauth/callback",
|
|
119
|
+
] as const;
|
|
120
|
+
|
|
121
|
+
/** An HTTP failure that already knows which browser page explains it. */
|
|
122
|
+
class GitHubBrowserFailure extends HTTPException {
|
|
123
|
+
constructor(
|
|
124
|
+
status: 400 | 403,
|
|
125
|
+
message: string,
|
|
126
|
+
readonly failure: GitHubConnectFailure,
|
|
127
|
+
) {
|
|
128
|
+
super(status, { message });
|
|
129
|
+
this.name = "GitHubBrowserFailure";
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
53
133
|
export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
54
134
|
const { db, settings, githubStateSecret } = deps;
|
|
135
|
+
// Registered before the routes so it wraps them. The status code is kept, so
|
|
136
|
+
// automation still sees the same outcome; only the body becomes a page. Every
|
|
137
|
+
// failure is rendered, not only HTTP ones: an organization policy denial or an
|
|
138
|
+
// unexpected fault must not reach the browser as the JSON error envelope.
|
|
139
|
+
const browserFailurePage: MiddlewareHandler = async (c, next) => {
|
|
140
|
+
let failure: unknown;
|
|
141
|
+
let handledStatus: number | undefined;
|
|
142
|
+
try {
|
|
143
|
+
await next();
|
|
144
|
+
if (!c.error) return;
|
|
145
|
+
failure = c.error;
|
|
146
|
+
// The app error handler has already answered this failure.
|
|
147
|
+
handledStatus = c.res.status;
|
|
148
|
+
} catch (error) {
|
|
149
|
+
failure = error;
|
|
150
|
+
}
|
|
151
|
+
c.res = c.html(
|
|
152
|
+
githubConnectFailureHtml(
|
|
153
|
+
githubBrowserFailureKind(failure),
|
|
154
|
+
githubFailureReturnUrl(deps, c),
|
|
155
|
+
githubBrowserFailureDetail(failure),
|
|
156
|
+
),
|
|
157
|
+
githubBrowserFailureStatus(failure, handledStatus) as ContentfulStatusCode,
|
|
158
|
+
);
|
|
159
|
+
};
|
|
160
|
+
for (const path of GITHUB_BROWSER_ROUTES) app.use(path, browserFailurePage);
|
|
55
161
|
|
|
56
162
|
app.get("/v1/workspaces/:workspaceId/github/app", async (c) => {
|
|
57
163
|
const workspaceId = c.req.param("workspaceId");
|
|
58
|
-
const
|
|
164
|
+
const access = await requireAccessGrantAuthorization(c, deps, workspaceId, "github:use");
|
|
165
|
+
const grant = access.grant;
|
|
59
166
|
const missing = githubAppMissingSettings(settings);
|
|
60
167
|
const slug = settings.githubAppSlug?.trim() || null;
|
|
61
168
|
const installations =
|
|
@@ -64,7 +171,9 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
64
171
|
: [];
|
|
65
172
|
const status = githubBindingStatus(missing.length === 0, installations);
|
|
66
173
|
const setupMode = settings.productAccessMode === "managed" ? "platform" : "operator";
|
|
67
|
-
const canManage =
|
|
174
|
+
const canManage =
|
|
175
|
+
!externalActorContinuationForAuthorization(access) &&
|
|
176
|
+
hasPermission(grant.permissions, "github:manage");
|
|
68
177
|
const returnPath = githubSessionReturnPath(c.req.query("returnPath"), grant.workspaceId);
|
|
69
178
|
const connectState =
|
|
70
179
|
missing.length === 0 && slug && canManage
|
|
@@ -99,6 +208,95 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
99
208
|
});
|
|
100
209
|
});
|
|
101
210
|
|
|
211
|
+
app.get("/v1/workspaces/:workspaceId/github/action-policies", async (c) => {
|
|
212
|
+
const workspaceId = c.req.param("workspaceId");
|
|
213
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "github:use");
|
|
214
|
+
const [installations, personalConnections] = await Promise.all([
|
|
215
|
+
listGitHubInstallationAccessForWorkspace(db, grant.workspaceId),
|
|
216
|
+
listPersonalGitHubConnections(deps, {
|
|
217
|
+
workspaceId: grant.workspaceId,
|
|
218
|
+
subjectId: grant.subjectId,
|
|
219
|
+
}),
|
|
220
|
+
]);
|
|
221
|
+
const personalConnection = canonicalPersonalGitHubPolicyConnection(personalConnections);
|
|
222
|
+
const actors = [
|
|
223
|
+
...installations.map(githubAppActionPolicyActor),
|
|
224
|
+
...(personalConnection
|
|
225
|
+
? [
|
|
226
|
+
personalGitHubActionPolicyActor({
|
|
227
|
+
connectionId: personalConnection.id,
|
|
228
|
+
githubLogin: PersonalGitHubConnectionMetadata.parse(personalConnection.metadata)
|
|
229
|
+
.githubLogin,
|
|
230
|
+
}),
|
|
231
|
+
]
|
|
232
|
+
: []),
|
|
233
|
+
];
|
|
234
|
+
return c.json(
|
|
235
|
+
GitHubActionPoliciesResponse.parse({
|
|
236
|
+
enabled: settings.githubRestMcpEnabled,
|
|
237
|
+
actors: await listGitHubActionPolicyActors(db, {
|
|
238
|
+
accountId: grant.accountId,
|
|
239
|
+
workspaceId: grant.workspaceId,
|
|
240
|
+
actors,
|
|
241
|
+
}),
|
|
242
|
+
}),
|
|
243
|
+
);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
app.patch("/v1/workspaces/:workspaceId/github/action-policies", async (c) => {
|
|
247
|
+
const workspaceId = c.req.param("workspaceId");
|
|
248
|
+
const payload = await parseRequestJson(c, UpdateGitHubActionPolicyRequest);
|
|
249
|
+
const actor = payload.actor;
|
|
250
|
+
if (actor.kind === "workspace_app") {
|
|
251
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "github:manage");
|
|
252
|
+
const installations = await listGitHubInstallationAccessForWorkspace(db, grant.workspaceId);
|
|
253
|
+
const installation = installations.find(
|
|
254
|
+
(candidate) => candidate.installationId === actor.installationId,
|
|
255
|
+
);
|
|
256
|
+
if (!installation || installation.accountId !== grant.accountId) {
|
|
257
|
+
throw new HTTPException(404, { message: "GitHub installation not found" });
|
|
258
|
+
}
|
|
259
|
+
return c.json(
|
|
260
|
+
GitHubActionPolicyActorState.parse(
|
|
261
|
+
await updateGitHubActionPolicyGroup(db, {
|
|
262
|
+
accountId: grant.accountId,
|
|
263
|
+
workspaceId: grant.workspaceId,
|
|
264
|
+
subjectId: grant.subjectId,
|
|
265
|
+
actor: githubAppActionPolicyActor(installation),
|
|
266
|
+
group: payload.group,
|
|
267
|
+
decision: payload.decision,
|
|
268
|
+
}),
|
|
269
|
+
),
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const access = await requireAccessGrantAuthorization(c, deps, workspaceId, "connections:write");
|
|
274
|
+
assertPersonalConnectionOwnerPrincipal(access, "My GitHub action policy");
|
|
275
|
+
const connections = await listPersonalGitHubConnections(deps, {
|
|
276
|
+
workspaceId: access.grant.workspaceId,
|
|
277
|
+
subjectId: access.grant.subjectId,
|
|
278
|
+
});
|
|
279
|
+
const connection = connections.find((candidate) => candidate.id === actor.connectionId);
|
|
280
|
+
if (!connection) {
|
|
281
|
+
throw new HTTPException(404, { message: "personal GitHub connection not found" });
|
|
282
|
+
}
|
|
283
|
+
return c.json(
|
|
284
|
+
GitHubActionPolicyActorState.parse(
|
|
285
|
+
await updateGitHubActionPolicyGroup(db, {
|
|
286
|
+
accountId: access.grant.accountId,
|
|
287
|
+
workspaceId: access.grant.workspaceId,
|
|
288
|
+
subjectId: access.grant.subjectId,
|
|
289
|
+
actor: personalGitHubActionPolicyActor({
|
|
290
|
+
connectionId: connection.id,
|
|
291
|
+
githubLogin: PersonalGitHubConnectionMetadata.parse(connection.metadata).githubLogin,
|
|
292
|
+
}),
|
|
293
|
+
group: payload.group,
|
|
294
|
+
decision: payload.decision,
|
|
295
|
+
}),
|
|
296
|
+
),
|
|
297
|
+
);
|
|
298
|
+
});
|
|
299
|
+
|
|
102
300
|
// Start with user authorization, not GitHub's install/configure selector.
|
|
103
301
|
// This lets an owner link an already-installed App without relying on
|
|
104
302
|
// GitHub's Configure page to preserve or return OpenGeni state.
|
|
@@ -177,6 +375,65 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
177
375
|
}
|
|
178
376
|
});
|
|
179
377
|
|
|
378
|
+
app.post("/v1/workspaces/:workspaceId/github/public-repositories/verify", async (c) => {
|
|
379
|
+
const workspaceId = c.req.param("workspaceId");
|
|
380
|
+
const grant = await requireAccessGrant(c, deps, workspaceId);
|
|
381
|
+
requirePublicGitHubRepositoryVerificationPermission(grant);
|
|
382
|
+
const request = await parseRequestJson(c, VerifyPublicGitHubRepositoryRefRequest);
|
|
383
|
+
let repository: ReturnType<typeof parseCanonicalGitHubRepositoryUrl>;
|
|
384
|
+
try {
|
|
385
|
+
repository = parseCanonicalGitHubRepositoryUrl(request.url);
|
|
386
|
+
} catch (error) {
|
|
387
|
+
throw new HTTPException(422, {
|
|
388
|
+
message: error instanceof Error ? error.message : "GitHub repository URL is invalid.",
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
return c.json(
|
|
393
|
+
await verifyPublicGitHubRepositoryRef(
|
|
394
|
+
{ repository, ref: request.ref },
|
|
395
|
+
deps.githubAnonymousFetch ?? fetch,
|
|
396
|
+
),
|
|
397
|
+
);
|
|
398
|
+
} catch (error) {
|
|
399
|
+
if (error instanceof GitHubPublicRepositoryVerificationError) {
|
|
400
|
+
throw new HTTPException(error.code === "provider_unavailable" ? 503 : 422, {
|
|
401
|
+
message: error.message,
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
throw new HTTPException(503, {
|
|
405
|
+
message: "GitHub public repository verification is temporarily unavailable.",
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
app.get(
|
|
411
|
+
"/v1/workspaces/:workspaceId/github/installations/:installationId/repositories/:repositoryId/branches",
|
|
412
|
+
async (c) => {
|
|
413
|
+
const workspaceId = c.req.param("workspaceId");
|
|
414
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "github:use");
|
|
415
|
+
const installationId = parsePositiveInteger(c.req.param("installationId"));
|
|
416
|
+
const repositoryId = parsePositiveInteger(c.req.param("repositoryId"));
|
|
417
|
+
if (installationId === null || repositoryId === null) {
|
|
418
|
+
throw new HTTPException(400, { message: "invalid GitHub repository identity" });
|
|
419
|
+
}
|
|
420
|
+
const query = ListGitHubRepositoryBranchesQuery.parse(c.req.query());
|
|
421
|
+
try {
|
|
422
|
+
return c.json(
|
|
423
|
+
await listWorkspaceGitHubRepositoryBranches(deps, {
|
|
424
|
+
accountId: grant.accountId,
|
|
425
|
+
workspaceId: grant.workspaceId,
|
|
426
|
+
installationId,
|
|
427
|
+
repositoryId,
|
|
428
|
+
query,
|
|
429
|
+
}),
|
|
430
|
+
);
|
|
431
|
+
} catch (error) {
|
|
432
|
+
throw githubRepositoryBranchesRouteError(error);
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
);
|
|
436
|
+
|
|
180
437
|
app.delete("/v1/workspaces/:workspaceId/github/installations/:installationId", async (c) => {
|
|
181
438
|
const workspaceId = c.req.param("workspaceId");
|
|
182
439
|
const grant = await requireAccessGrant(c, deps, workspaceId, "github:manage");
|
|
@@ -184,14 +441,11 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
184
441
|
if (installationId === null) {
|
|
185
442
|
throw new HTTPException(400, { message: "invalid GitHub installation id" });
|
|
186
443
|
}
|
|
187
|
-
|
|
444
|
+
await deleteGitHubInstallationBinding(db, {
|
|
188
445
|
accountId: grant.accountId,
|
|
189
446
|
workspaceId: grant.workspaceId,
|
|
190
447
|
installationId,
|
|
191
448
|
});
|
|
192
|
-
if (!deleted) {
|
|
193
|
-
throw new HTTPException(404, { message: "GitHub installation binding not found" });
|
|
194
|
-
}
|
|
195
449
|
return c.body(null, 204);
|
|
196
450
|
});
|
|
197
451
|
|
|
@@ -245,8 +499,10 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
245
499
|
app.post("/v1/workspaces/:workspaceId/github/app-manifest", async (c) => {
|
|
246
500
|
assertOperatorGitHubAppSetup(settings);
|
|
247
501
|
const workspaceId = c.req.param("workspaceId");
|
|
248
|
-
const
|
|
249
|
-
|
|
502
|
+
const access = await requireAccessGrantAuthorization(c, deps, workspaceId, "github:manage");
|
|
503
|
+
requireLegacyOAuthActor(access);
|
|
504
|
+
const grant = access.grant;
|
|
505
|
+
const payload = await parseRequestJson(c, GitHubAppManifestCreate);
|
|
250
506
|
const baseUrl = (settings.githubAppManifestBaseUrl ?? new URL(c.req.url).origin).replace(
|
|
251
507
|
/\/+$/,
|
|
252
508
|
"",
|
|
@@ -296,6 +552,14 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
296
552
|
});
|
|
297
553
|
|
|
298
554
|
const handleGitHubInstallCallback = async (c: Context) => {
|
|
555
|
+
if (isGitHubAppConnectState(deps, c.req.query("state")))
|
|
556
|
+
return completeGitHubAppConnect(deps, {
|
|
557
|
+
state: c.req.query("state"),
|
|
558
|
+
installationId: c.req.query("installation_id"),
|
|
559
|
+
setupAction: c.req.query("setup_action"),
|
|
560
|
+
error: c.req.query("error"),
|
|
561
|
+
requestUrl: c.req.url,
|
|
562
|
+
});
|
|
299
563
|
const state =
|
|
300
564
|
c.req.query("state") ??
|
|
301
565
|
allCookieValues(c, githubStateCookie).find((candidate) => {
|
|
@@ -373,8 +637,18 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
373
637
|
app.get("/v1/github/install/callback", handleGitHubInstallCallback);
|
|
374
638
|
|
|
375
639
|
app.get("/v1/github/oauth/callback", async (c) => {
|
|
640
|
+
if (isGitHubAppConnectState(deps, c.req.query("state")))
|
|
641
|
+
return completeGitHubAppConnect(deps, {
|
|
642
|
+
state: c.req.query("state"),
|
|
643
|
+
code: c.req.query("code"),
|
|
644
|
+
error: c.req.query("error"),
|
|
645
|
+
requestUrl: c.req.url,
|
|
646
|
+
});
|
|
376
647
|
const code = c.req.query("code");
|
|
377
648
|
const state = c.req.query("state");
|
|
649
|
+
if (c.req.query("error") === "access_denied") {
|
|
650
|
+
throw new GitHubBrowserFailure(400, "GitHub authorization was cancelled", "cancelled");
|
|
651
|
+
}
|
|
378
652
|
if (!code) {
|
|
379
653
|
throw new HTTPException(400, { message: "missing GitHub OAuth code" });
|
|
380
654
|
}
|
|
@@ -398,6 +672,7 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
398
672
|
message: "GitHub OAuth state does not match this workspace",
|
|
399
673
|
});
|
|
400
674
|
}
|
|
675
|
+
await withOrganizationIntegrationAcquisition(db, grant, ["github-app"], async () => {});
|
|
401
676
|
let candidates: GitHubInstallationBindingCandidate[] | null;
|
|
402
677
|
try {
|
|
403
678
|
candidates = deps.githubAppApi?.discoverInstallationBindingCandidates
|
|
@@ -428,14 +703,6 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
428
703
|
if (candidates.length === 0) {
|
|
429
704
|
return redirectToGitHubInstallation(c, deps, selectionState);
|
|
430
705
|
}
|
|
431
|
-
if (candidates.length === 1) {
|
|
432
|
-
return redirectToExactGitHubAuthorization(
|
|
433
|
-
c,
|
|
434
|
-
deps,
|
|
435
|
-
selectionState,
|
|
436
|
-
candidates[0]!.installation.installationId,
|
|
437
|
-
);
|
|
438
|
-
}
|
|
439
706
|
setGitHubStateCookie(c, deps, selectionState);
|
|
440
707
|
return c.html(
|
|
441
708
|
githubInstallationChooserHtml(
|
|
@@ -460,6 +727,7 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
460
727
|
message: "GitHub OAuth state does not match this workspace",
|
|
461
728
|
});
|
|
462
729
|
}
|
|
730
|
+
await withOrganizationIntegrationAcquisition(db, grant, ["github-app"], async () => {});
|
|
463
731
|
let proof;
|
|
464
732
|
try {
|
|
465
733
|
proof = deps.githubAppApi?.authorizeInstallationBinding
|
|
@@ -489,22 +757,30 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
489
757
|
const expiresAt = new Date((statePayload.iat + githubBindingStateMaxAgeSeconds) * 1_000);
|
|
490
758
|
let bound;
|
|
491
759
|
try {
|
|
492
|
-
bound = await
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
760
|
+
bound = await withOrganizationIntegrationAcquisition(
|
|
761
|
+
db,
|
|
762
|
+
grant,
|
|
763
|
+
["github-app"],
|
|
764
|
+
async (tx) => {
|
|
765
|
+
await grant.authorizeCommit(tx);
|
|
766
|
+
return bindAuthorizedGitHubInstallationRepositories(tx, {
|
|
767
|
+
accountId: grant.accountId,
|
|
768
|
+
workspaceId: grant.workspaceId,
|
|
769
|
+
installationId,
|
|
770
|
+
githubAccountId: proof.installation.accountId,
|
|
771
|
+
accountLogin: proof.installation.accountLogin,
|
|
772
|
+
accountType: proof.installation.accountType,
|
|
773
|
+
linkedBySubjectId: grant.subjectId,
|
|
774
|
+
githubActorId: proof.actorId,
|
|
775
|
+
githubActorLogin: proof.actorLogin,
|
|
776
|
+
authorityKind: proof.authorityKind,
|
|
777
|
+
authorityCheckedAt,
|
|
778
|
+
authorityExpiresAt: expiresAt,
|
|
779
|
+
authorityNonce: statePayload.nonce,
|
|
780
|
+
repositoryIds,
|
|
781
|
+
});
|
|
782
|
+
},
|
|
783
|
+
);
|
|
508
784
|
} catch (error) {
|
|
509
785
|
if (error instanceof GitHubInstallationAuthorityCommitError) {
|
|
510
786
|
throw new HTTPException(409, { message: error.message });
|
|
@@ -646,6 +922,25 @@ function githubInstallationSettingsUrl(installation: {
|
|
|
646
922
|
return new URL(`https://github.com/settings/installations/${installation.installationId}`);
|
|
647
923
|
}
|
|
648
924
|
|
|
925
|
+
function canonicalPersonalGitHubPolicyConnection<
|
|
926
|
+
T extends { id: string; status: string; updatedAt: string },
|
|
927
|
+
>(connections: T[]): T | null {
|
|
928
|
+
const statusRank: Record<string, number> = {
|
|
929
|
+
active: 0,
|
|
930
|
+
needs_reauth: 1,
|
|
931
|
+
error: 2,
|
|
932
|
+
revoked: 3,
|
|
933
|
+
};
|
|
934
|
+
return (
|
|
935
|
+
[...connections].sort((left, right) => {
|
|
936
|
+
const rank = (statusRank[left.status] ?? 4) - (statusRank[right.status] ?? 4);
|
|
937
|
+
if (rank !== 0) return rank;
|
|
938
|
+
const updated = right.updatedAt.localeCompare(left.updatedAt);
|
|
939
|
+
return updated !== 0 ? updated : right.id.localeCompare(left.id);
|
|
940
|
+
})[0] ?? null
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
|
|
649
944
|
function redirectToExactGitHubAuthorization(
|
|
650
945
|
c: Context,
|
|
651
946
|
deps: ApiRouteDeps,
|
|
@@ -702,16 +997,32 @@ async function requireGitHubManageGrant(
|
|
|
702
997
|
deps: ApiRouteDeps,
|
|
703
998
|
workspaceId: string,
|
|
704
999
|
expectedState: GitHubSignedStatePayload,
|
|
705
|
-
): Promise<
|
|
1000
|
+
): Promise<IntegrationCommitGrant> {
|
|
706
1001
|
try {
|
|
707
|
-
|
|
1002
|
+
const access = await requireAccessGrantAuthorization(c, deps, workspaceId, "github:manage");
|
|
1003
|
+
requireLegacyOAuthActor(access);
|
|
1004
|
+
return integrationCommitGrant(access, ["github:manage"], {
|
|
1005
|
+
settings: deps.settings,
|
|
1006
|
+
authorizationHeader: c.req.header("authorization"),
|
|
1007
|
+
});
|
|
708
1008
|
} catch (error) {
|
|
709
1009
|
if (!(error instanceof HTTPException) || error.status !== 401) {
|
|
710
1010
|
throw error;
|
|
711
1011
|
}
|
|
712
1012
|
const grant = githubBrowserGrantFromState(deps.settings, expectedState, workspaceId);
|
|
713
1013
|
if (grant) {
|
|
714
|
-
return
|
|
1014
|
+
return {
|
|
1015
|
+
...grant,
|
|
1016
|
+
authorizeCommit: async () => {
|
|
1017
|
+
const current = githubBrowserGrantFromState(deps.settings, expectedState, workspaceId);
|
|
1018
|
+
if (
|
|
1019
|
+
!current ||
|
|
1020
|
+
current.accountId !== grant.accountId ||
|
|
1021
|
+
current.subjectId !== grant.subjectId
|
|
1022
|
+
)
|
|
1023
|
+
throw new HTTPException(403, { message: "GitHub browser handoff expired or changed" });
|
|
1024
|
+
},
|
|
1025
|
+
};
|
|
715
1026
|
}
|
|
716
1027
|
throw error;
|
|
717
1028
|
}
|
|
@@ -739,7 +1050,7 @@ function githubAuthorityHttpError(error: unknown): HTTPException {
|
|
|
739
1050
|
}
|
|
740
1051
|
if (error instanceof GitHubInstallationAuthorityError) {
|
|
741
1052
|
if (error.reason === "authority_denied") {
|
|
742
|
-
return new
|
|
1053
|
+
return new GitHubBrowserFailure(403, error.message, "not_owner");
|
|
743
1054
|
}
|
|
744
1055
|
if (error.reason === "installation_missing") {
|
|
745
1056
|
return new HTTPException(404, { message: error.message });
|
|
@@ -757,6 +1068,50 @@ function githubAuthorityHttpError(error: unknown): HTTPException {
|
|
|
757
1068
|
return new HTTPException(502, { message: "GitHub authority verification failed" });
|
|
758
1069
|
}
|
|
759
1070
|
|
|
1071
|
+
function githubRepositoryBranchesRouteError(error: unknown): Error {
|
|
1072
|
+
if (error instanceof HTTPException) {
|
|
1073
|
+
return error;
|
|
1074
|
+
}
|
|
1075
|
+
if (error instanceof GitHubRepositoryBranchAuthorityError) {
|
|
1076
|
+
return error.code === "not_authorized"
|
|
1077
|
+
? new HTTPException(404, {
|
|
1078
|
+
message: "GitHub repository is not authorized for this workspace",
|
|
1079
|
+
})
|
|
1080
|
+
: new HTTPException(409, {
|
|
1081
|
+
message: "GitHub repository authorization changed; refresh and try again",
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
if (error instanceof GitHubAppConfigurationError) {
|
|
1085
|
+
return new HTTPException(409, { message: "GitHub App is not configured" });
|
|
1086
|
+
}
|
|
1087
|
+
if (error instanceof GitHubAppApiError) {
|
|
1088
|
+
if (error.status === 429) {
|
|
1089
|
+
return new HTTPException(503, { message: "GitHub is temporarily rate limited" });
|
|
1090
|
+
}
|
|
1091
|
+
if (error.status === 401 || error.status === 403 || error.status === 404) {
|
|
1092
|
+
return new HTTPException(409, {
|
|
1093
|
+
message: "GitHub repository access changed; refresh and try again",
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
return new HTTPException(502, { message: "GitHub branch discovery is unavailable" });
|
|
1097
|
+
}
|
|
1098
|
+
return new HTTPException(502, { message: "GitHub branch discovery is unavailable" });
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
export function requirePublicGitHubRepositoryVerificationPermission(
|
|
1102
|
+
grant: Pick<AccessGrant, "permissions">,
|
|
1103
|
+
): void {
|
|
1104
|
+
if (
|
|
1105
|
+
hasPermission(grant.permissions, "sessions:create") ||
|
|
1106
|
+
hasPermission(grant.permissions, "sessions:control")
|
|
1107
|
+
) {
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
throw new HTTPException(403, {
|
|
1111
|
+
message: "missing permission: sessions:create or sessions:control",
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
|
|
760
1115
|
function isSecureRequest(c: Context, deps: ApiRouteDeps): boolean {
|
|
761
1116
|
return (
|
|
762
1117
|
deps.settings.publicBaseUrl?.startsWith("https://") ||
|
|
@@ -765,62 +1120,6 @@ function isSecureRequest(c: Context, deps: ApiRouteDeps): boolean {
|
|
|
765
1120
|
);
|
|
766
1121
|
}
|
|
767
1122
|
|
|
768
|
-
function githubSuccessHtml(envLines: string[]): string {
|
|
769
|
-
const envText = envLines.join("\n");
|
|
770
|
-
const escaped = escapeHtml(envText);
|
|
771
|
-
return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>GitHub App Created</title><style>body{font-family:system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#0b0b0d;color:#f4f4f5}main{width:min(760px,calc(100vw - 32px));border:1px solid #27272a;border-radius:8px;padding:28px;background:#111114}h1{margin:0 0 10px;font-size:24px;line-height:1.2}p{margin:0 0 18px;color:#d4d4d8}.env-header{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:22px 0 8px}.env-header h2{margin:0;font-size:13px;line-height:1.2;text-transform:uppercase;letter-spacing:.08em;color:#a1a1aa}pre{white-space:pre-wrap;word-break:break-word;max-height:380px;overflow:auto;background:#09090b;border:1px solid #27272a;border-radius:8px;padding:16px;font-size:13px;line-height:1.5}button{display:inline-flex;align-items:center;justify-content:center;min-height:36px;border-radius:6px;border:1px solid #3f3f46;padding:0 12px;background:#f4f4f5;color:#09090b;font:600 14px system-ui,sans-serif;cursor:pointer}button:disabled{cursor:not-allowed;opacity:.7}</style></head><body><main><h1>GitHub App created</h1><p>Add these values to .env, then restart API and worker.</p><div class="env-header"><h2>Environment variables</h2><button id="copy-env" type="button">Copy env</button></div><pre id="env-lines">${escaped}</pre><script>(()=>{const button=document.getElementById("copy-env");const env=document.getElementById("env-lines");async function copyText(text){if(navigator.clipboard&&window.isSecureContext){await navigator.clipboard.writeText(text);return;}const area=document.createElement("textarea");area.value=text;area.setAttribute("readonly","");area.style.position="fixed";area.style.inset="-9999px";document.body.append(area);area.select();document.execCommand("copy");area.remove();}button?.addEventListener("click",async()=>{try{await copyText(env?.textContent||"");button.textContent="Copied";setTimeout(()=>button.textContent="Copy env",1600);}catch{button.textContent="Copy failed";setTimeout(()=>button.textContent="Copy env",2200);}});})();</script></main></body></html>`;
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
function githubSetupSuccessHtml(account: string, returnUrl: string): string {
|
|
775
|
-
return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>GitHub App Connected</title><style>body{font-family:system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#0b0b0d;color:#f4f4f5}main{width:min(640px,calc(100vw - 32px));border:1px solid #27272a;border-radius:8px;padding:28px;background:#111114}h1{margin:0 0 10px;font-size:24px;line-height:1.2}p{margin:0 0 18px;color:#d4d4d8}.button{display:inline-flex;align-items:center;justify-content:center;min-height:36px;border-radius:6px;border:1px solid #3f3f46;padding:0 12px;background:#f4f4f5;color:#09090b;font:600 14px system-ui,sans-serif;text-decoration:none}</style></head><body><main><h1>GitHub App connected</h1><p>${escapeHtml(account)} is now available to this OpenGeni workspace through an explicit repository allowlist.</p><a class="button" href="${escapeHtml(returnUrl)}">Back to OpenGeni</a></main></body></html>`;
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
function githubInstallationChooserHtml(
|
|
779
|
-
candidates: GitHubInstallationBindingCandidate[],
|
|
780
|
-
state: string,
|
|
781
|
-
workspaceId: string,
|
|
782
|
-
baseUrl: string,
|
|
783
|
-
): string {
|
|
784
|
-
const action = `${baseUrl}/v1/workspaces/${encodeURIComponent(workspaceId)}/github/installations/select`;
|
|
785
|
-
const options = candidates
|
|
786
|
-
.map(({ installation, authorityKind }) => {
|
|
787
|
-
const account = escapeHtml(
|
|
788
|
-
installation.accountLogin ?? `installation ${installation.installationId}`,
|
|
789
|
-
);
|
|
790
|
-
const label = authorityKind === "personal_owner" ? "Personal account" : "Organization owner";
|
|
791
|
-
return `<label class="option"><input type="radio" name="installation_id" value="${installation.installationId}" required><span><strong>${account}</strong><small>${label}</small></span></label>`;
|
|
792
|
-
})
|
|
793
|
-
.join("");
|
|
794
|
-
return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Choose GitHub installation</title><style>body{font-family:system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#0b0b0d;color:#f4f4f5}main{width:min(640px,calc(100vw - 32px));border:1px solid #27272a;border-radius:12px;padding:28px;background:#111114}h1{margin:0 0 10px;font-size:24px}p{margin:0 0 18px;color:#d4d4d8}.options{display:grid;gap:8px;margin-bottom:18px}.option{display:flex;align-items:center;gap:12px;border:1px solid #3f3f46;border-radius:8px;padding:12px;cursor:pointer}.option span{display:grid;gap:2px}.option small{color:#a1a1aa}button{min-height:38px;border-radius:7px;border:1px solid #3f3f46;padding:0 14px;background:#f4f4f5;color:#09090b;font:600 14px system-ui,sans-serif;cursor:pointer}.secondary{margin-left:8px;background:transparent;color:#f4f4f5}</style></head><body><main><h1>Choose a GitHub account</h1><p>Only installations where GitHub proved you are the personal owner or an active organization owner are shown.</p><form method="get" action="${escapeHtml(action)}"><input type="hidden" name="state" value="${escapeHtml(state)}"><div class="options">${options}</div><button type="submit">Connect selected</button><button class="secondary" type="submit" name="installation_id" value="new" formnovalidate>Install on another account</button></form></main></body></html>`;
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
function githubSetupPendingHtml(): string {
|
|
798
|
-
return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>GitHub App Requested</title><style>body{font-family:system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#0b0b0d;color:#f4f4f5}main{width:min(640px,calc(100vw - 32px));border:1px solid #27272a;border-radius:8px;padding:28px;background:#111114}h1{margin:0 0 10px;font-size:24px;line-height:1.2}p{margin:0;color:#d4d4d8}</style></head><body><main><h1>GitHub App request sent</h1><p>A GitHub organization owner must approve the installation. OpenGeni has not created a workspace binding.</p></main></body></html>`;
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
function isConsistentGitHubBindingCandidates(
|
|
802
|
-
candidates: GitHubInstallationBindingCandidate[],
|
|
803
|
-
): boolean {
|
|
804
|
-
const ids = new Set<number>();
|
|
805
|
-
return candidates.every(({ installation, authorityKind }) => {
|
|
806
|
-
if (
|
|
807
|
-
!Number.isSafeInteger(installation.installationId) ||
|
|
808
|
-
installation.installationId <= 0 ||
|
|
809
|
-
!Number.isSafeInteger(installation.accountId) ||
|
|
810
|
-
installation.accountId <= 0 ||
|
|
811
|
-
!installation.accountLogin?.trim() ||
|
|
812
|
-
installation.suspended ||
|
|
813
|
-
ids.has(installation.installationId)
|
|
814
|
-
) {
|
|
815
|
-
return false;
|
|
816
|
-
}
|
|
817
|
-
ids.add(installation.installationId);
|
|
818
|
-
return authorityKind === "personal_owner"
|
|
819
|
-
? installation.accountType === "User"
|
|
820
|
-
: installation.accountType === "Organization";
|
|
821
|
-
});
|
|
822
|
-
}
|
|
823
|
-
|
|
824
1123
|
function parsePositiveInteger(value: string | undefined | null): number | null {
|
|
825
1124
|
if (!value || !/^\d+$/.test(value)) {
|
|
826
1125
|
return null;
|
|
@@ -834,55 +1133,6 @@ function isFreshGitHubBindingState(payload: GitHubSignedStatePayload): boolean {
|
|
|
834
1133
|
return age >= 0 && age < githubBindingStateMaxAgeSeconds;
|
|
835
1134
|
}
|
|
836
1135
|
|
|
837
|
-
function isConsistentGitHubBindingProof(
|
|
838
|
-
proof: GitHubInstallationBindingProof,
|
|
839
|
-
installationId: number,
|
|
840
|
-
): boolean {
|
|
841
|
-
const installation = proof.installation;
|
|
842
|
-
if (
|
|
843
|
-
installation.installationId !== installationId ||
|
|
844
|
-
!Number.isSafeInteger(installation.accountId) ||
|
|
845
|
-
installation.accountId <= 0 ||
|
|
846
|
-
!installation.accountLogin?.trim() ||
|
|
847
|
-
installation.suspended ||
|
|
848
|
-
!Number.isSafeInteger(proof.actorId) ||
|
|
849
|
-
proof.actorId <= 0 ||
|
|
850
|
-
!proof.actorLogin.trim() ||
|
|
851
|
-
proof.repositories.length === 0
|
|
852
|
-
) {
|
|
853
|
-
return false;
|
|
854
|
-
}
|
|
855
|
-
if (
|
|
856
|
-
proof.authorityKind === "personal_owner"
|
|
857
|
-
? installation.accountType !== "User" || proof.actorId !== installation.accountId
|
|
858
|
-
: installation.accountType !== "Organization"
|
|
859
|
-
) {
|
|
860
|
-
return false;
|
|
861
|
-
}
|
|
862
|
-
return proof.repositories.every(
|
|
863
|
-
(repository) =>
|
|
864
|
-
Number.isSafeInteger(repository.id) &&
|
|
865
|
-
repository.id > 0 &&
|
|
866
|
-
repository.installationId === installationId &&
|
|
867
|
-
repository.accountLogin === installation.accountLogin &&
|
|
868
|
-
repository.accountType === installation.accountType,
|
|
869
|
-
);
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
function escapeHtml(value: string): string {
|
|
873
|
-
return value.replace(
|
|
874
|
-
/[&<>"']/g,
|
|
875
|
-
(char) =>
|
|
876
|
-
({
|
|
877
|
-
"&": "&",
|
|
878
|
-
"<": "<",
|
|
879
|
-
">": ">",
|
|
880
|
-
'"': """,
|
|
881
|
-
"'": "'",
|
|
882
|
-
})[char] ?? char,
|
|
883
|
-
);
|
|
884
|
-
}
|
|
885
|
-
|
|
886
1136
|
function openGeniReturnUrl(
|
|
887
1137
|
settings: ApiRouteDeps["settings"],
|
|
888
1138
|
c: Context,
|
|
@@ -904,3 +1154,55 @@ function openGeniReturnUrl(
|
|
|
904
1154
|
function openGeniBaseUrl(settings: ApiRouteDeps["settings"], c: Context): string {
|
|
905
1155
|
return githubBrowserBaseUrl(settings, new URL(c.req.url).origin);
|
|
906
1156
|
}
|
|
1157
|
+
|
|
1158
|
+
/** The status the app error handler gives this failure; the page keeps it. */
|
|
1159
|
+
function githubBrowserFailureStatus(error: unknown, handledStatus: number | undefined): number {
|
|
1160
|
+
if (error instanceof HTTPException) return error.status;
|
|
1161
|
+
if (error instanceof OrganizationIntegrationDeniedError) return 403;
|
|
1162
|
+
// An unexpected fault keeps whatever the error handler answered (500 by default).
|
|
1163
|
+
return handledStatus ?? 500;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
function githubBrowserFailureKind(error: unknown): GitHubConnectFailure {
|
|
1167
|
+
if (error instanceof OrganizationIntegrationDeniedError) return "policy_denied";
|
|
1168
|
+
if (!(error instanceof HTTPException)) return "failed";
|
|
1169
|
+
if (error instanceof GitHubBrowserFailure) return error.failure;
|
|
1170
|
+
if (error.status === 401) return "signed_out";
|
|
1171
|
+
if (error.status === 403) return "forbidden";
|
|
1172
|
+
// Every signed-state rejection names its state; they all mean "start again".
|
|
1173
|
+
if (error.status === 400 && /\bstate\b/iu.test(error.message)) return "expired";
|
|
1174
|
+
return "failed";
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
/**
|
|
1178
|
+
* Human-authored HTTP messages only; configuration errors carry a JSON
|
|
1179
|
+
* envelope. Anything else (an unexpected fault) shows no detail.
|
|
1180
|
+
*/
|
|
1181
|
+
function githubBrowserFailureDetail(error: unknown): string | null {
|
|
1182
|
+
if (!(error instanceof HTTPException)) return null;
|
|
1183
|
+
if (error instanceof GitHubBrowserFailure && error.failure === "cancelled") return null;
|
|
1184
|
+
try {
|
|
1185
|
+
const parsed = JSON.parse(error.message) as { message?: unknown };
|
|
1186
|
+
return typeof parsed?.message === "string" ? parsed.message : null;
|
|
1187
|
+
} catch {
|
|
1188
|
+
return error.message || null;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
/**
|
|
1193
|
+
* The failure page links back to the workspace integrations page when the
|
|
1194
|
+
* request names a workspace (its path, or a correctly signed state even if it
|
|
1195
|
+
* aged out); otherwise to the OpenGeni home. This is only a link target, so an
|
|
1196
|
+
* expired state is acceptable evidence of where the user came from.
|
|
1197
|
+
*/
|
|
1198
|
+
function githubFailureReturnUrl(deps: ApiRouteDeps, c: Context): string {
|
|
1199
|
+
const baseUrl = (
|
|
1200
|
+
deps.settings.webBaseUrl ??
|
|
1201
|
+
(openGeniBaseUrl(deps.settings, c) || new URL(c.req.url).origin)
|
|
1202
|
+
).replace(/\/+$/u, "");
|
|
1203
|
+
const rawState = c.req.query("state");
|
|
1204
|
+
const candidate =
|
|
1205
|
+
c.req.param("workspaceId") ??
|
|
1206
|
+
(rawState ? inspectSignedState(rawState, deps.githubStateSecret)?.workspaceId : undefined);
|
|
1207
|
+
return `${baseUrl}${workspaceIntegrationsPathForUntrusted(candidate) ?? "/"}`;
|
|
1208
|
+
}
|