@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,106 @@
1
+ import type { HttpResponse, HttpRequest } from "../http/index.js";
2
+ import type { ManagedIdentityClient } from "./managedClient.js";
3
+ import { createIdentityResponder, type IdentityResponseOptions } from "./response.js";
4
+ export type AdminRole = "admin" | "distributor";
5
+
6
+ /** Authorization outcome: a resolved admin `userId`, or a typed denial. */
7
+ export type AdminAuthorization =
8
+ | { ok: true; userId: string; role: AdminRole; isMachineActor: boolean }
9
+ | { ok: false; code: "UNAUTHORIZED" | "FORBIDDEN"; message: string; identityCode?: string };
10
+
11
+ /** A request-bound thunk that resolves the calling admin (or a denial). */
12
+ export type AuthorizeAdmin = () => Promise<AdminAuthorization>;
13
+
14
+ export async function authorizeAdminWithUser(
15
+ rawClient: unknown,
16
+ accessToken: string | null,
17
+ options: { allowedRoles?: readonly AdminRole[] } = {},
18
+ ): Promise<AdminAuthorization> {
19
+ const client = rawClient as ManagedIdentityClient;
20
+ if (!accessToken) {
21
+ return { ok: false, code: "UNAUTHORIZED", message: "Admin session required" };
22
+ }
23
+
24
+ const { data: userData, error: userError } = await client.auth.getUser(accessToken);
25
+ if (userError || !userData.user) {
26
+ return { ok: false, code: "UNAUTHORIZED", message: "Admin session required" };
27
+ }
28
+
29
+ const { data, error } = await client
30
+ .from("admin_users")
31
+ .select("id, role, is_machine_actor")
32
+ .eq("id", userData.user.id)
33
+ .eq("membership_state", "active")
34
+ .maybeSingle();
35
+ if (error) throw error;
36
+ if (!data) {
37
+ return { ok: false, code: "FORBIDDEN", message: "Admin role required" };
38
+ }
39
+
40
+ const role = normalizeAdminRole(data.role);
41
+ if (role === null || (options.allowedRoles && !options.allowedRoles.includes(role))) {
42
+ return { ok: false, code: "FORBIDDEN", message: "Admin role required" };
43
+ }
44
+
45
+ // Fail closed: mirror the activate RPC's `COALESCE(is_machine_actor, true)` — only an
46
+ // explicit `false` is a human actor; anything else is treated as a machine. The DB row
47
+ // (`admin_users.is_machine_actor`, re-derived here and in every write RPC) is the single
48
+ // source of truth for actor kind — never trusted from the caller.
49
+ return {
50
+ ok: true,
51
+ userId: userData.user.id,
52
+ role,
53
+ isMachineActor: data.is_machine_actor !== false,
54
+ };
55
+ }
56
+
57
+ export function authorizeCommerceAdminWithUser(
58
+ client: unknown,
59
+ accessToken: string | null,
60
+ ): Promise<AdminAuthorization> {
61
+ return authorizeAdminWithUser(client, accessToken, { allowedRoles: ["admin"] });
62
+ }
63
+
64
+ export async function authorizeAdminBooleanWithUser(
65
+ client: unknown,
66
+ accessToken: string | null,
67
+ options: { allowedRoles?: readonly AdminRole[] } = {},
68
+ ): Promise<boolean> {
69
+ const authorization = await authorizeAdminWithUser(client, accessToken, options);
70
+ return authorization.ok;
71
+ }
72
+
73
+ /**
74
+ * Shared authz gate: returns the resolved admin (`userId` + DB-derived
75
+ * `isMachineActor`) or sends the error envelope and returns null.
76
+ */
77
+ export async function resolveAdmin(
78
+ authorizeAdmin: AuthorizeAdmin,
79
+ res: HttpResponse,
80
+ options: IdentityResponseOptions = {},
81
+ ): Promise<{ userId: string; role: AdminRole; isMachineActor: boolean } | null> {
82
+ const { sendBffError } = createIdentityResponder(options);
83
+ let authz: AdminAuthorization;
84
+ try {
85
+ authz = await authorizeAdmin();
86
+ } catch {
87
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Admin authorization failed");
88
+ return null;
89
+ }
90
+ if (authz.ok === false) {
91
+ sendBffError(res, authz.code, authz.message);
92
+ return null;
93
+ }
94
+ return { userId: authz.userId, role: authz.role, isMachineActor: authz.isMachineActor };
95
+ }
96
+
97
+ function normalizeAdminRole(role: unknown): AdminRole | null {
98
+ return role === "admin" || role === "distributor" ? role : null;
99
+ }
100
+ export function readBearerToken(req: HttpRequest): string | null {
101
+ const raw = req.headers.authorization;
102
+ const value = Array.isArray(raw) ? raw[0] : raw;
103
+ const match = value?.match(/^Bearer\s+(.+)$/i);
104
+ return match?.[1]?.trim() || null;
105
+ }
106
+
@@ -0,0 +1,52 @@
1
+ import { z } from "zod";
2
+ z.config({ jitless: true });
3
+ export const adminUserRoleSchema = z.enum(["admin", "distributor"]);
4
+
5
+ export const adminPlatformMeRequestSchema = z.object({}).strict();
6
+
7
+ export const adminPlatformMeResponseSchema = z.object({
8
+ isAdmin: z.boolean(),
9
+ role: adminUserRoleSchema.nullable(),
10
+ });
11
+
12
+ export const adminUserRoleUpdateRequestSchema = z.object({
13
+ userId: z.string().min(1),
14
+ role: adminUserRoleSchema,
15
+ });
16
+
17
+ export const adminUserRoleUpdateResponseSchema = z.object({
18
+ userId: z.string().min(1),
19
+ role: adminUserRoleSchema,
20
+ saved: z.literal(true),
21
+ });
22
+
23
+ export const adminUserInviteRequestSchema = z.object({
24
+ email: z.string().trim().email().toLowerCase(),
25
+ role: adminUserRoleSchema,
26
+ });
27
+
28
+ export const adminUserInviteResponseSchema = z.discriminatedUnion("membershipState", [z.object({
29
+ principalId: z.string().min(1),
30
+ invitationId: z.string().min(1),
31
+ membershipState: z.literal("invited"),
32
+ role: adminUserRoleSchema,
33
+ invitedAt: z.iso.datetime(),
34
+ expiresAt: z.iso.datetime(),
35
+ code: z.enum(["INVITATION_CREATED", "ALREADY_INVITED", "INVITATION_REISSUED"]),
36
+ message: z.string().optional(),
37
+ }), z.object({
38
+ principalId: z.string().min(1),
39
+ membershipState: z.literal("active"),
40
+ role: adminUserRoleSchema,
41
+ code: z.enum(["MEMBERSHIP_CREATED", "ALREADY_ACTIVE"]),
42
+ message: z.string().optional(),
43
+ })]);
44
+
45
+ export const adminUserRemoveRequestSchema = z.object({
46
+ userId: z.string().min(1),
47
+ });
48
+
49
+ export const adminUserRemoveResponseSchema = z.object({
50
+ revoked: z.literal(true),
51
+ });
52
+
@@ -0,0 +1,132 @@
1
+ import { createMagicLinkOriginResolver } from "./authVerifyUrl.js";
2
+ import { createIdentityResponder, type IdentityResponseOptions } from "./response.js";
3
+ import type { z } from "zod";
4
+ import type { HttpRequest, HttpResponse } from "../http/index.js";
5
+ import type { MagicLinkAuthClient, MagicLinkRateLimitClient, MagicLinkDecision } from "./magicLinkPorts.js";
6
+ export interface AdminMagicLinkHandlerOptions extends IdentityResponseOptions {
7
+ requestSchema: z.ZodType<{ email: string }>;
8
+ responseSchema: z.ZodType<{ accepted: true }>;
9
+ readEnvironment: () => { url: string; anonKey: string; serviceRoleKey: string } | null;
10
+ createRuntime: (env: { url: string; anonKey: string; serviceRoleKey: string }) => {
11
+ authClient: MagicLinkAuthClient;
12
+ rateLimitClient: MagicLinkRateLimitClient;
13
+ platformPort: { checkAdminMagicLinkEligibility(email: string): Promise<string> };
14
+ };
15
+ checkAttempt: (input: { client: MagicLinkRateLimitClient; ip: string; email: string }) => Promise<MagicLinkDecision>;
16
+ extractClientIp: (headers: HttpRequest["headers"]) => string;
17
+ padResponseTime: (startedAt: number, floor: number) => Promise<void>;
18
+ redirectOrigins?: readonly string[];
19
+ resolveOrigin?: (req: HttpRequest) => string | null;
20
+ callbackPath: string;
21
+ }
22
+ const ADMIN_MAGIC_LINK_RESPONSE_FLOOR_MS = 400;
23
+ export function createAdminMagicLinkHandler(options: AdminMagicLinkHandlerOptions) {
24
+ const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = createIdentityResponder(options);
25
+ const readRequestOrigin = options.resolveOrigin ?? createMagicLinkOriginResolver(options.redirectOrigins ?? []);
26
+ const { requestSchema: adminMagicLinkRequestSchema, responseSchema: adminMagicLinkResponseSchema, readEnvironment: readSupabaseAdminServiceEnv, createRuntime: createAdminMagicLinkRuntime, checkAttempt: checkAndRecordAdminMagicLinkAttempt, extractClientIp, padResponseTime } = options;
27
+ return async (req: HttpRequest, res: HttpResponse): Promise<void> => {
28
+ if (req.method !== "POST") {
29
+ sendMethodNotAllowed(res, ["POST"]);
30
+ return;
31
+ }
32
+
33
+ const request = adminMagicLinkRequestSchema.safeParse(req.body);
34
+ if (!request.success) {
35
+ sendBffError(res, "BAD_REQUEST", "Invalid admin magic-link request", {
36
+ details: request.error.flatten(),
37
+ });
38
+ return;
39
+ }
40
+
41
+ const startedAt = Date.now();
42
+ const origin = readRequestOrigin(req);
43
+ if (!origin) {
44
+ await padResponseTime(startedAt, ADMIN_MAGIC_LINK_RESPONSE_FLOOR_MS);
45
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Magic link origin is not configured", {
46
+ details: { reason: "magic_link_origin_not_configured" },
47
+ });
48
+ return;
49
+ }
50
+
51
+ const env = readSupabaseAdminServiceEnv();
52
+ if (!env) {
53
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Admin auth is not configured", {
54
+ details: { feature: "admin_auth", requiredEnv: "SUPABASE_SERVICE_ROLE_KEY" },
55
+ });
56
+ return;
57
+ }
58
+
59
+ const runtime = createAdminMagicLinkRuntime(env);
60
+
61
+ const decision = await checkAndRecordAdminMagicLinkAttempt({
62
+ client: runtime.rateLimitClient,
63
+ ip: extractClientIp(req.headers),
64
+ email: request.data.email,
65
+ });
66
+
67
+ if (!decision.allowed && decision.reason === "rpc_error") {
68
+ console.warn("admin_magic_link_rate_limit_unavailable", JSON.stringify({ reason: decision.reason }));
69
+ await padResponseTime(startedAt, ADMIN_MAGIC_LINK_RESPONSE_FLOOR_MS);
70
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Magic link service temporarily unavailable");
71
+ return;
72
+ }
73
+
74
+ if (decision.allowed) {
75
+ let eligibility: Awaited<ReturnType<typeof runtime.platformPort.checkAdminMagicLinkEligibility>>;
76
+ try {
77
+ eligibility = await runtime.platformPort.checkAdminMagicLinkEligibility(request.data.email);
78
+ } catch {
79
+ console.warn("admin_magic_link_lookup_failed", JSON.stringify({ outcome: "lookup_error" }));
80
+ await padResponseTime(startedAt, ADMIN_MAGIC_LINK_RESPONSE_FLOOR_MS);
81
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Magic link service temporarily unavailable");
82
+ return;
83
+ }
84
+
85
+ if (eligibility === "eligible") {
86
+ const outcome = await sendMagicLink(runtime.authClient, request.data.email, origin);
87
+ console.info(
88
+ "admin_magic_link_outcome",
89
+ JSON.stringify({ outcome: outcome.status, code: outcome.code ?? null }),
90
+ );
91
+ } else {
92
+ console.warn("admin_magic_link_outcome", JSON.stringify({ outcome: eligibility }));
93
+ }
94
+ } else {
95
+ console.warn(
96
+ "admin_magic_link_outcome",
97
+ JSON.stringify({ outcome: "rate_limited", reason: decision.reason ?? "unknown" }),
98
+ );
99
+ }
100
+
101
+ await padResponseTime(startedAt, ADMIN_MAGIC_LINK_RESPONSE_FLOOR_MS);
102
+ sendBffSuccess(res, adminMagicLinkResponseSchema.parse({ accepted: true }));
103
+ };
104
+
105
+ async function sendMagicLink(
106
+ client: MagicLinkAuthClient,
107
+ email: string,
108
+ origin: string,
109
+ ): Promise<{ status: "sent" | "auth_error" | "threw"; code?: string }> {
110
+ try {
111
+ const { error } = await client.auth.signInWithOtp({
112
+ email,
113
+ options: {
114
+ shouldCreateUser: false,
115
+ emailRedirectTo: `${origin}${options.callbackPath}`,
116
+ },
117
+ });
118
+
119
+ if (error) {
120
+ console.warn("admin_magic_link_send_failed", JSON.stringify({ code: error.name ?? "AuthError" }));
121
+ return { status: "auth_error", code: error.name ?? "AuthError" };
122
+ }
123
+ return { status: "sent" };
124
+ } catch (error) {
125
+ const code = error instanceof Error ? error.name : "unknown";
126
+ console.warn("admin_magic_link_send_threw", JSON.stringify({ error: code }));
127
+ return { status: "threw", code };
128
+ }
129
+ }
130
+
131
+
132
+ }
@@ -0,0 +1,64 @@
1
+ import type { OperatorInvitationStore } from "./operatorInvitationPorts.js";
2
+ import { createIdentityResponder, type IdentityResponseOptions } from "./response.js";
3
+ import type { HttpRequest, HttpResponse } from "../http/index.js";
4
+ import { adminPlatformMeRequestSchema, adminPlatformMeResponseSchema } from "./adminContracts.js";
5
+ import type { PlatformUserAuthenticationResult } from "./platformAdminAuth.js";
6
+ export interface AdminPlatformMeDeps extends IdentityResponseOptions {
7
+ invitationStore?: Pick<OperatorInvitationStore, "acceptOperatorInvitation">;
8
+ platformPort: { getAdminMe(userId: string): Promise<{ isAdmin: boolean; role: "admin" | "distributor" | null }> };
9
+ authenticateUser: (req: HttpRequest) => Promise<PlatformUserAuthenticationResult>;
10
+ }
11
+
12
+ export function createAdminPlatformMeHandler({
13
+ platformPort, invitationStore,
14
+ authenticateUser, responseRequestId,
15
+ }: AdminPlatformMeDeps) {
16
+ const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = createIdentityResponder({ responseRequestId });
17
+ return async function handler(req: HttpRequest, res: HttpResponse): Promise<void> {
18
+ if (req.method !== "GET") {
19
+ sendMethodNotAllowed(res, ["GET"]);
20
+ return;
21
+ }
22
+
23
+ let authentication: PlatformUserAuthenticationResult;
24
+ try {
25
+ authentication = await authenticateUser(req);
26
+ } catch {
27
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Platform user authentication failed");
28
+ return;
29
+ }
30
+
31
+ if (authentication.ok === false) {
32
+ sendBffError(res, authentication.code, authentication.message);
33
+ return;
34
+ }
35
+
36
+ const request = adminPlatformMeRequestSchema.safeParse(req.query);
37
+ if (!request.success) {
38
+ sendBffError(res, "BAD_REQUEST", "Invalid platform me request", {
39
+ details: request.error.flatten(),
40
+ });
41
+ return;
42
+ }
43
+
44
+ try {
45
+ if (invitationStore) {
46
+ const outcome = await invitationStore.acceptOperatorInvitation(authentication.userId);
47
+ if (outcome.code === "INVITATION_EXPIRED" || outcome.code === "INVITATION_REVOKED") {
48
+ sendBffError(res, "FORBIDDEN", outcome.code, { details: { identityCode: outcome.code } });
49
+ return;
50
+ }
51
+ }
52
+ const result = await platformPort.getAdminMe(authentication.userId);
53
+ const response = adminPlatformMeResponseSchema.safeParse(result);
54
+ if (!response.success) {
55
+ sendBffError(res, "INVALID_RESPONSE", "Platform me returned invalid response");
56
+ return;
57
+ }
58
+
59
+ sendBffSuccess(res, response.data);
60
+ } catch {
61
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Platform me read failed");
62
+ }
63
+ };
64
+ }
@@ -0,0 +1,46 @@
1
+ import { createIdentityResponder, type IdentityResponseOptions } from "./response.js";
2
+ import type { HttpRequest, HttpResponse } from "../http/index.js";
3
+ import type { AdminAuthPort, AdminPrincipalRole } from "./ports.js";
4
+ import { readBearerToken } from "./adminAuthorization.js";
5
+ import { refusesMachineActorRequest, sendMachineActorWriteRefused } from "./machineActorWriteFence.js";
6
+
7
+ export interface AdminAuthBinding {
8
+ readonly identity: string;
9
+ run<T>(accessToken: string | null, work: (auth: AdminAuthPort) => Promise<T>): Promise<T>;
10
+ }
11
+ export function createAdminBffPreflight(options: IdentityResponseOptions & {
12
+ allowedRoles: (route: string) => readonly AdminPrincipalRole[] | null;
13
+ resolveBinding: () => { binding?: AdminAuthBinding; error?: string };
14
+ machineWriteRoutes?: readonly string[];
15
+ managedEnvironmentMessage?: string;
16
+ }) {
17
+ const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = createIdentityResponder(options);
18
+ return async (req: HttpRequest, res: HttpResponse, route: string): Promise<boolean> => {
19
+ const allowedRoles = options.allowedRoles(route);
20
+ if (!allowedRoles) return true;
21
+ const resolved = options.resolveBinding();
22
+ if (!resolved.binding) {
23
+ if (resolved.error === "managed_admin_auth_env_required") {
24
+ sendBffError(res, "INTERNAL", options.managedEnvironmentMessage ?? "Identity environment is not configured");
25
+ } else sendBffError(res, "UPSTREAM_UNAVAILABLE", "Admin authorization failed");
26
+ return false;
27
+ }
28
+ const accessToken = readBearerToken(req);
29
+ try {
30
+ const authz = await resolved.binding.run(accessToken, (auth) => auth.authorize(accessToken, { allowedRoles }));
31
+ if (authz.ok === false) {
32
+ sendBffError(res, authz.code, authz.message);
33
+ return false;
34
+ }
35
+ if (resolved.binding.identity !== "node-postgres"
36
+ && refusesMachineActorRequest(route, req.method, authz.isMachineActor, options.machineWriteRoutes)) {
37
+ sendMachineActorWriteRefused(res, options);
38
+ return false;
39
+ }
40
+ return true;
41
+ } catch {
42
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Admin authorization failed");
43
+ return false;
44
+ }
45
+ };
46
+ }
@@ -0,0 +1,78 @@
1
+ import { adminUserInviteResponseSchema } from "./adminContracts.js";
2
+ import type { IdentityAdminActionsPort } from "./adminUserHandlers.js";
3
+ import type { runInviteAdminUserUseCase } from "./inviteAdminUserUseCase.js";
4
+ import type { runRemoveAdminUserUseCase } from "./removeAdminUserUseCase.js";
5
+ export interface AdminUserInviteErrorDetails {
6
+ identityCode?: string;
7
+ invitationId?: string;
8
+ principalId?: string;
9
+ membershipState?: "active";
10
+ role?: "admin" | "distributor";
11
+ }
12
+ type AdminUserActionRuntimeEnv = Record<string, string | undefined>;
13
+ type AdminUserInviteRunner = (request: { accessToken: string | null; email: string; role: "admin" | "distributor" }, env?: AdminUserActionRuntimeEnv) => ReturnType<typeof runInviteAdminUserUseCase>;
14
+ type AdminUserRemoveRunner = (request: { accessToken: string | null; userId: string }, env?: AdminUserActionRuntimeEnv) => ReturnType<typeof runRemoveAdminUserUseCase>;
15
+ export function createAdminUserInvitePort(options: {
16
+ accessToken: string | null;
17
+ env?: AdminUserActionRuntimeEnv;
18
+ runInviteAdminUser: AdminUserInviteRunner;
19
+ }): Pick<IdentityAdminActionsPort, "inviteAdminUser"> {
20
+ const runInviteAdminUser = options.runInviteAdminUser;
21
+ return {
22
+ async inviteAdminUser(request) {
23
+ const result = await runInviteAdminUser({
24
+ accessToken: options.accessToken,
25
+ email: request.email,
26
+ role: request.role,
27
+ }, options.env);
28
+ if (result.status < 200 || result.status >= 300) {
29
+ const details: AdminUserInviteErrorDetails = {
30
+ identityCode: result.body.code,
31
+ ...(typeof result.body.invitationId === "string" ? { invitationId: result.body.invitationId } : {}),
32
+ ...(result.body.membershipState === "active" && typeof result.body.principalId === "string"
33
+ && (result.body.role === "admin" || result.body.role === "distributor")
34
+ ? { principalId: result.body.principalId, membershipState: "active", role: result.body.role } : {}),
35
+ };
36
+ throw Object.assign(new Error(String(result.body.error ?? `invite_admin_user_http_${result.status}`)), { bffCode: ({ 400: "BAD_REQUEST", 401: "UNAUTHORIZED", 403: "FORBIDDEN", 409: "CONFLICT" } as Record<number, string>)[result.status] ?? "UPSTREAM_UNAVAILABLE", inviteDetails: details });
37
+ }
38
+ if (result.body.error) throw new Error(result.body.error);
39
+
40
+ return adminUserInviteResponseSchema.parse(result.body);
41
+ },
42
+ };
43
+ }
44
+
45
+ export function createAdminUserRemovePort(options: {
46
+ accessToken: string | null;
47
+ env?: AdminUserActionRuntimeEnv;
48
+ runRemoveAdminUser: AdminUserRemoveRunner;
49
+ }): Pick<IdentityAdminActionsPort, "removeAdminUser"> {
50
+ const runRemoveAdminUser = options.runRemoveAdminUser;
51
+ return {
52
+ async removeAdminUser(request) {
53
+ const result = await runRemoveAdminUser({
54
+ accessToken: options.accessToken,
55
+ userId: request.userId,
56
+ }, options.env);
57
+ if (result.status < 200 || result.status >= 300) {
58
+ throw removePortError(
59
+ result.status,
60
+ String(result.body.error ?? `remove_admin_user_http_${result.status}`),
61
+ );
62
+ }
63
+ if (result.body.error) throw new Error(result.body.error);
64
+
65
+ if (result.body.revoked !== true) {
66
+ throw new Error("admin_revoke_admin_user returned an invalid response");
67
+ }
68
+ return { revoked: true };
69
+ },
70
+ };
71
+ }
72
+
73
+ function removePortError(status: number, message: string): Error {
74
+ const bffCode = ({ 400: "BAD_REQUEST", 403: "FORBIDDEN", 409: "CONFLICT" } as const)[status]
75
+ ?? "UPSTREAM_UNAVAILABLE";
76
+ return Object.assign(new Error(message), { bffCode });
77
+ }
78
+
@@ -0,0 +1,174 @@
1
+ import type { AdminUserInviteErrorDetails } from "./adminUserActionPorts.js";
2
+ import { createIdentityResponder, type IdentityResponseOptions } from "./response.js";
3
+ import type { HttpRequest, HttpResponse } from "../http/index.js";
4
+ import { adminUserRoleUpdateRequestSchema, adminUserRoleUpdateResponseSchema, adminUserInviteRequestSchema, adminUserInviteResponseSchema, adminUserRemoveRequestSchema, adminUserRemoveResponseSchema } from "./adminContracts.js";
5
+ import type { PlatformAdminAuthorizationResult } from "./platformAdminAuth.js";
6
+ import type { OperatorInviteResult } from "./contracts/operatorInvitations.js";
7
+ export interface IdentityAdminActionsPort {
8
+ inviteAdminUser(request: { email: string; role: "admin" | "distributor" }): Promise<OperatorInviteResult>;
9
+ removeAdminUser(request: { userId: string }): Promise<{ revoked: true }>;
10
+ updateAdminUserRole(request: { userId: string; role: "admin" | "distributor" }): Promise<{ userId: string; role: "admin" | "distributor"; saved: true }>;
11
+ }
12
+ export interface AdminUserRoleUpdateDeps extends IdentityResponseOptions { settingsPort: Pick<IdentityAdminActionsPort, "updateAdminUserRole">; authorizeAdmin: (req: HttpRequest) => Promise<PlatformAdminAuthorizationResult> }
13
+ export interface AdminUserInviteDeps extends IdentityResponseOptions { settingsPort: Pick<IdentityAdminActionsPort, "inviteAdminUser">; authorizeAdmin: (req: HttpRequest) => Promise<PlatformAdminAuthorizationResult> }
14
+ export interface AdminUserRemoveDeps extends IdentityResponseOptions { settingsPort: Pick<IdentityAdminActionsPort, "removeAdminUser">; authorizeAdmin: (req: HttpRequest) => Promise<PlatformAdminAuthorizationResult> }
15
+ export function createAdminUserRoleUpdateHandler({
16
+ settingsPort,
17
+ authorizeAdmin, responseRequestId,
18
+ }: AdminUserRoleUpdateDeps) {
19
+ const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = createIdentityResponder({ responseRequestId });
20
+ return async function handler(req: HttpRequest, res: HttpResponse): Promise<void> {
21
+ if (req.method !== "POST") {
22
+ sendMethodNotAllowed(res, ["POST"]);
23
+ return;
24
+ }
25
+
26
+ let authorization: PlatformAdminAuthorizationResult;
27
+ try {
28
+ authorization = await authorizeAdmin(req);
29
+ } catch {
30
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Admin authorization failed");
31
+ return;
32
+ }
33
+
34
+ if (authorization.ok === false) {
35
+ sendBffError(res, authorization.code, authorization.message);
36
+ return;
37
+ }
38
+
39
+ const request = adminUserRoleUpdateRequestSchema.safeParse(req.body);
40
+ if (!request.success) {
41
+ sendBffError(res, "BAD_REQUEST", "Invalid admin user role update request", {
42
+ details: request.error.flatten(),
43
+ });
44
+ return;
45
+ }
46
+
47
+ try {
48
+ const result = await settingsPort.updateAdminUserRole(request.data);
49
+ const response = adminUserRoleUpdateResponseSchema.safeParse(result);
50
+ if (!response.success) {
51
+ sendBffError(res, "INVALID_RESPONSE", "Admin user role update returned invalid response");
52
+ return;
53
+ }
54
+
55
+ sendBffSuccess(res, response.data);
56
+ } catch (error) {
57
+ const bffCode = (error as { bffCode?: string })?.bffCode;
58
+ if (bffCode) {
59
+ sendBffError(res, bffCode as Parameters<typeof sendBffError>[1], (error as Error).message);
60
+ return;
61
+ }
62
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Admin user role update failed");
63
+ }
64
+ };
65
+ }
66
+
67
+ export function createAdminUserInviteHandler({
68
+ settingsPort,
69
+ authorizeAdmin, responseRequestId,
70
+ }: AdminUserInviteDeps) {
71
+ const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = createIdentityResponder({ responseRequestId });
72
+ return async function handler(req: HttpRequest, res: HttpResponse): Promise<void> {
73
+ if (req.method !== "POST") {
74
+ sendMethodNotAllowed(res, ["POST"]);
75
+ return;
76
+ }
77
+
78
+ let authorization: PlatformAdminAuthorizationResult;
79
+ try {
80
+ authorization = await authorizeAdmin(req);
81
+ } catch {
82
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Admin authorization failed");
83
+ return;
84
+ }
85
+
86
+ if (authorization.ok === false) {
87
+ sendBffError(res, authorization.code, authorization.message);
88
+ return;
89
+ }
90
+
91
+ const request = adminUserInviteRequestSchema.safeParse(req.body);
92
+ if (!request.success) {
93
+ sendBffError(res, "BAD_REQUEST", "Invalid admin user invite request", {
94
+ details: request.error.flatten(),
95
+ });
96
+ return;
97
+ }
98
+
99
+ try {
100
+ const result = await settingsPort.inviteAdminUser(request.data);
101
+ const response = adminUserInviteResponseSchema.safeParse(result);
102
+ if (!response.success) {
103
+ sendBffError(res, "INVALID_RESPONSE", "Admin user invite returned invalid response");
104
+ return;
105
+ }
106
+
107
+ sendBffSuccess(res, response.data);
108
+ } catch (error) {
109
+ const failure = error as { bffCode?: string; inviteDetails?: AdminUserInviteErrorDetails };
110
+ sendBffError(res, (failure.bffCode ?? "UPSTREAM_UNAVAILABLE") as Parameters<typeof sendBffError>[1], errorMessage(error, "Admin user invite failed"), failure.inviteDetails ? { details: failure.inviteDetails } : {});
111
+ }
112
+ };
113
+ }
114
+
115
+ export function createAdminUserRemoveHandler({
116
+ settingsPort,
117
+ authorizeAdmin, responseRequestId,
118
+ }: AdminUserRemoveDeps) {
119
+ const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = createIdentityResponder({ responseRequestId });
120
+ return async function handler(req: HttpRequest, res: HttpResponse): Promise<void> {
121
+ if (req.method !== "POST") {
122
+ sendMethodNotAllowed(res, ["POST"]);
123
+ return;
124
+ }
125
+
126
+ let authorization: PlatformAdminAuthorizationResult;
127
+ try {
128
+ authorization = await authorizeAdmin(req);
129
+ } catch {
130
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Admin authorization failed");
131
+ return;
132
+ }
133
+
134
+ if (authorization.ok === false) {
135
+ sendBffError(res, authorization.code, authorization.message);
136
+ return;
137
+ }
138
+
139
+ const request = adminUserRemoveRequestSchema.safeParse(req.body);
140
+ if (!request.success) {
141
+ sendBffError(res, "BAD_REQUEST", "Invalid admin user remove request", {
142
+ details: request.error.flatten(),
143
+ });
144
+ return;
145
+ }
146
+
147
+ try {
148
+ const result = await settingsPort.removeAdminUser(request.data);
149
+ const response = adminUserRemoveResponseSchema.safeParse(result);
150
+ if (!response.success) {
151
+ sendBffError(res, "INVALID_RESPONSE", "Admin user remove returned invalid response");
152
+ return;
153
+ }
154
+
155
+ sendBffSuccess(res, response.data);
156
+ } catch (error) {
157
+ sendMutationError(res, error, "Admin user revoke failed", responseRequestId);
158
+ }
159
+ };
160
+ }
161
+
162
+ function errorMessage(error: unknown, fallback: string): string {
163
+ return error instanceof Error && error.message ? error.message : fallback;
164
+ }
165
+
166
+ function sendMutationError(res: HttpResponse, error: unknown, fallback: string, responseRequestId: IdentityResponseOptions["responseRequestId"]): void {
167
+ const { sendBffError } = createIdentityResponder({ responseRequestId });
168
+ const bffCode = (error as { bffCode?: string })?.bffCode;
169
+ if (bffCode) {
170
+ sendBffError(res, bffCode as Parameters<typeof sendBffError>[1], errorMessage(error, fallback));
171
+ return;
172
+ }
173
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", errorMessage(error, fallback));
174
+ }