@portaidentity/server 1.7.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 (742) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +33 -0
  4. package/dist/applications/cache.d.ts +64 -0
  5. package/dist/applications/cache.d.ts.map +1 -0
  6. package/dist/applications/cache.js +161 -0
  7. package/dist/applications/cache.js.map +1 -0
  8. package/dist/applications/errors.d.ts +25 -0
  9. package/dist/applications/errors.d.ts.map +1 -0
  10. package/dist/applications/errors.js +31 -0
  11. package/dist/applications/errors.js.map +1 -0
  12. package/dist/applications/index.d.ts +17 -0
  13. package/dist/applications/index.d.ts.map +1 -0
  14. package/dist/applications/index.js +19 -0
  15. package/dist/applications/index.js.map +1 -0
  16. package/dist/applications/repository.d.ts +184 -0
  17. package/dist/applications/repository.d.ts.map +1 -0
  18. package/dist/applications/repository.js +367 -0
  19. package/dist/applications/repository.js.map +1 -0
  20. package/dist/applications/service.d.ts +160 -0
  21. package/dist/applications/service.d.ts.map +1 -0
  22. package/dist/applications/service.js +388 -0
  23. package/dist/applications/service.js.map +1 -0
  24. package/dist/applications/slugs.d.ts +61 -0
  25. package/dist/applications/slugs.d.ts.map +1 -0
  26. package/dist/applications/slugs.js +115 -0
  27. package/dist/applications/slugs.js.map +1 -0
  28. package/dist/applications/types.d.ts +129 -0
  29. package/dist/applications/types.d.ts.map +1 -0
  30. package/dist/applications/types.js +62 -0
  31. package/dist/applications/types.js.map +1 -0
  32. package/dist/auth/csrf.d.ts +71 -0
  33. package/dist/auth/csrf.d.ts.map +1 -0
  34. package/dist/auth/csrf.js +112 -0
  35. package/dist/auth/csrf.js.map +1 -0
  36. package/dist/auth/email-renderer.d.ts +37 -0
  37. package/dist/auth/email-renderer.d.ts.map +1 -0
  38. package/dist/auth/email-renderer.js +116 -0
  39. package/dist/auth/email-renderer.js.map +1 -0
  40. package/dist/auth/email-service.d.ts +154 -0
  41. package/dist/auth/email-service.d.ts.map +1 -0
  42. package/dist/auth/email-service.js +415 -0
  43. package/dist/auth/email-service.js.map +1 -0
  44. package/dist/auth/email-transport.d.ts +65 -0
  45. package/dist/auth/email-transport.d.ts.map +1 -0
  46. package/dist/auth/email-transport.js +82 -0
  47. package/dist/auth/email-transport.js.map +1 -0
  48. package/dist/auth/i18n.d.ts +81 -0
  49. package/dist/auth/i18n.d.ts.map +1 -0
  50. package/dist/auth/i18n.js +395 -0
  51. package/dist/auth/i18n.js.map +1 -0
  52. package/dist/auth/index.d.ts +28 -0
  53. package/dist/auth/index.d.ts.map +1 -0
  54. package/dist/auth/index.js +31 -0
  55. package/dist/auth/index.js.map +1 -0
  56. package/dist/auth/interaction-authority.d.ts +39 -0
  57. package/dist/auth/interaction-authority.d.ts.map +1 -0
  58. package/dist/auth/interaction-authority.js +30 -0
  59. package/dist/auth/interaction-authority.js.map +1 -0
  60. package/dist/auth/magic-link-session.d.ts +81 -0
  61. package/dist/auth/magic-link-session.d.ts.map +1 -0
  62. package/dist/auth/magic-link-session.js +183 -0
  63. package/dist/auth/magic-link-session.js.map +1 -0
  64. package/dist/auth/rate-limiter.d.ts +143 -0
  65. package/dist/auth/rate-limiter.d.ts.map +1 -0
  66. package/dist/auth/rate-limiter.js +220 -0
  67. package/dist/auth/rate-limiter.js.map +1 -0
  68. package/dist/auth/recovery-crypto.d.ts +34 -0
  69. package/dist/auth/recovery-crypto.d.ts.map +1 -0
  70. package/dist/auth/recovery-crypto.js +103 -0
  71. package/dist/auth/recovery-crypto.js.map +1 -0
  72. package/dist/auth/recovery-job-processor.d.ts +24 -0
  73. package/dist/auth/recovery-job-processor.d.ts.map +1 -0
  74. package/dist/auth/recovery-job-processor.js +123 -0
  75. package/dist/auth/recovery-job-processor.js.map +1 -0
  76. package/dist/auth/recovery-job-repository.d.ts +219 -0
  77. package/dist/auth/recovery-job-repository.d.ts.map +1 -0
  78. package/dist/auth/recovery-job-repository.js +340 -0
  79. package/dist/auth/recovery-job-repository.js.map +1 -0
  80. package/dist/auth/recovery-service.d.ts +27 -0
  81. package/dist/auth/recovery-service.d.ts.map +1 -0
  82. package/dist/auth/recovery-service.js +53 -0
  83. package/dist/auth/recovery-service.js.map +1 -0
  84. package/dist/auth/recovery-worker.d.ts +158 -0
  85. package/dist/auth/recovery-worker.d.ts.map +1 -0
  86. package/dist/auth/recovery-worker.js +306 -0
  87. package/dist/auth/recovery-worker.js.map +1 -0
  88. package/dist/auth/template-engine.d.ts +134 -0
  89. package/dist/auth/template-engine.d.ts.map +1 -0
  90. package/dist/auth/template-engine.js +225 -0
  91. package/dist/auth/template-engine.js.map +1 -0
  92. package/dist/auth/token-repository.d.ts +238 -0
  93. package/dist/auth/token-repository.d.ts.map +1 -0
  94. package/dist/auth/token-repository.js +475 -0
  95. package/dist/auth/token-repository.js.map +1 -0
  96. package/dist/auth/tokens.d.ts +45 -0
  97. package/dist/auth/tokens.d.ts.map +1 -0
  98. package/dist/auth/tokens.js +51 -0
  99. package/dist/auth/tokens.js.map +1 -0
  100. package/dist/cli/bootstrap.d.ts +55 -0
  101. package/dist/cli/bootstrap.d.ts.map +1 -0
  102. package/dist/cli/bootstrap.js +90 -0
  103. package/dist/cli/bootstrap.js.map +1 -0
  104. package/dist/cli/commands/health.d.ts +20 -0
  105. package/dist/cli/commands/health.d.ts.map +1 -0
  106. package/dist/cli/commands/health.js +80 -0
  107. package/dist/cli/commands/health.js.map +1 -0
  108. package/dist/cli/commands/init.d.ts +48 -0
  109. package/dist/cli/commands/init.d.ts.map +1 -0
  110. package/dist/cli/commands/init.js +349 -0
  111. package/dist/cli/commands/init.js.map +1 -0
  112. package/dist/cli/commands/migrate.d.ts +19 -0
  113. package/dist/cli/commands/migrate.d.ts.map +1 -0
  114. package/dist/cli/commands/migrate.js +88 -0
  115. package/dist/cli/commands/migrate.js.map +1 -0
  116. package/dist/cli/commands/seed.d.ts +19 -0
  117. package/dist/cli/commands/seed.d.ts.map +1 -0
  118. package/dist/cli/commands/seed.js +71 -0
  119. package/dist/cli/commands/seed.js.map +1 -0
  120. package/dist/cli/commands/user-2fa.d.ts +29 -0
  121. package/dist/cli/commands/user-2fa.d.ts.map +1 -0
  122. package/dist/cli/commands/user-2fa.js +113 -0
  123. package/dist/cli/commands/user-2fa.js.map +1 -0
  124. package/dist/cli/error-handler.d.ts +31 -0
  125. package/dist/cli/error-handler.d.ts.map +1 -0
  126. package/dist/cli/error-handler.js +65 -0
  127. package/dist/cli/error-handler.js.map +1 -0
  128. package/dist/cli/index.d.ts +48 -0
  129. package/dist/cli/index.d.ts.map +1 -0
  130. package/dist/cli/index.js +99 -0
  131. package/dist/cli/index.js.map +1 -0
  132. package/dist/cli/output.d.ts +94 -0
  133. package/dist/cli/output.d.ts.map +1 -0
  134. package/dist/cli/output.js +124 -0
  135. package/dist/cli/output.js.map +1 -0
  136. package/dist/cli/prompt.d.ts +48 -0
  137. package/dist/cli/prompt.d.ts.map +1 -0
  138. package/dist/cli/prompt.js +96 -0
  139. package/dist/cli/prompt.js.map +1 -0
  140. package/dist/clients/cache.d.ts +63 -0
  141. package/dist/clients/cache.d.ts.map +1 -0
  142. package/dist/clients/cache.js +160 -0
  143. package/dist/clients/cache.js.map +1 -0
  144. package/dist/clients/crypto.d.ts +69 -0
  145. package/dist/clients/crypto.d.ts.map +1 -0
  146. package/dist/clients/crypto.js +83 -0
  147. package/dist/clients/crypto.js.map +1 -0
  148. package/dist/clients/errors.d.ts +27 -0
  149. package/dist/clients/errors.d.ts.map +1 -0
  150. package/dist/clients/errors.js +33 -0
  151. package/dist/clients/errors.js.map +1 -0
  152. package/dist/clients/index.d.ts +19 -0
  153. package/dist/clients/index.d.ts.map +1 -0
  154. package/dist/clients/index.js +23 -0
  155. package/dist/clients/index.js.map +1 -0
  156. package/dist/clients/repository.d.ts +161 -0
  157. package/dist/clients/repository.d.ts.map +1 -0
  158. package/dist/clients/repository.js +318 -0
  159. package/dist/clients/repository.js.map +1 -0
  160. package/dist/clients/resolve-login-methods.d.ts +74 -0
  161. package/dist/clients/resolve-login-methods.d.ts.map +1 -0
  162. package/dist/clients/resolve-login-methods.js +68 -0
  163. package/dist/clients/resolve-login-methods.js.map +1 -0
  164. package/dist/clients/secret-repository.d.ts +109 -0
  165. package/dist/clients/secret-repository.d.ts.map +1 -0
  166. package/dist/clients/secret-repository.js +166 -0
  167. package/dist/clients/secret-repository.js.map +1 -0
  168. package/dist/clients/secret-service.d.ts +87 -0
  169. package/dist/clients/secret-service.d.ts.map +1 -0
  170. package/dist/clients/secret-service.js +204 -0
  171. package/dist/clients/secret-service.js.map +1 -0
  172. package/dist/clients/service.d.ts +158 -0
  173. package/dist/clients/service.d.ts.map +1 -0
  174. package/dist/clients/service.js +523 -0
  175. package/dist/clients/service.js.map +1 -0
  176. package/dist/clients/types.d.ts +214 -0
  177. package/dist/clients/types.d.ts.map +1 -0
  178. package/dist/clients/types.js +77 -0
  179. package/dist/clients/types.js.map +1 -0
  180. package/dist/clients/validators.d.ts +87 -0
  181. package/dist/clients/validators.d.ts.map +1 -0
  182. package/dist/clients/validators.js +164 -0
  183. package/dist/clients/validators.js.map +1 -0
  184. package/dist/config/index.d.ts +23 -0
  185. package/dist/config/index.d.ts.map +1 -0
  186. package/dist/config/index.js +52 -0
  187. package/dist/config/index.js.map +1 -0
  188. package/dist/config/schema.d.ts +35 -0
  189. package/dist/config/schema.d.ts.map +1 -0
  190. package/dist/config/schema.js +167 -0
  191. package/dist/config/schema.js.map +1 -0
  192. package/dist/custom-claims/cache.d.ts +48 -0
  193. package/dist/custom-claims/cache.d.ts.map +1 -0
  194. package/dist/custom-claims/cache.js +122 -0
  195. package/dist/custom-claims/cache.js.map +1 -0
  196. package/dist/custom-claims/errors.d.ts +25 -0
  197. package/dist/custom-claims/errors.d.ts.map +1 -0
  198. package/dist/custom-claims/errors.js +31 -0
  199. package/dist/custom-claims/errors.js.map +1 -0
  200. package/dist/custom-claims/index.d.ts +14 -0
  201. package/dist/custom-claims/index.d.ts.map +1 -0
  202. package/dist/custom-claims/index.js +17 -0
  203. package/dist/custom-claims/index.js.map +1 -0
  204. package/dist/custom-claims/repository.d.ts +147 -0
  205. package/dist/custom-claims/repository.d.ts.map +1 -0
  206. package/dist/custom-claims/repository.js +338 -0
  207. package/dist/custom-claims/repository.js.map +1 -0
  208. package/dist/custom-claims/service.d.ts +129 -0
  209. package/dist/custom-claims/service.d.ts.map +1 -0
  210. package/dist/custom-claims/service.js +296 -0
  211. package/dist/custom-claims/service.js.map +1 -0
  212. package/dist/custom-claims/types.d.ts +132 -0
  213. package/dist/custom-claims/types.d.ts.map +1 -0
  214. package/dist/custom-claims/types.js +61 -0
  215. package/dist/custom-claims/types.js.map +1 -0
  216. package/dist/custom-claims/validators.d.ts +61 -0
  217. package/dist/custom-claims/validators.d.ts.map +1 -0
  218. package/dist/custom-claims/validators.js +136 -0
  219. package/dist/custom-claims/validators.js.map +1 -0
  220. package/dist/index.d.ts +14 -0
  221. package/dist/index.d.ts.map +1 -0
  222. package/dist/index.js +91 -0
  223. package/dist/index.js.map +1 -0
  224. package/dist/lib/admin-permissions.d.ts +147 -0
  225. package/dist/lib/admin-permissions.d.ts.map +1 -0
  226. package/dist/lib/admin-permissions.js +261 -0
  227. package/dist/lib/admin-permissions.js.map +1 -0
  228. package/dist/lib/audit-log.d.ts +45 -0
  229. package/dist/lib/audit-log.d.ts.map +1 -0
  230. package/dist/lib/audit-log.js +79 -0
  231. package/dist/lib/audit-log.js.map +1 -0
  232. package/dist/lib/branding-assets.d.ts +47 -0
  233. package/dist/lib/branding-assets.d.ts.map +1 -0
  234. package/dist/lib/branding-assets.js +93 -0
  235. package/dist/lib/branding-assets.js.map +1 -0
  236. package/dist/lib/bulk-operations.d.ts +54 -0
  237. package/dist/lib/bulk-operations.d.ts.map +1 -0
  238. package/dist/lib/bulk-operations.js +201 -0
  239. package/dist/lib/bulk-operations.js.map +1 -0
  240. package/dist/lib/cursor.d.ts +110 -0
  241. package/dist/lib/cursor.d.ts.map +1 -0
  242. package/dist/lib/cursor.js +115 -0
  243. package/dist/lib/cursor.js.map +1 -0
  244. package/dist/lib/data-export.d.ts +67 -0
  245. package/dist/lib/data-export.d.ts.map +1 -0
  246. package/dist/lib/data-export.js +272 -0
  247. package/dist/lib/data-export.js.map +1 -0
  248. package/dist/lib/data-import-plan.d.ts +50 -0
  249. package/dist/lib/data-import-plan.d.ts.map +1 -0
  250. package/dist/lib/data-import-plan.js +235 -0
  251. package/dist/lib/data-import-plan.js.map +1 -0
  252. package/dist/lib/data-import.d.ts +276 -0
  253. package/dist/lib/data-import.d.ts.map +1 -0
  254. package/dist/lib/data-import.js +1694 -0
  255. package/dist/lib/data-import.js.map +1 -0
  256. package/dist/lib/database.d.ts +22 -0
  257. package/dist/lib/database.d.ts.map +1 -0
  258. package/dist/lib/database.js +103 -0
  259. package/dist/lib/database.js.map +1 -0
  260. package/dist/lib/entity-history.d.ts +52 -0
  261. package/dist/lib/entity-history.d.ts.map +1 -0
  262. package/dist/lib/entity-history.js +121 -0
  263. package/dist/lib/entity-history.js.map +1 -0
  264. package/dist/lib/etag.d.ts +92 -0
  265. package/dist/lib/etag.d.ts.map +1 -0
  266. package/dist/lib/etag.js +155 -0
  267. package/dist/lib/etag.js.map +1 -0
  268. package/dist/lib/image-validator.d.ts +44 -0
  269. package/dist/lib/image-validator.d.ts.map +1 -0
  270. package/dist/lib/image-validator.js +204 -0
  271. package/dist/lib/image-validator.js.map +1 -0
  272. package/dist/lib/logger.d.ts +15 -0
  273. package/dist/lib/logger.d.ts.map +1 -0
  274. package/dist/lib/logger.js +84 -0
  275. package/dist/lib/logger.js.map +1 -0
  276. package/dist/lib/migrator.d.ts +14 -0
  277. package/dist/lib/migrator.d.ts.map +1 -0
  278. package/dist/lib/migrator.js +34 -0
  279. package/dist/lib/migrator.js.map +1 -0
  280. package/dist/lib/redis.d.ts +5 -0
  281. package/dist/lib/redis.d.ts.map +1 -0
  282. package/dist/lib/redis.js +29 -0
  283. package/dist/lib/redis.js.map +1 -0
  284. package/dist/lib/runtime-paths.d.ts +38 -0
  285. package/dist/lib/runtime-paths.d.ts.map +1 -0
  286. package/dist/lib/runtime-paths.js +50 -0
  287. package/dist/lib/runtime-paths.js.map +1 -0
  288. package/dist/lib/session-tracking.d.ts +81 -0
  289. package/dist/lib/session-tracking.d.ts.map +1 -0
  290. package/dist/lib/session-tracking.js +145 -0
  291. package/dist/lib/session-tracking.js.map +1 -0
  292. package/dist/lib/signing-key-crypto.d.ts +53 -0
  293. package/dist/lib/signing-key-crypto.d.ts.map +1 -0
  294. package/dist/lib/signing-key-crypto.js +108 -0
  295. package/dist/lib/signing-key-crypto.js.map +1 -0
  296. package/dist/lib/signing-keys.d.ts +141 -0
  297. package/dist/lib/signing-keys.d.ts.map +1 -0
  298. package/dist/lib/signing-keys.js +206 -0
  299. package/dist/lib/signing-keys.js.map +1 -0
  300. package/dist/lib/stats.d.ts +83 -0
  301. package/dist/lib/stats.d.ts.map +1 -0
  302. package/dist/lib/stats.js +187 -0
  303. package/dist/lib/stats.js.map +1 -0
  304. package/dist/lib/super-admin-protection.d.ts +69 -0
  305. package/dist/lib/super-admin-protection.d.ts.map +1 -0
  306. package/dist/lib/super-admin-protection.js +102 -0
  307. package/dist/lib/super-admin-protection.js.map +1 -0
  308. package/dist/lib/system-config.d.ts +79 -0
  309. package/dist/lib/system-config.d.ts.map +1 -0
  310. package/dist/lib/system-config.js +159 -0
  311. package/dist/lib/system-config.js.map +1 -0
  312. package/dist/middleware/admin-auth.d.ts +100 -0
  313. package/dist/middleware/admin-auth.d.ts.map +1 -0
  314. package/dist/middleware/admin-auth.js +241 -0
  315. package/dist/middleware/admin-auth.js.map +1 -0
  316. package/dist/middleware/admin-cors.d.ts +41 -0
  317. package/dist/middleware/admin-cors.d.ts.map +1 -0
  318. package/dist/middleware/admin-cors.js +83 -0
  319. package/dist/middleware/admin-cors.js.map +1 -0
  320. package/dist/middleware/admin-mutation-audit.d.ts +9 -0
  321. package/dist/middleware/admin-mutation-audit.d.ts.map +1 -0
  322. package/dist/middleware/admin-mutation-audit.js +76 -0
  323. package/dist/middleware/admin-mutation-audit.js.map +1 -0
  324. package/dist/middleware/admin-rate-limiter.d.ts +56 -0
  325. package/dist/middleware/admin-rate-limiter.d.ts.map +1 -0
  326. package/dist/middleware/admin-rate-limiter.js +97 -0
  327. package/dist/middleware/admin-rate-limiter.js.map +1 -0
  328. package/dist/middleware/client-secret-hash.d.ts +34 -0
  329. package/dist/middleware/client-secret-hash.d.ts.map +1 -0
  330. package/dist/middleware/client-secret-hash.js +74 -0
  331. package/dist/middleware/client-secret-hash.js.map +1 -0
  332. package/dist/middleware/error-handler.d.ts +3 -0
  333. package/dist/middleware/error-handler.d.ts.map +1 -0
  334. package/dist/middleware/error-handler.js +93 -0
  335. package/dist/middleware/error-handler.js.map +1 -0
  336. package/dist/middleware/health.d.ts +3 -0
  337. package/dist/middleware/health.d.ts.map +1 -0
  338. package/dist/middleware/health.js +35 -0
  339. package/dist/middleware/health.js.map +1 -0
  340. package/dist/middleware/metrics.d.ts +40 -0
  341. package/dist/middleware/metrics.d.ts.map +1 -0
  342. package/dist/middleware/metrics.js +75 -0
  343. package/dist/middleware/metrics.js.map +1 -0
  344. package/dist/middleware/oidc-client-tenant.d.ts +37 -0
  345. package/dist/middleware/oidc-client-tenant.d.ts.map +1 -0
  346. package/dist/middleware/oidc-client-tenant.js +156 -0
  347. package/dist/middleware/oidc-client-tenant.js.map +1 -0
  348. package/dist/middleware/oidc-cors.d.ts +33 -0
  349. package/dist/middleware/oidc-cors.d.ts.map +1 -0
  350. package/dist/middleware/oidc-cors.js +68 -0
  351. package/dist/middleware/oidc-cors.js.map +1 -0
  352. package/dist/middleware/oidc-preflight-cors.d.ts +54 -0
  353. package/dist/middleware/oidc-preflight-cors.d.ts.map +1 -0
  354. package/dist/middleware/oidc-preflight-cors.js +267 -0
  355. package/dist/middleware/oidc-preflight-cors.js.map +1 -0
  356. package/dist/middleware/prompt-login-reset.d.ts +103 -0
  357. package/dist/middleware/prompt-login-reset.d.ts.map +1 -0
  358. package/dist/middleware/prompt-login-reset.js +204 -0
  359. package/dist/middleware/prompt-login-reset.js.map +1 -0
  360. package/dist/middleware/ready.d.ts +23 -0
  361. package/dist/middleware/ready.d.ts.map +1 -0
  362. package/dist/middleware/ready.js +60 -0
  363. package/dist/middleware/ready.js.map +1 -0
  364. package/dist/middleware/request-logger.d.ts +27 -0
  365. package/dist/middleware/request-logger.d.ts.map +1 -0
  366. package/dist/middleware/request-logger.js +46 -0
  367. package/dist/middleware/request-logger.js.map +1 -0
  368. package/dist/middleware/require-permission.d.ts +28 -0
  369. package/dist/middleware/require-permission.d.ts.map +1 -0
  370. package/dist/middleware/require-permission.js +63 -0
  371. package/dist/middleware/require-permission.js.map +1 -0
  372. package/dist/middleware/require-user-organization.d.ts +18 -0
  373. package/dist/middleware/require-user-organization.d.ts.map +1 -0
  374. package/dist/middleware/require-user-organization.js +53 -0
  375. package/dist/middleware/require-user-organization.js.map +1 -0
  376. package/dist/middleware/root-page.d.ts +62 -0
  377. package/dist/middleware/root-page.d.ts.map +1 -0
  378. package/dist/middleware/root-page.js +134 -0
  379. package/dist/middleware/root-page.js.map +1 -0
  380. package/dist/middleware/security-headers.d.ts +98 -0
  381. package/dist/middleware/security-headers.d.ts.map +1 -0
  382. package/dist/middleware/security-headers.js +159 -0
  383. package/dist/middleware/security-headers.js.map +1 -0
  384. package/dist/middleware/tenant-resolver.d.ts +37 -0
  385. package/dist/middleware/tenant-resolver.d.ts.map +1 -0
  386. package/dist/middleware/tenant-resolver.js +71 -0
  387. package/dist/middleware/tenant-resolver.js.map +1 -0
  388. package/dist/middleware/token-rate-limiter.d.ts +89 -0
  389. package/dist/middleware/token-rate-limiter.d.ts.map +1 -0
  390. package/dist/middleware/token-rate-limiter.js +189 -0
  391. package/dist/middleware/token-rate-limiter.js.map +1 -0
  392. package/dist/oidc/account-finder.d.ts +52 -0
  393. package/dist/oidc/account-finder.d.ts.map +1 -0
  394. package/dist/oidc/account-finder.js +134 -0
  395. package/dist/oidc/account-finder.js.map +1 -0
  396. package/dist/oidc/adapter-factory.d.ts +90 -0
  397. package/dist/oidc/adapter-factory.d.ts.map +1 -0
  398. package/dist/oidc/adapter-factory.js +165 -0
  399. package/dist/oidc/adapter-factory.js.map +1 -0
  400. package/dist/oidc/configuration.d.ts +155 -0
  401. package/dist/oidc/configuration.d.ts.map +1 -0
  402. package/dist/oidc/configuration.js +531 -0
  403. package/dist/oidc/configuration.js.map +1 -0
  404. package/dist/oidc/postgres-adapter.d.ts +159 -0
  405. package/dist/oidc/postgres-adapter.d.ts.map +1 -0
  406. package/dist/oidc/postgres-adapter.js +222 -0
  407. package/dist/oidc/postgres-adapter.js.map +1 -0
  408. package/dist/oidc/protocol-security-observer.d.ts +38 -0
  409. package/dist/oidc/protocol-security-observer.d.ts.map +1 -0
  410. package/dist/oidc/protocol-security-observer.js +93 -0
  411. package/dist/oidc/protocol-security-observer.js.map +1 -0
  412. package/dist/oidc/provider.d.ts +37 -0
  413. package/dist/oidc/provider.d.ts.map +1 -0
  414. package/dist/oidc/provider.js +71 -0
  415. package/dist/oidc/provider.js.map +1 -0
  416. package/dist/oidc/redis-adapter.d.ts +140 -0
  417. package/dist/oidc/redis-adapter.d.ts.map +1 -0
  418. package/dist/oidc/redis-adapter.js +344 -0
  419. package/dist/oidc/redis-adapter.js.map +1 -0
  420. package/dist/organizations/cache.d.ts +60 -0
  421. package/dist/organizations/cache.d.ts.map +1 -0
  422. package/dist/organizations/cache.js +157 -0
  423. package/dist/organizations/cache.js.map +1 -0
  424. package/dist/organizations/errors.d.ts +25 -0
  425. package/dist/organizations/errors.d.ts.map +1 -0
  426. package/dist/organizations/errors.js +31 -0
  427. package/dist/organizations/errors.js.map +1 -0
  428. package/dist/organizations/index.d.ts +15 -0
  429. package/dist/organizations/index.d.ts.map +1 -0
  430. package/dist/organizations/index.js +15 -0
  431. package/dist/organizations/index.js.map +1 -0
  432. package/dist/organizations/repository.d.ts +183 -0
  433. package/dist/organizations/repository.d.ts.map +1 -0
  434. package/dist/organizations/repository.js +354 -0
  435. package/dist/organizations/repository.js.map +1 -0
  436. package/dist/organizations/service.d.ts +168 -0
  437. package/dist/organizations/service.d.ts.map +1 -0
  438. package/dist/organizations/service.js +511 -0
  439. package/dist/organizations/service.js.map +1 -0
  440. package/dist/organizations/slugs.d.ts +57 -0
  441. package/dist/organizations/slugs.d.ts.map +1 -0
  442. package/dist/organizations/slugs.js +130 -0
  443. package/dist/organizations/slugs.js.map +1 -0
  444. package/dist/organizations/types.d.ts +146 -0
  445. package/dist/organizations/types.d.ts.map +1 -0
  446. package/dist/organizations/types.js +49 -0
  447. package/dist/organizations/types.js.map +1 -0
  448. package/dist/rbac/cache.d.ts +110 -0
  449. package/dist/rbac/cache.d.ts.map +1 -0
  450. package/dist/rbac/cache.js +271 -0
  451. package/dist/rbac/cache.js.map +1 -0
  452. package/dist/rbac/errors.d.ts +34 -0
  453. package/dist/rbac/errors.d.ts.map +1 -0
  454. package/dist/rbac/errors.js +43 -0
  455. package/dist/rbac/errors.js.map +1 -0
  456. package/dist/rbac/index.d.ts +17 -0
  457. package/dist/rbac/index.d.ts.map +1 -0
  458. package/dist/rbac/index.js +21 -0
  459. package/dist/rbac/index.js.map +1 -0
  460. package/dist/rbac/mapping-repository.d.ts +121 -0
  461. package/dist/rbac/mapping-repository.d.ts.map +1 -0
  462. package/dist/rbac/mapping-repository.js +224 -0
  463. package/dist/rbac/mapping-repository.js.map +1 -0
  464. package/dist/rbac/permission-repository.d.ts +100 -0
  465. package/dist/rbac/permission-repository.d.ts.map +1 -0
  466. package/dist/rbac/permission-repository.js +190 -0
  467. package/dist/rbac/permission-repository.js.map +1 -0
  468. package/dist/rbac/permission-service.d.ts +85 -0
  469. package/dist/rbac/permission-service.d.ts.map +1 -0
  470. package/dist/rbac/permission-service.js +179 -0
  471. package/dist/rbac/permission-service.js.map +1 -0
  472. package/dist/rbac/role-repository.d.ts +104 -0
  473. package/dist/rbac/role-repository.d.ts.map +1 -0
  474. package/dist/rbac/role-repository.js +202 -0
  475. package/dist/rbac/role-repository.js.map +1 -0
  476. package/dist/rbac/role-service.d.ts +111 -0
  477. package/dist/rbac/role-service.d.ts.map +1 -0
  478. package/dist/rbac/role-service.js +256 -0
  479. package/dist/rbac/role-service.js.map +1 -0
  480. package/dist/rbac/slugs.d.ts +116 -0
  481. package/dist/rbac/slugs.d.ts.map +1 -0
  482. package/dist/rbac/slugs.js +178 -0
  483. package/dist/rbac/slugs.js.map +1 -0
  484. package/dist/rbac/types.d.ts +187 -0
  485. package/dist/rbac/types.d.ts.map +1 -0
  486. package/dist/rbac/types.js +75 -0
  487. package/dist/rbac/types.js.map +1 -0
  488. package/dist/rbac/user-role-service.d.ts +101 -0
  489. package/dist/rbac/user-role-service.d.ts.map +1 -0
  490. package/dist/rbac/user-role-service.js +166 -0
  491. package/dist/rbac/user-role-service.js.map +1 -0
  492. package/dist/routes/applications.d.ts +40 -0
  493. package/dist/routes/applications.d.ts.map +1 -0
  494. package/dist/routes/applications.js +284 -0
  495. package/dist/routes/applications.js.map +1 -0
  496. package/dist/routes/audit.d.ts +31 -0
  497. package/dist/routes/audit.d.ts.map +1 -0
  498. package/dist/routes/audit.js +140 -0
  499. package/dist/routes/audit.js.map +1 -0
  500. package/dist/routes/branding.d.ts +17 -0
  501. package/dist/routes/branding.d.ts.map +1 -0
  502. package/dist/routes/branding.js +104 -0
  503. package/dist/routes/branding.js.map +1 -0
  504. package/dist/routes/bulk.d.ts +37 -0
  505. package/dist/routes/bulk.d.ts.map +1 -0
  506. package/dist/routes/bulk.js +94 -0
  507. package/dist/routes/bulk.js.map +1 -0
  508. package/dist/routes/clients.d.ts +44 -0
  509. package/dist/routes/clients.d.ts.map +1 -0
  510. package/dist/routes/clients.js +353 -0
  511. package/dist/routes/clients.js.map +1 -0
  512. package/dist/routes/config.d.ts +35 -0
  513. package/dist/routes/config.d.ts.map +1 -0
  514. package/dist/routes/config.js +115 -0
  515. package/dist/routes/config.js.map +1 -0
  516. package/dist/routes/custom-claims.d.ts +38 -0
  517. package/dist/routes/custom-claims.d.ts.map +1 -0
  518. package/dist/routes/custom-claims.js +208 -0
  519. package/dist/routes/custom-claims.js.map +1 -0
  520. package/dist/routes/exports.d.ts +21 -0
  521. package/dist/routes/exports.d.ts.map +1 -0
  522. package/dist/routes/exports.js +98 -0
  523. package/dist/routes/exports.js.map +1 -0
  524. package/dist/routes/imports.d.ts +18 -0
  525. package/dist/routes/imports.d.ts.map +1 -0
  526. package/dist/routes/imports.js +72 -0
  527. package/dist/routes/imports.js.map +1 -0
  528. package/dist/routes/interactions.d.ts +111 -0
  529. package/dist/routes/interactions.d.ts.map +1 -0
  530. package/dist/routes/interactions.js +1074 -0
  531. package/dist/routes/interactions.js.map +1 -0
  532. package/dist/routes/invitation.d.ts +31 -0
  533. package/dist/routes/invitation.d.ts.map +1 -0
  534. package/dist/routes/invitation.js +342 -0
  535. package/dist/routes/invitation.js.map +1 -0
  536. package/dist/routes/keys.d.ts +28 -0
  537. package/dist/routes/keys.d.ts.map +1 -0
  538. package/dist/routes/keys.js +91 -0
  539. package/dist/routes/keys.js.map +1 -0
  540. package/dist/routes/magic-link.d.ts +43 -0
  541. package/dist/routes/magic-link.d.ts.map +1 -0
  542. package/dist/routes/magic-link.js +264 -0
  543. package/dist/routes/magic-link.js.map +1 -0
  544. package/dist/routes/organizations.d.ts +38 -0
  545. package/dist/routes/organizations.d.ts.map +1 -0
  546. package/dist/routes/organizations.js +347 -0
  547. package/dist/routes/organizations.js.map +1 -0
  548. package/dist/routes/password-reset.d.ts +65 -0
  549. package/dist/routes/password-reset.d.ts.map +1 -0
  550. package/dist/routes/password-reset.js +493 -0
  551. package/dist/routes/password-reset.js.map +1 -0
  552. package/dist/routes/permissions.d.ts +33 -0
  553. package/dist/routes/permissions.d.ts.map +1 -0
  554. package/dist/routes/permissions.js +166 -0
  555. package/dist/routes/permissions.js.map +1 -0
  556. package/dist/routes/roles.d.ts +37 -0
  557. package/dist/routes/roles.d.ts.map +1 -0
  558. package/dist/routes/roles.js +222 -0
  559. package/dist/routes/roles.js.map +1 -0
  560. package/dist/routes/sessions.d.ts +37 -0
  561. package/dist/routes/sessions.d.ts.map +1 -0
  562. package/dist/routes/sessions.js +121 -0
  563. package/dist/routes/sessions.js.map +1 -0
  564. package/dist/routes/stats.d.ts +28 -0
  565. package/dist/routes/stats.d.ts.map +1 -0
  566. package/dist/routes/stats.js +65 -0
  567. package/dist/routes/stats.js.map +1 -0
  568. package/dist/routes/two-factor-admin.d.ts +54 -0
  569. package/dist/routes/two-factor-admin.d.ts.map +1 -0
  570. package/dist/routes/two-factor-admin.js +344 -0
  571. package/dist/routes/two-factor-admin.js.map +1 -0
  572. package/dist/routes/two-factor.d.ts +31 -0
  573. package/dist/routes/two-factor.d.ts.map +1 -0
  574. package/dist/routes/two-factor.js +595 -0
  575. package/dist/routes/two-factor.js.map +1 -0
  576. package/dist/routes/user-roles.d.ts +33 -0
  577. package/dist/routes/user-roles.d.ts.map +1 -0
  578. package/dist/routes/user-roles.js +129 -0
  579. package/dist/routes/user-roles.js.map +1 -0
  580. package/dist/routes/users.d.ts +61 -0
  581. package/dist/routes/users.d.ts.map +1 -0
  582. package/dist/routes/users.js +868 -0
  583. package/dist/routes/users.js.map +1 -0
  584. package/dist/security/decision-context.d.ts +52 -0
  585. package/dist/security/decision-context.d.ts.map +1 -0
  586. package/dist/security/decision-context.js +150 -0
  587. package/dist/security/decision-context.js.map +1 -0
  588. package/dist/security/decision-event.d.ts +128 -0
  589. package/dist/security/decision-event.d.ts.map +1 -0
  590. package/dist/security/decision-event.js +225 -0
  591. package/dist/security/decision-event.js.map +1 -0
  592. package/dist/security/transport-decision.d.ts +6 -0
  593. package/dist/security/transport-decision.d.ts.map +1 -0
  594. package/dist/security/transport-decision.js +37 -0
  595. package/dist/security/transport-decision.js.map +1 -0
  596. package/dist/server.d.ts +50 -0
  597. package/dist/server.d.ts.map +1 -0
  598. package/dist/server.js +491 -0
  599. package/dist/server.js.map +1 -0
  600. package/dist/two-factor/cache.d.ts +47 -0
  601. package/dist/two-factor/cache.d.ts.map +1 -0
  602. package/dist/two-factor/cache.js +122 -0
  603. package/dist/two-factor/cache.js.map +1 -0
  604. package/dist/two-factor/crypto.d.ts +41 -0
  605. package/dist/two-factor/crypto.d.ts.map +1 -0
  606. package/dist/two-factor/crypto.js +94 -0
  607. package/dist/two-factor/crypto.js.map +1 -0
  608. package/dist/two-factor/errors.d.ts +82 -0
  609. package/dist/two-factor/errors.d.ts.map +1 -0
  610. package/dist/two-factor/errors.js +112 -0
  611. package/dist/two-factor/errors.js.map +1 -0
  612. package/dist/two-factor/index.d.ts +18 -0
  613. package/dist/two-factor/index.d.ts.map +1 -0
  614. package/dist/two-factor/index.js +25 -0
  615. package/dist/two-factor/index.js.map +1 -0
  616. package/dist/two-factor/otp.d.ts +43 -0
  617. package/dist/two-factor/otp.d.ts.map +1 -0
  618. package/dist/two-factor/otp.js +57 -0
  619. package/dist/two-factor/otp.js.map +1 -0
  620. package/dist/two-factor/recovery.d.ts +45 -0
  621. package/dist/two-factor/recovery.d.ts.map +1 -0
  622. package/dist/two-factor/recovery.js +104 -0
  623. package/dist/two-factor/recovery.js.map +1 -0
  624. package/dist/two-factor/repository.d.ts +156 -0
  625. package/dist/two-factor/repository.d.ts.map +1 -0
  626. package/dist/two-factor/repository.js +251 -0
  627. package/dist/two-factor/repository.js.map +1 -0
  628. package/dist/two-factor/service.d.ts +232 -0
  629. package/dist/two-factor/service.d.ts.map +1 -0
  630. package/dist/two-factor/service.js +599 -0
  631. package/dist/two-factor/service.js.map +1 -0
  632. package/dist/two-factor/totp.d.ts +57 -0
  633. package/dist/two-factor/totp.d.ts.map +1 -0
  634. package/dist/two-factor/totp.js +96 -0
  635. package/dist/two-factor/totp.js.map +1 -0
  636. package/dist/two-factor/types.d.ts +152 -0
  637. package/dist/two-factor/types.d.ts.map +1 -0
  638. package/dist/two-factor/types.js +64 -0
  639. package/dist/two-factor/types.js.map +1 -0
  640. package/dist/users/cache.d.ts +49 -0
  641. package/dist/users/cache.d.ts.map +1 -0
  642. package/dist/users/cache.js +140 -0
  643. package/dist/users/cache.js.map +1 -0
  644. package/dist/users/claims.d.ts +67 -0
  645. package/dist/users/claims.d.ts.map +1 -0
  646. package/dist/users/claims.js +104 -0
  647. package/dist/users/claims.js.map +1 -0
  648. package/dist/users/errors.d.ts +25 -0
  649. package/dist/users/errors.d.ts.map +1 -0
  650. package/dist/users/errors.js +31 -0
  651. package/dist/users/errors.js.map +1 -0
  652. package/dist/users/gdpr.d.ts +110 -0
  653. package/dist/users/gdpr.d.ts.map +1 -0
  654. package/dist/users/gdpr.js +211 -0
  655. package/dist/users/gdpr.js.map +1 -0
  656. package/dist/users/index.d.ts +20 -0
  657. package/dist/users/index.d.ts.map +1 -0
  658. package/dist/users/index.js +19 -0
  659. package/dist/users/index.js.map +1 -0
  660. package/dist/users/password.d.ts +85 -0
  661. package/dist/users/password.d.ts.map +1 -0
  662. package/dist/users/password.js +138 -0
  663. package/dist/users/password.js.map +1 -0
  664. package/dist/users/repository.d.ts +263 -0
  665. package/dist/users/repository.d.ts.map +1 -0
  666. package/dist/users/repository.js +499 -0
  667. package/dist/users/repository.js.map +1 -0
  668. package/dist/users/service.d.ts +299 -0
  669. package/dist/users/service.d.ts.map +1 -0
  670. package/dist/users/service.js +698 -0
  671. package/dist/users/service.js.map +1 -0
  672. package/dist/users/types.d.ts +220 -0
  673. package/dist/users/types.d.ts.map +1 -0
  674. package/dist/users/types.js +77 -0
  675. package/dist/users/types.js.map +1 -0
  676. package/locales/default/en/common.json +10 -0
  677. package/locales/default/en/consent.json +13 -0
  678. package/locales/default/en/emails.json +17 -0
  679. package/locales/default/en/errors.json +21 -0
  680. package/locales/default/en/forgot-password.json +11 -0
  681. package/locales/default/en/invitation.json +18 -0
  682. package/locales/default/en/login.json +17 -0
  683. package/locales/default/en/logout.json +9 -0
  684. package/locales/default/en/magic-link.json +12 -0
  685. package/locales/default/en/reset-password.json +16 -0
  686. package/locales/default/en/two-factor.json +29 -0
  687. package/migrations/.gitkeep +0 -0
  688. package/migrations/001_extensions.sql +22 -0
  689. package/migrations/002_organizations.sql +43 -0
  690. package/migrations/003_applications.sql +50 -0
  691. package/migrations/004_clients.sql +74 -0
  692. package/migrations/005_users.sql +119 -0
  693. package/migrations/006_roles_permissions.sql +80 -0
  694. package/migrations/007_custom_claims.sql +55 -0
  695. package/migrations/008_config.sql +51 -0
  696. package/migrations/009_audit_log.sql +31 -0
  697. package/migrations/010_oidc_adapter.sql +34 -0
  698. package/migrations/011_seed.sql +53 -0
  699. package/migrations/012_two_factor.sql +88 -0
  700. package/migrations/013_client_secret_sha256.sql +22 -0
  701. package/migrations/014_login_methods.sql +34 -0
  702. package/migrations/015_signing_key_encryption.sql +20 -0
  703. package/migrations/016_failed_login_count.sql +27 -0
  704. package/migrations/017_audit_retention.sql +11 -0
  705. package/migrations/018_admin_api_enhancements.sql +78 -0
  706. package/migrations/019_invitation_details.sql +27 -0
  707. package/migrations/020_two_factor_admin_permission.sql +108 -0
  708. package/migrations/021_auth_recovery_jobs.sql +74 -0
  709. package/migrations/022_recovery_artifact_ownership.sql +25 -0
  710. package/migrations/023_magic_link_authority.sql +39 -0
  711. package/package.json +106 -0
  712. package/templates/default/emails/invitation.hbs +39 -0
  713. package/templates/default/emails/invitation.txt.hbs +18 -0
  714. package/templates/default/emails/magic-link.hbs +30 -0
  715. package/templates/default/emails/magic-link.txt.hbs +11 -0
  716. package/templates/default/emails/otp-code.hbs +31 -0
  717. package/templates/default/emails/otp-code.txt.hbs +15 -0
  718. package/templates/default/emails/password-changed.hbs +24 -0
  719. package/templates/default/emails/password-changed.txt.hbs +7 -0
  720. package/templates/default/emails/password-reset.hbs +30 -0
  721. package/templates/default/emails/password-reset.txt.hbs +11 -0
  722. package/templates/default/emails/welcome.hbs +25 -0
  723. package/templates/default/emails/welcome.txt.hbs +9 -0
  724. package/templates/default/layouts/main.hbs +49 -0
  725. package/templates/default/pages/accept-invite.hbs +25 -0
  726. package/templates/default/pages/consent.hbs +27 -0
  727. package/templates/default/pages/error.hbs +15 -0
  728. package/templates/default/pages/forgot-password.hbs +21 -0
  729. package/templates/default/pages/invite-expired.hbs +6 -0
  730. package/templates/default/pages/invite-success.hbs +9 -0
  731. package/templates/default/pages/login.hbs +108 -0
  732. package/templates/default/pages/logout-success.hbs +5 -0
  733. package/templates/default/pages/logout.hbs +24 -0
  734. package/templates/default/pages/magic-link-sent.hbs +10 -0
  735. package/templates/default/pages/magic-link-success.hbs +17 -0
  736. package/templates/default/pages/reset-password.hbs +20 -0
  737. package/templates/default/pages/reset-success.hbs +9 -0
  738. package/templates/default/pages/two-factor-setup.hbs +65 -0
  739. package/templates/default/pages/two-factor-verify.hbs +77 -0
  740. package/templates/default/partials/flash-messages.hbs +10 -0
  741. package/templates/default/partials/footer.hbs +4 -0
  742. package/templates/default/partials/header.hbs +8 -0
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Magic link verification route handler.
3
+ *
4
+ * Handles the callback when a user clicks the magic link in their email.
5
+ * Verifies the token, marks the user's email as verified, records the login,
6
+ * and creates a short-lived `_ml_session` in Redis. Then redirects to the
7
+ * interaction login page where the session cookie is detected and either:
8
+ * - **Same browser**: OIDC flow completes seamlessly via interactionFinished()
9
+ * - **Different browser**: success page shown ("return to original browser")
10
+ *
11
+ * Route structure:
12
+ * GET /:orgSlug/auth/magic-link/:token → verifyMagicLink
13
+ *
14
+ * The tenant resolver middleware runs before this route, ensuring
15
+ * ctx.state.organization is populated with the resolved organization.
16
+ *
17
+ * Security features:
18
+ * - Token is hashed (SHA-256) before DB lookup — plaintext never stored
19
+ * - Token is single-use (marked as used after verification)
20
+ * - Expired/used tokens show a generic error page
21
+ * - `_ml_session` is HttpOnly, 5-min TTL, single-use (Redis-backed)
22
+ * - Audit logging for all magic link events
23
+ */
24
+ import Router from '@koa/router';
25
+ import { tenantResolver } from '../middleware/tenant-resolver.js';
26
+ import { hashToken } from '../auth/tokens.js';
27
+ import { consumeAuthorizedMagicLink } from '../auth/token-repository.js';
28
+ import { resolveLocale, getTranslationFunction } from '../auth/i18n.js';
29
+ import { renderPage } from '../auth/template-engine.js';
30
+ import { generateCsrfToken } from '../auth/csrf.js';
31
+ import { invalidateUserCache } from '../users/cache.js';
32
+ import { createMagicLinkSession } from '../auth/magic-link-session.js';
33
+ import { writeAuditLog } from '../lib/audit-log.js';
34
+ import { logger } from '../lib/logger.js';
35
+ import { createInteractionAuthorityResolver, } from '../auth/interaction-authority.js';
36
+ import { buildMagicLinkCallbackRateLimitKey, checkRateLimitStrict, loadMagicLinkRateLimitConfig, } from '../auth/rate-limiter.js';
37
+ import { magicLinkCallbackArtifactDigest } from '../auth/recovery-crypto.js';
38
+ /** Production dependencies used unless a test owns an explicit failure boundary. */
39
+ const DEFAULT_MAGIC_LINK_ROUTE_DEPENDENCIES = {
40
+ checkCallbackRateLimit: checkRateLimitStrict,
41
+ };
42
+ // ---------------------------------------------------------------------------
43
+ // Helpers
44
+ // ---------------------------------------------------------------------------
45
+ /**
46
+ * Build branding context from organization data.
47
+ *
48
+ * @param org - Organization with branding fields
49
+ * @returns Branding context for templates
50
+ */
51
+ function buildBrandingFromOrg(org) {
52
+ return {
53
+ logoUrl: org.brandingLogoUrl,
54
+ faviconUrl: org.brandingFaviconUrl,
55
+ primaryColor: org.brandingPrimaryColor ?? '#3B82F6',
56
+ companyName: org.brandingCompanyName ?? org.name,
57
+ customCss: org.brandingCustomCss,
58
+ };
59
+ }
60
+ /**
61
+ * Parse the optional interaction query without normalizing malformed input into standalone use.
62
+ *
63
+ * @param value - Raw Koa query value.
64
+ * @returns Exact bounded interaction authority and a fail-closed validity discriminator.
65
+ */
66
+ function parsePresentedInteraction(value) {
67
+ if (value === undefined)
68
+ return { valid: true, value: null };
69
+ if (typeof value !== 'string' || value.length < 1 || value.length > 128) {
70
+ return { valid: false, value: null };
71
+ }
72
+ return { valid: true, value };
73
+ }
74
+ // ---------------------------------------------------------------------------
75
+ // Router factory
76
+ // ---------------------------------------------------------------------------
77
+ /**
78
+ * Create the magic link auth router.
79
+ *
80
+ * Handles magic link token verification at /:orgSlug/auth/magic-link/:token. Interaction-bound
81
+ * callbacks resolve their current client through the provider before durable authority is used.
82
+ *
83
+ * @param provider - Provider-owned live interaction model. Omit only for standalone callbacks.
84
+ * @param dependencies - Fail-closed callback-limiter boundary.
85
+ * @returns Koa router with magic link routes
86
+ */
87
+ export function createMagicLinkRouter(provider, dependencies = DEFAULT_MAGIC_LINK_ROUTE_DEPENDENCIES) {
88
+ const router = new Router();
89
+ const interactionAuthority = provider ? createInteractionAuthorityResolver(provider) : null;
90
+ // Tenant resolver — resolves orgSlug to organization and sets ctx.state.organization.
91
+ // Applied at route level because this router is mounted directly on the Koa app
92
+ // (not under the OIDC catch-all router which has its own tenant resolver).
93
+ const resolve = tenantResolver();
94
+ // GET /:orgSlug/auth/magic-link/:token — Verify magic link
95
+ router.get('/:orgSlug/auth/magic-link/:token', resolve, async (ctx) => {
96
+ await verifyMagicLink(ctx, interactionAuthority, dependencies);
97
+ });
98
+ return router;
99
+ }
100
+ // ---------------------------------------------------------------------------
101
+ // Route handler
102
+ // ---------------------------------------------------------------------------
103
+ /**
104
+ * Verify a magic link token and set up the `_ml_session` for flow completion.
105
+ *
106
+ * 1. Hashes the token from URL params for DB lookup
107
+ * 2. Locks and validates the token, route tenant, account, and persisted interaction authority
108
+ * 3. If invalid/expired: renders error page with "link expired" message
109
+ * 4. If valid:
110
+ * a. Atomically consumes the token, updates the user, and writes the successful audit row
111
+ * e. Creates `_ml_session` in Redis (5-min TTL, single-use)
112
+ * f. Redirects to `/interaction/{uid}` where the interaction handler
113
+ * detects the session cookie and completes the OIDC flow (same
114
+ * browser) or shows a "return to original browser" page (different
115
+ * browser)
116
+ *
117
+ * @param ctx - Koa context with organization state
118
+ */
119
+ async function verifyMagicLink(ctx, interactionAuthority, dependencies) {
120
+ const org = ctx.state.organization;
121
+ const tokenPlaintext = ctx.params.token;
122
+ const presentedInteraction = parsePresentedInteraction(ctx.query.interaction);
123
+ // Resolve locale for error pages
124
+ const locale = await resolveLocale(undefined, ctx.get('Accept-Language') || undefined, org.defaultLocale);
125
+ const t = getTranslationFunction(locale, org.slug);
126
+ try {
127
+ // Step 1: Hash the token for DB lookup
128
+ const tokenHash = hashToken(tokenPlaintext);
129
+ const socketPeer = ctx.req.socket.remoteAddress ?? 'unavailable';
130
+ const rateLimitKey = buildMagicLinkCallbackRateLimitKey(org.id, socketPeer, magicLinkCallbackArtifactDigest(tokenPlaintext));
131
+ const rateLimit = await dependencies.checkCallbackRateLimit(rateLimitKey, await loadMagicLinkRateLimitConfig());
132
+ if (!rateLimit.allowed) {
133
+ await rejectMagicLink(ctx, org, locale, t);
134
+ return;
135
+ }
136
+ const liveAuthority = presentedInteraction.valid && presentedInteraction.value !== null && interactionAuthority
137
+ ? await interactionAuthority.resolve(presentedInteraction.value)
138
+ : null;
139
+ const interactionAccepted = presentedInteraction.valid &&
140
+ (presentedInteraction.value === null ||
141
+ (liveAuthority !== null && liveAuthority.interactionUid === presentedInteraction.value));
142
+ // Step 2: Lock and validate all durable authority before any successful mutation.
143
+ const authority = interactionAccepted
144
+ ? await consumeAuthorizedMagicLink({
145
+ tokenHash,
146
+ organizationId: org.id,
147
+ interactionUid: presentedInteraction.value,
148
+ clientId: liveAuthority?.clientId ?? null,
149
+ ipAddress: ctx.ip,
150
+ })
151
+ : null;
152
+ if (!authority) {
153
+ await rejectMagicLink(ctx, org, locale, t);
154
+ return;
155
+ }
156
+ // The durable transaction has committed. Remove any stale cached account representation.
157
+ await invalidateUserCache(authority.userId);
158
+ // Step 8: Complete the OIDC flow via _ml_session cookie.
159
+ //
160
+ // Create a magic link session and redirect to the interaction handler.
161
+ // The interaction handler will detect the session cookie and complete
162
+ // the OIDC flow (same browser) or show a success page (different browser).
163
+ if (authority.interactionUid) {
164
+ await createMagicLinkSession(ctx, {
165
+ userId: authority.userId,
166
+ interactionUid: authority.interactionUid,
167
+ organizationId: org.id,
168
+ });
169
+ ctx.redirect(`/interaction/${authority.interactionUid}`);
170
+ return;
171
+ }
172
+ // No interaction UID — magic link opened outside an OIDC flow.
173
+ // Show the magic link success page (standalone authentication confirmation).
174
+ logger.info({ event: 'magic-link-standalone-completed' }, 'Magic link verification completed');
175
+ await renderSuccessPageForAuth(ctx, org, locale, t);
176
+ }
177
+ catch {
178
+ logger.error({ event: 'magic-link-verification-failed' }, 'Magic link verification failed');
179
+ await renderErrorPageForAuth(ctx, org, locale, t, t('errors.generic'));
180
+ }
181
+ }
182
+ /** Render and audit the one generic callback rejection without retaining bearer authority. */
183
+ async function rejectMagicLink(ctx, org, locale, t) {
184
+ writeAuditLog({
185
+ organizationId: org.id,
186
+ eventType: 'user.magic_link.failed',
187
+ eventCategory: 'security',
188
+ description: 'Magic link verification failed: invalid or expired token',
189
+ ipAddress: ctx.ip,
190
+ });
191
+ await renderErrorPageForAuth(ctx, org, locale, t, t('errors.magic_link_expired'));
192
+ }
193
+ // ---------------------------------------------------------------------------
194
+ // Helper: render success page for auth routes
195
+ // ---------------------------------------------------------------------------
196
+ /**
197
+ * Render a success page when a magic link is verified outside an OIDC flow.
198
+ *
199
+ * This handles the edge case where a magic link URL doesn't have an
200
+ * interaction UID (e.g., link was malformed or interaction expired).
201
+ * Shows a generic "email verified" confirmation instead of redirecting
202
+ * to the forgot-password page.
203
+ *
204
+ * @param ctx - Koa context
205
+ * @param org - Organization for branding
206
+ * @param locale - Resolved locale
207
+ * @param t - Translation function
208
+ */
209
+ async function renderSuccessPageForAuth(ctx, org, locale, t) {
210
+ try {
211
+ const csrfToken = generateCsrfToken();
212
+ const context = {
213
+ branding: buildBrandingFromOrg(org),
214
+ locale,
215
+ t,
216
+ csrfToken,
217
+ orgSlug: org.slug,
218
+ };
219
+ const html = await renderPage('magic-link-success', context);
220
+ ctx.status = 200;
221
+ ctx.type = 'text/html';
222
+ ctx.body = html;
223
+ }
224
+ catch {
225
+ logger.error({ event: 'magic-link-success-render-failed' }, 'Magic link success page failed');
226
+ ctx.status = 200;
227
+ ctx.body = 'Your email has been verified. You may close this tab.';
228
+ }
229
+ }
230
+ // ---------------------------------------------------------------------------
231
+ // Helper: render error page for auth routes
232
+ // ---------------------------------------------------------------------------
233
+ /**
234
+ * Render an error page for auth routes (outside OIDC interaction flow).
235
+ *
236
+ * @param ctx - Koa context
237
+ * @param org - Organization for branding
238
+ * @param locale - Resolved locale
239
+ * @param t - Translation function
240
+ * @param errorMessage - Error message to display
241
+ */
242
+ async function renderErrorPageForAuth(ctx, org, locale, t, errorMessage) {
243
+ try {
244
+ const csrfToken = generateCsrfToken();
245
+ const context = {
246
+ branding: buildBrandingFromOrg(org),
247
+ locale,
248
+ t,
249
+ csrfToken,
250
+ orgSlug: org.slug,
251
+ errorMessage,
252
+ };
253
+ const html = await renderPage('error', context);
254
+ ctx.status = 400;
255
+ ctx.type = 'text/html';
256
+ ctx.body = html;
257
+ }
258
+ catch {
259
+ logger.error({ event: 'auth-error-render-failed' }, 'Authentication error page failed');
260
+ ctx.status = 500;
261
+ ctx.body = 'An error occurred';
262
+ }
263
+ }
264
+ //# sourceMappingURL=magic-link.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"magic-link.js","sourceRoot":"","sources":["../../src/routes/magic-link.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Organization management API routes.
3
+ *
4
+ * All routes are under `/api/admin/organizations` and require
5
+ * admin authentication (Bearer JWT). Provides CRUD, status lifecycle,
6
+ * branding management, and slug validation endpoints.
7
+ *
8
+ * Route structure:
9
+ * POST / — Create a new organization
10
+ * GET / — List organizations (paginated)
11
+ * GET /validate-slug — Validate slug availability
12
+ * GET /:id — Get organization by ID
13
+ * PUT /:id — Update organization
14
+ * PUT /:id/branding — Update branding
15
+ * POST /:id/suspend — Suspend organization
16
+ * POST /:id/activate — Activate organization
17
+ * POST /:id/archive — Archive organization
18
+ * POST /:id/restore — Restore organization
19
+ *
20
+ * Error mapping:
21
+ * OrganizationNotFoundError → 404
22
+ * OrganizationValidationError → 400
23
+ * ZodError → 400 with validation details
24
+ */
25
+ import Router from '@koa/router';
26
+ /**
27
+ * Create the organization management router.
28
+ *
29
+ * All routes require admin authentication (Bearer JWT via requireAdminAuth).
30
+ * The middleware validates the token, checks the user belongs to the
31
+ * super-admin org, and verifies the porta-admin role.
32
+ *
33
+ * Prefix: /api/admin/organizations
34
+ *
35
+ * @returns Configured Koa Router
36
+ */
37
+ export declare function createOrganizationRouter(): Router;
38
+ //# sourceMappingURL=organizations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"organizations.d.ts","sourceRoot":"","sources":["../../src/routes/organizations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,MAAM,MAAM,aAAa,CAAC;AAqIjC;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CA2MjD"}
@@ -0,0 +1,347 @@
1
+ /**
2
+ * Organization management API routes.
3
+ *
4
+ * All routes are under `/api/admin/organizations` and require
5
+ * admin authentication (Bearer JWT). Provides CRUD, status lifecycle,
6
+ * branding management, and slug validation endpoints.
7
+ *
8
+ * Route structure:
9
+ * POST / — Create a new organization
10
+ * GET / — List organizations (paginated)
11
+ * GET /validate-slug — Validate slug availability
12
+ * GET /:id — Get organization by ID
13
+ * PUT /:id — Update organization
14
+ * PUT /:id/branding — Update branding
15
+ * POST /:id/suspend — Suspend organization
16
+ * POST /:id/activate — Activate organization
17
+ * POST /:id/archive — Archive organization
18
+ * POST /:id/restore — Restore organization
19
+ *
20
+ * Error mapping:
21
+ * OrganizationNotFoundError → 404
22
+ * OrganizationValidationError → 400
23
+ * ZodError → 400 with validation details
24
+ */
25
+ import Router from '@koa/router';
26
+ import { z } from 'zod';
27
+ import { requireAdminAuth } from '../middleware/admin-auth.js';
28
+ import { requirePermission } from '../middleware/require-permission.js';
29
+ import { ADMIN_PERMISSIONS } from '../lib/admin-permissions.js';
30
+ import * as organizationService from '../organizations/service.js';
31
+ import { setETagHeader, checkIfMatch } from '../lib/etag.js';
32
+ import { getEntityHistory } from '../lib/entity-history.js';
33
+ import { OrganizationNotFoundError, OrganizationValidationError } from '../organizations/errors.js';
34
+ import { LOGIN_METHODS } from '../clients/types.js';
35
+ // ---------------------------------------------------------------------------
36
+ // Validation schemas
37
+ // ---------------------------------------------------------------------------
38
+ /**
39
+ * Login method Zod schema — single source of truth for HTTP payload validation.
40
+ * Uses the runtime `LOGIN_METHODS` const so adding a new method only requires
41
+ * updating the union in `src/clients/types.ts`.
42
+ */
43
+ const loginMethodSchema = z.enum(LOGIN_METHODS);
44
+ /**
45
+ * Organization-level default login methods — a non-empty array (empty arrays
46
+ * are rejected by the service layer and must be rejected at the HTTP boundary
47
+ * too so the 400 carries a useful validation message).
48
+ */
49
+ const defaultLoginMethodsSchema = z.array(loginMethodSchema).min(1);
50
+ const createOrganizationSchema = z.object({
51
+ name: z.string().min(1).max(255),
52
+ slug: z.string().min(3).max(100).optional(),
53
+ defaultLocale: z.string().min(2).max(10).optional(),
54
+ defaultLoginMethods: defaultLoginMethodsSchema.optional(),
55
+ branding: z
56
+ .object({
57
+ logoUrl: z.string().url().nullable().optional(),
58
+ faviconUrl: z.string().url().nullable().optional(),
59
+ primaryColor: z
60
+ .string()
61
+ .regex(/^#[0-9A-Fa-f]{6}$/)
62
+ .nullable()
63
+ .optional(),
64
+ companyName: z.string().max(255).nullable().optional(),
65
+ customCss: z.string().max(10000).nullable().optional(),
66
+ })
67
+ .optional(),
68
+ });
69
+ const updateOrganizationSchema = z.object({
70
+ name: z.string().min(1).max(255).optional(),
71
+ defaultLocale: z.string().min(2).max(10).optional(),
72
+ defaultLoginMethods: defaultLoginMethodsSchema.optional(),
73
+ branding: z
74
+ .object({
75
+ logoUrl: z.string().url().nullable().optional(),
76
+ faviconUrl: z.string().url().nullable().optional(),
77
+ primaryColor: z
78
+ .string()
79
+ .regex(/^#[0-9A-Fa-f]{6}$/)
80
+ .nullable()
81
+ .optional(),
82
+ companyName: z.string().max(255).nullable().optional(),
83
+ customCss: z.string().max(10000).nullable().optional(),
84
+ })
85
+ .optional(),
86
+ });
87
+ const updateBrandingSchema = z.object({
88
+ logoUrl: z.string().url().nullable().optional(),
89
+ faviconUrl: z.string().url().nullable().optional(),
90
+ primaryColor: z
91
+ .string()
92
+ .regex(/^#[0-9A-Fa-f]{6}$/)
93
+ .nullable()
94
+ .optional(),
95
+ companyName: z.string().max(255).nullable().optional(),
96
+ customCss: z.string().max(10000).nullable().optional(),
97
+ });
98
+ const listOrganizationsSchema = z.object({
99
+ page: z.coerce.number().int().min(1).default(1),
100
+ pageSize: z.coerce.number().int().min(1).max(100).default(20),
101
+ status: z.enum(['active', 'suspended', 'archived']).optional(),
102
+ search: z.string().max(255).optional(),
103
+ sortBy: z.enum(['name', 'created_at']).default('created_at'),
104
+ sortOrder: z.enum(['asc', 'desc']).default('desc'),
105
+ });
106
+ /** Schema for cursor-based listing (keyset pagination) */
107
+ const listOrganizationsCursorSchema = z.object({
108
+ cursor: z.string().optional(),
109
+ limit: z.coerce.number().int().min(1).max(100).default(25),
110
+ status: z.enum(['active', 'suspended', 'archived']).optional(),
111
+ search: z.string().max(255).optional(),
112
+ sortBy: z.enum(['name', 'created_at']).default('created_at'),
113
+ sortOrder: z.enum(['asc', 'desc']).default('desc'),
114
+ });
115
+ const validateSlugSchema = z.object({
116
+ slug: z.string().min(1),
117
+ });
118
+ // ---------------------------------------------------------------------------
119
+ // Error handler helper
120
+ // ---------------------------------------------------------------------------
121
+ /**
122
+ * Handle domain errors and map them to HTTP responses.
123
+ * Unknown errors are re-thrown for the global error handler.
124
+ */
125
+ function handleError(ctx, err) {
126
+ if (err instanceof OrganizationNotFoundError) {
127
+ ctx.throw(404, 'Organization not found');
128
+ }
129
+ if (err instanceof OrganizationValidationError) {
130
+ ctx.throw(400, 'Organization request is invalid');
131
+ }
132
+ if (err instanceof z.ZodError) {
133
+ ctx.status = 400;
134
+ ctx.body = { error: 'Organization request is invalid' };
135
+ return undefined;
136
+ }
137
+ throw err;
138
+ }
139
+ // ---------------------------------------------------------------------------
140
+ // Router factory
141
+ // ---------------------------------------------------------------------------
142
+ /**
143
+ * Create the organization management router.
144
+ *
145
+ * All routes require admin authentication (Bearer JWT via requireAdminAuth).
146
+ * The middleware validates the token, checks the user belongs to the
147
+ * super-admin org, and verifies the porta-admin role.
148
+ *
149
+ * Prefix: /api/admin/organizations
150
+ *
151
+ * @returns Configured Koa Router
152
+ */
153
+ export function createOrganizationRouter() {
154
+ const router = new Router({ prefix: '/api/admin/organizations' });
155
+ // All routes require admin authentication
156
+ router.use(requireAdminAuth());
157
+ // -------------------------------------------------------------------------
158
+ // POST / — Create organization
159
+ // -------------------------------------------------------------------------
160
+ router.post('/', requirePermission(ADMIN_PERMISSIONS.ORG_CREATE), async (ctx) => {
161
+ try {
162
+ const body = createOrganizationSchema.parse(ctx.request.body);
163
+ const org = await organizationService.createOrganization(body);
164
+ ctx.status = 201;
165
+ ctx.body = { data: org };
166
+ }
167
+ catch (err) {
168
+ handleError(ctx, err);
169
+ }
170
+ });
171
+ // -------------------------------------------------------------------------
172
+ // GET / — List organizations (paginated)
173
+ // -------------------------------------------------------------------------
174
+ router.get('/', requirePermission(ADMIN_PERMISSIONS.ORG_READ), async (ctx) => {
175
+ try {
176
+ // Cursor-based pagination when `cursor` or `limit` param is present
177
+ if (ctx.query.cursor !== undefined || ctx.query.limit !== undefined) {
178
+ const query = listOrganizationsCursorSchema.parse(ctx.query);
179
+ const result = await organizationService.listOrganizationsCursor(query);
180
+ ctx.body = result;
181
+ return;
182
+ }
183
+ // Default: offset-based pagination (backward compatible)
184
+ const query = listOrganizationsSchema.parse(ctx.query);
185
+ const result = await organizationService.listOrganizations(query);
186
+ ctx.body = result;
187
+ }
188
+ catch (err) {
189
+ handleError(ctx, err);
190
+ }
191
+ });
192
+ // -------------------------------------------------------------------------
193
+ // GET /validate-slug — Validate slug availability
194
+ // Must be BEFORE /:id to avoid matching "validate-slug" as an :id param
195
+ // -------------------------------------------------------------------------
196
+ router.get('/validate-slug', requirePermission(ADMIN_PERMISSIONS.ORG_READ), async (ctx) => {
197
+ try {
198
+ const { slug } = validateSlugSchema.parse(ctx.query);
199
+ const result = await organizationService.validateSlugAvailability(slug);
200
+ ctx.body = result;
201
+ }
202
+ catch (err) {
203
+ handleError(ctx, err);
204
+ }
205
+ });
206
+ // -------------------------------------------------------------------------
207
+ // GET /:id — Get organization by ID
208
+ // -------------------------------------------------------------------------
209
+ router.get('/:id', requirePermission(ADMIN_PERMISSIONS.ORG_READ), async (ctx) => {
210
+ const param = ctx.params.id;
211
+ // Support both UUID and slug lookups — CLI and API consumers may use either
212
+ const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(param);
213
+ const org = isUuid
214
+ ? await organizationService.getOrganizationById(param)
215
+ : await organizationService.getOrganizationBySlug(param);
216
+ if (!org) {
217
+ ctx.throw(404, 'Organization not found');
218
+ return; // unreachable — keeps TS narrowing happy
219
+ }
220
+ setETagHeader(ctx, 'organization', org.id, org.updatedAt);
221
+ ctx.body = { data: org };
222
+ });
223
+ // -------------------------------------------------------------------------
224
+ // PUT /:id — Update organization
225
+ // -------------------------------------------------------------------------
226
+ router.put('/:id', requirePermission(ADMIN_PERMISSIONS.ORG_UPDATE), async (ctx) => {
227
+ try {
228
+ const body = updateOrganizationSchema.parse(ctx.request.body);
229
+ // Check If-Match for optimistic concurrency (optional — backward compatible)
230
+ const current = await organizationService.getOrganizationById(ctx.params.id);
231
+ if (current && !checkIfMatch(ctx, 'organization', current.id, current.updatedAt, current))
232
+ return;
233
+ const org = await organizationService.updateOrganization(ctx.params.id, body);
234
+ setETagHeader(ctx, 'organization', org.id, org.updatedAt);
235
+ ctx.body = { data: org };
236
+ }
237
+ catch (err) {
238
+ handleError(ctx, err);
239
+ }
240
+ });
241
+ // -------------------------------------------------------------------------
242
+ // PUT /:id/branding — Update branding
243
+ // -------------------------------------------------------------------------
244
+ router.put('/:id/branding', requirePermission(ADMIN_PERMISSIONS.ORG_UPDATE), async (ctx) => {
245
+ try {
246
+ const body = updateBrandingSchema.parse(ctx.request.body);
247
+ const org = await organizationService.updateOrganizationBranding(ctx.params.id, body);
248
+ ctx.body = { data: org };
249
+ }
250
+ catch (err) {
251
+ handleError(ctx, err);
252
+ }
253
+ });
254
+ // -------------------------------------------------------------------------
255
+ // POST /:id/suspend — Suspend organization
256
+ // -------------------------------------------------------------------------
257
+ router.post('/:id/suspend', requirePermission(ADMIN_PERMISSIONS.ORG_SUSPEND), async (ctx) => {
258
+ try {
259
+ const body = ctx.request.body;
260
+ await organizationService.suspendOrganization(ctx.params.id, body?.reason);
261
+ ctx.status = 204;
262
+ }
263
+ catch (err) {
264
+ handleError(ctx, err);
265
+ }
266
+ });
267
+ // -------------------------------------------------------------------------
268
+ // POST /:id/activate — Activate organization
269
+ // -------------------------------------------------------------------------
270
+ router.post('/:id/activate', requirePermission(ADMIN_PERMISSIONS.ORG_SUSPEND), async (ctx) => {
271
+ try {
272
+ await organizationService.activateOrganization(ctx.params.id);
273
+ ctx.status = 204;
274
+ }
275
+ catch (err) {
276
+ handleError(ctx, err);
277
+ }
278
+ });
279
+ // -------------------------------------------------------------------------
280
+ // POST /:id/archive — Archive organization
281
+ // -------------------------------------------------------------------------
282
+ router.post('/:id/archive', requirePermission(ADMIN_PERMISSIONS.ORG_ARCHIVE), async (ctx) => {
283
+ try {
284
+ await organizationService.archiveOrganization(ctx.params.id);
285
+ ctx.status = 204;
286
+ }
287
+ catch (err) {
288
+ handleError(ctx, err);
289
+ }
290
+ });
291
+ // -------------------------------------------------------------------------
292
+ // GET /:id/history — Organization change history
293
+ // -------------------------------------------------------------------------
294
+ router.get('/:id/history', requirePermission(ADMIN_PERMISSIONS.ORG_READ), async (ctx) => {
295
+ const { limit, after, event_type } = ctx.query;
296
+ const result = await getEntityHistory('organization', ctx.params.id, {
297
+ limit: limit ? parseInt(limit, 10) : undefined,
298
+ after: after || undefined,
299
+ eventTypePrefix: event_type || undefined,
300
+ });
301
+ ctx.body = result;
302
+ });
303
+ // -------------------------------------------------------------------------
304
+ // POST /:id/restore — Restore organization
305
+ // -------------------------------------------------------------------------
306
+ router.post('/:id/restore', requirePermission(ADMIN_PERMISSIONS.ORG_ARCHIVE), async (ctx) => {
307
+ try {
308
+ await organizationService.restoreOrganization(ctx.params.id);
309
+ ctx.status = 204;
310
+ }
311
+ catch (err) {
312
+ handleError(ctx, err);
313
+ }
314
+ });
315
+ // -------------------------------------------------------------------------
316
+ // DELETE /:idOrSlug — Permanently destroy organization (cascade)
317
+ // -------------------------------------------------------------------------
318
+ router.delete('/:idOrSlug', requirePermission(ADMIN_PERMISSIONS.ORG_ARCHIVE), async (ctx) => {
319
+ try {
320
+ const { idOrSlug } = ctx.params;
321
+ const dryRun = ctx.query['dry-run'] === 'true';
322
+ if (dryRun) {
323
+ // Resolve org and return cascade counts without deleting
324
+ const org = (await organizationService.getOrganizationById(idOrSlug)) ??
325
+ (await organizationService.getOrganizationBySlug(idOrSlug));
326
+ if (!org) {
327
+ ctx.throw(404, `Organization not found: ${idOrSlug}`);
328
+ return;
329
+ }
330
+ if (org.isSuperAdmin) {
331
+ ctx.throw(400, 'Cannot destroy the super-admin organization');
332
+ return;
333
+ }
334
+ const cascadeCounts = await organizationService.getCascadeCounts(org.id);
335
+ ctx.body = { dryRun: true, organization: org, cascadeCounts };
336
+ return;
337
+ }
338
+ const result = await organizationService.destroyOrganization(idOrSlug, ctx.state.adminUser?.id);
339
+ ctx.body = result;
340
+ }
341
+ catch (err) {
342
+ handleError(ctx, err);
343
+ }
344
+ });
345
+ return router;
346
+ }
347
+ //# sourceMappingURL=organizations.js.map