@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.
Files changed (434) 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-source-session.d.ts +3 -0
  35. package/dist/editable-artifact-websocket.d.ts +10 -18
  36. package/dist/editable-artifact-workspace-files.d.ts +1 -1
  37. package/dist/fatal-process-boundary.d.ts +25 -0
  38. package/dist/github-access.d.ts +31 -2
  39. package/dist/http/acting-person.d.ts +24 -0
  40. package/dist/http/api-contract-compat.d.ts +19 -0
  41. package/dist/http/api-error.d.ts +27 -1
  42. package/dist/http/cors.d.ts +17 -0
  43. package/dist/http/deprecation.d.ts +42 -0
  44. package/dist/http/interaction-control-error.d.ts +13 -0
  45. package/dist/http/keyed-admission.d.ts +12 -0
  46. package/dist/http/local-browser-boundary.d.ts +72 -0
  47. package/dist/http/metrics-listener.d.ts +22 -0
  48. package/dist/http/path-identifier.d.ts +17 -0
  49. package/dist/http/registered-route-label.d.ts +16 -0
  50. package/dist/http/request-body.d.ts +27 -0
  51. package/dist/http/request-source.d.ts +52 -0
  52. package/dist/http/sse.d.ts +17 -6
  53. package/dist/http/unmatched-route.d.ts +18 -0
  54. package/dist/http/user-content.d.ts +36 -0
  55. package/dist/http/websocket-upgrade-dispatch.d.ts +18 -0
  56. package/dist/index.d.ts +10 -3
  57. package/dist/index.js +1091 -162
  58. package/dist/index.js.map +1 -1
  59. package/dist/integration-endpoint-test.d.ts +20 -0
  60. package/dist/integrations/api-integrations.d.ts +2 -2
  61. package/dist/integrations/atlassian.d.ts +29 -3
  62. package/dist/integrations/connect-authority.d.ts +1 -0
  63. package/dist/integrations/connect-callback-return.d.ts +6 -0
  64. package/dist/integrations/fiken.d.ts +16 -2
  65. package/dist/integrations/github-app-connect.d.ts +47 -0
  66. package/dist/integrations/github-installation-proof.d.ts +3 -0
  67. package/dist/integrations/github-lens-connect.d.ts +116 -0
  68. package/dist/integrations/github-skill-source.d.ts +1 -5
  69. package/dist/integrations/google-drive.d.ts +24 -0
  70. package/dist/integrations/integration-commit-authority.d.ts +13 -0
  71. package/dist/integrations/marketplace-plugin.d.ts +2691 -0
  72. package/dist/integrations/oauth-client.d.ts +178 -18
  73. package/dist/integrations/oauth-profiles.d.ts +222 -0
  74. package/dist/integrations/oauth-return-path.d.ts +1 -0
  75. package/dist/integrations/personal-github-repositories.d.ts +84 -0
  76. package/dist/integrations/personal-github.d.ts +42 -0
  77. package/dist/integrations/pr-review-provider.d.ts +22 -0
  78. package/dist/integrations/provider-oauth.d.ts +19 -1
  79. package/dist/integrations/slack-app-home.d.ts +24 -0
  80. package/dist/integrations/slack-bot.d.ts +367 -12
  81. package/dist/integrations/slack-file-upload-flow.d.ts +62 -0
  82. package/dist/integrations/slack-install.d.ts +20 -0
  83. package/dist/integrations/slack-interactions.d.ts +107 -9
  84. package/dist/integrations/slack-mrkdwn.d.ts +41 -0
  85. package/dist/integrations/slack-routing.d.ts +120 -0
  86. package/dist/integrations/slack-session-defaults.d.ts +58 -0
  87. package/dist/integrations/slack-task-file-upload.d.ts +36 -0
  88. package/dist/integrations/social-oauth.d.ts +7 -0
  89. package/dist/interaction-agent-access.d.ts +15 -0
  90. package/dist/interaction-frame-proxy.d.ts +68 -0
  91. package/dist/interaction-metrics.d.ts +2 -0
  92. package/dist/mcp/action-catalog-types.d.ts +9 -0
  93. package/dist/mcp/action-catalog.gen.d.ts +2 -0
  94. package/dist/mcp/capability-account-readiness.d.ts +7 -0
  95. package/dist/mcp/child-read-evidence.d.ts +9 -0
  96. package/dist/mcp/command-read.d.ts +24 -0
  97. package/dist/mcp/company-profile-agent-admin.d.ts +44 -0
  98. package/dist/mcp/contract-input.d.ts +11 -0
  99. package/dist/mcp/documents.d.ts +7 -5
  100. package/dist/mcp/files.d.ts +2 -2
  101. package/dist/mcp/first-party-tool-permissions.d.ts +650 -0
  102. package/dist/mcp/knowledge-entries.d.ts +5 -0
  103. package/dist/mcp/orchestration-failure-diagnostic.d.ts +43 -0
  104. package/dist/mcp/receipts.d.ts +9 -0
  105. package/dist/mcp/request-abort.d.ts +19 -0
  106. package/dist/mcp/scheduled-task-input.d.ts +1189 -0
  107. package/dist/mcp/scheduled-task-view.d.ts +22 -4
  108. package/dist/mcp/server.d.ts +255 -3
  109. package/dist/mcp/session-event-view.d.ts +94 -0
  110. package/dist/mcp/session-view.d.ts +66 -2
  111. package/dist/mcp/session-wait.d.ts +174 -0
  112. package/dist/mcp-oauth.d.ts +52 -0
  113. package/dist/model-catalog.d.ts +11 -31
  114. package/dist/observability.d.ts +3 -1
  115. package/dist/organization-mcp.d.ts +52 -0
  116. package/dist/organization-recovery-notifications.d.ts +41 -0
  117. package/dist/routes/analytics-consent.d.ts +26 -0
  118. package/dist/routes/api-integrations.d.ts +25 -0
  119. package/dist/routes/api-keys.d.ts +17 -0
  120. package/dist/routes/artifact-catalog.d.ts +6 -0
  121. package/dist/routes/automations.d.ts +19 -0
  122. package/dist/routes/billing.d.ts +49 -0
  123. package/dist/routes/browser-sessions.d.ts +59 -2
  124. package/dist/routes/claude-subscription-accounts.d.ts +3 -0
  125. package/dist/routes/claude-subscription-oauth.d.ts +3 -0
  126. package/dist/routes/client-errors.d.ts +146 -0
  127. package/dist/routes/codex.d.ts +76 -2
  128. package/dist/routes/company-brain.d.ts +3 -0
  129. package/dist/routes/company-profile.d.ts +2 -1
  130. package/dist/routes/computer-sessions.d.ts +14 -0
  131. package/dist/routes/connect.d.ts +5 -0
  132. package/dist/routes/editable-artifacts.d.ts +2 -0
  133. package/dist/routes/external-identity-links.d.ts +6 -0
  134. package/dist/routes/feedback.d.ts +3 -0
  135. package/dist/routes/github-browser-pages.d.ts +12 -0
  136. package/dist/routes/github.d.ts +2 -0
  137. package/dist/routes/insights-response-cache.d.ts +10 -0
  138. package/dist/routes/insights-usage.d.ts +12 -0
  139. package/dist/routes/insights.d.ts +30 -1
  140. package/dist/routes/knowledge.d.ts +3 -0
  141. package/dist/routes/managed-auth-session-sets.d.ts +26 -0
  142. package/dist/routes/managed-onboarding.d.ts +29 -0
  143. package/dist/routes/managed-sign-in-methods.d.ts +6 -0
  144. package/dist/routes/model-connection-access.d.ts +3 -0
  145. package/dist/routes/organization-integration-policy.d.ts +3 -0
  146. package/dist/routes/organization-integrations.d.ts +6 -0
  147. package/dist/routes/organization-mcp-connections.d.ts +3 -0
  148. package/dist/routes/organization-model-providers.d.ts +3 -0
  149. package/dist/routes/organization-recovery.d.ts +19 -0
  150. package/dist/routes/organization-service-accounts.d.ts +3 -0
  151. package/dist/routes/organization-sessions.d.ts +26 -0
  152. package/dist/routes/personal-github-git-broker.d.ts +29 -0
  153. package/dist/routes/personal-github.d.ts +3 -0
  154. package/dist/routes/pr-review-github.d.ts +3 -0
  155. package/dist/routes/pr-review.d.ts +3 -0
  156. package/dist/routes/session-artifact-associations.d.ts +4 -0
  157. package/dist/routes/session-history-imports.d.ts +9 -0
  158. package/dist/routes/sessions.d.ts +44 -6
  159. package/dist/routes/skill-content.d.ts +4 -0
  160. package/dist/routes/skill-install-authority.d.ts +6 -0
  161. package/dist/routes/subscription-account-pools.d.ts +34 -0
  162. package/dist/routes/subscription-pool-access.d.ts +16 -0
  163. package/dist/routes/supergrok.d.ts +6 -1
  164. package/dist/routes/usage-allowances.d.ts +12 -0
  165. package/dist/routes/user-resource-authorities.d.ts +3 -0
  166. package/dist/routes/workspace-artifacts.d.ts +2 -1
  167. package/dist/routes/workspace-capture.d.ts +10 -2
  168. package/dist/routes/workspace-integrations.d.ts +35 -0
  169. package/dist/routes/workspace-learning.d.ts +3 -0
  170. package/dist/routes/workspace-model-providers.d.ts +4 -0
  171. package/dist/routes/workspaces.d.ts +29 -0
  172. package/dist/sandbox/auth-callout.d.ts +8 -0
  173. package/dist/sandbox/channel-a.d.ts +19 -1
  174. package/dist/sandbox/connection-authority.d.ts +3 -0
  175. package/dist/sandbox/enrollment.d.ts +9 -1
  176. package/dist/sandbox/machines.d.ts +5 -2
  177. package/dist/sandbox/metrics-ingestion.d.ts +28 -20
  178. package/dist/sandbox/rematerialize.d.ts +2 -0
  179. package/dist/sandbox/session-attach-variable-sets.d.ts +30 -0
  180. package/dist/sandbox/viewer.d.ts +26 -1
  181. package/dist/sandbox-file-artifacts.d.ts +50 -0
  182. package/dist/scheduled-task-deletion.d.ts +13 -0
  183. package/dist/session-create-observability.d.ts +5 -0
  184. package/dist/session-event-view-MPS2OZD3.js +292 -0
  185. package/dist/session-event-view-MPS2OZD3.js.map +1 -0
  186. package/dist/site-session-origin.d.ts +13 -0
  187. package/dist/site-uploads.d.ts +41 -0
  188. package/dist/slack-reaction-files.d.ts +1 -1
  189. package/dist/temporal-schedule-cleanup.d.ts +1 -0
  190. package/dist/temporal-schedule-sync.d.ts +26 -0
  191. package/dist/transcription/providers/xai-subscription.d.ts +1 -0
  192. package/dist/transcription/service.d.ts +2 -1
  193. package/dist/work-discovery-observability.d.ts +33 -0
  194. package/dist/workspace-artifact-content.d.ts +28 -0
  195. package/dist/workspace-artifact-provenance.d.ts +7 -0
  196. package/dist/workspace-delete-observability.d.ts +10 -0
  197. package/dist/workspace-deletion.d.ts +6 -0
  198. package/dist/workspace-state-projection.d.ts +3 -5
  199. package/dist/workspace-tool-gateway-observability.d.ts +17 -0
  200. package/dist/workspace-tool-gateway.d.ts +123 -0
  201. package/dist/workspace-webhook-dispatch.d.ts +26 -0
  202. package/package.json +23 -19
  203. package/src/access-grant-rls.ts +75 -0
  204. package/src/admission-trace.ts +14 -0
  205. package/src/api-websocket.ts +23 -0
  206. package/src/app.ts +1967 -252
  207. package/src/artifact-catalog-cursor.ts +60 -0
  208. package/src/auth/managed-auth-attempt-context.ts +70 -0
  209. package/src/auth/managed-auth-rate-limits.ts +252 -0
  210. package/src/auth/managed-auth-session-adapter.ts +222 -0
  211. package/src/auth/managed-auth.ts +536 -60
  212. package/src/auth/managed-email.ts +174 -0
  213. package/src/auth/managed-sign-in-notifications.ts +81 -0
  214. package/src/auth/new-signups-gate.ts +49 -0
  215. package/src/auth/organization-user-setup.ts +307 -0
  216. package/src/auth/signup-funnel-metrics.ts +164 -0
  217. package/src/browser-controller-authority.ts +1 -186
  218. package/src/browser-ephemeral.ts +39 -0
  219. package/src/browser-state-authority.ts +1 -236
  220. package/src/claude-subscription-account-conflicts.ts +12 -0
  221. package/src/claude-subscription-account-usage.ts +48 -0
  222. package/src/claude-subscription-oauth.ts +362 -0
  223. package/src/claude-subscription-profile.ts +73 -0
  224. package/src/claude-subscription-usage.ts +97 -0
  225. package/src/claude-workspace-connection.ts +82 -0
  226. package/src/codemode.ts +194 -30
  227. package/src/codex-realtime.ts +8 -2
  228. package/src/company-brain-okf.ts +340 -0
  229. package/src/connected-machine-computer-access.ts +33 -0
  230. package/src/connection-ownership.ts +199 -0
  231. package/src/controller-data-plane.ts +81 -0
  232. package/src/editable-artifact-native-kernel.ts +26 -15
  233. package/src/editable-artifact-office-import.ts +28 -3
  234. package/src/editable-artifact-production.ts +23 -7
  235. package/src/editable-artifact-source-session.ts +123 -0
  236. package/src/editable-artifact-websocket.ts +27 -21
  237. package/src/editable-artifact-workspace-files.ts +31 -6
  238. package/src/fatal-process-boundary.ts +246 -0
  239. package/src/gateway-realtime.ts +1 -1
  240. package/src/github-access.ts +147 -5
  241. package/src/http/acting-person.ts +71 -0
  242. package/src/http/api-contract-compat.ts +49 -0
  243. package/src/http/api-error.ts +115 -1
  244. package/src/http/auth.ts +48 -0
  245. package/src/http/cors.ts +67 -0
  246. package/src/http/deprecation.ts +118 -0
  247. package/src/http/interaction-control-error.ts +164 -0
  248. package/src/http/keyed-admission.ts +29 -0
  249. package/src/http/local-browser-boundary.ts +343 -0
  250. package/src/http/metrics-listener.ts +57 -0
  251. package/src/http/path-identifier.ts +54 -0
  252. package/src/http/registered-route-label.ts +34 -0
  253. package/src/http/request-body.ts +127 -0
  254. package/src/http/request-source.ts +178 -0
  255. package/src/http/sse.ts +486 -132
  256. package/src/http/unmatched-route.ts +60 -0
  257. package/src/http/user-content.ts +158 -0
  258. package/src/http/websocket-upgrade-dispatch.ts +29 -0
  259. package/src/index.ts +157 -52
  260. package/src/integration-endpoint-test.ts +291 -0
  261. package/src/integrations/api-integrations.ts +98 -11
  262. package/src/integrations/atlassian.ts +474 -136
  263. package/src/integrations/connect-authority.ts +2 -0
  264. package/src/integrations/connect-callback-return.ts +86 -0
  265. package/src/integrations/fiken.ts +354 -64
  266. package/src/integrations/github-app-connect.ts +435 -0
  267. package/src/integrations/github-installation-proof.ts +60 -0
  268. package/src/integrations/github-lens-connect.ts +76 -0
  269. package/src/integrations/github-skill-source.ts +2 -142
  270. package/src/integrations/google-drive.ts +717 -225
  271. package/src/integrations/integration-commit-authority.ts +69 -0
  272. package/src/integrations/marketplace-plugin.ts +48 -0
  273. package/src/integrations/oauth-client.ts +1356 -459
  274. package/src/integrations/oauth-profiles.ts +537 -0
  275. package/src/integrations/oauth-return-path.ts +18 -0
  276. package/src/integrations/personal-github-repositories.ts +674 -0
  277. package/src/integrations/personal-github.ts +825 -0
  278. package/src/integrations/pr-review-provider.ts +246 -0
  279. package/src/integrations/provider-oauth.ts +250 -22
  280. package/src/integrations/slack-app-home.ts +300 -0
  281. package/src/integrations/slack-bot.ts +963 -239
  282. package/src/integrations/slack-file-upload-flow.ts +147 -0
  283. package/src/integrations/slack-install.ts +83 -0
  284. package/src/integrations/slack-interactions.ts +3167 -474
  285. package/src/integrations/slack-mrkdwn.ts +193 -0
  286. package/src/integrations/slack-routing.ts +337 -0
  287. package/src/integrations/slack-session-defaults.ts +224 -0
  288. package/src/integrations/slack-task-file-upload.ts +323 -0
  289. package/src/integrations/social-oauth.ts +213 -15
  290. package/src/interaction-agent-access.ts +48 -0
  291. package/src/interaction-frame-proxy.ts +409 -0
  292. package/src/interaction-metrics.ts +30 -0
  293. package/src/mcp/action-catalog-types.ts +9 -0
  294. package/src/mcp/action-catalog.gen.ts +6696 -0
  295. package/src/mcp/capability-account-readiness.ts +17 -0
  296. package/src/mcp/child-read-evidence.ts +19 -0
  297. package/src/mcp/command-read.ts +109 -0
  298. package/src/mcp/company-profile-agent-admin.ts +198 -0
  299. package/src/mcp/contract-input.ts +141 -0
  300. package/src/mcp/documents.ts +65 -377
  301. package/src/mcp/editable-artifacts.ts +12 -6
  302. package/src/mcp/files.ts +80 -43
  303. package/src/mcp/first-party-tool-permissions.ts +305 -0
  304. package/src/mcp/knowledge-entries.ts +322 -0
  305. package/src/mcp/orchestration-failure-diagnostic.ts +43 -0
  306. package/src/mcp/receipts.ts +34 -0
  307. package/src/mcp/request-abort.ts +44 -0
  308. package/src/mcp/scheduled-task-input.ts +76 -0
  309. package/src/mcp/scheduled-task-view.ts +44 -3
  310. package/src/mcp/server.ts +3030 -1267
  311. package/src/mcp/session-event-view.ts +383 -0
  312. package/src/mcp/session-view.ts +84 -3
  313. package/src/mcp/session-wait.ts +665 -0
  314. package/src/mcp-oauth.ts +1123 -0
  315. package/src/model-catalog.ts +91 -333
  316. package/src/observability.ts +15 -1
  317. package/src/organization-mcp.ts +397 -0
  318. package/src/organization-recovery-notifications.ts +103 -0
  319. package/src/routes/analytics-consent.ts +123 -0
  320. package/src/routes/api-integrations.ts +364 -74
  321. package/src/routes/api-keys.ts +336 -10
  322. package/src/routes/artifact-catalog.ts +279 -0
  323. package/src/routes/automations.ts +768 -0
  324. package/src/routes/billing.ts +461 -46
  325. package/src/routes/browser-sessions.ts +780 -112
  326. package/src/routes/canonical-human-identities.ts +90 -14
  327. package/src/routes/capabilities.ts +107 -6
  328. package/src/routes/channels.ts +20 -3
  329. package/src/routes/claude-subscription-accounts.ts +334 -0
  330. package/src/routes/claude-subscription-oauth.ts +142 -0
  331. package/src/routes/client-errors.ts +362 -0
  332. package/src/routes/codex.ts +833 -116
  333. package/src/routes/company-brain.ts +322 -0
  334. package/src/routes/company-profile.ts +65 -1
  335. package/src/routes/computer-sessions.ts +557 -106
  336. package/src/routes/connect.ts +1938 -0
  337. package/src/routes/connections.ts +1039 -349
  338. package/src/routes/documents.ts +488 -287
  339. package/src/routes/editable-artifacts.ts +79 -9
  340. package/src/routes/enrollments.ts +135 -39
  341. package/src/routes/environments.ts +154 -58
  342. package/src/routes/external-identity-links.ts +201 -0
  343. package/src/routes/feedback.ts +113 -0
  344. package/src/routes/files.ts +267 -33
  345. package/src/routes/github-browser-pages.ts +162 -0
  346. package/src/routes/github.ts +446 -144
  347. package/src/routes/insights-response-cache.ts +83 -0
  348. package/src/routes/insights-usage.ts +345 -0
  349. package/src/routes/insights.ts +153 -19
  350. package/src/routes/install.ts +133 -38
  351. package/src/routes/integration-facets.ts +107 -33
  352. package/src/routes/interaction-resources.ts +30 -3
  353. package/src/routes/knowledge.ts +488 -0
  354. package/src/routes/machines.ts +313 -15
  355. package/src/routes/managed-auth-session-sets.ts +1218 -0
  356. package/src/routes/managed-onboarding.ts +337 -0
  357. package/src/routes/managed-sign-in-methods.ts +433 -0
  358. package/src/routes/memory-slack-publications.ts +3 -2
  359. package/src/routes/model-connection-access.ts +208 -0
  360. package/src/routes/organization-integration-policy.ts +116 -0
  361. package/src/routes/organization-integrations.ts +326 -0
  362. package/src/routes/organization-mcp-connections.ts +178 -0
  363. package/src/routes/organization-memberships.ts +1019 -27
  364. package/src/routes/organization-model-providers.ts +293 -0
  365. package/src/routes/organization-recovery.ts +325 -0
  366. package/src/routes/organization-service-accounts.ts +128 -0
  367. package/src/routes/organization-sessions.ts +350 -0
  368. package/src/routes/personal-github-git-broker.ts +785 -0
  369. package/src/routes/personal-github.ts +370 -0
  370. package/src/routes/plugins.ts +251 -22
  371. package/src/routes/pr-review-github.ts +881 -0
  372. package/src/routes/pr-review.ts +622 -0
  373. package/src/routes/preference-registry.ts +12 -93
  374. package/src/routes/rigs.ts +167 -50
  375. package/src/routes/scheduled-tasks.ts +346 -41
  376. package/src/routes/session-artifact-associations.ts +87 -0
  377. package/src/routes/session-history-imports.ts +280 -0
  378. package/src/routes/sessions.ts +2306 -264
  379. package/src/routes/skill-content.ts +297 -0
  380. package/src/routes/skill-install-authority.ts +64 -0
  381. package/src/routes/skills.ts +62 -9
  382. package/src/routes/social.ts +46 -18
  383. package/src/routes/subscription-account-pools.ts +436 -0
  384. package/src/routes/subscription-pool-access.ts +126 -0
  385. package/src/routes/supergrok.ts +219 -303
  386. package/src/routes/transcription-recordings.ts +38 -3
  387. package/src/routes/transcriptions.ts +4 -0
  388. package/src/routes/usage-allowances.ts +378 -0
  389. package/src/routes/user-resource-authorities.ts +138 -0
  390. package/src/routes/video-generation.ts +14 -10
  391. package/src/routes/workspace-artifacts.ts +275 -69
  392. package/src/routes/workspace-capture.ts +21 -0
  393. package/src/routes/workspace-integrations.ts +576 -0
  394. package/src/routes/workspace-learning.ts +159 -0
  395. package/src/routes/workspace-model-providers.ts +148 -0
  396. package/src/routes/workspace-state.ts +38 -25
  397. package/src/routes/workspaces.ts +1004 -127
  398. package/src/sandbox/auth-callout.ts +96 -19
  399. package/src/sandbox/channel-a.ts +323 -24
  400. package/src/sandbox/connection-authority.ts +3 -0
  401. package/src/sandbox/enrollment.ts +81 -6
  402. package/src/sandbox/machines.ts +218 -76
  403. package/src/sandbox/metrics-ingestion.ts +411 -62
  404. package/src/sandbox/rematerialize.ts +121 -19
  405. package/src/sandbox/session-attach-variable-sets.ts +131 -0
  406. package/src/sandbox/viewer.ts +280 -25
  407. package/src/sandbox-file-artifacts.ts +413 -0
  408. package/src/scheduled-task-deletion.ts +138 -0
  409. package/src/session-create-observability.ts +46 -0
  410. package/src/session-realtime-context.ts +6 -3
  411. package/src/site-session-origin.ts +48 -0
  412. package/src/site-uploads.ts +224 -0
  413. package/src/slack-reaction-files.ts +16 -5
  414. package/src/temporal-schedule-cleanup.ts +13 -0
  415. package/src/temporal-schedule-sync.ts +74 -0
  416. package/src/transcription/providers/codex-subscription.ts +2 -0
  417. package/src/transcription/providers/openai.ts +1 -0
  418. package/src/transcription/providers/xai-subscription.ts +49 -3
  419. package/src/transcription/segmenter.ts +8 -2
  420. package/src/transcription/service.ts +43 -6
  421. package/src/work-discovery-observability.ts +121 -0
  422. package/src/workspace-artifact-content.ts +144 -0
  423. package/src/workspace-artifact-provenance.ts +104 -0
  424. package/src/workspace-delete-observability.ts +75 -0
  425. package/src/workspace-deletion.ts +73 -0
  426. package/src/workspace-state-projection.ts +18 -148
  427. package/src/workspace-tool-gateway-observability.ts +100 -0
  428. package/src/workspace-tool-gateway.ts +870 -0
  429. package/src/workspace-webhook-dispatch.ts +190 -0
  430. package/src/xai-subscription-auth.ts +9 -2
  431. package/dist/chunk-WCXHI3FF.js +0 -49920
  432. package/dist/chunk-WCXHI3FF.js.map +0 -1
  433. package/dist/routes/packs.d.ts +0 -3
  434. package/src/routes/packs.ts +0 -729
@@ -1,18 +1,49 @@
1
- import type { Settings } from "@opengeni/config";
2
- import { type ManagedAuth } from "@opengeni/core";
1
+ import { canonicalPublicOrigin, managedUserEmailAllowed, type Settings } from "@opengeni/config";
2
+ import { MANAGED_AUTH_NEW_SIGNUPS_PAUSED_CODE } from "@opengeni/contracts";
3
+ import {
4
+ configureManagedUserAdmission,
5
+ type ManagedAuth,
6
+ type ManagedEmailMessage,
7
+ type ManagedEmailTransport,
8
+ } from "@opengeni/core";
3
9
  import type { Database } from "@opengeni/db";
4
10
  import { ensureManagedAccessForUser } from "@opengeni/db";
5
11
  import {
6
12
  ensureCanonicalHumanIdentityForAuthUser,
7
13
  getCanonicalHumanIdentityProjection,
14
+ getCanonicalHumanExactLoginBindingForAuthUser,
8
15
  synchronizeCanonicalHumanLoginBindings,
9
16
  } from "@opengeni/db/canonical-human-identities";
17
+ import type { Observability } from "@opengeni/observability";
10
18
  import { betterAuth } from "better-auth";
11
- import { createEmailVerificationToken } from "better-auth/api";
12
- import { Pool } from "pg";
13
- import { Resend } from "resend";
19
+ import { APIError, createEmailVerificationToken } from "better-auth/api";
20
+ import { hashPassword, verifyPassword } from "better-auth/crypto";
21
+ import { sql } from "drizzle-orm";
22
+ import { Pool, type PoolConfig } from "pg";
14
23
 
24
+ import { TRUSTED_CLIENT_ADDRESS_HEADER } from "../http/request-source";
15
25
  import { decideCanonicalHumanSessionAdmission } from "./canonical-human-session-admission";
26
+ import {
27
+ createManagedAuthEmailThrottleHook,
28
+ MANAGED_AUTH_CLIENT_RATE_LIMIT_RULES,
29
+ } from "./managed-auth-rate-limits";
30
+ import { deliverManagedSignInNotification } from "./managed-sign-in-notifications";
31
+ import {
32
+ createManagedAuthNewSignupsGate,
33
+ type ManagedAuthNewSignupsGate,
34
+ } from "./new-signups-gate";
35
+ import { createSignupFunnelMetrics } from "./signup-funnel-metrics";
36
+ import {
37
+ currentManagedAuthProviderId,
38
+ currentManagedAuthAttemptId,
39
+ recordCurrentManagedAuthSession,
40
+ shouldDiscardCurrentManagedAuthProviderSession,
41
+ currentManagedSignInConnectIntent,
42
+ } from "./managed-auth-attempt-context";
43
+
44
+ // Only used when no Better Auth secret is configured (local development);
45
+ // the email throttle key is a content-free digest either way.
46
+ const MANAGED_AUTH_EMAIL_THROTTLE_FALLBACK_KEY = "opengeni:managed-auth:email-throttle:local";
16
47
 
17
48
  // `ManagedAuth` (the Better Auth `Auth<any>` alias) is owned by @opengeni/core
18
49
  // (`managed-auth-type.ts`) — `dependencies.ts`/`access` reference it as a
@@ -20,19 +51,214 @@ import { decideCanonicalHumanSessionAdmission } from "./canonical-human-session-
20
51
  // importers (`app.ts`) keep the same import path.
21
52
  export type { ManagedAuth };
22
53
 
23
- export function createManagedAuth(settings: Settings, db: Database): ManagedAuth | null {
54
+ export function managedAuthRequiresEmailVerification(
55
+ settings: Pick<Settings, "environment">,
56
+ ): boolean {
57
+ return settings.environment !== "local";
58
+ }
59
+
60
+ export function managedAuthUserCreateOverride(
61
+ settings: Pick<Settings, "environment">,
62
+ user: { emailVerified: boolean } & Record<string, unknown>,
63
+ ): { data: typeof user } | undefined {
64
+ if (managedAuthRequiresEmailVerification(settings)) return undefined;
65
+ return { data: { ...user, emailVerified: true } };
66
+ }
67
+
68
+ export const MANAGED_AUTH_NEW_SIGNUPS_PAUSED_MESSAGE =
69
+ "We're at capacity for new accounts right now. Please try again later.";
70
+
71
+ /**
72
+ * Typed refusal for a new account while the deployment has paused sign-ups.
73
+ * Existing accounts are unaffected; the body mirrors Better Auth's
74
+ * `{ code, message }` error shape so browser and SDK callers parse it alike.
75
+ */
76
+ export function managedAuthNewSignupsPausedResponse(): Response {
77
+ return Response.json(
78
+ {
79
+ code: MANAGED_AUTH_NEW_SIGNUPS_PAUSED_CODE,
80
+ message: MANAGED_AUTH_NEW_SIGNUPS_PAUSED_MESSAGE,
81
+ },
82
+ { status: 403, headers: { "cache-control": "no-store" } },
83
+ );
84
+ }
85
+
86
+ /**
87
+ * Thrown from the user-create hook while the runtime switch has paused new
88
+ * sign-ups. Better Auth returns it as `403 { code: "NEW_SIGNUPS_PAUSED" }` on
89
+ * email sign-up, and its OAuth callback turns the message into the
90
+ * `error=signup_disabled` redirect it also uses for a static provider refusal.
91
+ */
92
+ export class ManagedAuthNewSignupsPausedError extends APIError {
93
+ constructor() {
94
+ super("FORBIDDEN", { code: MANAGED_AUTH_NEW_SIGNUPS_PAUSED_CODE, message: "signup disabled" });
95
+ }
96
+ }
97
+
98
+ export function managedAuthUserCreateAdmission(
99
+ settings: Pick<Settings, "environment" | "allowedUserEmails"> &
100
+ Partial<Pick<Settings, "managedAuthNewSignupsEnabled">>,
101
+ user: { emailVerified: boolean } & Record<string, unknown>,
102
+ providerId: string,
103
+ ): { data: typeof user } | false | undefined {
104
+ // Backstop for every Better Auth user-create path. Invited-user account
105
+ // setup does not create users through Better Auth, so it is unaffected.
106
+ if (settings.managedAuthNewSignupsEnabled === false) return false;
107
+ if (!managedUserEmailAllowed(settings.allowedUserEmails, user.email)) return false;
108
+ if (
109
+ managedAuthRequiresEmailVerification(settings) &&
110
+ providerId !== "credential" &&
111
+ !user.emailVerified
112
+ ) {
113
+ return false;
114
+ }
115
+ return managedAuthUserCreateOverride(settings, user);
116
+ }
117
+
118
+ /**
119
+ * Per-provider Better Auth options while sign-ups are paused. A social sign-in
120
+ * for an existing human still signs in (and may link a verified provider);
121
+ * an unknown human is redirected back with `error=signup_disabled`.
122
+ * `disableSignUp` is the hard switch: `disableImplicitSignUp` alone can be
123
+ * overridden by a client-supplied `requestSignUp`.
124
+ */
125
+ export function managedAuthSocialSignupOptions(
126
+ settings: Pick<Settings, "managedAuthNewSignupsEnabled">,
127
+ ): { disableImplicitSignUp?: true; disableSignUp?: true } {
128
+ return settings.managedAuthNewSignupsEnabled
129
+ ? {}
130
+ : { disableImplicitSignUp: true, disableSignUp: true };
131
+ }
132
+
133
+ /** Keep Better Auth password policy and storage format behind this boundary. */
134
+ export async function hashManagedAuthPassword(password: string): Promise<string> {
135
+ return await hashPassword(password);
136
+ }
137
+
138
+ export async function verifyManagedAuthPassword(password: string, hash: string): Promise<boolean> {
139
+ return await verifyPassword({ password, hash });
140
+ }
141
+
142
+ type ManagedAuthPoolObservability = Pick<Observability, "warn" | "incrementCounter">;
143
+
144
+ /**
145
+ * Bounded pool settings for Better Auth's dedicated `pg` pool. A connection
146
+ * attempt fails after 10 s instead of queueing requests indefinitely while the
147
+ * database is unreachable, and connections recycle like the main postgres-js
148
+ * pool so a pooler- or failover-replaced backend is not held forever.
149
+ */
150
+ export const MANAGED_AUTH_DATABASE_POOL_OPTIONS = {
151
+ max: 10,
152
+ connectionTimeoutMillis: 10_000,
153
+ idleTimeoutMillis: 30_000,
154
+ maxLifetimeSeconds: 1_800,
155
+ keepAlive: true,
156
+ } as const satisfies PoolConfig;
157
+
158
+ /**
159
+ * Better Auth's `pg` pool. When the server closes a connection (a restart,
160
+ * failover, or maintenance window) `pg` emits `error`: on the pool for an idle
161
+ * client, and on the client itself for one checked out by Better Auth's query
162
+ * layer. Without listeners either becomes an uncaught exception and the API's
163
+ * fatal process boundary exits. Both listeners record one content-free signal
164
+ * and keep serving. The pool has already discarded a failed idle client and
165
+ * opens a fresh connection for the next checkout; a checked-out client's
166
+ * in-flight query already rejects to its caller, and the pool discards the
167
+ * dead client on release. An idle client's error also reaches its own
168
+ * listener, so that listener records only while the client is checked out,
169
+ * and at most once per checkout.
170
+ */
171
+ export function createManagedAuthDatabasePool(
172
+ databaseUrl: string,
173
+ observability?: ManagedAuthPoolObservability,
174
+ ): Pool {
175
+ const pool = new Pool({ connectionString: databaseUrl, ...MANAGED_AUTH_DATABASE_POOL_OPTIONS });
176
+ const checkedOut = new WeakSet<object>();
177
+ pool.on("acquire", (client) => checkedOut.add(client));
178
+ pool.on("release", (_error, client) => checkedOut.delete(client));
179
+ pool.on("connect", (client) => {
180
+ client.on("error", () => {
181
+ // One lost connection can emit twice (the server's termination
182
+ // message, then the socket end), so count it once.
183
+ if (checkedOut.delete(client)) {
184
+ recordManagedAuthPoolError(observability, "checked_out_connection_failed");
185
+ }
186
+ });
187
+ });
188
+ pool.on("error", () => recordManagedAuthPoolError(observability, "idle_connection_discarded"));
189
+ return pool;
190
+ }
191
+
192
+ export const MANAGED_AUTH_DATABASE_POOL_ERRORS_METRIC = {
193
+ name: "opengeni_managed_auth_database_pool_errors_total",
194
+ help: "Managed-auth database connections the server closed or lost, by pool state.",
195
+ } as const;
196
+
197
+ function recordManagedAuthPoolError(
198
+ observability: ManagedAuthPoolObservability | undefined,
199
+ outcome: "idle_connection_discarded" | "checked_out_connection_failed",
200
+ ): void {
201
+ try {
202
+ observability?.incrementCounter({
203
+ ...MANAGED_AUTH_DATABASE_POOL_ERRORS_METRIC,
204
+ labels: { outcome },
205
+ });
206
+ observability?.warn("Managed auth database connection failed", {
207
+ dependency: "managed_auth_database",
208
+ outcome,
209
+ });
210
+ } catch {
211
+ // An observer failure must not turn a recovered pool error into a crash.
212
+ }
213
+ }
214
+
215
+ export function createManagedAuth(
216
+ settings: Settings,
217
+ db: Database,
218
+ managedEmailTransport: ManagedEmailTransport,
219
+ options: {
220
+ observability?: ManagedAuthPoolObservability;
221
+ newSignupsGate?: ManagedAuthNewSignupsGate;
222
+ } = {},
223
+ ): ManagedAuth | null {
24
224
  if (settings.productAccessMode !== "managed") {
25
225
  return null;
26
226
  }
27
- const pool = new Pool({ connectionString: settings.databaseUrl });
28
- return betterAuth({
29
- appName: "OpenGeni",
227
+ // Content-free sign-up funnel counters; recording never fails an auth flow.
228
+ const funnel = options.observability
229
+ ? createSignupFunnelMetrics(options.observability)
230
+ : undefined;
231
+ const requireEmailVerification = managedAuthRequiresEmailVerification(settings);
232
+ const pool = createManagedAuthDatabasePool(settings.databaseUrl, options.observability);
233
+ // The deployment ceiling is static Better Auth configuration; the runtime
234
+ // switch is checked per user create in the hook below.
235
+ const newSignupsSocialProviderOptions = managedAuthSocialSignupOptions(settings);
236
+ const newSignupsGate =
237
+ options.newSignupsGate ??
238
+ createManagedAuthNewSignupsGate({ db, settings, observability: options.observability });
239
+ const auth = betterAuth({
240
+ appName: "Opengeni",
30
241
  baseURL: betterAuthBaseUrl(settings),
31
242
  basePath: "/v1/auth",
32
243
  secret: settings.betterAuthSecret,
33
244
  database: pool,
34
245
  trustedOrigins: betterAuthTrustedOrigins(settings),
246
+ hooks: {
247
+ before: createManagedAuthEmailThrottleHook(
248
+ db,
249
+ settings.betterAuthSecret ?? MANAGED_AUTH_EMAIL_THROTTLE_FALLBACK_KEY,
250
+ ),
251
+ },
35
252
  advanced: {
253
+ // Better Auth reads client addresses only from request headers. The API
254
+ // strips this header from every inbound request and re-stamps it on
255
+ // managed-auth routes with the trusted source address (transport peer,
256
+ // or the forwarded client when OPENGENI_API_TRUSTED_PROXY_HOPS declares
257
+ // a proxy chain), so callers cannot choose their rate-limit bucket or
258
+ // recorded session address.
259
+ ipAddress: {
260
+ ipAddressHeaders: [TRUSTED_CLIENT_ADDRESS_HEADER],
261
+ },
36
262
  useSecureCookies: settings.publicBaseUrl?.startsWith("https://") ?? false,
37
263
  ...(settings.betterAuthCookieDomain
38
264
  ? {
@@ -53,6 +279,7 @@ export function createManagedAuth(settings: Settings, db: Database): ManagedAuth
53
279
  fields: {
54
280
  lastRequest: "last_request",
55
281
  },
282
+ customRules: MANAGED_AUTH_CLIENT_RATE_LIMIT_RULES,
56
283
  },
57
284
  user: {
58
285
  modelName: "auth_users",
@@ -93,6 +320,25 @@ export function createManagedAuth(settings: Settings, db: Database): ManagedAuth
93
320
  returned: false,
94
321
  bigint: true,
95
322
  },
323
+ loginBindingId: {
324
+ type: "string",
325
+ fieldName: "login_binding_id",
326
+ input: false,
327
+ returned: false,
328
+ },
329
+ loginBindingRevision: {
330
+ type: "number",
331
+ fieldName: "login_binding_revision",
332
+ input: false,
333
+ returned: false,
334
+ bigint: true,
335
+ },
336
+ managedAuthLoginTransactionId: {
337
+ type: "string",
338
+ fieldName: "managed_auth_login_transaction_id",
339
+ input: false,
340
+ returned: false,
341
+ },
96
342
  },
97
343
  },
98
344
  account: {
@@ -110,8 +356,42 @@ export function createManagedAuth(settings: Settings, db: Database): ManagedAuth
110
356
  updatedAt: "updated_at",
111
357
  },
112
358
  accountLinking: {
113
- enabled: false,
359
+ enabled: true,
360
+ requireLocalEmailVerified: true,
361
+ trustedProviders: [],
362
+ allowDifferentEmails: false,
363
+ },
364
+ additionalFields: {
365
+ managedLinkIntentId: {
366
+ type: "string",
367
+ fieldName: "managed_link_intent_id",
368
+ input: false,
369
+ returned: false,
370
+ },
114
371
  },
372
+ encryptOAuthTokens: true,
373
+ storeStateStrategy: "database",
374
+ },
375
+ socialProviders: {
376
+ ...(settings.managedAuthGoogleClientId && settings.managedAuthGoogleClientSecret
377
+ ? {
378
+ google: {
379
+ clientId: settings.managedAuthGoogleClientId,
380
+ clientSecret: settings.managedAuthGoogleClientSecret,
381
+ prompt: "select_account" as const,
382
+ ...newSignupsSocialProviderOptions,
383
+ },
384
+ }
385
+ : {}),
386
+ ...(settings.managedAuthGithubClientId && settings.managedAuthGithubClientSecret
387
+ ? {
388
+ github: {
389
+ clientId: settings.managedAuthGithubClientId,
390
+ clientSecret: settings.managedAuthGithubClientSecret,
391
+ ...newSignupsSocialProviderOptions,
392
+ },
393
+ }
394
+ : {}),
115
395
  },
116
396
  verification: {
117
397
  modelName: "auth_verifications",
@@ -124,50 +404,94 @@ export function createManagedAuth(settings: Settings, db: Database): ManagedAuth
124
404
  },
125
405
  emailAndPassword: {
126
406
  enabled: true,
127
- requireEmailVerification: true,
407
+ // Sign-in, password reset, and verification stay enabled; only new
408
+ // account creation is refused while sign-ups are paused.
409
+ disableSignUp: !settings.managedAuthNewSignupsEnabled,
410
+ // Local managed mode exists so the complete human/org tenancy flow can
411
+ // be exercised without first configuring a transactional-email vendor.
412
+ // Every non-local deployment retains the verified-email boundary.
413
+ requireEmailVerification,
128
414
  revokeSessionsOnPasswordReset: true,
129
415
  onExistingUserSignUp: async ({ user }) => {
130
- if (!user.emailVerified) {
416
+ if (requireEmailVerification && !user.emailVerified) {
131
417
  const url = await verificationUrl(settings, user.email);
132
- await sendEmail(settings, {
133
- to: user.email,
134
- subject: "Verify your OpenGeni email",
135
- text: `Verify your OpenGeni email: ${url}`,
136
- html: `<p>Verify your OpenGeni email:</p><p><a href="${escapeHtml(url)}">Verify email</a></p>`,
137
- });
418
+ await sendManagedAuthEmail(
419
+ managedEmailTransport,
420
+ emailVerificationMessage(user.email, url),
421
+ );
138
422
  }
139
423
  },
140
424
  sendResetPassword: async ({ user, url }) => {
141
- await sendEmail(settings, {
425
+ await sendManagedAuthEmail(managedEmailTransport, {
426
+ kind: "password_reset",
142
427
  to: user.email,
143
- subject: "Reset your OpenGeni password",
144
- text: `Reset your OpenGeni password: ${url}`,
145
- html: `<p>Reset your OpenGeni password:</p><p><a href="${escapeHtml(url)}">Reset password</a></p>`,
428
+ subject: "Reset your Opengeni password",
429
+ text: `Reset your Opengeni password: ${url}`,
430
+ html: `<p>Reset your Opengeni password:</p><p><a href="${escapeHtml(url)}">Reset password</a></p>`,
146
431
  });
147
432
  },
148
433
  },
149
434
  emailVerification: {
150
- sendOnSignUp: true,
435
+ sendOnSignUp: requireEmailVerification,
436
+ // The first successful verification link click signs the user in (a
437
+ // reused link creates no session). Only legacy mode: session-set modes
438
+ // bind sign-in to an isolated browser transaction, and the provider
439
+ // session this would create is discarded there by design.
440
+ autoSignInAfterVerification: settings.managedAuthSessionSetMode === "legacy",
151
441
  sendVerificationEmail: async ({ user, url }) => {
152
- await sendEmail(settings, {
153
- to: user.email,
154
- subject: "Verify your OpenGeni email",
155
- text: `Verify your OpenGeni email: ${url}`,
156
- html: `<p>Verify your OpenGeni email:</p><p><a href="${escapeHtml(url)}">Verify email</a></p>`,
157
- });
442
+ await sendManagedAuthEmail(
443
+ managedEmailTransport,
444
+ emailVerificationMessage(user.email, url),
445
+ );
158
446
  },
159
447
  afterEmailVerification: async (user) => {
448
+ funnel?.recordEmailVerified();
160
449
  await ensureManagedAccessForUser(db, {
161
450
  userId: user.id,
162
451
  email: user.email,
163
452
  name: user.name,
453
+ emailVerified: true,
454
+ provisionFallbackOrganization: false,
164
455
  });
165
456
  },
166
457
  },
167
458
  databaseHooks: {
459
+ account: {
460
+ create: {
461
+ before: async (account) => ({
462
+ data: {
463
+ ...account,
464
+ ...(currentManagedSignInConnectIntent()
465
+ ? { managedLinkIntentId: currentManagedSignInConnectIntent() }
466
+ : {}),
467
+ },
468
+ }),
469
+ after: async (account) => {
470
+ if (
471
+ !["google", "github"].includes(account.providerId) ||
472
+ currentManagedSignInConnectIntent()
473
+ )
474
+ return;
475
+ await deliverManagedSignInNotification(db, managedEmailTransport, account.id);
476
+ },
477
+ },
478
+ },
168
479
  session: {
169
480
  create: {
170
481
  before: async (session) => {
482
+ if (settings.allowedUserEmails !== undefined) {
483
+ const result = await pool.query<{ email: string; email_verified: boolean }>(
484
+ "select email, email_verified from auth_users where id = $1",
485
+ [session.userId],
486
+ );
487
+ const user = result.rows[0];
488
+ if (
489
+ !user?.email_verified ||
490
+ !managedUserEmailAllowed(settings.allowedUserEmails, user.email)
491
+ )
492
+ return false;
493
+ }
494
+ const providerId = currentManagedAuthProviderId();
171
495
  await ensureCanonicalHumanIdentityForAuthUser(db, session.userId);
172
496
  const preflightProjection = await getCanonicalHumanIdentityProjection(
173
497
  db,
@@ -179,8 +503,13 @@ export function createManagedAuth(settings: Settings, db: Database): ManagedAuth
179
503
  binding: null,
180
504
  });
181
505
  if (!preflight.allowed) {
506
+ const exactRecoveryBinding = await getCanonicalHumanExactLoginBindingForAuthUser(db, {
507
+ authUserId: session.userId,
508
+ providerId,
509
+ });
182
510
  const recoveryBinding = preflightProjection.loginBindings.find(
183
- (binding) => binding.status === "recovery_pending",
511
+ (binding) =>
512
+ binding.id === exactRecoveryBinding.id && binding.status === "recovery_pending",
184
513
  );
185
514
  const recoveryAdmission = decideCanonicalHumanSessionAdmission({
186
515
  intent: "recovery_completion",
@@ -199,17 +528,27 @@ export function createManagedAuth(settings: Settings, db: Database): ManagedAuth
199
528
  return {
200
529
  data: {
201
530
  ...session,
531
+ ...(shouldDiscardCurrentManagedAuthProviderSession()
532
+ ? { expiresAt: new Date(0) }
533
+ : {}),
202
534
  identityId: preflightProjection.activeIdentity.id,
203
535
  identityRevision: preflightProjection.activeIdentity.identityRevision,
204
536
  authRevision: preflightProjection.activeIdentity.authRevision,
537
+ loginBindingId: recoveryBinding!.id,
538
+ loginBindingRevision: recoveryBinding!.revision,
539
+ managedAuthLoginTransactionId: currentManagedAuthAttemptId(),
205
540
  },
206
541
  };
207
542
  }
208
543
 
209
544
  await synchronizeCanonicalHumanLoginBindings(db, session.userId);
210
545
  const projection = await getCanonicalHumanIdentityProjection(db, session.userId);
546
+ const exactBinding = await getCanonicalHumanExactLoginBindingForAuthUser(db, {
547
+ authUserId: session.userId,
548
+ providerId,
549
+ });
211
550
  const activeBinding = projection.loginBindings.find(
212
- (binding) => binding.id === projection.activeIdentity.activeLoginBindingId,
551
+ (binding) => binding.id === exactBinding.id,
213
552
  );
214
553
  const admission = decideCanonicalHumanSessionAdmission({
215
554
  intent: "ordinary_session",
@@ -229,27 +568,174 @@ export function createManagedAuth(settings: Settings, db: Database): ManagedAuth
229
568
  return {
230
569
  data: {
231
570
  ...session,
571
+ ...(shouldDiscardCurrentManagedAuthProviderSession()
572
+ ? { expiresAt: new Date(0) }
573
+ : {}),
232
574
  identityId: projection.activeIdentity.id,
233
575
  identityRevision: projection.activeIdentity.identityRevision,
234
576
  authRevision: projection.activeIdentity.authRevision,
577
+ loginBindingId: exactBinding.id,
578
+ loginBindingRevision: exactBinding.revision,
579
+ managedAuthLoginTransactionId: currentManagedAuthAttemptId(),
235
580
  },
236
581
  };
237
582
  },
583
+ after: async (session) => {
584
+ funnel?.recordSignIn();
585
+ recordCurrentManagedAuthSession(session.id);
586
+ if (!shouldDiscardCurrentManagedAuthProviderSession()) return;
587
+ await db.execute(sql`delete from auth_sessions where id = ${session.id}`);
588
+ },
238
589
  },
239
590
  },
240
591
  user: {
241
592
  create: {
242
- after: async (user) => {
593
+ before: async (user) => {
594
+ const admission = managedAuthUserCreateAdmission(
595
+ settings,
596
+ user,
597
+ currentManagedAuthProviderId(),
598
+ );
599
+ if (admission === false) return false;
600
+ if (!(await newSignupsGate.signupsOpen())) throw new ManagedAuthNewSignupsPausedError();
601
+ return admission;
602
+ },
603
+ after: async (user, context) => {
604
+ await funnel?.recordSignUp(context);
605
+ if (!user.emailVerified) return;
243
606
  await ensureManagedAccessForUser(db, {
244
607
  userId: user.id,
245
608
  email: user.email,
246
609
  name: user.name,
610
+ emailVerified: true,
611
+ provisionFallbackOrganization: false,
247
612
  });
248
613
  },
249
614
  },
250
615
  },
251
616
  },
252
617
  }) as ManagedAuth;
618
+ configureManagedUserAdmission(auth, settings.allowedUserEmails);
619
+ return auth;
620
+ }
621
+
622
+ export type ManagedAuthOAuthAttempt = {
623
+ transactionId: string;
624
+ provider: "google" | "github";
625
+ authorityHash: string;
626
+ transactionSecretHash: string;
627
+ expectedGeneration: string;
628
+ expectedActorEpoch: string;
629
+ };
630
+
631
+ /**
632
+ * Resolve OpenGeni's server-only login transaction proof from Better Auth's
633
+ * database-backed OAuth state before the provider callback consumes it.
634
+ */
635
+ export async function resolveManagedAuthOAuthAttempt(
636
+ auth: ManagedAuth,
637
+ request: Request,
638
+ provider: "google" | "github",
639
+ publicBaseUrl: string,
640
+ ): Promise<ManagedAuthOAuthAttempt | null> {
641
+ const expectedOrigin = canonicalPublicOrigin(publicBaseUrl);
642
+ if (!expectedOrigin) return null;
643
+ const state = new URL(request.url).searchParams.get("state");
644
+ if (!state) return null;
645
+ const verification = await (await auth.$context).internalAdapter.findVerificationValue(state);
646
+ if (!verification?.value) return null;
647
+ let parsed: unknown;
648
+ try {
649
+ parsed = JSON.parse(verification.value);
650
+ } catch {
651
+ return null;
652
+ }
653
+ if (!parsed || typeof parsed !== "object") return null;
654
+ const stateData = parsed as Record<string, unknown>;
655
+ const proof = stateData.opengeniManagedAuth;
656
+ if (!proof || typeof proof !== "object") return null;
657
+ const value = proof as Record<string, unknown>;
658
+ if (
659
+ value.version !== 1 ||
660
+ value.provider !== provider ||
661
+ typeof value.transactionId !== "string" ||
662
+ typeof value.authorityHash !== "string" ||
663
+ typeof value.transactionSecretHash !== "string" ||
664
+ typeof value.expectedGeneration !== "string" ||
665
+ typeof value.expectedActorEpoch !== "string" ||
666
+ !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(
667
+ value.transactionId,
668
+ ) ||
669
+ !/^[0-9a-f]{64}$/u.test(value.authorityHash) ||
670
+ !/^[0-9a-f]{64}$/u.test(value.transactionSecretHash) ||
671
+ !/^[1-9][0-9]*$/u.test(value.expectedGeneration) ||
672
+ !/^[1-9][0-9]*$/u.test(value.expectedActorEpoch)
673
+ ) {
674
+ return null;
675
+ }
676
+ const callbackURL = typeof stateData.callbackURL === "string" ? stateData.callbackURL : null;
677
+ const errorURL = typeof stateData.errorURL === "string" ? stateData.errorURL : null;
678
+ if (
679
+ !callbackURL ||
680
+ !errorURL ||
681
+ !managedAuthOAuthReturnMatches(callbackURL, expectedOrigin, value.transactionId, "complete") ||
682
+ !managedAuthOAuthReturnMatches(errorURL, expectedOrigin, value.transactionId, "error")
683
+ ) {
684
+ return null;
685
+ }
686
+ return {
687
+ transactionId: value.transactionId,
688
+ provider,
689
+ authorityHash: value.authorityHash,
690
+ transactionSecretHash: value.transactionSecretHash,
691
+ expectedGeneration: value.expectedGeneration,
692
+ expectedActorEpoch: value.expectedActorEpoch,
693
+ };
694
+ }
695
+
696
+ export async function isolatedManagedAuthOAuthCallbackRequest(
697
+ auth: ManagedAuth,
698
+ request: Request,
699
+ ): Promise<{ request: Request; stateCookieName: string }> {
700
+ const context = await auth.$context;
701
+ const stateCookieName = context.createAuthCookie("state").name;
702
+ const headers = new Headers(request.headers);
703
+ const stateCookie = cookiePair(headers.get("cookie"), stateCookieName);
704
+ if (stateCookie) headers.set("cookie", stateCookie);
705
+ else headers.delete("cookie");
706
+ headers.delete("authorization");
707
+ headers.delete("x-forwarded-user");
708
+ return { request: new Request(request, { headers }), stateCookieName };
709
+ }
710
+
711
+ export function managedAuthOAuthReturnMatches(
712
+ raw: string,
713
+ expectedOrigin: string,
714
+ transactionId: string,
715
+ outcome: "complete" | "error",
716
+ ): boolean {
717
+ try {
718
+ const url = new URL(raw);
719
+ return (
720
+ url.origin === expectedOrigin &&
721
+ url.pathname === "/account-auth" &&
722
+ url.searchParams.size === 2 &&
723
+ url.searchParams.get("transaction") === transactionId &&
724
+ url.searchParams.get("social") === outcome &&
725
+ !url.hash
726
+ );
727
+ } catch {
728
+ return false;
729
+ }
730
+ }
731
+
732
+ function cookiePair(header: string | null, name: string): string | null {
733
+ for (const part of header?.split(";") ?? []) {
734
+ const separator = part.indexOf("=");
735
+ if (separator < 0 || part.slice(0, separator).trim() !== name) continue;
736
+ return `${name}=${part.slice(separator + 1).trim()}`;
737
+ }
738
+ return null;
253
739
  }
254
740
 
255
741
  function betterAuthBaseUrl(settings: Settings) {
@@ -275,35 +761,25 @@ function betterAuthTrustedOrigins(settings: Settings): string[] {
275
761
  return [...origins];
276
762
  }
277
763
 
278
- async function sendEmail(
279
- settings: Settings,
280
- input: {
281
- to: string;
282
- subject: string;
283
- text: string;
284
- html: string;
285
- },
764
+ export async function sendManagedAuthEmail(
765
+ transport: ManagedEmailTransport,
766
+ input: Omit<ManagedEmailMessage, "from">,
286
767
  ): Promise<void> {
287
- if (!settings.resendApiKey) {
288
- if (settings.environment === "local" || settings.environment === "test") {
289
- console.warn(
290
- `[opengeni] Skipping email to ${input.to}: OPENGENI_RESEND_API_KEY is not configured`,
291
- );
292
- return;
293
- }
294
- throw new Error("OPENGENI_RESEND_API_KEY is required to send managed auth email");
295
- }
296
- const resend = new Resend(settings.resendApiKey);
297
- const result = await resend.emails.send({
298
- from: settings.emailFrom,
299
- to: input.to,
300
- subject: input.subject,
301
- text: input.text,
302
- html: input.html,
303
- });
304
- if (result.error) {
305
- throw new Error(result.error.message);
306
- }
768
+ const result = await transport.send({ ...input, from: transport.sender });
769
+ if (result.status !== "sent") throw new Error(`managed email ${result.status}`);
770
+ }
771
+
772
+ // Verification can sign the clicker in (autoSignInAfterVerification), so an
773
+ // unsolicited verification email must say plainly that it can be ignored.
774
+ function emailVerificationMessage(to: string, url: string): Omit<ManagedEmailMessage, "from"> {
775
+ const ignore = "If you did not create an Opengeni account, ignore this email.";
776
+ return {
777
+ kind: "email_verification",
778
+ to,
779
+ subject: "Verify your Opengeni email",
780
+ text: `Verify your Opengeni email: ${url}\n\n${ignore}`,
781
+ html: `<p>Verify your Opengeni email:</p><p><a href="${escapeHtml(url)}">Verify email</a></p><p>${ignore}</p>`,
782
+ };
307
783
  }
308
784
 
309
785
  async function verificationUrl(settings: Settings, email: string): Promise<string> {