@opengeni/api-router 0.30.1 → 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.
Files changed (436) hide show
  1. package/dist/access-grant-rls.d.ts +3 -0
  2. package/dist/admission-trace.d.ts +8 -0
  3. package/dist/api-websocket.d.ts +18 -0
  4. package/dist/app.d.ts +50 -2
  5. package/dist/app.js +12 -1
  6. package/dist/artifact-catalog-cursor.d.ts +27 -0
  7. package/dist/auth/managed-auth-attempt-context.d.ts +10 -0
  8. package/dist/auth/managed-auth-rate-limits.d.ts +167 -0
  9. package/dist/auth/managed-auth-session-adapter.d.ts +6 -0
  10. package/dist/auth/managed-auth.d.ts +102 -3
  11. package/dist/auth/managed-email.d.ts +29 -0
  12. package/dist/auth/managed-sign-in-notifications.d.ts +4 -0
  13. package/dist/auth/new-signups-gate.d.ts +23 -0
  14. package/dist/auth/organization-user-setup.d.ts +80 -0
  15. package/dist/auth/signup-funnel-metrics.d.ts +44 -0
  16. package/dist/browser-controller-authority.d.ts +1 -61
  17. package/dist/browser-ephemeral.d.ts +5 -0
  18. package/dist/browser-state-authority.d.ts +1 -27
  19. package/dist/chunk-6TZ5LG3O.js +123948 -0
  20. package/dist/chunk-6TZ5LG3O.js.map +1 -0
  21. package/dist/chunk-OGQDXVXY.js +18 -0
  22. package/dist/chunk-OGQDXVXY.js.map +1 -0
  23. package/dist/claude-subscription-account-conflicts.d.ts +1 -0
  24. package/dist/claude-subscription-account-usage.d.ts +39 -0
  25. package/dist/claude-subscription-oauth.d.ts +25 -0
  26. package/dist/claude-subscription-profile.d.ts +9 -0
  27. package/dist/claude-subscription-usage.d.ts +44 -0
  28. package/dist/claude-workspace-connection.d.ts +15 -0
  29. package/dist/codemode.d.ts +32 -1
  30. package/dist/company-brain-okf.d.ts +24 -0
  31. package/dist/connected-machine-computer-access.d.ts +11 -0
  32. package/dist/connection-ownership.d.ts +54 -0
  33. package/dist/controller-data-plane.d.ts +32 -0
  34. package/dist/editable-artifact-office-import.d.ts +7 -4
  35. package/dist/editable-artifact-source-session.d.ts +3 -0
  36. package/dist/editable-artifact-websocket.d.ts +10 -18
  37. package/dist/editable-artifact-workspace-files.d.ts +1 -1
  38. package/dist/fatal-process-boundary.d.ts +25 -0
  39. package/dist/github-access.d.ts +31 -2
  40. package/dist/http/acting-person.d.ts +24 -0
  41. package/dist/http/api-contract-compat.d.ts +19 -0
  42. package/dist/http/api-error.d.ts +27 -1
  43. package/dist/http/cors.d.ts +17 -0
  44. package/dist/http/deprecation.d.ts +42 -0
  45. package/dist/http/interaction-control-error.d.ts +13 -0
  46. package/dist/http/keyed-admission.d.ts +12 -0
  47. package/dist/http/local-browser-boundary.d.ts +72 -0
  48. package/dist/http/metrics-listener.d.ts +22 -0
  49. package/dist/http/path-identifier.d.ts +17 -0
  50. package/dist/http/registered-route-label.d.ts +16 -0
  51. package/dist/http/request-body.d.ts +27 -0
  52. package/dist/http/request-source.d.ts +52 -0
  53. package/dist/http/sse.d.ts +17 -6
  54. package/dist/http/unmatched-route.d.ts +18 -0
  55. package/dist/http/user-content.d.ts +36 -0
  56. package/dist/http/websocket-upgrade-dispatch.d.ts +18 -0
  57. package/dist/index.d.ts +10 -3
  58. package/dist/index.js +1129 -166
  59. package/dist/index.js.map +1 -1
  60. package/dist/integration-endpoint-test.d.ts +20 -0
  61. package/dist/integrations/api-integrations.d.ts +2 -2
  62. package/dist/integrations/atlassian.d.ts +29 -3
  63. package/dist/integrations/connect-authority.d.ts +1 -0
  64. package/dist/integrations/connect-callback-return.d.ts +6 -0
  65. package/dist/integrations/fiken.d.ts +16 -2
  66. package/dist/integrations/github-app-connect.d.ts +47 -0
  67. package/dist/integrations/github-installation-proof.d.ts +3 -0
  68. package/dist/integrations/github-lens-connect.d.ts +116 -0
  69. package/dist/integrations/github-skill-source.d.ts +1 -5
  70. package/dist/integrations/google-drive.d.ts +25 -0
  71. package/dist/integrations/integration-commit-authority.d.ts +13 -0
  72. package/dist/integrations/marketplace-plugin.d.ts +2691 -0
  73. package/dist/integrations/oauth-client.d.ts +178 -18
  74. package/dist/integrations/oauth-profiles.d.ts +222 -0
  75. package/dist/integrations/oauth-return-path.d.ts +1 -0
  76. package/dist/integrations/personal-github-repositories.d.ts +84 -0
  77. package/dist/integrations/personal-github.d.ts +42 -0
  78. package/dist/integrations/pr-review-provider.d.ts +22 -0
  79. package/dist/integrations/provider-oauth.d.ts +19 -1
  80. package/dist/integrations/slack-app-home.d.ts +24 -0
  81. package/dist/integrations/slack-bot.d.ts +367 -12
  82. package/dist/integrations/slack-file-upload-flow.d.ts +62 -0
  83. package/dist/integrations/slack-install.d.ts +20 -0
  84. package/dist/integrations/slack-interactions.d.ts +113 -10
  85. package/dist/integrations/slack-mrkdwn.d.ts +41 -0
  86. package/dist/integrations/slack-routing.d.ts +120 -0
  87. package/dist/integrations/slack-session-defaults.d.ts +58 -0
  88. package/dist/integrations/slack-task-file-upload.d.ts +36 -0
  89. package/dist/integrations/social-oauth.d.ts +7 -0
  90. package/dist/interaction-agent-access.d.ts +15 -0
  91. package/dist/interaction-frame-proxy.d.ts +68 -0
  92. package/dist/interaction-metrics.d.ts +2 -0
  93. package/dist/mcp/action-catalog-types.d.ts +9 -0
  94. package/dist/mcp/action-catalog.gen.d.ts +2 -0
  95. package/dist/mcp/capability-account-readiness.d.ts +7 -0
  96. package/dist/mcp/child-read-evidence.d.ts +9 -0
  97. package/dist/mcp/command-read.d.ts +24 -0
  98. package/dist/mcp/company-profile-agent-admin.d.ts +44 -0
  99. package/dist/mcp/contract-input.d.ts +11 -0
  100. package/dist/mcp/documents.d.ts +7 -5
  101. package/dist/mcp/files.d.ts +2 -2
  102. package/dist/mcp/first-party-tool-permissions.d.ts +650 -0
  103. package/dist/mcp/knowledge-entries.d.ts +5 -0
  104. package/dist/mcp/orchestration-failure-diagnostic.d.ts +43 -0
  105. package/dist/mcp/receipts.d.ts +9 -0
  106. package/dist/mcp/request-abort.d.ts +19 -0
  107. package/dist/mcp/scheduled-task-input.d.ts +1189 -0
  108. package/dist/mcp/scheduled-task-view.d.ts +22 -4
  109. package/dist/mcp/server.d.ts +255 -3
  110. package/dist/mcp/session-event-view.d.ts +94 -0
  111. package/dist/mcp/session-view.d.ts +66 -2
  112. package/dist/mcp/session-wait.d.ts +174 -0
  113. package/dist/mcp-oauth.d.ts +52 -0
  114. package/dist/model-catalog.d.ts +11 -31
  115. package/dist/observability.d.ts +3 -1
  116. package/dist/organization-mcp.d.ts +52 -0
  117. package/dist/organization-recovery-notifications.d.ts +41 -0
  118. package/dist/routes/analytics-consent.d.ts +26 -0
  119. package/dist/routes/api-integrations.d.ts +25 -0
  120. package/dist/routes/api-keys.d.ts +17 -0
  121. package/dist/routes/artifact-catalog.d.ts +6 -0
  122. package/dist/routes/automations.d.ts +19 -0
  123. package/dist/routes/billing.d.ts +49 -0
  124. package/dist/routes/browser-sessions.d.ts +71 -3
  125. package/dist/routes/claude-subscription-accounts.d.ts +3 -0
  126. package/dist/routes/claude-subscription-oauth.d.ts +3 -0
  127. package/dist/routes/client-errors.d.ts +146 -0
  128. package/dist/routes/codex.d.ts +76 -2
  129. package/dist/routes/company-brain.d.ts +3 -0
  130. package/dist/routes/company-profile.d.ts +2 -1
  131. package/dist/routes/computer-sessions.d.ts +14 -0
  132. package/dist/routes/connect.d.ts +5 -0
  133. package/dist/routes/editable-artifacts.d.ts +2 -0
  134. package/dist/routes/external-identity-links.d.ts +6 -0
  135. package/dist/routes/feedback.d.ts +3 -0
  136. package/dist/routes/files.d.ts +4 -0
  137. package/dist/routes/github-browser-pages.d.ts +12 -0
  138. package/dist/routes/github.d.ts +2 -0
  139. package/dist/routes/insights-response-cache.d.ts +10 -0
  140. package/dist/routes/insights-usage.d.ts +12 -0
  141. package/dist/routes/insights.d.ts +30 -1
  142. package/dist/routes/knowledge.d.ts +3 -0
  143. package/dist/routes/managed-auth-session-sets.d.ts +26 -0
  144. package/dist/routes/managed-onboarding.d.ts +29 -0
  145. package/dist/routes/managed-sign-in-methods.d.ts +6 -0
  146. package/dist/routes/model-connection-access.d.ts +3 -0
  147. package/dist/routes/organization-integration-policy.d.ts +3 -0
  148. package/dist/routes/organization-integrations.d.ts +6 -0
  149. package/dist/routes/organization-mcp-connections.d.ts +3 -0
  150. package/dist/routes/organization-model-providers.d.ts +3 -0
  151. package/dist/routes/organization-recovery.d.ts +19 -0
  152. package/dist/routes/organization-service-accounts.d.ts +3 -0
  153. package/dist/routes/organization-sessions.d.ts +26 -0
  154. package/dist/routes/personal-github-git-broker.d.ts +29 -0
  155. package/dist/routes/personal-github.d.ts +3 -0
  156. package/dist/routes/pr-review-github.d.ts +3 -0
  157. package/dist/routes/pr-review.d.ts +3 -0
  158. package/dist/routes/session-artifact-associations.d.ts +4 -0
  159. package/dist/routes/session-history-imports.d.ts +9 -0
  160. package/dist/routes/sessions.d.ts +44 -6
  161. package/dist/routes/skill-content.d.ts +4 -0
  162. package/dist/routes/skill-install-authority.d.ts +6 -0
  163. package/dist/routes/subscription-account-pools.d.ts +34 -0
  164. package/dist/routes/subscription-pool-access.d.ts +16 -0
  165. package/dist/routes/supergrok.d.ts +6 -1
  166. package/dist/routes/usage-allowances.d.ts +12 -0
  167. package/dist/routes/user-resource-authorities.d.ts +3 -0
  168. package/dist/routes/workspace-artifacts.d.ts +2 -1
  169. package/dist/routes/workspace-capture.d.ts +10 -2
  170. package/dist/routes/workspace-integrations.d.ts +35 -0
  171. package/dist/routes/workspace-learning.d.ts +3 -0
  172. package/dist/routes/workspace-model-providers.d.ts +4 -0
  173. package/dist/routes/workspaces.d.ts +29 -0
  174. package/dist/sandbox/auth-callout.d.ts +8 -0
  175. package/dist/sandbox/channel-a.d.ts +19 -1
  176. package/dist/sandbox/connection-authority.d.ts +3 -0
  177. package/dist/sandbox/enrollment.d.ts +9 -1
  178. package/dist/sandbox/machines.d.ts +5 -2
  179. package/dist/sandbox/metrics-ingestion.d.ts +28 -20
  180. package/dist/sandbox/rematerialize.d.ts +2 -0
  181. package/dist/sandbox/session-attach-variable-sets.d.ts +30 -0
  182. package/dist/sandbox/viewer.d.ts +26 -1
  183. package/dist/sandbox-file-artifacts.d.ts +50 -0
  184. package/dist/scheduled-task-deletion.d.ts +13 -0
  185. package/dist/session-create-observability.d.ts +5 -0
  186. package/dist/session-event-view-MPS2OZD3.js +292 -0
  187. package/dist/session-event-view-MPS2OZD3.js.map +1 -0
  188. package/dist/site-session-origin.d.ts +13 -0
  189. package/dist/site-uploads.d.ts +41 -0
  190. package/dist/slack-reaction-files.d.ts +1 -1
  191. package/dist/temporal-schedule-cleanup.d.ts +1 -0
  192. package/dist/temporal-schedule-sync.d.ts +26 -0
  193. package/dist/transcription/providers/xai-subscription.d.ts +1 -0
  194. package/dist/transcription/service.d.ts +2 -1
  195. package/dist/work-discovery-observability.d.ts +33 -0
  196. package/dist/workspace-artifact-content.d.ts +28 -0
  197. package/dist/workspace-artifact-provenance.d.ts +7 -0
  198. package/dist/workspace-delete-observability.d.ts +10 -0
  199. package/dist/workspace-deletion.d.ts +6 -0
  200. package/dist/workspace-state-projection.d.ts +3 -5
  201. package/dist/workspace-tool-gateway-observability.d.ts +17 -0
  202. package/dist/workspace-tool-gateway.d.ts +123 -0
  203. package/dist/workspace-webhook-dispatch.d.ts +26 -0
  204. package/package.json +23 -19
  205. package/src/access-grant-rls.ts +75 -0
  206. package/src/admission-trace.ts +14 -0
  207. package/src/api-websocket.ts +23 -0
  208. package/src/app.ts +1974 -256
  209. package/src/artifact-catalog-cursor.ts +60 -0
  210. package/src/auth/managed-auth-attempt-context.ts +70 -0
  211. package/src/auth/managed-auth-rate-limits.ts +252 -0
  212. package/src/auth/managed-auth-session-adapter.ts +222 -0
  213. package/src/auth/managed-auth.ts +536 -60
  214. package/src/auth/managed-email.ts +174 -0
  215. package/src/auth/managed-sign-in-notifications.ts +81 -0
  216. package/src/auth/new-signups-gate.ts +49 -0
  217. package/src/auth/organization-user-setup.ts +307 -0
  218. package/src/auth/signup-funnel-metrics.ts +164 -0
  219. package/src/browser-controller-authority.ts +1 -186
  220. package/src/browser-ephemeral.ts +39 -0
  221. package/src/browser-state-authority.ts +1 -236
  222. package/src/claude-subscription-account-conflicts.ts +12 -0
  223. package/src/claude-subscription-account-usage.ts +48 -0
  224. package/src/claude-subscription-oauth.ts +362 -0
  225. package/src/claude-subscription-profile.ts +73 -0
  226. package/src/claude-subscription-usage.ts +97 -0
  227. package/src/claude-workspace-connection.ts +82 -0
  228. package/src/codemode.ts +194 -30
  229. package/src/codex-realtime.ts +8 -2
  230. package/src/company-brain-okf.ts +340 -0
  231. package/src/connected-machine-computer-access.ts +33 -0
  232. package/src/connection-ownership.ts +199 -0
  233. package/src/controller-data-plane.ts +81 -0
  234. package/src/editable-artifact-native-kernel.ts +26 -15
  235. package/src/editable-artifact-office-import.ts +77 -9
  236. package/src/editable-artifact-production.ts +23 -7
  237. package/src/editable-artifact-source-session.ts +123 -0
  238. package/src/editable-artifact-websocket.ts +27 -21
  239. package/src/editable-artifact-workspace-files.ts +31 -6
  240. package/src/fatal-process-boundary.ts +246 -0
  241. package/src/gateway-realtime.ts +1 -1
  242. package/src/github-access.ts +147 -5
  243. package/src/http/acting-person.ts +71 -0
  244. package/src/http/api-contract-compat.ts +49 -0
  245. package/src/http/api-error.ts +115 -1
  246. package/src/http/auth.ts +48 -0
  247. package/src/http/cors.ts +67 -0
  248. package/src/http/deprecation.ts +118 -0
  249. package/src/http/interaction-control-error.ts +164 -0
  250. package/src/http/keyed-admission.ts +29 -0
  251. package/src/http/local-browser-boundary.ts +343 -0
  252. package/src/http/metrics-listener.ts +57 -0
  253. package/src/http/path-identifier.ts +54 -0
  254. package/src/http/registered-route-label.ts +34 -0
  255. package/src/http/request-body.ts +127 -0
  256. package/src/http/request-source.ts +178 -0
  257. package/src/http/sse.ts +486 -132
  258. package/src/http/unmatched-route.ts +60 -0
  259. package/src/http/user-content.ts +158 -0
  260. package/src/http/websocket-upgrade-dispatch.ts +29 -0
  261. package/src/index.ts +157 -52
  262. package/src/integration-endpoint-test.ts +291 -0
  263. package/src/integrations/api-integrations.ts +98 -11
  264. package/src/integrations/atlassian.ts +474 -136
  265. package/src/integrations/connect-authority.ts +2 -0
  266. package/src/integrations/connect-callback-return.ts +86 -0
  267. package/src/integrations/fiken.ts +354 -64
  268. package/src/integrations/github-app-connect.ts +435 -0
  269. package/src/integrations/github-installation-proof.ts +60 -0
  270. package/src/integrations/github-lens-connect.ts +76 -0
  271. package/src/integrations/github-skill-source.ts +2 -142
  272. package/src/integrations/google-drive.ts +820 -222
  273. package/src/integrations/integration-commit-authority.ts +69 -0
  274. package/src/integrations/marketplace-plugin.ts +48 -0
  275. package/src/integrations/oauth-client.ts +1356 -459
  276. package/src/integrations/oauth-profiles.ts +537 -0
  277. package/src/integrations/oauth-return-path.ts +18 -0
  278. package/src/integrations/personal-github-repositories.ts +674 -0
  279. package/src/integrations/personal-github.ts +825 -0
  280. package/src/integrations/pr-review-provider.ts +246 -0
  281. package/src/integrations/provider-oauth.ts +250 -22
  282. package/src/integrations/slack-app-home.ts +300 -0
  283. package/src/integrations/slack-bot.ts +963 -239
  284. package/src/integrations/slack-file-upload-flow.ts +147 -0
  285. package/src/integrations/slack-install.ts +83 -0
  286. package/src/integrations/slack-interactions.ts +3170 -474
  287. package/src/integrations/slack-mrkdwn.ts +193 -0
  288. package/src/integrations/slack-routing.ts +337 -0
  289. package/src/integrations/slack-session-defaults.ts +224 -0
  290. package/src/integrations/slack-task-file-upload.ts +323 -0
  291. package/src/integrations/social-oauth.ts +213 -15
  292. package/src/interaction-agent-access.ts +48 -0
  293. package/src/interaction-frame-proxy.ts +409 -0
  294. package/src/interaction-metrics.ts +30 -0
  295. package/src/mcp/action-catalog-types.ts +9 -0
  296. package/src/mcp/action-catalog.gen.ts +6696 -0
  297. package/src/mcp/capability-account-readiness.ts +17 -0
  298. package/src/mcp/child-read-evidence.ts +19 -0
  299. package/src/mcp/command-read.ts +109 -0
  300. package/src/mcp/company-profile-agent-admin.ts +198 -0
  301. package/src/mcp/contract-input.ts +141 -0
  302. package/src/mcp/documents.ts +65 -377
  303. package/src/mcp/editable-artifacts.ts +12 -6
  304. package/src/mcp/files.ts +80 -38
  305. package/src/mcp/first-party-tool-permissions.ts +305 -0
  306. package/src/mcp/knowledge-entries.ts +322 -0
  307. package/src/mcp/orchestration-failure-diagnostic.ts +43 -0
  308. package/src/mcp/receipts.ts +34 -0
  309. package/src/mcp/request-abort.ts +44 -0
  310. package/src/mcp/scheduled-task-input.ts +76 -0
  311. package/src/mcp/scheduled-task-view.ts +44 -3
  312. package/src/mcp/server.ts +3030 -1267
  313. package/src/mcp/session-event-view.ts +383 -0
  314. package/src/mcp/session-view.ts +84 -3
  315. package/src/mcp/session-wait.ts +665 -0
  316. package/src/mcp-oauth.ts +1123 -0
  317. package/src/model-catalog.ts +91 -333
  318. package/src/observability.ts +15 -1
  319. package/src/organization-mcp.ts +397 -0
  320. package/src/organization-recovery-notifications.ts +103 -0
  321. package/src/routes/analytics-consent.ts +123 -0
  322. package/src/routes/api-integrations.ts +364 -74
  323. package/src/routes/api-keys.ts +336 -10
  324. package/src/routes/artifact-catalog.ts +279 -0
  325. package/src/routes/automations.ts +768 -0
  326. package/src/routes/billing.ts +461 -46
  327. package/src/routes/browser-sessions.ts +839 -131
  328. package/src/routes/canonical-human-identities.ts +90 -14
  329. package/src/routes/capabilities.ts +107 -6
  330. package/src/routes/channels.ts +20 -3
  331. package/src/routes/claude-subscription-accounts.ts +334 -0
  332. package/src/routes/claude-subscription-oauth.ts +142 -0
  333. package/src/routes/client-errors.ts +362 -0
  334. package/src/routes/codex.ts +833 -116
  335. package/src/routes/company-brain.ts +322 -0
  336. package/src/routes/company-profile.ts +65 -1
  337. package/src/routes/computer-sessions.ts +557 -106
  338. package/src/routes/connect.ts +1938 -0
  339. package/src/routes/connections.ts +1040 -349
  340. package/src/routes/documents.ts +488 -287
  341. package/src/routes/editable-artifacts.ts +79 -9
  342. package/src/routes/enrollments.ts +135 -39
  343. package/src/routes/environments.ts +154 -58
  344. package/src/routes/external-identity-links.ts +201 -0
  345. package/src/routes/feedback.ts +113 -0
  346. package/src/routes/files.ts +337 -39
  347. package/src/routes/github-browser-pages.ts +162 -0
  348. package/src/routes/github.ts +446 -144
  349. package/src/routes/insights-response-cache.ts +83 -0
  350. package/src/routes/insights-usage.ts +345 -0
  351. package/src/routes/insights.ts +153 -19
  352. package/src/routes/install.ts +133 -38
  353. package/src/routes/integration-facets.ts +107 -33
  354. package/src/routes/interaction-resources.ts +30 -3
  355. package/src/routes/knowledge.ts +488 -0
  356. package/src/routes/machines.ts +313 -15
  357. package/src/routes/managed-auth-session-sets.ts +1218 -0
  358. package/src/routes/managed-onboarding.ts +337 -0
  359. package/src/routes/managed-sign-in-methods.ts +433 -0
  360. package/src/routes/memory-slack-publications.ts +3 -2
  361. package/src/routes/model-connection-access.ts +208 -0
  362. package/src/routes/organization-integration-policy.ts +116 -0
  363. package/src/routes/organization-integrations.ts +326 -0
  364. package/src/routes/organization-mcp-connections.ts +178 -0
  365. package/src/routes/organization-memberships.ts +1019 -27
  366. package/src/routes/organization-model-providers.ts +293 -0
  367. package/src/routes/organization-recovery.ts +325 -0
  368. package/src/routes/organization-service-accounts.ts +128 -0
  369. package/src/routes/organization-sessions.ts +350 -0
  370. package/src/routes/personal-github-git-broker.ts +785 -0
  371. package/src/routes/personal-github.ts +370 -0
  372. package/src/routes/plugins.ts +251 -22
  373. package/src/routes/pr-review-github.ts +881 -0
  374. package/src/routes/pr-review.ts +622 -0
  375. package/src/routes/preference-registry.ts +12 -93
  376. package/src/routes/rigs.ts +167 -50
  377. package/src/routes/scheduled-tasks.ts +346 -41
  378. package/src/routes/session-artifact-associations.ts +87 -0
  379. package/src/routes/session-history-imports.ts +280 -0
  380. package/src/routes/sessions.ts +2306 -264
  381. package/src/routes/skill-content.ts +297 -0
  382. package/src/routes/skill-install-authority.ts +64 -0
  383. package/src/routes/skills.ts +62 -9
  384. package/src/routes/social.ts +46 -18
  385. package/src/routes/subscription-account-pools.ts +436 -0
  386. package/src/routes/subscription-pool-access.ts +126 -0
  387. package/src/routes/supergrok.ts +219 -303
  388. package/src/routes/transcription-recordings.ts +38 -3
  389. package/src/routes/transcriptions.ts +4 -0
  390. package/src/routes/usage-allowances.ts +378 -0
  391. package/src/routes/user-resource-authorities.ts +138 -0
  392. package/src/routes/video-generation.ts +14 -10
  393. package/src/routes/workspace-artifacts.ts +275 -69
  394. package/src/routes/workspace-capture.ts +21 -0
  395. package/src/routes/workspace-integrations.ts +576 -0
  396. package/src/routes/workspace-learning.ts +159 -0
  397. package/src/routes/workspace-model-providers.ts +148 -0
  398. package/src/routes/workspace-state.ts +38 -25
  399. package/src/routes/workspaces.ts +1004 -127
  400. package/src/sandbox/auth-callout.ts +96 -19
  401. package/src/sandbox/channel-a.ts +323 -24
  402. package/src/sandbox/connection-authority.ts +3 -0
  403. package/src/sandbox/enrollment.ts +81 -6
  404. package/src/sandbox/machines.ts +218 -76
  405. package/src/sandbox/metrics-ingestion.ts +411 -62
  406. package/src/sandbox/rematerialize.ts +121 -19
  407. package/src/sandbox/session-attach-variable-sets.ts +131 -0
  408. package/src/sandbox/viewer.ts +280 -25
  409. package/src/sandbox-file-artifacts.ts +413 -0
  410. package/src/scheduled-task-deletion.ts +138 -0
  411. package/src/session-create-observability.ts +46 -0
  412. package/src/session-realtime-context.ts +6 -3
  413. package/src/site-session-origin.ts +48 -0
  414. package/src/site-uploads.ts +224 -0
  415. package/src/slack-reaction-files.ts +16 -5
  416. package/src/temporal-schedule-cleanup.ts +13 -0
  417. package/src/temporal-schedule-sync.ts +74 -0
  418. package/src/transcription/providers/codex-subscription.ts +2 -0
  419. package/src/transcription/providers/openai.ts +1 -0
  420. package/src/transcription/providers/xai-subscription.ts +49 -3
  421. package/src/transcription/segmenter.ts +8 -2
  422. package/src/transcription/service.ts +43 -6
  423. package/src/work-discovery-observability.ts +121 -0
  424. package/src/workspace-artifact-content.ts +144 -0
  425. package/src/workspace-artifact-provenance.ts +104 -0
  426. package/src/workspace-delete-observability.ts +75 -0
  427. package/src/workspace-deletion.ts +73 -0
  428. package/src/workspace-state-projection.ts +18 -148
  429. package/src/workspace-tool-gateway-observability.ts +100 -0
  430. package/src/workspace-tool-gateway.ts +870 -0
  431. package/src/workspace-webhook-dispatch.ts +190 -0
  432. package/src/xai-subscription-auth.ts +9 -2
  433. package/dist/chunk-2PQMSRCB.js +0 -49759
  434. package/dist/chunk-2PQMSRCB.js.map +0 -1
  435. package/dist/routes/packs.d.ts +0 -3
  436. package/src/routes/packs.ts +0 -729
@@ -0,0 +1,1218 @@
1
+ import { assertManagedUserAdmission, getManagedSession } from "@opengeni/core";
2
+ import { randomUUID } from "node:crypto";
3
+ import {
4
+ BeginManagedAuthLoginTransactionRequest,
5
+ BootstrapManagedAuthSessionSetRequest,
6
+ CancelManagedAuthLoginTransactionRequest,
7
+ CompleteManagedAuthEmailPasswordTransactionRequest,
8
+ CompleteManagedAuthLoginTransactionResponse,
9
+ LogoutManagedAuthLoginSlotRequest,
10
+ LogoutManagedAuthSessionSetRequest,
11
+ MANAGED_AUTH_TRANSACTION_TTL_SECONDS,
12
+ MANAGED_AUTH_SESSION_SET_API_CONTRACT_HEADER,
13
+ MANAGED_AUTH_SESSION_SET_API_CONTRACT_REVISION,
14
+ ManagedAuthDeepLinkResolution,
15
+ ManagedAuthLoginTransaction,
16
+ ManagedAuthSessionSetProjection,
17
+ ManagedAuthSessionSetErrorCode,
18
+ ResolveManagedAuthDeepLinkRequest,
19
+ SelectManagedAuthLoginSlotRequest,
20
+ StartManagedAuthSocialTransactionRequest,
21
+ StartManagedAuthSocialTransactionResponse,
22
+ type ManagedAuthSessionSetProjection as ManagedAuthSessionSetProjectionType,
23
+ type ManagedAuthSessionSetErrorCode as ManagedAuthSessionSetErrorCodeType,
24
+ } from "@opengeni/contracts/managed-auth-session-sets";
25
+ import { hasPermission, requireSessionAuthorization, type ApiRouteDeps } from "@opengeni/core";
26
+ import {
27
+ authenticateAndAdoptManagedAuthSession,
28
+ isolatedManagedAuthHeaders,
29
+ MANAGED_AUTH_ACTOR_EPOCH_HEADER,
30
+ MANAGED_AUTH_LOGIN_TRANSACTION_COOKIE,
31
+ MANAGED_AUTH_LOGIN_TRANSACTION_COOKIE_PATH,
32
+ MANAGED_AUTH_SESSION_SET_COOKIE,
33
+ ManagedAuthActorChangeError,
34
+ ManagedAuthCompletionOutcomeUnknownError,
35
+ ManagedAuthRequestAdmissionError,
36
+ managedAuthCsrfHash,
37
+ managedAuthDerivedUuid,
38
+ managedAuthRandomAuthority,
39
+ managedAuthSecretRequestDigest,
40
+ managedAuthSha256,
41
+ managedAuthTransactionSecret,
42
+ requireManagedAuthMutationAdmission,
43
+ resolveManagedAuthSelectedSession,
44
+ withManagedAuthCsrfToken,
45
+ } from "@opengeni/core/managed-auth-session-sets";
46
+ import {
47
+ beginManagedAuthLoginTransaction,
48
+ bootstrapManagedAuthSessionSet,
49
+ getManagedAuthAdoptedSessionSnapshot,
50
+ getManagedAuthSessionSetOperationReceipt,
51
+ getManagedAuthSessionSetSnapshot,
52
+ getManagedAuthSessionSetAuthorityState,
53
+ ManagedAuthLoginSlotLimitError,
54
+ ManagedAuthLoginSlotAlreadyExistsError,
55
+ ManagedAuthLoginSlotUnavailableError,
56
+ ManagedAuthActorMutationInFlightError,
57
+ ManagedAuthLoginTransactionRateLimitError,
58
+ ManagedAuthSessionSetAuthorityError,
59
+ ManagedAuthSessionSetGenerationConflictError,
60
+ ManagedAuthSessionSetOperationReuseError,
61
+ mutateManagedAuthSessionSet,
62
+ } from "@opengeni/db/managed-auth-session-sets";
63
+ import { ensureManagedAccessForUser, getSession } from "@opengeni/db";
64
+ import { sql } from "drizzle-orm";
65
+ import type { Context, Hono } from "hono";
66
+ import { deleteCookie, getCookie, setCookie } from "hono/cookie";
67
+ import { HTTPException } from "hono/http-exception";
68
+ import { SIGNUP_ATTRIBUTION_AUTH_FIELD } from "../auth/signup-funnel-metrics";
69
+ import { ApiHttpError } from "../http/api-error";
70
+ import { trustedRequestSourceRateLimitKey } from "../http/request-source";
71
+ import { ManagedAuthEmailThrottleError } from "../auth/managed-auth-rate-limits";
72
+ import { z } from "zod";
73
+
74
+ const ManagedAuthSocialStartReceipt = z
75
+ .object({
76
+ version: z.literal(1),
77
+ requestDigest: z.string().regex(/^[0-9a-f]{64}$/u),
78
+ url: z.string().url().max(4_096),
79
+ stateCookie: z.string().min(1).max(4_096),
80
+ })
81
+ .strict();
82
+
83
+ export function registerManagedAuthSessionSetRoutes(app: Hono, deps: ApiRouteDeps): void {
84
+ const noStore = async (context: Context, next: () => Promise<void>) => {
85
+ context.header("cache-control", "no-store");
86
+ context.header("pragma", "no-cache");
87
+ if (deps.settings.managedAuthSessionSetMode === "broker") {
88
+ appendCookies(
89
+ context,
90
+ await requireAvailable(deps).adapter.createLegacySelectedSessionCookies(
91
+ null,
92
+ context.req.header("cookie") ?? null,
93
+ ),
94
+ );
95
+ }
96
+ await next();
97
+ };
98
+ app.use("/v1/auth/session-set", noStore);
99
+ app.use("/v1/auth/session-set/*", noStore);
100
+
101
+ app.get("/v1/auth/get-session", async (context) => {
102
+ if (deps.settings.managedAuthSessionSetMode === "legacy") {
103
+ if (!deps.managedAuth) throw new HTTPException(404);
104
+ if (deps.settings.allowedUserEmails !== undefined) {
105
+ const session = await getManagedSession(context, deps.managedAuth, { db: deps.db });
106
+ context.header("cache-control", "no-store");
107
+ return context.json(session ? safeBetterAuthSession(session) : null);
108
+ }
109
+ return await deps.managedAuth.handler(context.req.raw);
110
+ }
111
+ context.header("cache-control", "no-store");
112
+ const available = requireAvailable(deps);
113
+ if (deps.settings.managedAuthSessionSetMode === "broker") {
114
+ appendCookies(
115
+ context,
116
+ await available.adapter.createLegacySelectedSessionCookies(
117
+ null,
118
+ context.req.header("cookie") ?? null,
119
+ ),
120
+ );
121
+ }
122
+ const authority = getAuthority(context);
123
+ try {
124
+ if (authority) {
125
+ const expectedActorEpoch = context.req.header(MANAGED_AUTH_ACTOR_EPOCH_HEADER) ?? null;
126
+ const ambient =
127
+ deps.settings.managedAuthSessionSetMode === "dual" && expectedActorEpoch === null
128
+ ? await available.adapter.resolveAmbientSession(context.req.raw.headers)
129
+ : null;
130
+ const selected = await resolveManagedAuthSelectedSession({
131
+ db: deps.db,
132
+ adapter: available.adapter,
133
+ authority,
134
+ mode: deps.settings.managedAuthSessionSetMode,
135
+ expectedActorEpoch,
136
+ legacyAmbientSessionId: ambient?.session.id ?? null,
137
+ });
138
+ if (!selected?.session) return context.json(null);
139
+ return jsonWithActorEpoch(
140
+ context,
141
+ selected.projection.actorEpoch,
142
+ safeBetterAuthSession(selected.session),
143
+ );
144
+ }
145
+ if (deps.settings.managedAuthSessionSetMode === "broker") return context.json(null);
146
+ const ambient = await available.adapter.resolveAmbientSession(context.req.raw.headers);
147
+ if (!ambient?.session.id) return context.json(null);
148
+ const adopted = await getManagedAuthAdoptedSessionSnapshot(deps.db, ambient.session.id);
149
+ if (adopted) {
150
+ if (
151
+ adopted.actorEpoch !== "1" ||
152
+ !adopted.selected ||
153
+ adopted.selected.authSessionId !== ambient.session.id ||
154
+ adopted.selected.authUserId !== ambient.user.id
155
+ ) {
156
+ throw new ManagedAuthActorChangeError();
157
+ }
158
+ return jsonWithActorEpoch(context, adopted.actorEpoch, safeBetterAuthSession(ambient));
159
+ }
160
+ return context.json(safeBetterAuthSession(ambient));
161
+ } catch (error) {
162
+ if (error instanceof ManagedAuthActorChangeError) {
163
+ context.header("x-opengeni-actor-state", "changed");
164
+ throw managedAuthApiError(409, "actor_change_required", { cause: error });
165
+ }
166
+ throw error;
167
+ }
168
+ });
169
+
170
+ app.get("/v1/auth/session-set", async (context) => {
171
+ requireAvailable(deps);
172
+ const existingAuthority = getAuthority(context);
173
+ const snapshot = existingAuthority ? await snapshotFor(deps, existingAuthority) : null;
174
+ if (snapshot && existingAuthority) {
175
+ return jsonWithActorEpoch(
176
+ context,
177
+ snapshot.projection.actorEpoch,
178
+ project(deps, existingAuthority, snapshot.projection),
179
+ );
180
+ }
181
+ if (
182
+ existingAuthority &&
183
+ (await getManagedAuthSessionSetAuthorityState(
184
+ deps.db,
185
+ managedAuthSha256(existingAuthority),
186
+ )) === "absent"
187
+ ) {
188
+ const projection = emptyProjection(deps, existingAuthority);
189
+ return jsonWithActorEpoch(context, projection.actorEpoch, projection);
190
+ }
191
+ const authority = managedAuthRandomAuthority();
192
+ setAuthorityCookie(context, deps, authority);
193
+ const projection = emptyProjection(deps, authority);
194
+ return jsonWithActorEpoch(context, projection.actorEpoch, projection);
195
+ });
196
+
197
+ app.post("/v1/auth/session-set/bootstrap", async (context) => {
198
+ const body = await bodyAs(context, BootstrapManagedAuthSessionSetRequest);
199
+ const { authority, actorEpoch } = await requireMutation(context, deps, body.expectedGeneration);
200
+ const ambient = await requireAvailable(deps).managedAuth!.api.getSession({
201
+ headers: context.req.raw.headers,
202
+ returnHeaders: true,
203
+ });
204
+ assertManagedUserAdmission(deps.managedAuth!, ambient.response?.user);
205
+ const authSessionId = ambient.response?.session?.id;
206
+ if (typeof authSessionId !== "string") {
207
+ throw managedAuthApiError(401, "managed_authentication_required");
208
+ }
209
+ try {
210
+ const projection = await bootstrapManagedAuthSessionSet(deps.db, {
211
+ authorityHash: managedAuthSha256(authority),
212
+ csrfHash: managedAuthCsrfHash(authority),
213
+ authSessionId,
214
+ mode: deps.settings.managedAuthSessionSetMode,
215
+ operationId: body.operationId,
216
+ requestDigest: digest(deps, body),
217
+ expectedGeneration: body.expectedGeneration,
218
+ expectedActorEpoch: actorEpoch,
219
+ });
220
+ if (deps.settings.managedAuthSessionSetMode === "dual") {
221
+ for (const cookie of setCookieHeaders(ambient.headers)) {
222
+ context.header("set-cookie", cookie, { append: true });
223
+ }
224
+ } else {
225
+ appendCookies(
226
+ context,
227
+ await requireAvailable(deps).adapter.createLegacySelectedSessionCookies(
228
+ null,
229
+ context.req.header("cookie") ?? null,
230
+ ),
231
+ );
232
+ }
233
+ return jsonWithActorEpoch(
234
+ context,
235
+ projection.actorEpoch,
236
+ project(deps, authority, projection),
237
+ );
238
+ } catch (error) {
239
+ throwHttp(error);
240
+ }
241
+ });
242
+
243
+ app.post("/v1/auth/session-set/transactions", async (context) => {
244
+ const body = await bodyAs(context, BeginManagedAuthLoginTransactionRequest);
245
+ const { authority, actorEpoch } = await requireMutation(context, deps, body.expectedGeneration);
246
+ const transactionId = randomUUID();
247
+ const transactionSecret = managedAuthTransactionSecret(
248
+ requireSigningSecret(deps),
249
+ authority,
250
+ body.operationId,
251
+ );
252
+ try {
253
+ const transaction = ManagedAuthLoginTransaction.parse(
254
+ await beginManagedAuthLoginTransaction(deps.db, {
255
+ authorityHash: managedAuthSha256(authority),
256
+ csrfHash: managedAuthCsrfHash(authority),
257
+ rateScopeHash: loginTransactionClientScope(context, deps),
258
+ operationId: body.operationId,
259
+ requestDigest: digest(deps, body),
260
+ expectedGeneration: body.expectedGeneration,
261
+ expectedActorEpoch: actorEpoch,
262
+ transactionId,
263
+ transactionSecretHash: managedAuthSha256(transactionSecret),
264
+ kind: body.kind,
265
+ targetSlotId: body.slotId ?? null,
266
+ returnIntentId: body.returnIntent
267
+ ? managedAuthDerivedUuid("opengeni:managed-auth:return-intent", body.operationId)
268
+ : null,
269
+ returnPath: body.returnIntent ?? null,
270
+ expiresAt: new Date(Date.now() + 600_000),
271
+ }),
272
+ );
273
+ setTransactionCookie(context, deps, transaction.id, transactionSecret);
274
+ return jsonWithActorEpoch(context, actorEpoch, transaction);
275
+ } catch (error) {
276
+ throwHttp(error);
277
+ }
278
+ });
279
+
280
+ app.post("/v1/auth/session-set/transactions/email-password", async (context) => {
281
+ const body = await bodyAs(context, CompleteManagedAuthEmailPasswordTransactionRequest);
282
+ const { authority, actorEpoch } = await requireMutation(context, deps, body.expectedGeneration);
283
+ const available = requireAvailable(deps);
284
+ const requestDigest = digest(deps, body);
285
+ try {
286
+ let completed: Awaited<ReturnType<typeof getManagedAuthSessionSetOperationReceipt>>;
287
+ try {
288
+ completed = await getManagedAuthSessionSetOperationReceipt(deps.db, {
289
+ authorityHash: managedAuthSha256(authority),
290
+ operationId: body.operationId,
291
+ requestDigest,
292
+ });
293
+ } catch (error) {
294
+ throw new ManagedAuthCompletionOutcomeUnknownError({ cause: error });
295
+ }
296
+ if (!completed) {
297
+ const transactionSecret = requireTransactionSecret(context, body.transactionId);
298
+ completed = await authenticateAndAdoptManagedAuthSession({
299
+ db: deps.db,
300
+ adapter: available.adapter,
301
+ isolatedHeaders: isolatedManagedAuthHeaders(context.req.raw),
302
+ authority,
303
+ csrfHash: managedAuthCsrfHash(authority),
304
+ operationId: body.operationId,
305
+ requestDigest,
306
+ expectedGeneration: body.expectedGeneration,
307
+ expectedActorEpoch: actorEpoch,
308
+ transactionId: body.transactionId,
309
+ transactionSecret,
310
+ email: body.email,
311
+ password: body.password,
312
+ mode: deps.settings.managedAuthSessionSetMode,
313
+ });
314
+ }
315
+ deleteCookie(context, MANAGED_AUTH_LOGIN_TRANSACTION_COOKIE, transactionCookieOptions(deps));
316
+ await mirrorCurrentSelection(context, deps, authority);
317
+ return jsonWithActorEpoch(
318
+ context,
319
+ completed.projection.actorEpoch,
320
+ CompleteManagedAuthLoginTransactionResponse.parse({
321
+ projection: project(deps, authority, completed.projection),
322
+ returnIntent: completed.returnIntent,
323
+ }),
324
+ );
325
+ } catch (error) {
326
+ throwHttp(error, context);
327
+ }
328
+ });
329
+
330
+ app.post("/v1/auth/session-set/transactions/social", async (context) => {
331
+ const body = await bodyAs(context, StartManagedAuthSocialTransactionRequest);
332
+ const { authority, actorEpoch } = await requireMutation(context, deps, body.expectedGeneration);
333
+ const available = requireAvailable(deps);
334
+ if (!managedAuthSocialProviderConfigured(deps, body.provider)) {
335
+ throw managedAuthApiError(404, "managed_authentication_unavailable");
336
+ }
337
+ const transactionSecret = requireTransactionSecret(context, body.transactionId);
338
+ const publicBaseUrl = deps.settings.publicBaseUrl;
339
+ if (!publicBaseUrl) {
340
+ throw managedAuthApiError(503, "managed_authentication_unavailable", {
341
+ retryable: true,
342
+ });
343
+ }
344
+ const callbackURL = new URL("/account-auth", publicBaseUrl);
345
+ callbackURL.searchParams.set("transaction", body.transactionId);
346
+ callbackURL.searchParams.set("social", "complete");
347
+ const errorCallbackURL = new URL(callbackURL);
348
+ errorCallbackURL.searchParams.set("social", "error");
349
+ try {
350
+ const authorityHash = managedAuthSha256(authority);
351
+ const transactionSecretHash = managedAuthSha256(transactionSecret);
352
+ const requestDigest = digest(deps, {
353
+ operation: "social_start",
354
+ authorityHash,
355
+ actorEpoch,
356
+ transactionSecretHash,
357
+ request: body,
358
+ });
359
+ const receiptIdentifier = `opengeni-managed-social-start:${authorityHash}:${body.operationId}`;
360
+ const receipt = await deps.db.transaction(async (transaction) => {
361
+ await transaction.execute(
362
+ sql`select pg_advisory_xact_lock(hashtextextended(${receiptIdentifier}, 0))`,
363
+ );
364
+ const authContext = await available.managedAuth.$context;
365
+ const stored = await authContext.internalAdapter.findVerificationValue(receiptIdentifier);
366
+ if (stored && new Date(stored.expiresAt).getTime() > Date.now()) {
367
+ let parsed: ReturnType<typeof ManagedAuthSocialStartReceipt.safeParse>;
368
+ try {
369
+ parsed = ManagedAuthSocialStartReceipt.safeParse(JSON.parse(stored.value));
370
+ } catch (error) {
371
+ throw new ManagedAuthCompletionOutcomeUnknownError({
372
+ cause: error,
373
+ });
374
+ }
375
+ if (!parsed.success) {
376
+ throw new ManagedAuthCompletionOutcomeUnknownError({
377
+ cause: parsed.error,
378
+ });
379
+ }
380
+ if (parsed.data.requestDigest !== requestDigest) {
381
+ throw new ManagedAuthSessionSetOperationReuseError();
382
+ }
383
+ return parsed.data;
384
+ }
385
+ if (stored) {
386
+ await authContext.internalAdapter.deleteVerificationByIdentifier(receiptIdentifier);
387
+ }
388
+ const result = await available.managedAuth.api.signInSocial({
389
+ body: {
390
+ provider: body.provider,
391
+ disableRedirect: true,
392
+ callbackURL: callbackURL.toString(),
393
+ errorCallbackURL: errorCallbackURL.toString(),
394
+ additionalData: {
395
+ opengeniManagedAuth: {
396
+ version: 1,
397
+ operationId: body.operationId,
398
+ provider: body.provider,
399
+ transactionId: body.transactionId,
400
+ authorityHash,
401
+ transactionSecretHash,
402
+ expectedGeneration: body.expectedGeneration,
403
+ expectedActorEpoch: actorEpoch,
404
+ },
405
+ // Content-free acquisition metrics read this from the OAuth
406
+ // state if the callback creates a new account.
407
+ ...(body.attribution ? { [SIGNUP_ATTRIBUTION_AUTH_FIELD]: body.attribution } : {}),
408
+ },
409
+ },
410
+ headers: isolatedManagedAuthHeaders(context.req.raw),
411
+ returnHeaders: true,
412
+ });
413
+ const url = validatedManagedAuthSocialAuthorizationUrl({
414
+ provider: body.provider,
415
+ rawUrl: result.response?.url,
416
+ expectedClientId:
417
+ body.provider === "google"
418
+ ? deps.settings.managedAuthGoogleClientId!
419
+ : deps.settings.managedAuthGithubClientId!,
420
+ expectedCallbackUrl: new URL(
421
+ `/v1/auth/callback/${body.provider}`,
422
+ publicBaseUrl,
423
+ ).toString(),
424
+ });
425
+ const stateCookieName = authContext.createAuthCookie("state").name;
426
+ const stateCookie = setCookieHeaders(result.headers).find((cookie) =>
427
+ cookie.startsWith(`${stateCookieName}=`),
428
+ );
429
+ const parsed = ManagedAuthSocialStartReceipt.parse({
430
+ version: 1,
431
+ requestDigest,
432
+ url,
433
+ stateCookie,
434
+ });
435
+ const created = await authContext.internalAdapter.createVerificationValue({
436
+ identifier: receiptIdentifier,
437
+ value: JSON.stringify(parsed),
438
+ expiresAt: new Date(Date.now() + MANAGED_AUTH_TRANSACTION_TTL_SECONDS * 1_000),
439
+ });
440
+ if (!created) {
441
+ throw new ManagedAuthCompletionOutcomeUnknownError();
442
+ }
443
+ return parsed;
444
+ });
445
+ context.header("set-cookie", receipt.stateCookie, { append: true });
446
+ return jsonWithActorEpoch(
447
+ context,
448
+ actorEpoch,
449
+ StartManagedAuthSocialTransactionResponse.parse({ url: receipt.url }),
450
+ );
451
+ } catch (error) {
452
+ throwHttp(error);
453
+ }
454
+ });
455
+
456
+ app.delete("/v1/auth/session-set/transactions/:transactionId", async (context) => {
457
+ const body = await bodyAs(context, CancelManagedAuthLoginTransactionRequest);
458
+ if (body.transactionId !== context.req.param("transactionId")) invalid();
459
+ const { authority, actorEpoch } = await requireMutation(context, deps, body.expectedGeneration);
460
+ const transactionSecret = requireTransactionSecret(context, body.transactionId);
461
+ try {
462
+ const projection = await mutateManagedAuthSessionSet(deps.db, {
463
+ authorityHash: managedAuthSha256(authority),
464
+ csrfHash: managedAuthCsrfHash(authority),
465
+ operationId: body.operationId,
466
+ requestDigest: digest(deps, body),
467
+ expectedGeneration: body.expectedGeneration,
468
+ expectedActorEpoch: actorEpoch,
469
+ operationType: "cancel_transaction",
470
+ transactionId: body.transactionId,
471
+ transactionSecretHash: managedAuthSha256(transactionSecret),
472
+ mode: deps.settings.managedAuthSessionSetMode,
473
+ });
474
+ deleteCookie(context, MANAGED_AUTH_LOGIN_TRANSACTION_COOKIE, transactionCookieOptions(deps));
475
+ return jsonWithActorEpoch(
476
+ context,
477
+ (projection as ManagedAuthSessionSetProjectionType).actorEpoch,
478
+ project(deps, authority, projection as never),
479
+ );
480
+ } catch (error) {
481
+ throwHttp(error);
482
+ }
483
+ });
484
+
485
+ app.post("/v1/auth/session-set/select", async (context) => {
486
+ const body = await bodyAs(context, SelectManagedAuthLoginSlotRequest);
487
+ const { authority, actorEpoch } = await requireMutation(context, deps, body.expectedGeneration);
488
+ try {
489
+ const projection = await mutateManagedAuthSessionSet(deps.db, {
490
+ authorityHash: managedAuthSha256(authority),
491
+ csrfHash: managedAuthCsrfHash(authority),
492
+ operationId: body.operationId,
493
+ requestDigest: digest(deps, body),
494
+ expectedGeneration: body.expectedGeneration,
495
+ expectedActorEpoch: actorEpoch,
496
+ operationType: "select",
497
+ targetSlotId: body.slotId,
498
+ mode: deps.settings.managedAuthSessionSetMode,
499
+ });
500
+ await mirrorCurrentSelection(context, deps, authority);
501
+ return jsonWithActorEpoch(
502
+ context,
503
+ (projection as ManagedAuthSessionSetProjectionType).actorEpoch,
504
+ project(deps, authority, projection as never),
505
+ );
506
+ } catch (error) {
507
+ throwHttp(error);
508
+ }
509
+ });
510
+
511
+ app.post("/v1/auth/session-set/logout-one", async (context) => {
512
+ const body = await bodyAs(context, LogoutManagedAuthLoginSlotRequest);
513
+ const { authority, actorEpoch } = await requireMutation(context, deps, body.expectedGeneration);
514
+ try {
515
+ const projection = await mutateManagedAuthSessionSet(deps.db, {
516
+ authorityHash: managedAuthSha256(authority),
517
+ csrfHash: managedAuthCsrfHash(authority),
518
+ operationId: body.operationId,
519
+ requestDigest: digest(deps, body),
520
+ expectedGeneration: body.expectedGeneration,
521
+ expectedActorEpoch: actorEpoch,
522
+ operationType: "logout_one",
523
+ targetSlotId: body.slotId,
524
+ replacementSlotId: body.replacementSlotId,
525
+ mode: deps.settings.managedAuthSessionSetMode,
526
+ });
527
+ await mirrorCurrentSelection(context, deps, authority);
528
+ return jsonWithActorEpoch(
529
+ context,
530
+ (projection as ManagedAuthSessionSetProjectionType).actorEpoch,
531
+ project(deps, authority, projection as never),
532
+ );
533
+ } catch (error) {
534
+ throwHttp(error);
535
+ }
536
+ });
537
+
538
+ app.post("/v1/auth/session-set/logout-all", async (context) => {
539
+ const body = await bodyAs(context, LogoutManagedAuthSessionSetRequest);
540
+ const { authority, actorEpoch } = await requireMutation(context, deps, body.expectedGeneration);
541
+ try {
542
+ const receipt = await mutateManagedAuthSessionSet(deps.db, {
543
+ authorityHash: managedAuthSha256(authority),
544
+ csrfHash: managedAuthCsrfHash(authority),
545
+ operationId: body.operationId,
546
+ requestDigest: digest(deps, body),
547
+ expectedGeneration: body.expectedGeneration,
548
+ expectedActorEpoch: actorEpoch,
549
+ operationType: "logout_all",
550
+ mode: deps.settings.managedAuthSessionSetMode,
551
+ });
552
+ // Keep the now-retired authority cookie until the next authoritative GET
553
+ // rotates it. If response headers commit but the body is lost, the SDK
554
+ // can still replay this exact operation and recover its durable receipt;
555
+ // every new command remains denied by the revoked server-side set.
556
+ appendCookies(
557
+ context,
558
+ await requireAvailable(deps).adapter.createLegacySelectedSessionCookies(
559
+ null,
560
+ context.req.header("cookie") ?? null,
561
+ ),
562
+ );
563
+ return jsonWithActorEpoch(context, receipt.actorEpoch, receipt);
564
+ } catch (error) {
565
+ throwHttp(error);
566
+ }
567
+ });
568
+
569
+ app.post("/v1/auth/session-set/deep-link/resolve", async (context) => {
570
+ const body = await bodyAs(context, ResolveManagedAuthDeepLinkRequest);
571
+ const authority = getAuthority(context);
572
+ if (!authority)
573
+ return context.json(ManagedAuthDeepLinkResolution.parse({ kind: "unavailable" }));
574
+ const snapshot = await snapshotFor(deps, authority);
575
+ if (!snapshot)
576
+ return context.json(ManagedAuthDeepLinkResolution.parse({ kind: "unavailable" }));
577
+ requireApiContract(context);
578
+ try {
579
+ requireManagedAuthMutationAdmission({
580
+ request: context.req.raw,
581
+ allowedOrigins: allowedOrigins(deps),
582
+ authority,
583
+ signingSecret: requireSigningSecret(deps),
584
+ expectedGeneration: snapshot.projection.generation,
585
+ });
586
+ } catch (error) {
587
+ if (error instanceof ManagedAuthRequestAdmissionError) {
588
+ throw managedAuthApiError(403, "origin_rejected", { cause: error });
589
+ }
590
+ throw error;
591
+ }
592
+ requireActorEpoch(context, snapshot.projection);
593
+ return jsonWithActorEpoch(
594
+ context,
595
+ snapshot.projection.actorEpoch,
596
+ await resolveCanonicalDeepLink(deps, authority, body.path, snapshot.projection.actorEpoch),
597
+ );
598
+ });
599
+ }
600
+
601
+ export function validatedManagedAuthSocialAuthorizationUrl(input: {
602
+ provider: "google" | "github";
603
+ rawUrl: unknown;
604
+ expectedClientId: string;
605
+ expectedCallbackUrl: string;
606
+ }): string {
607
+ if (typeof input.rawUrl !== "string") {
608
+ throw new ManagedAuthCompletionOutcomeUnknownError();
609
+ }
610
+ let url: URL;
611
+ try {
612
+ url = new URL(input.rawUrl);
613
+ } catch (error) {
614
+ throw new ManagedAuthCompletionOutcomeUnknownError({ cause: error });
615
+ }
616
+ const expectedEndpoint =
617
+ input.provider === "google"
618
+ ? { origin: "https://accounts.google.com", pathname: "/o/oauth2/v2/auth" }
619
+ : { origin: "https://github.com", pathname: "/login/oauth/authorize" };
620
+ const exactlyOne = (name: string, expected?: string): boolean => {
621
+ const values = url.searchParams.getAll(name);
622
+ return (
623
+ values.length === 1 && values[0] !== "" && (expected === undefined || values[0] === expected)
624
+ );
625
+ };
626
+ if (
627
+ url.origin !== expectedEndpoint.origin ||
628
+ url.pathname !== expectedEndpoint.pathname ||
629
+ url.username ||
630
+ url.password ||
631
+ url.hash ||
632
+ !exactlyOne("client_id", input.expectedClientId) ||
633
+ !exactlyOne("redirect_uri", input.expectedCallbackUrl) ||
634
+ !exactlyOne("state")
635
+ ) {
636
+ throw new ManagedAuthCompletionOutcomeUnknownError();
637
+ }
638
+ return input.rawUrl;
639
+ }
640
+
641
+ /**
642
+ * Better Auth 1.6.26 provider lifecycle routes retained while session-set mode
643
+ * owns all selected-session reads, enumeration, revocation, and user mutation.
644
+ * The list is intentionally exact so a dependency upgrade cannot silently add
645
+ * a selected-session capability under the wildcard.
646
+ */
647
+ export function requireManagedAuthProviderRouteAllowed(method: string, pathname: string): void {
648
+ const normalizedMethod = method.toUpperCase();
649
+ const allowed =
650
+ (normalizedMethod === "POST" &&
651
+ new Set([
652
+ "/v1/auth/sign-up/email",
653
+ "/v1/auth/sign-in/email",
654
+ "/v1/auth/send-verification-email",
655
+ "/v1/auth/request-password-reset",
656
+ "/v1/auth/reset-password",
657
+ ]).has(pathname)) ||
658
+ (normalizedMethod === "GET" &&
659
+ (pathname === "/v1/auth/verify-email" ||
660
+ pathname === "/v1/auth/error" ||
661
+ pathname === "/v1/auth/ok" ||
662
+ /^\/v1\/auth\/callback\/(?:google|github)$/.test(pathname) ||
663
+ /^\/v1\/auth\/reset-password\/[^/]+$/.test(pathname)));
664
+ if (!allowed) throw managedAuthApiError(409, "provider_route_blocked");
665
+ }
666
+
667
+ function managedAuthSocialProviderConfigured(
668
+ deps: ApiRouteDeps,
669
+ provider: "google" | "github",
670
+ ): boolean {
671
+ return provider === "google"
672
+ ? Boolean(
673
+ deps.settings.managedAuthGoogleClientId && deps.settings.managedAuthGoogleClientSecret,
674
+ )
675
+ : Boolean(
676
+ deps.settings.managedAuthGithubClientId && deps.settings.managedAuthGithubClientSecret,
677
+ );
678
+ }
679
+
680
+ /** Remove provider bearer/session material and enforce browser-auth cache/cookie policy. */
681
+ export async function scrubManagedAuthProviderResponse(
682
+ response: Response,
683
+ options: {
684
+ replacementCookies?: readonly string[] | undefined;
685
+ preserveCookieNames?: readonly string[] | undefined;
686
+ } = {},
687
+ ): Promise<Response> {
688
+ const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
689
+ const headers = new Headers(response.headers);
690
+ headers.set("cache-control", "no-store");
691
+ headers.set("pragma", "no-cache");
692
+ if (options.replacementCookies !== undefined) {
693
+ const preserved = setCookieHeaders(response.headers).filter((cookie) =>
694
+ options.preserveCookieNames?.some((name) => cookie.startsWith(`${name}=`)),
695
+ );
696
+ headers.delete("set-cookie");
697
+ for (const cookie of preserved) headers.append("set-cookie", cookie);
698
+ for (const cookie of options.replacementCookies) headers.append("set-cookie", cookie);
699
+ }
700
+ let body: BodyInit | null = response.body;
701
+ if (contentType.includes("application/json")) {
702
+ const value = await response
703
+ .clone()
704
+ .json()
705
+ .catch(() => undefined);
706
+ if (value !== undefined) {
707
+ headers.delete("content-length");
708
+ body = JSON.stringify(removeProviderSecrets(value));
709
+ }
710
+ }
711
+ return new Response(body, {
712
+ status: response.status,
713
+ statusText: response.statusText,
714
+ headers,
715
+ });
716
+ }
717
+
718
+ function removeProviderSecrets(value: unknown): unknown {
719
+ if (Array.isArray(value)) return value.map(removeProviderSecrets);
720
+ if (!value || typeof value !== "object") return value;
721
+ const secretKeys = new Set([
722
+ "token",
723
+ "session",
724
+ "accessToken",
725
+ "refreshToken",
726
+ "idToken",
727
+ "access_token",
728
+ "refresh_token",
729
+ "id_token",
730
+ ]);
731
+ return Object.fromEntries(
732
+ Object.entries(value as Record<string, unknown>)
733
+ .filter(([key]) => !secretKeys.has(key))
734
+ .map(([key, child]) => [key, removeProviderSecrets(child)]),
735
+ );
736
+ }
737
+
738
+ function jsonWithActorEpoch(context: Context, actorEpoch: string, value: unknown): Response {
739
+ context.header(MANAGED_AUTH_ACTOR_EPOCH_HEADER, actorEpoch);
740
+ return context.json(value);
741
+ }
742
+
743
+ function safeBetterAuthSession(resolved: {
744
+ session: { id: string; userId: string; [key: string]: unknown };
745
+ user: {
746
+ id: string;
747
+ email: string;
748
+ name: string;
749
+ emailVerified: boolean;
750
+ [key: string]: unknown;
751
+ };
752
+ }) {
753
+ const safeDate = (value: unknown): string | Date | undefined =>
754
+ value instanceof Date || typeof value === "string" ? value : undefined;
755
+ const session = resolved.session;
756
+ const user = resolved.user;
757
+ return {
758
+ session: {
759
+ id: session.id,
760
+ userId: session.userId,
761
+ ...(safeDate(session.createdAt) ? { createdAt: safeDate(session.createdAt) } : {}),
762
+ ...(safeDate(session.updatedAt) ? { updatedAt: safeDate(session.updatedAt) } : {}),
763
+ ...(safeDate(session.expiresAt) ? { expiresAt: safeDate(session.expiresAt) } : {}),
764
+ ...(typeof session.ipAddress === "string" ? { ipAddress: session.ipAddress } : {}),
765
+ ...(typeof session.userAgent === "string" ? { userAgent: session.userAgent } : {}),
766
+ },
767
+ user: {
768
+ id: user.id,
769
+ email: user.email,
770
+ name: user.name,
771
+ emailVerified: user.emailVerified,
772
+ ...(typeof user.image === "string" ? { image: user.image } : {}),
773
+ ...(safeDate(user.createdAt) ? { createdAt: safeDate(user.createdAt) } : {}),
774
+ ...(safeDate(user.updatedAt) ? { updatedAt: safeDate(user.updatedAt) } : {}),
775
+ },
776
+ };
777
+ }
778
+
779
+ async function resolveCanonicalDeepLink(
780
+ deps: ApiRouteDeps,
781
+ authority: string,
782
+ path: string,
783
+ expectedActorEpoch: string,
784
+ ) {
785
+ const target = parseSupportedDeepLink(path);
786
+ if (!target) return ManagedAuthDeepLinkResolution.parse({ kind: "unavailable" });
787
+ const snapshot = await getManagedAuthSessionSetSnapshot(deps.db, {
788
+ authorityHash: managedAuthSha256(authority),
789
+ mode: deps.settings.managedAuthSessionSetMode,
790
+ includeInternal: true,
791
+ readOnly: true,
792
+ });
793
+ if (!snapshot) return ManagedAuthDeepLinkResolution.parse({ kind: "unavailable" });
794
+ if (snapshot.projection.actorEpoch !== expectedActorEpoch) {
795
+ throw managedAuthApiError(409, "actor_change_required");
796
+ }
797
+ const adapter = requireAvailable(deps).adapter;
798
+ const selectedSlotId = snapshot.projection.selectedSlotId;
799
+ const orderedSlots = [
800
+ ...snapshot.internalSlots.filter((slot) => slot.slotId === selectedSlotId),
801
+ ...snapshot.internalSlots.filter((slot) => slot.slotId !== selectedSlotId),
802
+ ];
803
+ const eligibleSlotIds: string[] = [];
804
+ for (const slot of orderedSlots) {
805
+ const resolved = await adapter.resolveSelectedSession(slot);
806
+ if (
807
+ !resolved ||
808
+ resolved.session.id !== slot.authSessionId ||
809
+ resolved.user.id !== slot.authUserId
810
+ ) {
811
+ continue;
812
+ }
813
+ const access = await ensureManagedAccessForUser(deps.db, {
814
+ userId: resolved.user.id,
815
+ email: resolved.user.email,
816
+ name: resolved.user.name,
817
+ emailVerified: resolved.user.emailVerified,
818
+ provisionFallbackOrganization: false,
819
+ bindPendingInvitations: false,
820
+ });
821
+ const grants = target.workspaceId
822
+ ? access.workspaceGrants.filter((grant) => grant.workspaceId === target.workspaceId)
823
+ : access.workspaceGrants;
824
+ let authorized = false;
825
+ for (const grant of grants) {
826
+ if (
827
+ !hasPermission(grant.permissions, target.sessionId ? "sessions:read" : target.permission)
828
+ ) {
829
+ continue;
830
+ }
831
+ if (target.sessionId) {
832
+ const session = await getSession(deps.db, grant.workspaceId, target.sessionId);
833
+ if (!session) continue;
834
+ try {
835
+ await requireSessionAuthorization(deps, grant, {
836
+ sessionId: target.sessionId,
837
+ operation: "session.read",
838
+ surface: "http",
839
+ });
840
+ } catch {
841
+ continue;
842
+ }
843
+ }
844
+ authorized = true;
845
+ break;
846
+ }
847
+ if (authorized) {
848
+ if (slot.slotId === selectedSlotId) {
849
+ return ManagedAuthDeepLinkResolution.parse({ kind: "current" });
850
+ }
851
+ eligibleSlotIds.push(slot.slotId);
852
+ }
853
+ }
854
+ if (eligibleSlotIds.length !== 1) {
855
+ return ManagedAuthDeepLinkResolution.parse({ kind: "unavailable" });
856
+ }
857
+ const safeSlot = snapshot.projection.slots.find((slot) => slot.id === eligibleSlotIds[0]);
858
+ return safeSlot
859
+ ? ManagedAuthDeepLinkResolution.parse({ kind: "switch_required", slot: safeSlot })
860
+ : ManagedAuthDeepLinkResolution.parse({ kind: "unavailable" });
861
+ }
862
+
863
+ export function parseSupportedDeepLink(path: string): {
864
+ workspaceId: string | null;
865
+ sessionId: string | null;
866
+ permission: "workspace:read" | "sessions:read";
867
+ } | null {
868
+ if (
869
+ /%(?:00|2f|5c)/i.test(path) ||
870
+ path.includes("?") ||
871
+ path.includes("#") ||
872
+ path.includes("\\") ||
873
+ /[\u0000-\u001f\u007f]/.test(path)
874
+ )
875
+ return null;
876
+ let url: URL;
877
+ try {
878
+ url = new URL(path, "https://opengeni.invalid");
879
+ } catch {
880
+ return null;
881
+ }
882
+ if (url.origin !== "https://opengeni.invalid") return null;
883
+ if (url.search || url.hash) return null;
884
+ const uuid = "([0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})";
885
+ const workspace = url.pathname.match(new RegExp(`^/workspaces/${uuid}$`, "i"));
886
+ if (workspace?.[1]) {
887
+ return { workspaceId: workspace[1], sessionId: null, permission: "workspace:read" };
888
+ }
889
+ const sessionIndex = url.pathname.match(new RegExp(`^/workspaces/${uuid}/sessions$`, "i"));
890
+ if (sessionIndex?.[1]) {
891
+ return { workspaceId: sessionIndex[1], sessionId: null, permission: "sessions:read" };
892
+ }
893
+ const workspaceSession = url.pathname.match(
894
+ new RegExp(`^/workspaces/${uuid}/sessions/${uuid}$`, "i"),
895
+ );
896
+ if (workspaceSession?.[1] && workspaceSession[2]) {
897
+ return {
898
+ workspaceId: workspaceSession[1],
899
+ sessionId: workspaceSession[2],
900
+ permission: "sessions:read",
901
+ };
902
+ }
903
+ const compatibilitySession = url.pathname.match(new RegExp(`^/sessions/${uuid}$`, "i"));
904
+ return compatibilitySession?.[1]
905
+ ? { workspaceId: null, sessionId: compatibilitySession[1], permission: "sessions:read" }
906
+ : null;
907
+ }
908
+
909
+ function requireAvailable(deps: ApiRouteDeps) {
910
+ if (
911
+ deps.settings.productAccessMode !== "managed" ||
912
+ deps.settings.managedAuthSessionSetMode === "legacy" ||
913
+ !deps.managedAuth ||
914
+ !deps.managedAuthSessionAdapter
915
+ ) {
916
+ throw managedAuthApiError(404, "browser_session_set_unavailable");
917
+ }
918
+ requireSigningSecret(deps);
919
+ return { managedAuth: deps.managedAuth, adapter: deps.managedAuthSessionAdapter };
920
+ }
921
+
922
+ function requireSigningSecret(deps: ApiRouteDeps): string {
923
+ const secret = deps.settings.betterAuthSecret;
924
+ if (!secret)
925
+ throw managedAuthApiError(503, "managed_authentication_unavailable", { retryable: true });
926
+ return secret;
927
+ }
928
+
929
+ async function bodyAs<S extends z.ZodType>(context: Context, schema: S): Promise<z.infer<S>> {
930
+ const parsed = schema.safeParse(await context.req.json().catch(() => null));
931
+ if (!parsed.success) invalid();
932
+ return parsed.data;
933
+ }
934
+
935
+ function invalid(): never {
936
+ throw managedAuthApiError(422, "invalid_browser_session_set_request");
937
+ }
938
+
939
+ function getAuthority(context: Context): string | null {
940
+ const value = getCookie(context, MANAGED_AUTH_SESSION_SET_COOKIE);
941
+ return value && /^[A-Za-z0-9_-]{43}$/.test(value) ? value : null;
942
+ }
943
+
944
+ async function requireMutation(
945
+ context: Context,
946
+ deps: ApiRouteDeps,
947
+ expectedGeneration: string,
948
+ ): Promise<{ authority: string; actorEpoch: string }> {
949
+ requireAvailable(deps);
950
+ requireApiContract(context);
951
+ const authority = getAuthority(context);
952
+ if (!authority) throw managedAuthApiError(401, "browser_session_set_required");
953
+ try {
954
+ requireManagedAuthMutationAdmission({
955
+ request: context.req.raw,
956
+ allowedOrigins: allowedOrigins(deps),
957
+ authority,
958
+ signingSecret: requireSigningSecret(deps),
959
+ expectedGeneration,
960
+ });
961
+ } catch (error) {
962
+ if (error instanceof ManagedAuthRequestAdmissionError) {
963
+ throw managedAuthApiError(403, "origin_rejected", { cause: error });
964
+ }
965
+ throw error;
966
+ }
967
+ const actorEpoch = context.req.header(MANAGED_AUTH_ACTOR_EPOCH_HEADER);
968
+ if (!actorEpoch || !/^[1-9][0-9]*$/.test(actorEpoch)) {
969
+ context.header("x-opengeni-actor-state", "changed");
970
+ throw managedAuthApiError(409, "actor_change_required");
971
+ }
972
+ return { authority, actorEpoch };
973
+ }
974
+
975
+ function requireApiContract(context: Context): void {
976
+ if (
977
+ context.req.header(MANAGED_AUTH_SESSION_SET_API_CONTRACT_HEADER) !==
978
+ MANAGED_AUTH_SESSION_SET_API_CONTRACT_REVISION
979
+ ) {
980
+ throw managedAuthApiError(409, "api_contract_changed");
981
+ }
982
+ }
983
+
984
+ function requireActorEpoch(context: Context, projection: { actorEpoch: string }): void {
985
+ if (context.req.header(MANAGED_AUTH_ACTOR_EPOCH_HEADER) !== projection.actorEpoch) {
986
+ context.header("x-opengeni-actor-state", "changed");
987
+ throw managedAuthApiError(409, "actor_change_required");
988
+ }
989
+ }
990
+
991
+ async function snapshotFor(deps: ApiRouteDeps, authority: string) {
992
+ return await getManagedAuthSessionSetSnapshot(deps.db, {
993
+ authorityHash: managedAuthSha256(authority),
994
+ mode: deps.settings.managedAuthSessionSetMode,
995
+ readOnly: true,
996
+ });
997
+ }
998
+
999
+ function project(
1000
+ deps: ApiRouteDeps,
1001
+ authority: string,
1002
+ projection: Omit<ManagedAuthSessionSetProjectionType, "csrfToken">,
1003
+ ): ManagedAuthSessionSetProjectionType {
1004
+ return ManagedAuthSessionSetProjection.parse(
1005
+ withManagedAuthCsrfToken(projection, requireSigningSecret(deps), authority),
1006
+ );
1007
+ }
1008
+
1009
+ function emptyProjection(deps: ApiRouteDeps, authority: string) {
1010
+ return project(deps, authority, {
1011
+ mode: deps.settings.managedAuthSessionSetMode,
1012
+ generation: "1",
1013
+ actorEpoch: "1",
1014
+ selectedSlotId: null,
1015
+ state: "ready",
1016
+ slots: [],
1017
+ });
1018
+ }
1019
+
1020
+ function digest(deps: ApiRouteDeps, value: unknown): string {
1021
+ return managedAuthSecretRequestDigest(requireSigningSecret(deps), value);
1022
+ }
1023
+
1024
+ function loginTransactionClientScope(context: Context, deps: ApiRouteDeps): string {
1025
+ return digest(deps, {
1026
+ purpose: "managed-auth-login-transaction-rate-limit",
1027
+ client: trustedRequestSourceRateLimitKey(context, deps.settings),
1028
+ });
1029
+ }
1030
+
1031
+ function allowedOrigins(deps: ApiRouteDeps): string[] {
1032
+ const origins = new Set<string>();
1033
+ for (const candidate of [
1034
+ deps.settings.publicBaseUrl,
1035
+ deps.settings.webBaseUrl,
1036
+ ...deps.settings.betterAuthTrustedOrigins.split(","),
1037
+ ]) {
1038
+ if (!candidate?.trim()) continue;
1039
+ try {
1040
+ origins.add(new URL(candidate.trim()).origin);
1041
+ } catch {
1042
+ continue;
1043
+ }
1044
+ }
1045
+ return [...origins];
1046
+ }
1047
+
1048
+ function setAuthorityCookie(context: Context, deps: ApiRouteDeps, authority: string): void {
1049
+ setCookie(context, MANAGED_AUTH_SESSION_SET_COOKIE, authority, authorityCookieOptions(deps));
1050
+ }
1051
+
1052
+ function authorityCookieOptions(deps: ApiRouteDeps) {
1053
+ return {
1054
+ httpOnly: true,
1055
+ secure: deps.settings.publicBaseUrl?.startsWith("https://") ?? false,
1056
+ sameSite: "Lax" as const,
1057
+ path: "/",
1058
+ maxAge: 180 * 24 * 60 * 60,
1059
+ };
1060
+ }
1061
+
1062
+ function transactionCookieOptions(deps: ApiRouteDeps) {
1063
+ return {
1064
+ httpOnly: true,
1065
+ secure: deps.settings.publicBaseUrl?.startsWith("https://") ?? false,
1066
+ sameSite: "Strict" as const,
1067
+ path: MANAGED_AUTH_LOGIN_TRANSACTION_COOKIE_PATH,
1068
+ maxAge: 600,
1069
+ };
1070
+ }
1071
+
1072
+ function setTransactionCookie(
1073
+ context: Context,
1074
+ deps: ApiRouteDeps,
1075
+ transactionId: string,
1076
+ transactionSecret: string,
1077
+ ): void {
1078
+ setCookie(
1079
+ context,
1080
+ MANAGED_AUTH_LOGIN_TRANSACTION_COOKIE,
1081
+ `${transactionId}.${transactionSecret}`,
1082
+ transactionCookieOptions(deps),
1083
+ );
1084
+ }
1085
+
1086
+ function requireTransactionSecret(context: Context, transactionId: string): string {
1087
+ const value = getCookie(context, MANAGED_AUTH_LOGIN_TRANSACTION_COOKIE);
1088
+ const prefix = `${transactionId}.`;
1089
+ const secret = value?.startsWith(prefix) ? value.slice(prefix.length) : null;
1090
+ if (!secret || !/^[A-Za-z0-9_-]{43}$/.test(secret)) {
1091
+ throw managedAuthApiError(401, "invalid_transaction");
1092
+ }
1093
+ return secret;
1094
+ }
1095
+
1096
+ async function mirrorCurrentSelection(
1097
+ context: Context,
1098
+ deps: ApiRouteDeps,
1099
+ authority: string,
1100
+ ): Promise<void> {
1101
+ if (deps.settings.managedAuthSessionSetMode !== "dual") return;
1102
+ const snapshot = await getManagedAuthSessionSetSnapshot(deps.db, {
1103
+ authorityHash: managedAuthSha256(authority),
1104
+ mode: "dual",
1105
+ includeInternal: true,
1106
+ readOnly: true,
1107
+ });
1108
+ appendCookies(
1109
+ context,
1110
+ await requireAvailable(deps).adapter.createLegacySelectedSessionCookies(
1111
+ snapshot?.selected ?? null,
1112
+ context.req.header("cookie") ?? null,
1113
+ ),
1114
+ );
1115
+ }
1116
+
1117
+ function appendCookies(context: Context, cookies: readonly string[]): void {
1118
+ for (const cookie of cookies) context.header("set-cookie", cookie, { append: true });
1119
+ }
1120
+
1121
+ function throwHttp(error: unknown, context?: Context): never {
1122
+ if (error instanceof HTTPException) throw error;
1123
+ if (error instanceof ManagedAuthSessionSetGenerationConflictError) {
1124
+ throw managedAuthApiError(409, "generation_conflict", { cause: error, retryable: true });
1125
+ }
1126
+ if (error instanceof ManagedAuthSessionSetOperationReuseError) {
1127
+ throw managedAuthApiError(409, "operation_reused", { cause: error });
1128
+ }
1129
+ if (error instanceof ManagedAuthLoginSlotUnavailableError) {
1130
+ throw managedAuthApiError(409, "slot_unavailable", { cause: error });
1131
+ }
1132
+ if (error instanceof ManagedAuthLoginSlotLimitError) {
1133
+ throw managedAuthApiError(409, "slot_limit_reached", { cause: error });
1134
+ }
1135
+ if (error instanceof ManagedAuthLoginSlotAlreadyExistsError) {
1136
+ throw managedAuthApiError(409, "slot_already_exists", { cause: error });
1137
+ }
1138
+ if (error instanceof ManagedAuthActorMutationInFlightError) {
1139
+ throw managedAuthApiError(409, "actor_mutation_in_flight", { cause: error, retryable: true });
1140
+ }
1141
+ if (error instanceof ManagedAuthEmailThrottleError) {
1142
+ // A per-email window lasts 15 minutes to an hour; tell the client when,
1143
+ // unlike the short login-transaction backoff.
1144
+ context?.header("Retry-After", String(error.retryAfterSeconds));
1145
+ throw managedAuthApiError(429, "login_transaction_rate_limited", {
1146
+ cause: error,
1147
+ retryable: true,
1148
+ retryAfterSeconds: error.retryAfterSeconds,
1149
+ });
1150
+ }
1151
+ if (error instanceof ManagedAuthLoginTransactionRateLimitError) {
1152
+ throw managedAuthApiError(429, "login_transaction_rate_limited", {
1153
+ cause: error,
1154
+ retryable: true,
1155
+ });
1156
+ }
1157
+ if (error instanceof ManagedAuthSessionSetAuthorityError) {
1158
+ throw managedAuthApiError(401, "browser_session_set_required", { cause: error });
1159
+ }
1160
+ if (error instanceof ManagedAuthCompletionOutcomeUnknownError) {
1161
+ throw managedAuthApiError(503, "operation_outcome_unknown", {
1162
+ cause: error,
1163
+ retryable: true,
1164
+ outcomeUnknown: true,
1165
+ });
1166
+ }
1167
+ throw error;
1168
+ }
1169
+
1170
+ function managedAuthApiError(
1171
+ status: 401 | 403 | 404 | 409 | 422 | 429 | 503,
1172
+ code: ManagedAuthSessionSetErrorCodeType,
1173
+ options: {
1174
+ cause?: unknown;
1175
+ retryable?: boolean;
1176
+ outcomeUnknown?: boolean;
1177
+ retryAfterSeconds?: number;
1178
+ } = {},
1179
+ ): ApiHttpError {
1180
+ ManagedAuthSessionSetErrorCode.parse(code);
1181
+ const outerCode =
1182
+ status === 401
1183
+ ? "unauthenticated"
1184
+ : status === 403
1185
+ ? "forbidden"
1186
+ : status === 404
1187
+ ? "not_found"
1188
+ : status === 422
1189
+ ? "validation_failed"
1190
+ : status === 429
1191
+ ? "limit_exceeded"
1192
+ : status === 503
1193
+ ? "upstream_unavailable"
1194
+ : code === "operation_reused"
1195
+ ? "idempotency_conflict"
1196
+ : "conflict";
1197
+ const error = new ApiHttpError(status, {
1198
+ code: outerCode,
1199
+ message: code,
1200
+ retryable: options.retryable ?? false,
1201
+ ...(options.outcomeUnknown === undefined ? {} : { outcomeUnknown: options.outcomeUnknown }),
1202
+ details: {
1203
+ managedAuthCode: code,
1204
+ ...(options.retryAfterSeconds === undefined
1205
+ ? {}
1206
+ : { retryAfterSeconds: options.retryAfterSeconds }),
1207
+ },
1208
+ });
1209
+ if (options.cause !== undefined) (error as Error & { cause?: unknown }).cause = options.cause;
1210
+ return error;
1211
+ }
1212
+
1213
+ function setCookieHeaders(headers: Headers): string[] {
1214
+ const getter = (headers as Headers & { getSetCookie?: () => string[] }).getSetCookie;
1215
+ if (getter) return getter.call(headers);
1216
+ const cookie = headers.get("set-cookie");
1217
+ return cookie ? [cookie] : [];
1218
+ }