@openlup/platform 0.0.0 → 0.16.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 (395) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/LICENSE +204 -0
  3. package/README.md +471 -1
  4. package/dist/bff/dispatch.d.ts +27 -0
  5. package/dist/bff/dispatch.js +47 -0
  6. package/dist/bff/envelope.d.ts +46 -0
  7. package/dist/bff/envelope.js +80 -0
  8. package/dist/bff/health.d.ts +24 -0
  9. package/dist/bff/health.js +19 -0
  10. package/dist/bff/index.d.ts +6 -0
  11. package/dist/bff/index.js +6 -0
  12. package/dist/bff/lazyRoute.d.ts +4 -0
  13. package/dist/bff/lazyRoute.js +13 -0
  14. package/dist/bff/requestContext.d.ts +17 -0
  15. package/dist/bff/requestContext.js +26 -0
  16. package/dist/bff/routerRewrite.d.ts +2 -0
  17. package/dist/bff/routerRewrite.js +31 -0
  18. package/dist/blob/binding.d.ts +16 -0
  19. package/dist/blob/binding.js +37 -0
  20. package/dist/blob/filesystem.d.ts +8 -0
  21. package/dist/blob/filesystem.js +71 -0
  22. package/dist/blob/index.d.ts +6 -0
  23. package/dist/blob/index.js +6 -0
  24. package/dist/blob/s3.d.ts +32 -0
  25. package/dist/blob/s3.js +56 -0
  26. package/dist/blob/s3Client.d.ts +25 -0
  27. package/dist/blob/s3Client.js +76 -0
  28. package/dist/blob/supabase.d.ts +40 -0
  29. package/dist/blob/supabase.js +59 -0
  30. package/dist/blob/vercel.d.ts +63 -0
  31. package/dist/blob/vercel.js +69 -0
  32. package/dist/composition/composeBundle.d.ts +47 -0
  33. package/dist/composition/composeBundle.js +45 -0
  34. package/dist/composition/httpBinding.d.ts +8 -0
  35. package/dist/composition/httpBinding.js +18 -0
  36. package/dist/composition/index.d.ts +3 -0
  37. package/dist/composition/index.js +3 -0
  38. package/dist/composition/nodeHost.d.ts +103 -0
  39. package/dist/composition/nodeHost.js +145 -0
  40. package/dist/data/binding.d.ts +34 -0
  41. package/dist/data/binding.js +92 -0
  42. package/dist/data/index.d.ts +5 -0
  43. package/dist/data/index.js +5 -0
  44. package/dist/data/postgresGateway.d.ts +42 -0
  45. package/dist/data/postgresGateway.js +135 -0
  46. package/dist/data/queryBuilder.d.ts +54 -0
  47. package/dist/data/queryBuilder.js +240 -0
  48. package/dist/data/queryBuilderSql.d.ts +76 -0
  49. package/dist/data/queryBuilderSql.js +145 -0
  50. package/dist/data/supabaseClientFactory.d.ts +34 -0
  51. package/dist/data/supabaseClientFactory.js +56 -0
  52. package/dist/data/supabaseGateway.d.ts +22 -0
  53. package/dist/data/supabaseGateway.js +49 -0
  54. package/dist/http/deadline.d.ts +20 -0
  55. package/dist/http/deadline.js +93 -0
  56. package/dist/http/index.d.ts +6 -0
  57. package/dist/http/index.js +5 -0
  58. package/dist/http/requestAdapter.d.ts +38 -0
  59. package/dist/http/requestAdapter.js +203 -0
  60. package/dist/http/runtime.d.ts +83 -0
  61. package/dist/http/runtime.js +199 -0
  62. package/dist/http/security.d.ts +44 -0
  63. package/dist/http/security.js +114 -0
  64. package/dist/http/staticAssets.d.ts +3 -0
  65. package/dist/http/staticAssets.js +42 -0
  66. package/dist/http/types.d.ts +23 -0
  67. package/dist/http/types.js +6 -0
  68. package/dist/identity/adminAuthBinding.d.ts +28 -0
  69. package/dist/identity/adminAuthBinding.js +66 -0
  70. package/dist/identity/adminAuthorization.d.ts +34 -0
  71. package/dist/identity/adminAuthorization.js +72 -0
  72. package/dist/identity/adminContracts.d.ts +70 -0
  73. package/dist/identity/adminContracts.js +43 -0
  74. package/dist/identity/adminMagicLinkHandler.d.ts +39 -0
  75. package/dist/identity/adminMagicLinkHandler.js +94 -0
  76. package/dist/identity/adminMeHandler.d.ts +15 -0
  77. package/dist/identity/adminMeHandler.js +49 -0
  78. package/dist/identity/adminPreflight.d.ts +16 -0
  79. package/dist/identity/adminPreflight.js +38 -0
  80. package/dist/identity/adminUserActionPorts.d.ts +31 -0
  81. package/dist/identity/adminUserActionPorts.js +51 -0
  82. package/dist/identity/adminUserHandlers.d.ts +38 -0
  83. package/dist/identity/adminUserHandlers.js +140 -0
  84. package/dist/identity/authVerifyUrl.d.ts +6 -0
  85. package/dist/identity/authVerifyUrl.js +54 -0
  86. package/dist/identity/contracts/contracts.d.ts +8 -0
  87. package/dist/identity/contracts/contracts.js +2 -0
  88. package/dist/identity/contracts/emailTrust.d.ts +3 -0
  89. package/dist/identity/contracts/emailTrust.js +4 -0
  90. package/dist/identity/contracts/index.d.ts +8 -0
  91. package/dist/identity/contracts/index.js +8 -0
  92. package/dist/identity/contracts/oidc.d.ts +3 -0
  93. package/dist/identity/contracts/oidc.js +9 -0
  94. package/dist/identity/contracts/operatorAuth.d.ts +35 -0
  95. package/dist/identity/contracts/operatorAuth.js +53 -0
  96. package/dist/identity/contracts/operatorInvitations.d.ts +37 -0
  97. package/dist/identity/contracts/operatorInvitations.js +1 -0
  98. package/dist/identity/contracts/ports.d.ts +22 -0
  99. package/dist/identity/contracts/ports.js +1 -0
  100. package/dist/identity/contracts/privacy.d.ts +62 -0
  101. package/dist/identity/contracts/privacy.js +99 -0
  102. package/dist/identity/contracts/providerList.d.ts +3 -0
  103. package/dist/identity/contracts/providerList.js +11 -0
  104. package/dist/identity/contracts/types.d.ts +16 -0
  105. package/dist/identity/contracts/types.js +4 -0
  106. package/dist/identity/contribution.d.ts +8 -0
  107. package/dist/identity/contribution.js +22 -0
  108. package/dist/identity/customerAuth.d.ts +8 -0
  109. package/dist/identity/customerAuth.js +1 -0
  110. package/dist/identity/customerAuthorization.d.ts +5 -0
  111. package/dist/identity/customerAuthorization.js +22 -0
  112. package/dist/identity/customerContracts.d.ts +42 -0
  113. package/dist/identity/customerContracts.js +43 -0
  114. package/dist/identity/customerIdentityBinding.d.ts +44 -0
  115. package/dist/identity/customerIdentityBinding.js +60 -0
  116. package/dist/identity/customerIdentityVerifier.d.ts +8 -0
  117. package/dist/identity/customerIdentityVerifier.js +101 -0
  118. package/dist/identity/customerMagicLinkHandler.d.ts +38 -0
  119. package/dist/identity/customerMagicLinkHandler.js +115 -0
  120. package/dist/identity/customerMeHandler.d.ts +11 -0
  121. package/dist/identity/customerMeHandler.js +46 -0
  122. package/dist/identity/customerPorts.d.ts +4 -0
  123. package/dist/identity/customerPorts.js +1 -0
  124. package/dist/identity/customerSessionBinding.d.ts +38 -0
  125. package/dist/identity/customerSessionBinding.js +80 -0
  126. package/dist/identity/customerSessionIssuer.d.ts +10 -0
  127. package/dist/identity/customerSessionIssuer.js +57 -0
  128. package/dist/identity/customerVerifyOtpHandler.d.ts +8 -0
  129. package/dist/identity/customerVerifyOtpHandler.js +41 -0
  130. package/dist/identity/directOperatorInvitationStore.d.ts +19 -0
  131. package/dist/identity/directOperatorInvitationStore.js +193 -0
  132. package/dist/identity/emailTrustPolicy.d.ts +14 -0
  133. package/dist/identity/emailTrustPolicy.js +16 -0
  134. package/dist/identity/identityPersonalDataContributor.d.ts +7 -0
  135. package/dist/identity/identityPersonalDataContributor.js +46 -0
  136. package/dist/identity/identityReadiness.d.ts +10 -0
  137. package/dist/identity/identityReadiness.js +25 -0
  138. package/dist/identity/index.d.ts +67 -0
  139. package/dist/identity/index.js +66 -0
  140. package/dist/identity/inviteAdminUserUseCase.d.ts +89 -0
  141. package/dist/identity/inviteAdminUserUseCase.js +75 -0
  142. package/dist/identity/jwtLibrary.d.ts +54 -0
  143. package/dist/identity/jwtLibrary.js +1 -0
  144. package/dist/identity/machineActorWriteFence.d.ts +4 -0
  145. package/dist/identity/machineActorWriteFence.js +13 -0
  146. package/dist/identity/magicLinkPorts.d.ts +28 -0
  147. package/dist/identity/magicLinkPorts.js +1 -0
  148. package/dist/identity/managedAccountLinkStore.d.ts +2 -0
  149. package/dist/identity/managedAccountLinkStore.js +93 -0
  150. package/dist/identity/managedAdminAuthVerifier.d.ts +7 -0
  151. package/dist/identity/managedAdminAuthVerifier.js +27 -0
  152. package/dist/identity/managedAdminUserGateways.d.ts +123 -0
  153. package/dist/identity/managedAdminUserGateways.js +127 -0
  154. package/dist/identity/managedClient.d.ts +90 -0
  155. package/dist/identity/managedClient.js +10 -0
  156. package/dist/identity/managedClientAuthLink.d.ts +3 -0
  157. package/dist/identity/managedClientAuthLink.js +115 -0
  158. package/dist/identity/managedCustomerMe.d.ts +13 -0
  159. package/dist/identity/managedCustomerMe.js +30 -0
  160. package/dist/identity/managedEmailTrustPolicy.d.ts +11 -0
  161. package/dist/identity/managedEmailTrustPolicy.js +105 -0
  162. package/dist/identity/managedIdentityVerifier.d.ts +2 -0
  163. package/dist/identity/managedIdentityVerifier.js +16 -0
  164. package/dist/identity/managedPrivacy.d.ts +4 -0
  165. package/dist/identity/managedPrivacy.js +39 -0
  166. package/dist/identity/messages.d.ts +29 -0
  167. package/dist/identity/messages.js +39 -0
  168. package/dist/identity/oidcIdentityVerifier.d.ts +45 -0
  169. package/dist/identity/oidcIdentityVerifier.js +309 -0
  170. package/dist/identity/oidcPrincipal.d.ts +2 -0
  171. package/dist/identity/oidcPrincipal.js +10 -0
  172. package/dist/identity/operatorAdminAuth.d.ts +6 -0
  173. package/dist/identity/operatorAdminAuth.js +13 -0
  174. package/dist/identity/operatorAuthHandlers.d.ts +18 -0
  175. package/dist/identity/operatorAuthHandlers.js +71 -0
  176. package/dist/identity/operatorIdentityVerifier.d.ts +8 -0
  177. package/dist/identity/operatorIdentityVerifier.js +19 -0
  178. package/dist/identity/operatorInvitationLifecycle.d.ts +7 -0
  179. package/dist/identity/operatorInvitationLifecycle.js +15 -0
  180. package/dist/identity/operatorInvitationPorts.d.ts +31 -0
  181. package/dist/identity/operatorInvitationPorts.js +1 -0
  182. package/dist/identity/operatorPorts.d.ts +37 -0
  183. package/dist/identity/operatorPorts.js +1 -0
  184. package/dist/identity/operatorSession.d.ts +13 -0
  185. package/dist/identity/operatorSession.js +31 -0
  186. package/dist/identity/operatorTokenAdminAuth.d.ts +15 -0
  187. package/dist/identity/operatorTokenAdminAuth.js +50 -0
  188. package/dist/identity/personalDataRegistry.d.ts +4 -0
  189. package/dist/identity/personalDataRegistry.js +11 -0
  190. package/dist/identity/platformAdminAuth.d.ts +15 -0
  191. package/dist/identity/platformAdminAuth.js +1 -0
  192. package/dist/identity/ports.d.ts +127 -0
  193. package/dist/identity/ports.js +38 -0
  194. package/dist/identity/postgresAccountLinkStore.d.ts +7 -0
  195. package/dist/identity/postgresAccountLinkStore.js +52 -0
  196. package/dist/identity/postgresCustomerMe.d.ts +3 -0
  197. package/dist/identity/postgresCustomerMe.js +21 -0
  198. package/dist/identity/postgresCustomerRecovery.d.ts +11 -0
  199. package/dist/identity/postgresCustomerRecovery.js +57 -0
  200. package/dist/identity/postgresOperatorIdentity.d.ts +5 -0
  201. package/dist/identity/postgresOperatorIdentity.js +137 -0
  202. package/dist/identity/postgresPrivacy.d.ts +7 -0
  203. package/dist/identity/postgresPrivacy.js +126 -0
  204. package/dist/identity/privacyHandlers.d.ts +10 -0
  205. package/dist/identity/privacyHandlers.js +35 -0
  206. package/dist/identity/privacyPorts.d.ts +72 -0
  207. package/dist/identity/privacyPorts.js +8 -0
  208. package/dist/identity/privacyUseCases.d.ts +5 -0
  209. package/dist/identity/privacyUseCases.js +55 -0
  210. package/dist/identity/reconcileAccountForPrincipal.d.ts +20 -0
  211. package/dist/identity/reconcileAccountForPrincipal.js +66 -0
  212. package/dist/identity/reconcileAccountHandler.d.ts +11 -0
  213. package/dist/identity/reconcileAccountHandler.js +30 -0
  214. package/dist/identity/removeAdminUserUseCase.d.ts +31 -0
  215. package/dist/identity/removeAdminUserUseCase.js +60 -0
  216. package/dist/identity/response.d.ts +3 -0
  217. package/dist/identity/response.js +2 -0
  218. package/dist/identity/sql.d.ts +44 -0
  219. package/dist/identity/sql.js +10 -0
  220. package/dist/job-ledger/index.d.ts +5 -0
  221. package/dist/job-ledger/index.js +3 -0
  222. package/dist/job-ledger/ports.d.ts +10 -0
  223. package/dist/job-ledger/ports.js +7 -0
  224. package/dist/job-ledger/postgres.d.ts +10 -0
  225. package/dist/job-ledger/postgres.js +101 -0
  226. package/dist/job-ledger/sql.d.ts +7 -0
  227. package/dist/job-ledger/sql.js +10 -0
  228. package/dist/job-ledger/supabase.d.ts +77 -0
  229. package/dist/job-ledger/supabase.js +202 -0
  230. package/dist/kernel/index.d.ts +1 -0
  231. package/dist/kernel/index.js +1 -0
  232. package/dist/kernel/platformKernel.d.ts +34 -0
  233. package/dist/kernel/platformKernel.js +115 -0
  234. package/dist/migrations/index.d.ts +1 -0
  235. package/dist/migrations/index.js +1 -0
  236. package/dist/migrations/postgresRunner.d.ts +50 -0
  237. package/dist/migrations/postgresRunner.js +176 -0
  238. package/dist/scheduler/advisoryLock.d.ts +44 -0
  239. package/dist/scheduler/advisoryLock.js +114 -0
  240. package/dist/scheduler/cronGuard.d.ts +10 -0
  241. package/dist/scheduler/cronGuard.js +19 -0
  242. package/dist/scheduler/cronSecret.d.ts +5 -0
  243. package/dist/scheduler/cronSecret.js +23 -0
  244. package/dist/scheduler/handlers.d.ts +9 -0
  245. package/dist/scheduler/handlers.js +10 -0
  246. package/dist/scheduler/httpEntry.d.ts +16 -0
  247. package/dist/scheduler/httpEntry.js +31 -0
  248. package/dist/scheduler/index.d.ts +9 -0
  249. package/dist/scheduler/index.js +9 -0
  250. package/dist/scheduler/jobRegistry.d.ts +21 -0
  251. package/dist/scheduler/jobRegistry.js +39 -0
  252. package/dist/scheduler/jobs.d.ts +48 -0
  253. package/dist/scheduler/jobs.js +92 -0
  254. package/dist/scheduler/nodeScheduler.d.ts +42 -0
  255. package/dist/scheduler/nodeScheduler.js +73 -0
  256. package/dist/scheduler/vercelScheduler.d.ts +29 -0
  257. package/dist/scheduler/vercelScheduler.js +51 -0
  258. package/package.json +116 -3
  259. package/release-gates.json +143 -0
  260. package/sql/20260809150000_job_run_ledger.sql +301 -0
  261. package/sql/20260809150001_job_run_ledger_v3.sql +70 -0
  262. package/sql/20260810180001_self_host_actor_auth.sql +40 -0
  263. package/sql/20260810204057_customer_identity_read.sql +31 -0
  264. package/sql/20260814090000_customer_selfservice_recovery.sql +477 -0
  265. package/sql/20260901143000_admin_membership_authority_foundation.sql +234 -0
  266. package/sql/20261018120000_identity_operator_invitations.sql +42 -0
  267. package/sql/20261018180000_identity_operator_challenges.sql +56 -0
  268. package/sql/20261018190000_identity_privacy_erasures.sql +15 -0
  269. package/src/bff/dispatch.ts +81 -0
  270. package/src/bff/envelope.ts +142 -0
  271. package/src/bff/health.ts +37 -0
  272. package/src/bff/index.ts +30 -0
  273. package/src/bff/lazyRoute.ts +28 -0
  274. package/src/bff/requestContext.ts +48 -0
  275. package/src/bff/routerRewrite.ts +38 -0
  276. package/src/blob/binding.ts +55 -0
  277. package/src/blob/filesystem.ts +86 -0
  278. package/src/blob/index.ts +17 -0
  279. package/src/blob/s3.ts +87 -0
  280. package/src/blob/s3Client.ts +91 -0
  281. package/src/blob/supabase.ts +88 -0
  282. package/src/blob/vercel.ts +127 -0
  283. package/src/composition/composeBundle.ts +109 -0
  284. package/src/composition/httpBinding.ts +30 -0
  285. package/src/composition/index.ts +17 -0
  286. package/src/composition/nodeHost.ts +289 -0
  287. package/src/data/binding.ts +157 -0
  288. package/src/data/index.ts +33 -0
  289. package/src/data/postgresGateway.ts +186 -0
  290. package/src/data/queryBuilder.ts +300 -0
  291. package/src/data/queryBuilderSql.ts +183 -0
  292. package/src/data/supabaseClientFactory.ts +92 -0
  293. package/src/data/supabaseGateway.ts +76 -0
  294. package/src/http/deadline.ts +117 -0
  295. package/src/http/index.ts +36 -0
  296. package/src/http/requestAdapter.ts +215 -0
  297. package/src/http/runtime.ts +319 -0
  298. package/src/http/security.ts +155 -0
  299. package/src/http/staticAssets.ts +42 -0
  300. package/src/http/types.ts +31 -0
  301. package/src/identity/adminAuthBinding.ts +76 -0
  302. package/src/identity/adminAuthorization.ts +106 -0
  303. package/src/identity/adminContracts.ts +52 -0
  304. package/src/identity/adminMagicLinkHandler.ts +132 -0
  305. package/src/identity/adminMeHandler.ts +64 -0
  306. package/src/identity/adminPreflight.ts +46 -0
  307. package/src/identity/adminUserActionPorts.ts +78 -0
  308. package/src/identity/adminUserHandlers.ts +174 -0
  309. package/src/identity/authVerifyUrl.ts +56 -0
  310. package/src/identity/contracts/contracts.ts +15 -0
  311. package/src/identity/contracts/emailTrust.ts +5 -0
  312. package/src/identity/contracts/index.ts +8 -0
  313. package/src/identity/contracts/oidc.ts +10 -0
  314. package/src/identity/contracts/operatorAuth.ts +61 -0
  315. package/src/identity/contracts/operatorInvitations.ts +32 -0
  316. package/src/identity/contracts/ports.ts +25 -0
  317. package/src/identity/contracts/privacy.ts +86 -0
  318. package/src/identity/contracts/providerList.ts +13 -0
  319. package/src/identity/contracts/types.ts +25 -0
  320. package/src/identity/contribution.ts +29 -0
  321. package/src/identity/customerAuth.ts +3 -0
  322. package/src/identity/customerAuthorization.ts +38 -0
  323. package/src/identity/customerContracts.ts +52 -0
  324. package/src/identity/customerIdentityBinding.ts +125 -0
  325. package/src/identity/customerIdentityVerifier.ts +141 -0
  326. package/src/identity/customerMagicLinkHandler.ts +171 -0
  327. package/src/identity/customerMeHandler.ts +64 -0
  328. package/src/identity/customerPorts.ts +2 -0
  329. package/src/identity/customerSessionBinding.ts +114 -0
  330. package/src/identity/customerSessionIssuer.ts +71 -0
  331. package/src/identity/customerVerifyOtpHandler.ts +44 -0
  332. package/src/identity/directOperatorInvitationStore.ts +178 -0
  333. package/src/identity/emailTrustPolicy.ts +16 -0
  334. package/src/identity/identityPersonalDataContributor.ts +48 -0
  335. package/src/identity/identityReadiness.ts +26 -0
  336. package/src/identity/index.ts +69 -0
  337. package/src/identity/inviteAdminUserUseCase.ts +134 -0
  338. package/src/identity/jwtLibrary.ts +24 -0
  339. package/src/identity/machineActorWriteFence.ts +19 -0
  340. package/src/identity/magicLinkPorts.ts +7 -0
  341. package/src/identity/managedAccountLinkStore.ts +105 -0
  342. package/src/identity/managedAdminAuthVerifier.ts +40 -0
  343. package/src/identity/managedAdminUserGateways.ts +164 -0
  344. package/src/identity/managedClient.ts +58 -0
  345. package/src/identity/managedClientAuthLink.ts +120 -0
  346. package/src/identity/managedCustomerMe.ts +52 -0
  347. package/src/identity/managedEmailTrustPolicy.ts +90 -0
  348. package/src/identity/managedIdentityVerifier.ts +20 -0
  349. package/src/identity/managedPrivacy.ts +37 -0
  350. package/src/identity/messages.ts +41 -0
  351. package/src/identity/oidcIdentityVerifier.ts +229 -0
  352. package/src/identity/oidcPrincipal.ts +10 -0
  353. package/src/identity/operatorAdminAuth.ts +14 -0
  354. package/src/identity/operatorAuthHandlers.ts +52 -0
  355. package/src/identity/operatorIdentityVerifier.ts +19 -0
  356. package/src/identity/operatorInvitationLifecycle.ts +14 -0
  357. package/src/identity/operatorInvitationPorts.ts +11 -0
  358. package/src/identity/operatorPorts.ts +11 -0
  359. package/src/identity/operatorSession.ts +30 -0
  360. package/src/identity/operatorTokenAdminAuth.ts +79 -0
  361. package/src/identity/personalDataRegistry.ts +8 -0
  362. package/src/identity/platformAdminAuth.ts +7 -0
  363. package/src/identity/ports.ts +158 -0
  364. package/src/identity/postgresAccountLinkStore.ts +49 -0
  365. package/src/identity/postgresCustomerMe.ts +33 -0
  366. package/src/identity/postgresCustomerRecovery.ts +72 -0
  367. package/src/identity/postgresOperatorIdentity.ts +123 -0
  368. package/src/identity/postgresPrivacy.ts +117 -0
  369. package/src/identity/privacyHandlers.ts +33 -0
  370. package/src/identity/privacyPorts.ts +52 -0
  371. package/src/identity/privacyUseCases.ts +48 -0
  372. package/src/identity/reconcileAccountForPrincipal.ts +99 -0
  373. package/src/identity/reconcileAccountHandler.ts +36 -0
  374. package/src/identity/removeAdminUserUseCase.ts +85 -0
  375. package/src/identity/response.ts +3 -0
  376. package/src/identity/sql.ts +10 -0
  377. package/src/job-ledger/index.ts +25 -0
  378. package/src/job-ledger/ports.ts +27 -0
  379. package/src/job-ledger/postgres.ts +121 -0
  380. package/src/job-ledger/sql.ts +12 -0
  381. package/src/job-ledger/supabase.ts +294 -0
  382. package/src/kernel/index.ts +17 -0
  383. package/src/kernel/platformKernel.ts +153 -0
  384. package/src/migrations/index.ts +10 -0
  385. package/src/migrations/postgresRunner.ts +255 -0
  386. package/src/scheduler/advisoryLock.ts +145 -0
  387. package/src/scheduler/cronGuard.ts +30 -0
  388. package/src/scheduler/cronSecret.ts +26 -0
  389. package/src/scheduler/handlers.ts +24 -0
  390. package/src/scheduler/httpEntry.ts +49 -0
  391. package/src/scheduler/index.ts +47 -0
  392. package/src/scheduler/jobRegistry.ts +57 -0
  393. package/src/scheduler/jobs.ts +119 -0
  394. package/src/scheduler/nodeScheduler.ts +123 -0
  395. package/src/scheduler/vercelScheduler.ts +71 -0
@@ -0,0 +1,42 @@
1
+ // Filesystem reader for a built static site, used by the Node runtime's static branch.
2
+ import { readFile, stat } from "node:fs/promises";
3
+ import { join, normalize, resolve } from "node:path";
4
+ import { contentTypeFor } from "./security.js";
5
+ const SPA_FALLBACK_DOCUMENT = "spa-fallback.html";
6
+ /** Read files below `distDir` (path-traversal safe; null for a missing file or a directory). */
7
+ export function createDistStaticReader(distDir) {
8
+ const root = resolve(distDir);
9
+ const safe = (relative) => {
10
+ const full = normalize(join(root, relative));
11
+ return full.startsWith(root) ? full : null;
12
+ };
13
+ return {
14
+ async read(relative) {
15
+ const full = safe(relative);
16
+ if (!full)
17
+ return null;
18
+ try {
19
+ const info = await stat(full);
20
+ if (!info.isFile())
21
+ return null;
22
+ const body = await readFile(full);
23
+ return { body, contentType: contentTypeFor(relative) };
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ },
29
+ async readIndex() {
30
+ // Prefer the client fallback document; a build without a pre-render step has only
31
+ // index.html, which then serves as the fallback.
32
+ try {
33
+ const body = await readFile(join(root, SPA_FALLBACK_DOCUMENT));
34
+ return { body, contentType: "text/html; charset=utf-8" };
35
+ }
36
+ catch {
37
+ const body = await readFile(join(root, "index.html"));
38
+ return { body, contentType: "text/html; charset=utf-8" };
39
+ }
40
+ },
41
+ };
42
+ }
@@ -0,0 +1,23 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ /** A Node request decorated with the parsed query, cookies and body. */
3
+ export interface HttpRequest extends IncomingMessage {
4
+ query: Record<string, string | string[] | undefined>;
5
+ cookies?: Record<string, string>;
6
+ body?: unknown;
7
+ /** Cooperative cancellation owned by the portable host adapter. */
8
+ abortSignal?: AbortSignal;
9
+ }
10
+ /** A Node response decorated with the helper methods hosted functions provide. */
11
+ export interface HttpResponse extends ServerResponse {
12
+ status(code: number): this;
13
+ json(body: unknown): this;
14
+ send(body: string | Buffer | object): this;
15
+ redirect(url: string): this;
16
+ redirect(status: number, url: string): this;
17
+ }
18
+ /** A default-export handler shared by every physical API entrypoint. */
19
+ export type HttpHandler = (req: HttpRequest, res: HttpResponse) => Promise<unknown> | unknown;
20
+ /** A lazily loaded module exposing one default-export handler. */
21
+ export type EntrypointLoader = () => Promise<{
22
+ default: HttpHandler;
23
+ }>;
@@ -0,0 +1,6 @@
1
+ // Host-neutral request and response shapes for every HTTP entrypoint.
2
+ //
3
+ // They are defined over node:http so that a handler runs unchanged on a hosted
4
+ // serverless function, the Node runtime of this package, or a test double.
5
+ // No host SDK is required to name them.
6
+ export {};
@@ -0,0 +1,28 @@
1
+ import type { AdminAuthPort } from "./ports.js";
2
+ import type { AdminAuthBinding } from "./adminPreflight.js";
3
+ type Env = Record<string, string | undefined>;
4
+ export interface PlatformOperatorChecker {
5
+ isOperatorAllowed(principalId: string): Promise<boolean>;
6
+ close(): Promise<void>;
7
+ }
8
+ export type PlatformOperatorCheckerFactory = (env: Env) => PlatformOperatorChecker | Promise<PlatformOperatorChecker>;
9
+ export type AdminAuthBindingResolution = {
10
+ readonly binding: AdminAuthBinding;
11
+ readonly error?: undefined;
12
+ } | {
13
+ readonly binding?: undefined;
14
+ readonly error: "managed_admin_auth_env_required" | "direct_admin_auth_config_invalid";
15
+ };
16
+ export interface AdminAuthBindingOptions {
17
+ humanAuthFactory?: (env: Env) => AdminAuthPort & {
18
+ close(): Promise<void>;
19
+ };
20
+ checkerFactory?: PlatformOperatorCheckerFactory;
21
+ managedAuthFactory?: (env: {
22
+ url: string;
23
+ anonKey: string;
24
+ }, accessToken: string | null) => AdminAuthPort;
25
+ }
26
+ /** Bind the admin authorizer selected by PLATFORM_BUNDLE. */
27
+ export declare function createAdminAuthBindingResolver(defaults: Required<Pick<AdminAuthBindingOptions, "checkerFactory" | "managedAuthFactory">> & Pick<AdminAuthBindingOptions, "humanAuthFactory">): (env: Env, options?: AdminAuthBindingOptions) => AdminAuthBindingResolution;
28
+ export {};
@@ -0,0 +1,66 @@
1
+ import { createOperatorTokenAdminAuth, resolvePlatformOperatorAuthConfig } from "./operatorTokenAdminAuth.js";
2
+ import { resolveBundleId } from "../kernel/index.js";
3
+ function managedAuthEnv(env) {
4
+ const url = env.SUPABASE_URL ?? env.VITE_SUPABASE_URL;
5
+ const anonKey = env.VITE_SUPABASE_PUBLISHABLE_KEY ?? env.VITE_SUPABASE_ANON_KEY ?? env.SUPABASE_ANON_KEY;
6
+ return url && anonKey ? { url, anonKey } : null;
7
+ }
8
+ /** Bind the admin authorizer selected by PLATFORM_BUNDLE. */
9
+ export function createAdminAuthBindingResolver(defaults) {
10
+ return function resolveAdminAuthBinding(env, options = {}) {
11
+ const bundleId = resolveBundleId(env);
12
+ if (bundleId === "node-postgres") {
13
+ const config = resolvePlatformOperatorAuthConfig(env);
14
+ const humanFactory = options.humanAuthFactory ?? defaults.humanAuthFactory;
15
+ const humanEnabled = !!env.OPERATOR_IDENTITY_AUDIENCE && !!humanFactory;
16
+ if (!config.config && !humanEnabled)
17
+ return { error: "direct_admin_auth_config_invalid" };
18
+ const checkerFactory = options.checkerFactory ?? defaults.checkerFactory;
19
+ return {
20
+ binding: {
21
+ identity: bundleId,
22
+ async run(accessToken, work) {
23
+ const scope = {};
24
+ const machine = config.config ? createOperatorTokenAdminAuth(config.config, async (principalId) => {
25
+ scope.checker ??= await checkerFactory(env);
26
+ return scope.checker.isOperatorAllowed(principalId);
27
+ }) : null;
28
+ let human;
29
+ const auth = { async authorize(token, restrictions) {
30
+ // Only a credential mismatch may select human verification; machine
31
+ // revocation and database errors retain their existing result.
32
+ const result = machine ? await machine.authorize(token, restrictions) : null;
33
+ if (result && (result.ok || result.code !== "UNAUTHORIZED"))
34
+ return result;
35
+ if (!humanEnabled || !token)
36
+ return result ?? { ok: false, code: "UNAUTHORIZED", message: "Operator authentication required" };
37
+ human ??= humanFactory(env);
38
+ return human.authorize(token, restrictions);
39
+ } };
40
+ try {
41
+ return await work(auth);
42
+ }
43
+ finally {
44
+ try {
45
+ await scope.checker?.close();
46
+ }
47
+ finally {
48
+ await human?.close();
49
+ }
50
+ }
51
+ },
52
+ },
53
+ };
54
+ }
55
+ const authEnv = managedAuthEnv(env);
56
+ if (!authEnv)
57
+ return { error: "managed_admin_auth_env_required" };
58
+ const managedAuthFactory = options.managedAuthFactory ?? defaults.managedAuthFactory;
59
+ return {
60
+ binding: {
61
+ identity: bundleId,
62
+ run: (accessToken, work) => work(managedAuthFactory(authEnv, accessToken)),
63
+ },
64
+ };
65
+ };
66
+ }
@@ -0,0 +1,34 @@
1
+ import type { HttpResponse, HttpRequest } from "../http/index.js";
2
+ import { type IdentityResponseOptions } from "./response.js";
3
+ export type AdminRole = "admin" | "distributor";
4
+ /** Authorization outcome: a resolved admin `userId`, or a typed denial. */
5
+ export type AdminAuthorization = {
6
+ ok: true;
7
+ userId: string;
8
+ role: AdminRole;
9
+ isMachineActor: boolean;
10
+ } | {
11
+ ok: false;
12
+ code: "UNAUTHORIZED" | "FORBIDDEN";
13
+ message: string;
14
+ identityCode?: string;
15
+ };
16
+ /** A request-bound thunk that resolves the calling admin (or a denial). */
17
+ export type AuthorizeAdmin = () => Promise<AdminAuthorization>;
18
+ export declare function authorizeAdminWithUser(rawClient: unknown, accessToken: string | null, options?: {
19
+ allowedRoles?: readonly AdminRole[];
20
+ }): Promise<AdminAuthorization>;
21
+ export declare function authorizeCommerceAdminWithUser(client: unknown, accessToken: string | null): Promise<AdminAuthorization>;
22
+ export declare function authorizeAdminBooleanWithUser(client: unknown, accessToken: string | null, options?: {
23
+ allowedRoles?: readonly AdminRole[];
24
+ }): Promise<boolean>;
25
+ /**
26
+ * Shared authz gate: returns the resolved admin (`userId` + DB-derived
27
+ * `isMachineActor`) or sends the error envelope and returns null.
28
+ */
29
+ export declare function resolveAdmin(authorizeAdmin: AuthorizeAdmin, res: HttpResponse, options?: IdentityResponseOptions): Promise<{
30
+ userId: string;
31
+ role: AdminRole;
32
+ isMachineActor: boolean;
33
+ } | null>;
34
+ export declare function readBearerToken(req: HttpRequest): string | null;
@@ -0,0 +1,72 @@
1
+ import { createIdentityResponder } from "./response.js";
2
+ export async function authorizeAdminWithUser(rawClient, accessToken, options = {}) {
3
+ const client = rawClient;
4
+ if (!accessToken) {
5
+ return { ok: false, code: "UNAUTHORIZED", message: "Admin session required" };
6
+ }
7
+ const { data: userData, error: userError } = await client.auth.getUser(accessToken);
8
+ if (userError || !userData.user) {
9
+ return { ok: false, code: "UNAUTHORIZED", message: "Admin session required" };
10
+ }
11
+ const { data, error } = await client
12
+ .from("admin_users")
13
+ .select("id, role, is_machine_actor")
14
+ .eq("id", userData.user.id)
15
+ .eq("membership_state", "active")
16
+ .maybeSingle();
17
+ if (error)
18
+ throw error;
19
+ if (!data) {
20
+ return { ok: false, code: "FORBIDDEN", message: "Admin role required" };
21
+ }
22
+ const role = normalizeAdminRole(data.role);
23
+ if (role === null || (options.allowedRoles && !options.allowedRoles.includes(role))) {
24
+ return { ok: false, code: "FORBIDDEN", message: "Admin role required" };
25
+ }
26
+ // Fail closed: mirror the activate RPC's `COALESCE(is_machine_actor, true)` — only an
27
+ // explicit `false` is a human actor; anything else is treated as a machine. The DB row
28
+ // (`admin_users.is_machine_actor`, re-derived here and in every write RPC) is the single
29
+ // source of truth for actor kind — never trusted from the caller.
30
+ return {
31
+ ok: true,
32
+ userId: userData.user.id,
33
+ role,
34
+ isMachineActor: data.is_machine_actor !== false,
35
+ };
36
+ }
37
+ export function authorizeCommerceAdminWithUser(client, accessToken) {
38
+ return authorizeAdminWithUser(client, accessToken, { allowedRoles: ["admin"] });
39
+ }
40
+ export async function authorizeAdminBooleanWithUser(client, accessToken, options = {}) {
41
+ const authorization = await authorizeAdminWithUser(client, accessToken, options);
42
+ return authorization.ok;
43
+ }
44
+ /**
45
+ * Shared authz gate: returns the resolved admin (`userId` + DB-derived
46
+ * `isMachineActor`) or sends the error envelope and returns null.
47
+ */
48
+ export async function resolveAdmin(authorizeAdmin, res, options = {}) {
49
+ const { sendBffError } = createIdentityResponder(options);
50
+ let authz;
51
+ try {
52
+ authz = await authorizeAdmin();
53
+ }
54
+ catch {
55
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Admin authorization failed");
56
+ return null;
57
+ }
58
+ if (authz.ok === false) {
59
+ sendBffError(res, authz.code, authz.message);
60
+ return null;
61
+ }
62
+ return { userId: authz.userId, role: authz.role, isMachineActor: authz.isMachineActor };
63
+ }
64
+ function normalizeAdminRole(role) {
65
+ return role === "admin" || role === "distributor" ? role : null;
66
+ }
67
+ export function readBearerToken(req) {
68
+ const raw = req.headers.authorization;
69
+ const value = Array.isArray(raw) ? raw[0] : raw;
70
+ const match = value?.match(/^Bearer\s+(.+)$/i);
71
+ return match?.[1]?.trim() || null;
72
+ }
@@ -0,0 +1,70 @@
1
+ import { z } from "zod";
2
+ export declare const adminUserRoleSchema: z.ZodEnum<{
3
+ admin: "admin";
4
+ distributor: "distributor";
5
+ }>;
6
+ export declare const adminPlatformMeRequestSchema: z.ZodObject<{}, z.core.$strict>;
7
+ export declare const adminPlatformMeResponseSchema: z.ZodObject<{
8
+ isAdmin: z.ZodBoolean;
9
+ role: z.ZodNullable<z.ZodEnum<{
10
+ admin: "admin";
11
+ distributor: "distributor";
12
+ }>>;
13
+ }, z.core.$strip>;
14
+ export declare const adminUserRoleUpdateRequestSchema: z.ZodObject<{
15
+ userId: z.ZodString;
16
+ role: z.ZodEnum<{
17
+ admin: "admin";
18
+ distributor: "distributor";
19
+ }>;
20
+ }, z.core.$strip>;
21
+ export declare const adminUserRoleUpdateResponseSchema: z.ZodObject<{
22
+ userId: z.ZodString;
23
+ role: z.ZodEnum<{
24
+ admin: "admin";
25
+ distributor: "distributor";
26
+ }>;
27
+ saved: z.ZodLiteral<true>;
28
+ }, z.core.$strip>;
29
+ export declare const adminUserInviteRequestSchema: z.ZodObject<{
30
+ email: z.ZodString;
31
+ role: z.ZodEnum<{
32
+ admin: "admin";
33
+ distributor: "distributor";
34
+ }>;
35
+ }, z.core.$strip>;
36
+ export declare const adminUserInviteResponseSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
37
+ principalId: z.ZodString;
38
+ invitationId: z.ZodString;
39
+ membershipState: z.ZodLiteral<"invited">;
40
+ role: z.ZodEnum<{
41
+ admin: "admin";
42
+ distributor: "distributor";
43
+ }>;
44
+ invitedAt: z.ZodISODateTime;
45
+ expiresAt: z.ZodISODateTime;
46
+ code: z.ZodEnum<{
47
+ INVITATION_CREATED: "INVITATION_CREATED";
48
+ ALREADY_INVITED: "ALREADY_INVITED";
49
+ INVITATION_REISSUED: "INVITATION_REISSUED";
50
+ }>;
51
+ message: z.ZodOptional<z.ZodString>;
52
+ }, z.core.$strip>, z.ZodObject<{
53
+ principalId: z.ZodString;
54
+ membershipState: z.ZodLiteral<"active">;
55
+ role: z.ZodEnum<{
56
+ admin: "admin";
57
+ distributor: "distributor";
58
+ }>;
59
+ code: z.ZodEnum<{
60
+ MEMBERSHIP_CREATED: "MEMBERSHIP_CREATED";
61
+ ALREADY_ACTIVE: "ALREADY_ACTIVE";
62
+ }>;
63
+ message: z.ZodOptional<z.ZodString>;
64
+ }, z.core.$strip>], "membershipState">;
65
+ export declare const adminUserRemoveRequestSchema: z.ZodObject<{
66
+ userId: z.ZodString;
67
+ }, z.core.$strip>;
68
+ export declare const adminUserRemoveResponseSchema: z.ZodObject<{
69
+ revoked: z.ZodLiteral<true>;
70
+ }, z.core.$strip>;
@@ -0,0 +1,43 @@
1
+ import { z } from "zod";
2
+ z.config({ jitless: true });
3
+ export const adminUserRoleSchema = z.enum(["admin", "distributor"]);
4
+ export const adminPlatformMeRequestSchema = z.object({}).strict();
5
+ export const adminPlatformMeResponseSchema = z.object({
6
+ isAdmin: z.boolean(),
7
+ role: adminUserRoleSchema.nullable(),
8
+ });
9
+ export const adminUserRoleUpdateRequestSchema = z.object({
10
+ userId: z.string().min(1),
11
+ role: adminUserRoleSchema,
12
+ });
13
+ export const adminUserRoleUpdateResponseSchema = z.object({
14
+ userId: z.string().min(1),
15
+ role: adminUserRoleSchema,
16
+ saved: z.literal(true),
17
+ });
18
+ export const adminUserInviteRequestSchema = z.object({
19
+ email: z.string().trim().email().toLowerCase(),
20
+ role: adminUserRoleSchema,
21
+ });
22
+ export const adminUserInviteResponseSchema = z.discriminatedUnion("membershipState", [z.object({
23
+ principalId: z.string().min(1),
24
+ invitationId: z.string().min(1),
25
+ membershipState: z.literal("invited"),
26
+ role: adminUserRoleSchema,
27
+ invitedAt: z.iso.datetime(),
28
+ expiresAt: z.iso.datetime(),
29
+ code: z.enum(["INVITATION_CREATED", "ALREADY_INVITED", "INVITATION_REISSUED"]),
30
+ message: z.string().optional(),
31
+ }), z.object({
32
+ principalId: z.string().min(1),
33
+ membershipState: z.literal("active"),
34
+ role: adminUserRoleSchema,
35
+ code: z.enum(["MEMBERSHIP_CREATED", "ALREADY_ACTIVE"]),
36
+ message: z.string().optional(),
37
+ })]);
38
+ export const adminUserRemoveRequestSchema = z.object({
39
+ userId: z.string().min(1),
40
+ });
41
+ export const adminUserRemoveResponseSchema = z.object({
42
+ revoked: z.literal(true),
43
+ });
@@ -0,0 +1,39 @@
1
+ import { type IdentityResponseOptions } from "./response.js";
2
+ import type { z } from "zod";
3
+ import type { HttpRequest, HttpResponse } from "../http/index.js";
4
+ import type { MagicLinkAuthClient, MagicLinkRateLimitClient, MagicLinkDecision } from "./magicLinkPorts.js";
5
+ export interface AdminMagicLinkHandlerOptions extends IdentityResponseOptions {
6
+ requestSchema: z.ZodType<{
7
+ email: string;
8
+ }>;
9
+ responseSchema: z.ZodType<{
10
+ accepted: true;
11
+ }>;
12
+ readEnvironment: () => {
13
+ url: string;
14
+ anonKey: string;
15
+ serviceRoleKey: string;
16
+ } | null;
17
+ createRuntime: (env: {
18
+ url: string;
19
+ anonKey: string;
20
+ serviceRoleKey: string;
21
+ }) => {
22
+ authClient: MagicLinkAuthClient;
23
+ rateLimitClient: MagicLinkRateLimitClient;
24
+ platformPort: {
25
+ checkAdminMagicLinkEligibility(email: string): Promise<string>;
26
+ };
27
+ };
28
+ checkAttempt: (input: {
29
+ client: MagicLinkRateLimitClient;
30
+ ip: string;
31
+ email: string;
32
+ }) => Promise<MagicLinkDecision>;
33
+ extractClientIp: (headers: HttpRequest["headers"]) => string;
34
+ padResponseTime: (startedAt: number, floor: number) => Promise<void>;
35
+ redirectOrigins?: readonly string[];
36
+ resolveOrigin?: (req: HttpRequest) => string | null;
37
+ callbackPath: string;
38
+ }
39
+ export declare function createAdminMagicLinkHandler(options: AdminMagicLinkHandlerOptions): (req: HttpRequest, res: HttpResponse) => Promise<void>;
@@ -0,0 +1,94 @@
1
+ import { createMagicLinkOriginResolver } from "./authVerifyUrl.js";
2
+ import { createIdentityResponder } from "./response.js";
3
+ const ADMIN_MAGIC_LINK_RESPONSE_FLOOR_MS = 400;
4
+ export function createAdminMagicLinkHandler(options) {
5
+ const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = createIdentityResponder(options);
6
+ const readRequestOrigin = options.resolveOrigin ?? createMagicLinkOriginResolver(options.redirectOrigins ?? []);
7
+ const { requestSchema: adminMagicLinkRequestSchema, responseSchema: adminMagicLinkResponseSchema, readEnvironment: readSupabaseAdminServiceEnv, createRuntime: createAdminMagicLinkRuntime, checkAttempt: checkAndRecordAdminMagicLinkAttempt, extractClientIp, padResponseTime } = options;
8
+ return async (req, res) => {
9
+ if (req.method !== "POST") {
10
+ sendMethodNotAllowed(res, ["POST"]);
11
+ return;
12
+ }
13
+ const request = adminMagicLinkRequestSchema.safeParse(req.body);
14
+ if (!request.success) {
15
+ sendBffError(res, "BAD_REQUEST", "Invalid admin magic-link request", {
16
+ details: request.error.flatten(),
17
+ });
18
+ return;
19
+ }
20
+ const startedAt = Date.now();
21
+ const origin = readRequestOrigin(req);
22
+ if (!origin) {
23
+ await padResponseTime(startedAt, ADMIN_MAGIC_LINK_RESPONSE_FLOOR_MS);
24
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Magic link origin is not configured", {
25
+ details: { reason: "magic_link_origin_not_configured" },
26
+ });
27
+ return;
28
+ }
29
+ const env = readSupabaseAdminServiceEnv();
30
+ if (!env) {
31
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Admin auth is not configured", {
32
+ details: { feature: "admin_auth", requiredEnv: "SUPABASE_SERVICE_ROLE_KEY" },
33
+ });
34
+ return;
35
+ }
36
+ const runtime = createAdminMagicLinkRuntime(env);
37
+ const decision = await checkAndRecordAdminMagicLinkAttempt({
38
+ client: runtime.rateLimitClient,
39
+ ip: extractClientIp(req.headers),
40
+ email: request.data.email,
41
+ });
42
+ if (!decision.allowed && decision.reason === "rpc_error") {
43
+ console.warn("admin_magic_link_rate_limit_unavailable", JSON.stringify({ reason: decision.reason }));
44
+ await padResponseTime(startedAt, ADMIN_MAGIC_LINK_RESPONSE_FLOOR_MS);
45
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Magic link service temporarily unavailable");
46
+ return;
47
+ }
48
+ if (decision.allowed) {
49
+ let eligibility;
50
+ try {
51
+ eligibility = await runtime.platformPort.checkAdminMagicLinkEligibility(request.data.email);
52
+ }
53
+ catch {
54
+ console.warn("admin_magic_link_lookup_failed", JSON.stringify({ outcome: "lookup_error" }));
55
+ await padResponseTime(startedAt, ADMIN_MAGIC_LINK_RESPONSE_FLOOR_MS);
56
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Magic link service temporarily unavailable");
57
+ return;
58
+ }
59
+ if (eligibility === "eligible") {
60
+ const outcome = await sendMagicLink(runtime.authClient, request.data.email, origin);
61
+ console.info("admin_magic_link_outcome", JSON.stringify({ outcome: outcome.status, code: outcome.code ?? null }));
62
+ }
63
+ else {
64
+ console.warn("admin_magic_link_outcome", JSON.stringify({ outcome: eligibility }));
65
+ }
66
+ }
67
+ else {
68
+ console.warn("admin_magic_link_outcome", JSON.stringify({ outcome: "rate_limited", reason: decision.reason ?? "unknown" }));
69
+ }
70
+ await padResponseTime(startedAt, ADMIN_MAGIC_LINK_RESPONSE_FLOOR_MS);
71
+ sendBffSuccess(res, adminMagicLinkResponseSchema.parse({ accepted: true }));
72
+ };
73
+ async function sendMagicLink(client, email, origin) {
74
+ try {
75
+ const { error } = await client.auth.signInWithOtp({
76
+ email,
77
+ options: {
78
+ shouldCreateUser: false,
79
+ emailRedirectTo: `${origin}${options.callbackPath}`,
80
+ },
81
+ });
82
+ if (error) {
83
+ console.warn("admin_magic_link_send_failed", JSON.stringify({ code: error.name ?? "AuthError" }));
84
+ return { status: "auth_error", code: error.name ?? "AuthError" };
85
+ }
86
+ return { status: "sent" };
87
+ }
88
+ catch (error) {
89
+ const code = error instanceof Error ? error.name : "unknown";
90
+ console.warn("admin_magic_link_send_threw", JSON.stringify({ error: code }));
91
+ return { status: "threw", code };
92
+ }
93
+ }
94
+ }
@@ -0,0 +1,15 @@
1
+ import type { OperatorInvitationStore } from "./operatorInvitationPorts.js";
2
+ import { type IdentityResponseOptions } from "./response.js";
3
+ import type { HttpRequest, HttpResponse } from "../http/index.js";
4
+ import type { PlatformUserAuthenticationResult } from "./platformAdminAuth.js";
5
+ export interface AdminPlatformMeDeps extends IdentityResponseOptions {
6
+ invitationStore?: Pick<OperatorInvitationStore, "acceptOperatorInvitation">;
7
+ platformPort: {
8
+ getAdminMe(userId: string): Promise<{
9
+ isAdmin: boolean;
10
+ role: "admin" | "distributor" | null;
11
+ }>;
12
+ };
13
+ authenticateUser: (req: HttpRequest) => Promise<PlatformUserAuthenticationResult>;
14
+ }
15
+ export declare function createAdminPlatformMeHandler({ platformPort, invitationStore, authenticateUser, responseRequestId, }: AdminPlatformMeDeps): (req: HttpRequest, res: HttpResponse) => Promise<void>;
@@ -0,0 +1,49 @@
1
+ import { createIdentityResponder } from "./response.js";
2
+ import { adminPlatformMeRequestSchema, adminPlatformMeResponseSchema } from "./adminContracts.js";
3
+ export function createAdminPlatformMeHandler({ platformPort, invitationStore, authenticateUser, responseRequestId, }) {
4
+ const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = createIdentityResponder({ responseRequestId });
5
+ return async function handler(req, res) {
6
+ if (req.method !== "GET") {
7
+ sendMethodNotAllowed(res, ["GET"]);
8
+ return;
9
+ }
10
+ let authentication;
11
+ try {
12
+ authentication = await authenticateUser(req);
13
+ }
14
+ catch {
15
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Platform user authentication failed");
16
+ return;
17
+ }
18
+ if (authentication.ok === false) {
19
+ sendBffError(res, authentication.code, authentication.message);
20
+ return;
21
+ }
22
+ const request = adminPlatformMeRequestSchema.safeParse(req.query);
23
+ if (!request.success) {
24
+ sendBffError(res, "BAD_REQUEST", "Invalid platform me request", {
25
+ details: request.error.flatten(),
26
+ });
27
+ return;
28
+ }
29
+ try {
30
+ if (invitationStore) {
31
+ const outcome = await invitationStore.acceptOperatorInvitation(authentication.userId);
32
+ if (outcome.code === "INVITATION_EXPIRED" || outcome.code === "INVITATION_REVOKED") {
33
+ sendBffError(res, "FORBIDDEN", outcome.code, { details: { identityCode: outcome.code } });
34
+ return;
35
+ }
36
+ }
37
+ const result = await platformPort.getAdminMe(authentication.userId);
38
+ const response = adminPlatformMeResponseSchema.safeParse(result);
39
+ if (!response.success) {
40
+ sendBffError(res, "INVALID_RESPONSE", "Platform me returned invalid response");
41
+ return;
42
+ }
43
+ sendBffSuccess(res, response.data);
44
+ }
45
+ catch {
46
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Platform me read failed");
47
+ }
48
+ };
49
+ }
@@ -0,0 +1,16 @@
1
+ import { type IdentityResponseOptions } from "./response.js";
2
+ import type { HttpRequest, HttpResponse } from "../http/index.js";
3
+ import type { AdminAuthPort, AdminPrincipalRole } from "./ports.js";
4
+ export interface AdminAuthBinding {
5
+ readonly identity: string;
6
+ run<T>(accessToken: string | null, work: (auth: AdminAuthPort) => Promise<T>): Promise<T>;
7
+ }
8
+ export declare function createAdminBffPreflight(options: IdentityResponseOptions & {
9
+ allowedRoles: (route: string) => readonly AdminPrincipalRole[] | null;
10
+ resolveBinding: () => {
11
+ binding?: AdminAuthBinding;
12
+ error?: string;
13
+ };
14
+ machineWriteRoutes?: readonly string[];
15
+ managedEnvironmentMessage?: string;
16
+ }): (req: HttpRequest, res: HttpResponse, route: string) => Promise<boolean>;
@@ -0,0 +1,38 @@
1
+ import { createIdentityResponder } from "./response.js";
2
+ import { readBearerToken } from "./adminAuthorization.js";
3
+ import { refusesMachineActorRequest, sendMachineActorWriteRefused } from "./machineActorWriteFence.js";
4
+ export function createAdminBffPreflight(options) {
5
+ const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = createIdentityResponder(options);
6
+ return async (req, res, route) => {
7
+ const allowedRoles = options.allowedRoles(route);
8
+ if (!allowedRoles)
9
+ return true;
10
+ const resolved = options.resolveBinding();
11
+ if (!resolved.binding) {
12
+ if (resolved.error === "managed_admin_auth_env_required") {
13
+ sendBffError(res, "INTERNAL", options.managedEnvironmentMessage ?? "Identity environment is not configured");
14
+ }
15
+ else
16
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Admin authorization failed");
17
+ return false;
18
+ }
19
+ const accessToken = readBearerToken(req);
20
+ try {
21
+ const authz = await resolved.binding.run(accessToken, (auth) => auth.authorize(accessToken, { allowedRoles }));
22
+ if (authz.ok === false) {
23
+ sendBffError(res, authz.code, authz.message);
24
+ return false;
25
+ }
26
+ if (resolved.binding.identity !== "node-postgres"
27
+ && refusesMachineActorRequest(route, req.method, authz.isMachineActor, options.machineWriteRoutes)) {
28
+ sendMachineActorWriteRefused(res, options);
29
+ return false;
30
+ }
31
+ return true;
32
+ }
33
+ catch {
34
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Admin authorization failed");
35
+ return false;
36
+ }
37
+ };
38
+ }