@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,39 @@
1
+ import { PrivacyRefusal } from "./privacyPorts.js";
2
+ /** Reads only committed service projections. No managed write or provider delete is composed. */
3
+ export function createManagedPrivacyStore() {
4
+ const refuse = async () => { throw new PrivacyRefusal("MANAGED_PRIVACY_ERASURE_UNAVAILABLE"); };
5
+ return {
6
+ erasureSupported: false, prepareErasure: refuse, authorizeErasure: refuse, eraseIdentity: refuse, saveReceipt: refuse,
7
+ async findReceipt() { return null; },
8
+ async resolve(client, ref) {
9
+ const query = client.from("clients").select("id, email, auth_user_id, first_name, last_name, phone");
10
+ const { data: contact, error } = await query.eq(ref.kind === "contact" ? "id" : "auth_user_id", ref.kind === "contact" ? ref.contactId : ref.principalId).maybeSingle();
11
+ if (error)
12
+ throw new PrivacyRefusal("PRIVACY_ASSESSMENT_UNAVAILABLE");
13
+ const principalId = ref.kind === "principal" ? ref.principalId : contact?.auth_user_id ?? null;
14
+ let principal = null;
15
+ let membership = null;
16
+ if (principalId) {
17
+ const result = await client.auth.admin.getUserById(principalId);
18
+ if (result.error) {
19
+ const failure = result.error;
20
+ if (!contact && (failure.status === 404 || failure.code === "user_not_found"))
21
+ return null;
22
+ throw new PrivacyRefusal("PRIVACY_ASSESSMENT_UNAVAILABLE");
23
+ }
24
+ if (result.data.user)
25
+ principal = { principal_id: result.data.user.id, email: result.data.user.email ?? null, email_confirmed_at: result.data.user.email_confirmed_at ?? null };
26
+ const read = await client.from("admin_users").select("id, membership_state, role, is_machine_actor").eq("id", principalId).maybeSingle();
27
+ if (read.error)
28
+ throw new PrivacyRefusal("PRIVACY_ASSESSMENT_UNAVAILABLE");
29
+ membership = read.data ? JSON.parse(JSON.stringify(read.data)) : null;
30
+ }
31
+ if (!contact && !principal)
32
+ return null;
33
+ return { contactId: contact?.id ?? null, principalId, identity: {
34
+ contact: contact ? JSON.parse(JSON.stringify(contact)) : null, principal, membership,
35
+ unavailable: ["contact.country", "identity.audit-history", "identity.invitation-history", "identity.erasure-receipts"],
36
+ } };
37
+ },
38
+ };
39
+ }
@@ -0,0 +1,29 @@
1
+ type Role = "admin" | "distributor";
2
+ declare const en: {
3
+ unauthorized: string;
4
+ invalidToken: string;
5
+ inviteForbidden: string;
6
+ emailRequired: string;
7
+ invalidRole: string;
8
+ alreadyAdded: string;
9
+ addFailed: (detail: string) => string;
10
+ notificationFailed: (detail: string) => string;
11
+ roleAddedDisabled: (email: string, role: Role) => string;
12
+ roleAddedSuppressed: (email: string, role: Role) => string;
13
+ roleAdded: (email: string, role: Role) => string;
14
+ inviteFailed: (detail: string) => string;
15
+ roleInsertFailed: (detail: string) => string;
16
+ invited: (email: string, role: Role) => string;
17
+ removeForbidden: string;
18
+ userIdRequired: string;
19
+ selfRemoveForbidden: string;
20
+ lastAdmin: string;
21
+ selfRevoke: string;
22
+ machineRevoke: string;
23
+ adminNotFound: string;
24
+ adminRequired: string;
25
+ revokeFailed: (detail: string) => string;
26
+ };
27
+ export type IdentityMessages = typeof en;
28
+ export declare const identityMessages: Readonly<Record<"en" | "pl", IdentityMessages>>;
29
+ export {};
@@ -0,0 +1,39 @@
1
+ const roleName = (role) => role === "admin" ? "Admin" : "Dystrybutor";
2
+ const en = {
3
+ unauthorized: "Unauthorized", invalidToken: "Invalid token",
4
+ inviteForbidden: "Only admins may invite users", emailRequired: "Email required",
5
+ invalidRole: "Invalid role", alreadyAdded: "This user is already added",
6
+ addFailed: (detail) => `Adding user failed: ${detail}`,
7
+ notificationFailed: (detail) => `Role added, but notification failed: ${detail}`,
8
+ roleAddedDisabled: (email, role) => `User ${email} already had an account; added role ${role}, but notification was disabled and not sent.`,
9
+ roleAddedSuppressed: (email, role) => `User ${email} already had an account; added role ${role}, but notification was suppressed and not sent.`,
10
+ roleAdded: (email, role) => `User ${email} already had an account; added role ${role} and sent a notification.`,
11
+ inviteFailed: (detail) => `Invitation failed: ${detail}`,
12
+ roleInsertFailed: (detail) => `Account created, but adding role failed: ${detail}`,
13
+ invited: (email, role) => `Invitation with role ${role} sent to ${email}`,
14
+ removeForbidden: "Only admins may revoke user access", userIdRequired: "User ID required",
15
+ selfRemoveForbidden: "You cannot revoke your own access",
16
+ lastAdmin: "At least one active human admin must remain.",
17
+ selfRevoke: "You cannot revoke your own access.",
18
+ machineRevoke: "Machine actors cannot be revoked through this route.",
19
+ adminNotFound: "Admin user not found.", adminRequired: "Admin role required.",
20
+ revokeFailed: (detail) => `Admin access revoke failed: ${detail}`,
21
+ };
22
+ export const identityMessages = {
23
+ en,
24
+ pl: {
25
+ ...en,
26
+ inviteForbidden: "Tylko admini mogą zapraszać użytkowników", emailRequired: "Brak emaila",
27
+ invalidRole: "Nieprawidłowa rola", alreadyAdded: "Ten użytkownik jest już dodany",
28
+ addFailed: (detail) => `Błąd dodawania: ${detail}`,
29
+ notificationFailed: (detail) => `Rola dodana, ale nie udało się wysłać powiadomienia: ${detail}`,
30
+ roleAddedDisabled: (email, role) => `Użytkownik ${email} już miał konto — dodano rolę ${roleName(role)}, ale powiadomienie było wyłączone i nie zostało wysłane.`,
31
+ roleAddedSuppressed: (email, role) => `Użytkownik ${email} już miał konto — dodano rolę ${roleName(role)}, ale wysłanie powiadomienia zostało zablokowane i nie zostało wysłane.`,
32
+ roleAdded: (email, role) => `Użytkownik ${email} już miał konto — dodano rolę ${roleName(role)} i wysłano powiadomienie.`,
33
+ inviteFailed: (detail) => `Błąd zaproszenia: ${detail}`,
34
+ roleInsertFailed: (detail) => `Konto utworzone, ale błąd dodawania roli: ${detail}`,
35
+ invited: (email, role) => `Zaproszenie z rolą ${roleName(role)} wysłane na ${email}`,
36
+ removeForbidden: "Tylko administratorzy mogą odbierać dostęp użytkownikom",
37
+ userIdRequired: "Brak ID użytkownika", selfRemoveForbidden: "Nie możesz odebrać sobie dostępu",
38
+ },
39
+ };
@@ -0,0 +1,45 @@
1
+ import type { IdentityJwtLibraryLoader } from "./jwtLibrary.js";
2
+ import type { IdentityVerifierPort, VerifiedPrincipal } from "./ports.js";
3
+ import type { OidcFailureCode } from "./contracts/oidc.js";
4
+ export interface OidcIdentityVerifierConfig {
5
+ issuer: string;
6
+ audience: string;
7
+ algorithms: readonly ("RS256" | "ES256")[];
8
+ maxTokenLifetimeSeconds: number;
9
+ requireSingleAudience?: boolean;
10
+ /** Conformance only: literal loopback addresses, never arbitrary HTTP hosts. */
11
+ allowLoopbackHttp?: boolean;
12
+ }
13
+ export type OidcVerificationResult = {
14
+ ok: true;
15
+ principal: VerifiedPrincipal;
16
+ expiresAt: string;
17
+ } | {
18
+ ok: false;
19
+ code: OidcFailureCode;
20
+ };
21
+ export interface OidcReadinessReport {
22
+ state: "ready" | "unknown" | "unsatisfied";
23
+ discovery: {
24
+ reachable: boolean;
25
+ valid: boolean;
26
+ checkedAt: string | null;
27
+ expiresAt: string | null;
28
+ };
29
+ jwks: {
30
+ loaded: boolean;
31
+ checkedAt: string | null;
32
+ expiresAt: string | null;
33
+ };
34
+ code?: OidcFailureCode;
35
+ }
36
+ export interface OidcIdentityVerifier extends IdentityVerifierPort {
37
+ verifyToken(token: string): Promise<OidcVerificationResult>;
38
+ readiness(): Promise<OidcReadinessReport>;
39
+ }
40
+ /** One binding per host/configuration. Imports and construction perform no I/O or JWT loading. */
41
+ export declare function createOidcIdentityVerifier(config: OidcIdentityVerifierConfig, dependencies: {
42
+ loadJwt: IdentityJwtLibraryLoader;
43
+ fetch?: typeof globalThis.fetch;
44
+ now?: () => number;
45
+ }): OidcIdentityVerifier;
@@ -0,0 +1,309 @@
1
+ import { deriveOidcPrincipalId } from "./oidcPrincipal.js";
2
+ class Refusal extends Error {
3
+ code;
4
+ constructor(code) {
5
+ super(code);
6
+ this.code = code;
7
+ }
8
+ }
9
+ const refuse = (code) => { throw new Refusal(code); };
10
+ const record = (value) => !!value && typeof value === "object" && !Array.isArray(value);
11
+ const bounded = (value, max) => typeof value === "string" && value.length > 0 && Buffer.byteLength(value) <= max && !Array.from(value).some(character => character.charCodeAt(0) <= 0x20 || character.charCodeAt(0) === 0x7f);
12
+ const stamp = (value) => value === null ? null : new Date(value).toISOString();
13
+ const unavailable = (code) => code.endsWith("_UNAVAILABLE") || code === "OIDC_JWKS_REFRESH_RATE_LIMITED";
14
+ /** One binding per host/configuration. Imports and construction perform no I/O or JWT loading. */
15
+ export function createOidcIdentityVerifier(config, dependencies) {
16
+ const settings = { ...config, algorithms: [...config.algorithms] };
17
+ const now = dependencies.now ?? Date.now;
18
+ const transport = dependencies.fetch ?? globalThis.fetch;
19
+ function validUrl(value) {
20
+ const url = new URL(value);
21
+ if (url.username || url.password || url.search || url.hash || (url.protocol !== "https:"
22
+ && !(settings.allowLoopbackHttp && url.protocol === "http:" && ["127.0.0.1", "[::1]"].includes(url.hostname))))
23
+ throw new Error();
24
+ return url;
25
+ }
26
+ let origin = "", configurationValid = true;
27
+ try {
28
+ if (!bounded(settings.issuer, 2048) || !bounded(settings.audience, 256)
29
+ || !settings.algorithms.length || settings.algorithms.some(alg => alg !== "RS256" && alg !== "ES256")
30
+ || !Number.isInteger(settings.maxTokenLifetimeSeconds) || settings.maxTokenLifetimeSeconds < 1 || settings.maxTokenLifetimeSeconds > 3600)
31
+ throw new Error();
32
+ origin = validUrl(settings.issuer).origin;
33
+ }
34
+ catch {
35
+ configurationValid = false;
36
+ }
37
+ let library;
38
+ const jwt = () => library ??= Promise.resolve().then(dependencies.loadJwt).catch(() => { library = undefined; return refuse("OIDC_JWT_LIBRARY_UNAVAILABLE"); });
39
+ let discovery;
40
+ let keys;
41
+ let discoveryCheck = null, keyCheck = null, lastKeyAttempt = -Infinity;
42
+ let reachable = false, discoveryValid = false;
43
+ let discoveryFlight;
44
+ let keyFlight;
45
+ let keyFailure;
46
+ async function document(url, kind, maxAge) {
47
+ const controller = new AbortController();
48
+ let reader;
49
+ let timer;
50
+ const deadline = new Promise((_, reject) => {
51
+ timer = setTimeout(() => {
52
+ controller.abort();
53
+ void reader?.cancel().catch(() => { });
54
+ reject(new Refusal(`OIDC_${kind}_UNAVAILABLE`));
55
+ }, 3000);
56
+ });
57
+ try {
58
+ return await Promise.race([deadline, (async () => {
59
+ const response = await transport(url, { redirect: "error", signal: controller.signal, headers: { accept: "application/json" } });
60
+ if (kind === "DISCOVERY")
61
+ reachable = true;
62
+ if (!response.ok)
63
+ return refuse(`OIDC_${kind}_UNAVAILABLE`);
64
+ if (response.redirected || (response.url && response.url !== url))
65
+ return refuse(`OIDC_${kind}_INVALID`);
66
+ const length = response.headers.get("content-length");
67
+ if (length && (!/^\d+$/.test(length) || Number(length) > 65536))
68
+ return refuse(`OIDC_${kind}_INVALID`);
69
+ if (!response.body)
70
+ return refuse(`OIDC_${kind}_INVALID`);
71
+ reader = response.body.getReader();
72
+ const chunks = [];
73
+ let size = 0;
74
+ for (;;) {
75
+ const part = await reader.read();
76
+ if (part.done)
77
+ break;
78
+ size += part.value.byteLength;
79
+ if (size > 65536) {
80
+ void reader.cancel().catch(() => { });
81
+ return refuse(`OIDC_${kind}_INVALID`);
82
+ }
83
+ chunks.push(part.value);
84
+ }
85
+ let value;
86
+ try {
87
+ value = JSON.parse(Buffer.concat(chunks).toString("utf8"));
88
+ }
89
+ catch {
90
+ return refuse(`OIDC_${kind}_INVALID`);
91
+ }
92
+ const control = response.headers.get("cache-control") ?? "";
93
+ const ages = [...control.matchAll(/(?:^|,)\s*max-age\s*=\s*"?(\d+)"?/gi)].map(match => Number(match[1]));
94
+ const age = Number(response.headers.get("age") ?? "0");
95
+ // Revalidate no-cache documents at the next permitted JWKS refresh.
96
+ const cacheBound = /(?:^|,)\s*no-cache(?:\s|,|$)/i.test(control) ? 30 : maxAge;
97
+ const seconds = /(?:^|,)\s*no-store(?:\s|,|$)/i.test(control) ? 0
98
+ : Math.max(0, Math.min(maxAge, cacheBound, ...ages) - (Number.isFinite(age) && age >= 0 ? age : maxAge));
99
+ return { value, expires: now() + seconds * 1000 };
100
+ })()]);
101
+ }
102
+ catch (error) {
103
+ if (error instanceof Refusal)
104
+ throw error;
105
+ return refuse(`OIDC_${kind}_UNAVAILABLE`);
106
+ }
107
+ finally {
108
+ clearTimeout(timer);
109
+ controller.abort();
110
+ }
111
+ }
112
+ async function metadata() {
113
+ if (!configurationValid)
114
+ return refuse("OIDC_CONFIGURATION_INVALID");
115
+ if (discovery && discovery.expires > now())
116
+ return discovery;
117
+ if (discoveryFlight)
118
+ return discoveryFlight;
119
+ discoveryFlight = (async () => {
120
+ discoveryCheck = now();
121
+ reachable = false;
122
+ discoveryValid = false;
123
+ const fetched = await document(`${settings.issuer.replace(/\/$/, "")}/.well-known/openid-configuration`, "DISCOVERY", 3600);
124
+ if (!record(fetched.value) || fetched.value.issuer !== settings.issuer || typeof fetched.value.jwks_uri !== "string")
125
+ return refuse("OIDC_DISCOVERY_INVALID");
126
+ try {
127
+ if (validUrl(fetched.value.jwks_uri).origin !== origin)
128
+ throw new Error();
129
+ }
130
+ catch {
131
+ return refuse("OIDC_DISCOVERY_INVALID");
132
+ }
133
+ if (discovery?.jwksUrl !== fetched.value.jwks_uri)
134
+ keys = undefined;
135
+ discoveryValid = true;
136
+ return discovery = { jwksUrl: fetched.value.jwks_uri, expires: fetched.expires };
137
+ })();
138
+ try {
139
+ return await discoveryFlight;
140
+ }
141
+ finally {
142
+ discoveryFlight = undefined;
143
+ }
144
+ }
145
+ async function keySet(force = false) {
146
+ const meta = await metadata();
147
+ if (!force && keys && keys.expires > now())
148
+ return keys;
149
+ if (keyFlight)
150
+ return keyFlight;
151
+ if (now() - lastKeyAttempt < 30000)
152
+ return refuse("OIDC_JWKS_REFRESH_RATE_LIMITED");
153
+ lastKeyAttempt = now();
154
+ keyCheck = now();
155
+ keyFlight = (async () => {
156
+ const fetched = await document(meta.jwksUrl, "JWKS", 300);
157
+ const value = fetched.value;
158
+ if (!record(value) || !Array.isArray(value.keys) || !value.keys.length || value.keys.length > 64)
159
+ return refuse("OIDC_JWKS_INVALID");
160
+ const eligible = [];
161
+ for (const key of value.keys) {
162
+ if (!record(key) || ["d", "p", "q", "dp", "dq", "qi", "oth", "k"].some(field => field in key))
163
+ return refuse("OIDC_JWKS_INVALID");
164
+ if ((key.use !== undefined && key.use !== "sig") || (key.key_ops !== undefined && (!Array.isArray(key.key_ops) || !key.key_ops.length || !key.key_ops.every(operation => operation === "verify"))))
165
+ continue;
166
+ const alg = key.alg === undefined ? (key.kty === "RSA" ? "RS256" : key.kty === "EC" && key.crv === "P-256" ? "ES256" : "") : key.alg;
167
+ if (!settings.algorithms.includes(alg)
168
+ || (alg === "RS256" ? key.kty !== "RSA" : key.kty !== "EC" || key.crv !== "P-256"))
169
+ continue;
170
+ if (!bounded(key.kid, 128) || (alg === "RS256" ? !bounded(key.n, 2048) || !bounded(key.e, 16)
171
+ : !bounded(key.x, 128) || !bounded(key.y, 128)))
172
+ return refuse("OIDC_JWKS_INVALID");
173
+ eligible.push({ ...key, alg: String(alg) });
174
+ }
175
+ if (!eligible.length || new Set(eligible.map(key => key.kid)).size !== eligible.length)
176
+ return refuse("OIDC_JWKS_INVALID");
177
+ const lib = await jwt();
178
+ try {
179
+ await Promise.all(eligible.map(key => lib.importJWK(key, key.alg)));
180
+ }
181
+ catch {
182
+ return refuse("OIDC_JWKS_INVALID");
183
+ }
184
+ keyFailure = undefined;
185
+ return keys = { values: eligible, expires: fetched.expires };
186
+ })();
187
+ try {
188
+ return await keyFlight;
189
+ }
190
+ catch (error) {
191
+ keyFailure = error instanceof Refusal ? error.code : "OIDC_JWKS_UNAVAILABLE";
192
+ throw error;
193
+ }
194
+ finally {
195
+ keyFlight = undefined;
196
+ }
197
+ }
198
+ async function verifyToken(token) {
199
+ try {
200
+ if (!configurationValid)
201
+ return refuse("OIDC_CONFIGURATION_INVALID");
202
+ if (typeof token !== "string" || Buffer.byteLength(token) > 16384 || !/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(token))
203
+ return refuse("OIDC_TOKEN_INVALID");
204
+ let header, unverifiedPayload;
205
+ try {
206
+ const parts = token.split(".").map(part => {
207
+ const bytes = Buffer.from(part, "base64url");
208
+ if (bytes.toString("base64url") !== part)
209
+ return refuse("OIDC_TOKEN_INVALID");
210
+ return bytes;
211
+ });
212
+ const decoder = new TextDecoder("utf-8", { fatal: true });
213
+ header = JSON.parse(decoder.decode(parts[0]));
214
+ unverifiedPayload = JSON.parse(decoder.decode(parts[1]));
215
+ }
216
+ catch {
217
+ return refuse("OIDC_TOKEN_INVALID");
218
+ }
219
+ if (!record(header) || !record(unverifiedPayload) || !bounded(header.kid, 128)
220
+ || !settings.algorithms.includes(header.alg))
221
+ return refuse("OIDC_TOKEN_INVALID");
222
+ // Unverified claims may refuse before I/O; only verified claims can admit.
223
+ if (unverifiedPayload.iss !== settings.issuer)
224
+ return refuse("OIDC_ISSUER_MISMATCH");
225
+ let set = await keySet();
226
+ const select = () => set.values.find(key => key.kid === header.kid && key.alg === header.alg);
227
+ if (!select()) {
228
+ set = await keySet(true);
229
+ if (!select())
230
+ return refuse("OIDC_KID_UNKNOWN");
231
+ }
232
+ const lib = await jwt();
233
+ const selected = select();
234
+ let payload;
235
+ try {
236
+ const key = await lib.importJWK(selected, String(header.alg));
237
+ ({ payload } = await lib.jwtVerify(token, async (protectedHeader) => {
238
+ if (protectedHeader.alg !== header.alg || protectedHeader.kid !== header.kid)
239
+ return refuse("OIDC_TOKEN_INVALID");
240
+ return key;
241
+ }, { algorithms: settings.algorithms, audience: settings.audience, issuer: settings.issuer, currentDate: new Date(now()), clockTolerance: 0 }));
242
+ }
243
+ catch (error) {
244
+ if (record(error)) {
245
+ if (error.code === "ERR_JWT_EXPIRED")
246
+ return refuse("OIDC_TOKEN_EXPIRED");
247
+ if (error.code === "ERR_JWT_CLAIM_VALIDATION_FAILED") {
248
+ if (error.claim === "iss")
249
+ return refuse("OIDC_ISSUER_MISMATCH");
250
+ if (error.claim === "aud")
251
+ return refuse("OIDC_AUDIENCE_MISMATCH");
252
+ if (error.claim === "nbf")
253
+ return refuse("OIDC_TOKEN_NOT_ACTIVE");
254
+ }
255
+ }
256
+ return refuse("OIDC_TOKEN_INVALID");
257
+ }
258
+ if (payload.iss !== settings.issuer)
259
+ return refuse("OIDC_ISSUER_MISMATCH");
260
+ if (settings.requireSingleAudience ? payload.aud !== settings.audience
261
+ : !(payload.aud === settings.audience || (Array.isArray(payload.aud) && payload.aud.every(aud => typeof aud === "string") && payload.aud.includes(settings.audience))))
262
+ return refuse("OIDC_AUDIENCE_MISMATCH");
263
+ const seconds = now() / 1000;
264
+ if (!Number.isSafeInteger(payload.iat) || !Number.isSafeInteger(payload.exp) || payload.exp <= payload.iat
265
+ || payload.exp - payload.iat > settings.maxTokenLifetimeSeconds || payload.iat > seconds)
266
+ return refuse("OIDC_TOKEN_LIFETIME_INVALID");
267
+ if (payload.exp <= seconds)
268
+ return refuse("OIDC_TOKEN_EXPIRED");
269
+ if (payload.nbf !== undefined && (!Number.isSafeInteger(payload.nbf) || payload.nbf > seconds))
270
+ return refuse("OIDC_TOKEN_NOT_ACTIVE");
271
+ if (!bounded(payload.sub, 1024))
272
+ return refuse("OIDC_SUBJECT_INVALID");
273
+ if (!bounded(payload.email, 254) || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(payload.email))
274
+ return refuse("OIDC_EMAIL_INVALID");
275
+ if (payload.email_verified !== true)
276
+ return refuse("OIDC_EMAIL_UNVERIFIED");
277
+ return { ok: true, principal: { principalId: deriveOidcPrincipalId(settings.issuer, payload.sub), email: payload.email.trim().toLowerCase(), emailVerified: true }, expiresAt: stamp(payload.exp * 1000) };
278
+ }
279
+ catch (error) {
280
+ return { ok: false, code: error instanceof Refusal ? error.code : "OIDC_TOKEN_INVALID" };
281
+ }
282
+ }
283
+ return {
284
+ verifyToken,
285
+ async verifyAccessToken(token) {
286
+ const result = await verifyToken(token);
287
+ if (result.ok)
288
+ return result.principal;
289
+ if (unavailable(result.code))
290
+ throw new Error("OIDC identity verification unavailable");
291
+ return null;
292
+ },
293
+ async readiness() {
294
+ let code;
295
+ try {
296
+ await keySet();
297
+ }
298
+ catch (error) {
299
+ code = error instanceof Refusal ? error.code : "OIDC_JWKS_UNAVAILABLE";
300
+ }
301
+ if (code === "OIDC_JWKS_REFRESH_RATE_LIMITED" && keyFailure)
302
+ code = keyFailure;
303
+ const fresh = !!keys && keys.expires > now();
304
+ return { state: code ? unavailable(code) ? "unknown" : "unsatisfied" : fresh ? "ready" : "unsatisfied",
305
+ discovery: { reachable, valid: discoveryValid && !!discovery && discovery.expires > now(), checkedAt: stamp(discoveryCheck), expiresAt: stamp(discovery?.expires ?? null) },
306
+ jwks: { loaded: fresh, checkedAt: stamp(keyCheck), expiresAt: stamp(keys?.expires ?? null) }, ...(code ? { code } : {}) };
307
+ },
308
+ };
309
+ }
@@ -0,0 +1,2 @@
1
+ /** Frozen UTF-8, domain-separated SHA-256 UUIDv8 mapping of the exact issuer/subject pair. */
2
+ export declare function deriveOidcPrincipalId(issuer: string, subject: string): string;
@@ -0,0 +1,10 @@
1
+ import { createHash } from "node:crypto";
2
+ /** Frozen UTF-8, domain-separated SHA-256 UUIDv8 mapping of the exact issuer/subject pair. */
3
+ export function deriveOidcPrincipalId(issuer, subject) {
4
+ const bytes = createHash("sha256").update("openlup:oidc-principal:v1\0", "utf8")
5
+ .update(JSON.stringify([issuer, subject]), "utf8").digest().subarray(0, 16);
6
+ bytes[6] = (bytes[6] & 0x0f) | 0x80;
7
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
8
+ const hex = bytes.toString("hex");
9
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
10
+ }
@@ -0,0 +1,6 @@
1
+ import type { AdminAuthPort, IdentityVerifierPort } from "./ports.js";
2
+ import type { OperatorIdentityStore } from "./operatorPorts.js";
3
+ export declare function createOperatorAdminAuth(options: {
4
+ verifier: IdentityVerifierPort;
5
+ authority: Pick<OperatorIdentityStore, "findMembership">;
6
+ }): AdminAuthPort;
@@ -0,0 +1,13 @@
1
+ export function createOperatorAdminAuth(options) {
2
+ return { async authorize(token, restrictions) {
3
+ const principal = token ? await options.verifier.verifyAccessToken(token) : null;
4
+ if (!principal)
5
+ return { ok: false, code: "UNAUTHORIZED", message: "Operator authentication required" };
6
+ const member = await options.authority.findMembership(principal.principalId);
7
+ if (!member || member.membershipState !== "active" || (member.role !== "admin" && member.role !== "distributor")
8
+ || (restrictions?.allowedRoles && !restrictions.allowedRoles.includes(member.role))) {
9
+ return { ok: false, code: "FORBIDDEN", message: "Operator access denied" };
10
+ }
11
+ return { ok: true, principalId: principal.principalId, role: member.role, isMachineActor: false };
12
+ } };
13
+ }
@@ -0,0 +1,18 @@
1
+ import { type IdentityResponseOptions } from "./response.js";
2
+ import type { HttpRequest, HttpResponse } from "../http/index.js";
3
+ import type { createOperatorSessionService } from "./operatorSession.js";
4
+ export interface OperatorAuthMessages {
5
+ invalidRequest: string;
6
+ forbidden: string;
7
+ unauthorized: string;
8
+ unavailable: string;
9
+ }
10
+ export declare function createOperatorAuthHandlers(options: IdentityResponseOptions & {
11
+ sessions: ReturnType<typeof createOperatorSessionService>;
12
+ origin: string;
13
+ messages: OperatorAuthMessages;
14
+ padResponseTime(startedAt: number, minimumMs: number): Promise<void>;
15
+ }): {
16
+ requestChallenge(req: HttpRequest, res: HttpResponse): Promise<void>;
17
+ verifyChallenge(req: HttpRequest, res: HttpResponse): Promise<void>;
18
+ };
@@ -0,0 +1,71 @@
1
+ import { operatorChallengeRequestSchema, operatorChallengeVerificationSchema, operatorSessionSchema } from "./contracts/operatorAuth.js";
2
+ import { createMagicLinkOriginResolver } from "./authVerifyUrl.js";
3
+ import { createIdentityResponder } from "./response.js";
4
+ export function createOperatorAuthHandlers(options) {
5
+ const responder = createIdentityResponder(options);
6
+ const origin = createMagicLinkOriginResolver([options.origin]);
7
+ function admitted(req, res) {
8
+ if (req.method !== "POST") {
9
+ responder.sendMethodNotAllowed(res, ["POST"]);
10
+ return false;
11
+ }
12
+ const configured = origin(req);
13
+ if (!configured) {
14
+ responder.sendBffError(res, "UPSTREAM_UNAVAILABLE", options.messages.unavailable);
15
+ return false;
16
+ }
17
+ const supplied = req.headers.origin;
18
+ const contentType = req.headers["content-type"];
19
+ if (supplied !== configured || typeof contentType !== "string" || !/^application\/json(?:;|$)/i.test(contentType)) {
20
+ responder.sendBffError(res, "FORBIDDEN", options.messages.forbidden);
21
+ return false;
22
+ }
23
+ return true;
24
+ }
25
+ return {
26
+ async requestChallenge(req, res) {
27
+ if (!admitted(req, res))
28
+ return;
29
+ const parsed = operatorChallengeRequestSchema.safeParse(req.body);
30
+ if (!parsed.success) {
31
+ responder.sendBffError(res, "BAD_REQUEST", options.messages.invalidRequest);
32
+ return;
33
+ }
34
+ const started = Date.now();
35
+ try {
36
+ await options.sessions.requestChallenge(parsed.data.email, async () => {
37
+ await options.padResponseTime(started, 400);
38
+ responder.sendBffSuccess(res, { accepted: true });
39
+ });
40
+ }
41
+ catch {
42
+ responder.sendBffError(res, "UPSTREAM_UNAVAILABLE", options.messages.unavailable);
43
+ }
44
+ },
45
+ async verifyChallenge(req, res) {
46
+ if (!admitted(req, res))
47
+ return;
48
+ // A token-shaped refusal is authentication failure; an invalid envelope is a bad request.
49
+ if (!req.body || typeof req.body !== "object" || Object.keys(req.body).length !== 1 || typeof req.body.token !== "string") {
50
+ responder.sendBffError(res, "BAD_REQUEST", options.messages.invalidRequest);
51
+ return;
52
+ }
53
+ const parsed = operatorChallengeVerificationSchema.safeParse(req.body);
54
+ if (!parsed.success) {
55
+ responder.sendBffError(res, "UNAUTHORIZED", options.messages.unauthorized);
56
+ return;
57
+ }
58
+ try {
59
+ const session = await options.sessions.verifyChallenge(parsed.data.token);
60
+ if (!session) {
61
+ responder.sendBffError(res, "UNAUTHORIZED", options.messages.unauthorized);
62
+ return;
63
+ }
64
+ responder.sendBffSuccess(res, operatorSessionSchema.parse(session));
65
+ }
66
+ catch {
67
+ responder.sendBffError(res, "UPSTREAM_UNAVAILABLE", options.messages.unavailable);
68
+ }
69
+ },
70
+ };
71
+ }
@@ -0,0 +1,8 @@
1
+ import type { IdentityJwtLibraryLoader } from "./jwtLibrary.js";
2
+ import type { IdentityVerifierPort } from "./ports.js";
3
+ export declare function createOperatorIdentityVerifier(config: {
4
+ issuer: string;
5
+ audience: string;
6
+ customerAudience: string;
7
+ jwks: string;
8
+ }, loadJwt: IdentityJwtLibraryLoader): IdentityVerifierPort;
@@ -0,0 +1,19 @@
1
+ import { createCustomerIdentityVerifier } from "./customerIdentityVerifier.js";
2
+ export function createOperatorIdentityVerifier(config, loadJwt) {
3
+ if (!config.customerAudience.trim() || config.audience === config.customerAudience)
4
+ throw new Error("operator_audience_must_be_distinct");
5
+ // Inspect the payload returned by the same cryptographic verification, never an unverified decode.
6
+ return createCustomerIdentityVerifier(config, async () => {
7
+ const library = await loadJwt();
8
+ return { ...library, async jwtVerify(token, key, options) {
9
+ const verified = await library.jwtVerify(token, key, options);
10
+ const payload = verified.payload;
11
+ const { iat, exp } = payload;
12
+ if (payload.aud !== config.audience || typeof iat !== "number" || typeof exp !== "number"
13
+ || !Number.isInteger(iat) || !Number.isInteger(exp) || exp <= iat || exp - iat > 900
14
+ || iat > Math.floor(Date.now() / 1000) || exp <= Math.floor(Date.now() / 1000))
15
+ throw new Error("operator_token_profile_invalid");
16
+ return verified;
17
+ } };
18
+ });
19
+ }
@@ -0,0 +1,7 @@
1
+ export declare function assertOperatorInvitationTtl(value: number): void;
2
+ export declare function invitationFailure(identityCode: string, bffCode?: string): Error & {
3
+ identityCode: string;
4
+ bffCode: string;
5
+ };
6
+ /** Pure parser; direct hosts call it before effects and pass the number to identity. */
7
+ export declare function parseOperatorInvitationTtl(raw?: string): number;
@@ -0,0 +1,15 @@
1
+ export function assertOperatorInvitationTtl(value) {
2
+ if (!Number.isInteger(value) || value < 60 || value > 2592000)
3
+ throw new Error("operator_invitation_ttl_invalid");
4
+ }
5
+ export function invitationFailure(identityCode, bffCode = "CONFLICT") {
6
+ return Object.assign(new Error(identityCode), { identityCode, bffCode });
7
+ }
8
+ /** Pure parser; direct hosts call it before effects and pass the number to identity. */
9
+ export function parseOperatorInvitationTtl(raw) {
10
+ if (raw !== undefined && !/^\d+$/.test(raw))
11
+ throw new Error("operator_invitation_ttl_invalid");
12
+ const value = raw === undefined ? 604800 : Number(raw);
13
+ assertOperatorInvitationTtl(value);
14
+ return value;
15
+ }