@portaidentity/server 1.7.3 → 1.8.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 (487) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/dist/applications/index.d.ts +2 -2
  3. package/dist/applications/index.d.ts.map +1 -1
  4. package/dist/applications/index.js +2 -2
  5. package/dist/applications/index.js.map +1 -1
  6. package/dist/applications/repository.d.ts +49 -4
  7. package/dist/applications/repository.d.ts.map +1 -1
  8. package/dist/applications/repository.js +148 -10
  9. package/dist/applications/repository.js.map +1 -1
  10. package/dist/applications/service.d.ts +35 -17
  11. package/dist/applications/service.d.ts.map +1 -1
  12. package/dist/applications/service.js +144 -35
  13. package/dist/applications/service.js.map +1 -1
  14. package/dist/applications/types.d.ts +1 -1
  15. package/dist/applications/types.d.ts.map +1 -1
  16. package/dist/applications/validators.d.ts +14 -0
  17. package/dist/applications/validators.d.ts.map +1 -0
  18. package/dist/applications/validators.js +14 -0
  19. package/dist/applications/validators.js.map +1 -0
  20. package/dist/auth/effective-branding.d.ts +35 -0
  21. package/dist/auth/effective-branding.d.ts.map +1 -0
  22. package/dist/auth/effective-branding.js +81 -0
  23. package/dist/auth/effective-branding.js.map +1 -0
  24. package/dist/auth/email-service.d.ts +3 -8
  25. package/dist/auth/email-service.d.ts.map +1 -1
  26. package/dist/auth/email-service.js +22 -30
  27. package/dist/auth/email-service.js.map +1 -1
  28. package/dist/auth/i18n.d.ts +5 -0
  29. package/dist/auth/i18n.d.ts.map +1 -1
  30. package/dist/auth/i18n.js +6 -5
  31. package/dist/auth/i18n.js.map +1 -1
  32. package/dist/auth/rate-limiter.d.ts +6 -10
  33. package/dist/auth/rate-limiter.d.ts.map +1 -1
  34. package/dist/auth/rate-limiter.js +12 -16
  35. package/dist/auth/rate-limiter.js.map +1 -1
  36. package/dist/auth/recovery-job-processor.d.ts.map +1 -1
  37. package/dist/auth/recovery-job-processor.js +1 -1
  38. package/dist/auth/recovery-job-processor.js.map +1 -1
  39. package/dist/auth/template-engine.d.ts +3 -1
  40. package/dist/auth/template-engine.d.ts.map +1 -1
  41. package/dist/auth/template-engine.js +1 -1
  42. package/dist/auth/template-engine.js.map +1 -1
  43. package/dist/cli/commands/init.d.ts +1 -1
  44. package/dist/cli/commands/init.d.ts.map +1 -1
  45. package/dist/cli/commands/init.js +12 -12
  46. package/dist/cli/commands/init.js.map +1 -1
  47. package/dist/cli/error-handler.d.ts +3 -2
  48. package/dist/cli/error-handler.d.ts.map +1 -1
  49. package/dist/cli/error-handler.js +9 -2
  50. package/dist/cli/error-handler.js.map +1 -1
  51. package/dist/cli/index.d.ts +1 -1
  52. package/dist/cli/index.d.ts.map +1 -1
  53. package/dist/cli/index.js +1 -1
  54. package/dist/cli/index.js.map +1 -1
  55. package/dist/clients/index.d.ts +1 -1
  56. package/dist/clients/index.js +1 -1
  57. package/dist/clients/repository.d.ts +18 -0
  58. package/dist/clients/repository.d.ts.map +1 -1
  59. package/dist/clients/repository.js +36 -0
  60. package/dist/clients/repository.js.map +1 -1
  61. package/dist/clients/secret-repository.d.ts +35 -8
  62. package/dist/clients/secret-repository.d.ts.map +1 -1
  63. package/dist/clients/secret-repository.js +69 -16
  64. package/dist/clients/secret-repository.js.map +1 -1
  65. package/dist/clients/secret-service.d.ts +5 -9
  66. package/dist/clients/secret-service.d.ts.map +1 -1
  67. package/dist/clients/secret-service.js +13 -22
  68. package/dist/clients/secret-service.js.map +1 -1
  69. package/dist/clients/service.d.ts +10 -13
  70. package/dist/clients/service.d.ts.map +1 -1
  71. package/dist/clients/service.js +112 -40
  72. package/dist/clients/service.js.map +1 -1
  73. package/dist/clients/types.d.ts +1 -1
  74. package/dist/clients/types.d.ts.map +1 -1
  75. package/dist/clients/validators.d.ts +83 -1
  76. package/dist/clients/validators.d.ts.map +1 -1
  77. package/dist/clients/validators.js +110 -0
  78. package/dist/clients/validators.js.map +1 -1
  79. package/dist/config/index.d.ts +1 -0
  80. package/dist/config/index.d.ts.map +1 -1
  81. package/dist/config/index.js +1 -0
  82. package/dist/config/index.js.map +1 -1
  83. package/dist/config/schema.d.ts +1 -0
  84. package/dist/config/schema.d.ts.map +1 -1
  85. package/dist/config/schema.js +23 -1
  86. package/dist/config/schema.js.map +1 -1
  87. package/dist/custom-claims/repository.d.ts +14 -9
  88. package/dist/custom-claims/repository.d.ts.map +1 -1
  89. package/dist/custom-claims/repository.js +38 -11
  90. package/dist/custom-claims/repository.js.map +1 -1
  91. package/dist/custom-claims/service.d.ts +11 -6
  92. package/dist/custom-claims/service.d.ts.map +1 -1
  93. package/dist/custom-claims/service.js +43 -20
  94. package/dist/custom-claims/service.js.map +1 -1
  95. package/dist/index.js +8 -2
  96. package/dist/index.js.map +1 -1
  97. package/dist/lib/admin-permissions.d.ts +12 -7
  98. package/dist/lib/admin-permissions.d.ts.map +1 -1
  99. package/dist/lib/admin-permissions.js +26 -16
  100. package/dist/lib/admin-permissions.js.map +1 -1
  101. package/dist/lib/audit-log.d.ts +2 -2
  102. package/dist/lib/audit-log.d.ts.map +1 -1
  103. package/dist/lib/audit-log.js +1 -1
  104. package/dist/lib/audit-log.js.map +1 -1
  105. package/dist/lib/authority-revocation.d.ts +18 -0
  106. package/dist/lib/authority-revocation.d.ts.map +1 -0
  107. package/dist/lib/authority-revocation.js +37 -0
  108. package/dist/lib/authority-revocation.js.map +1 -0
  109. package/dist/lib/branding-assets.d.ts +35 -5
  110. package/dist/lib/branding-assets.d.ts.map +1 -1
  111. package/dist/lib/branding-assets.js +33 -26
  112. package/dist/lib/branding-assets.js.map +1 -1
  113. package/dist/lib/bulk-operations.d.ts +1 -1
  114. package/dist/lib/bulk-operations.d.ts.map +1 -1
  115. package/dist/lib/bulk-operations.js +1 -13
  116. package/dist/lib/bulk-operations.js.map +1 -1
  117. package/dist/lib/cursor.d.ts +2 -0
  118. package/dist/lib/cursor.d.ts.map +1 -1
  119. package/dist/lib/cursor.js +2 -7
  120. package/dist/lib/cursor.js.map +1 -1
  121. package/dist/lib/database.d.ts.map +1 -1
  122. package/dist/lib/database.js +5 -0
  123. package/dist/lib/database.js.map +1 -1
  124. package/dist/lib/deletion-cleanup.d.ts +43 -0
  125. package/dist/lib/deletion-cleanup.d.ts.map +1 -0
  126. package/dist/lib/deletion-cleanup.js +207 -0
  127. package/dist/lib/deletion-cleanup.js.map +1 -0
  128. package/dist/lib/image-validator.d.ts +3 -1
  129. package/dist/lib/image-validator.d.ts.map +1 -1
  130. package/dist/lib/image-validator.js +32 -19
  131. package/dist/lib/image-validator.js.map +1 -1
  132. package/dist/lib/log-format.d.ts +20 -0
  133. package/dist/lib/log-format.d.ts.map +1 -0
  134. package/dist/lib/log-format.js +22 -0
  135. package/dist/lib/log-format.js.map +1 -0
  136. package/dist/lib/logger.d.ts +3 -1
  137. package/dist/lib/logger.d.ts.map +1 -1
  138. package/dist/lib/logger.js +21 -4
  139. package/dist/lib/logger.js.map +1 -1
  140. package/dist/lib/redis.d.ts.map +1 -1
  141. package/dist/lib/redis.js +3 -0
  142. package/dist/lib/redis.js.map +1 -1
  143. package/dist/lib/session-tracking.d.ts +73 -11
  144. package/dist/lib/session-tracking.d.ts.map +1 -1
  145. package/dist/lib/session-tracking.js +76 -32
  146. package/dist/lib/session-tracking.js.map +1 -1
  147. package/dist/lib/signing-key-crypto.d.ts.map +1 -1
  148. package/dist/lib/signing-key-crypto.js +4 -5
  149. package/dist/lib/signing-key-crypto.js.map +1 -1
  150. package/dist/lib/signing-keys.d.ts +3 -4
  151. package/dist/lib/signing-keys.d.ts.map +1 -1
  152. package/dist/lib/signing-keys.js +66 -41
  153. package/dist/lib/signing-keys.js.map +1 -1
  154. package/dist/lib/super-admin-protection.d.ts +5 -5
  155. package/dist/lib/super-admin-protection.d.ts.map +1 -1
  156. package/dist/lib/super-admin-protection.js +6 -9
  157. package/dist/lib/super-admin-protection.js.map +1 -1
  158. package/dist/lib/system-config-catalog.d.ts +268 -0
  159. package/dist/lib/system-config-catalog.d.ts.map +1 -0
  160. package/dist/lib/system-config-catalog.js +90 -0
  161. package/dist/lib/system-config-catalog.js.map +1 -0
  162. package/dist/lib/system-config.d.ts +30 -33
  163. package/dist/lib/system-config.d.ts.map +1 -1
  164. package/dist/lib/system-config.js +78 -93
  165. package/dist/lib/system-config.js.map +1 -1
  166. package/dist/lib/with-timeout.d.ts +32 -0
  167. package/dist/lib/with-timeout.d.ts.map +1 -0
  168. package/dist/lib/with-timeout.js +45 -0
  169. package/dist/lib/with-timeout.js.map +1 -0
  170. package/dist/middleware/admin-auth.d.ts +6 -5
  171. package/dist/middleware/admin-auth.d.ts.map +1 -1
  172. package/dist/middleware/admin-auth.js +30 -20
  173. package/dist/middleware/admin-auth.js.map +1 -1
  174. package/dist/middleware/admin-cors.d.ts +3 -3
  175. package/dist/middleware/admin-cors.js +3 -3
  176. package/dist/middleware/admin-mutation-audit.d.ts +3 -2
  177. package/dist/middleware/admin-mutation-audit.d.ts.map +1 -1
  178. package/dist/middleware/admin-mutation-audit.js +12 -5
  179. package/dist/middleware/admin-mutation-audit.js.map +1 -1
  180. package/dist/middleware/admin-rate-limiter.d.ts +7 -8
  181. package/dist/middleware/admin-rate-limiter.d.ts.map +1 -1
  182. package/dist/middleware/admin-rate-limiter.js +7 -8
  183. package/dist/middleware/admin-rate-limiter.js.map +1 -1
  184. package/dist/middleware/client-secret-hash.d.ts +10 -29
  185. package/dist/middleware/client-secret-hash.d.ts.map +1 -1
  186. package/dist/middleware/client-secret-hash.js +161 -59
  187. package/dist/middleware/client-secret-hash.js.map +1 -1
  188. package/dist/middleware/health.d.ts +17 -0
  189. package/dist/middleware/health.d.ts.map +1 -1
  190. package/dist/middleware/health.js +25 -21
  191. package/dist/middleware/health.js.map +1 -1
  192. package/dist/middleware/ready.d.ts.map +1 -1
  193. package/dist/middleware/ready.js +1 -12
  194. package/dist/middleware/ready.js.map +1 -1
  195. package/dist/middleware/require-existing-organization.d.ts +18 -0
  196. package/dist/middleware/require-existing-organization.d.ts.map +1 -0
  197. package/dist/middleware/require-existing-organization.js +38 -0
  198. package/dist/middleware/require-existing-organization.js.map +1 -0
  199. package/dist/middleware/require-permission.d.ts.map +1 -1
  200. package/dist/middleware/require-permission.js +35 -1
  201. package/dist/middleware/require-permission.js.map +1 -1
  202. package/dist/middleware/require-user-organization.d.ts.map +1 -1
  203. package/dist/middleware/require-user-organization.js +4 -2
  204. package/dist/middleware/require-user-organization.js.map +1 -1
  205. package/dist/middleware/security-headers.d.ts +18 -0
  206. package/dist/middleware/security-headers.d.ts.map +1 -1
  207. package/dist/middleware/security-headers.js +32 -1
  208. package/dist/middleware/security-headers.js.map +1 -1
  209. package/dist/middleware/tenant-resolver.d.ts +1 -2
  210. package/dist/middleware/tenant-resolver.d.ts.map +1 -1
  211. package/dist/middleware/tenant-resolver.js +1 -5
  212. package/dist/middleware/tenant-resolver.js.map +1 -1
  213. package/dist/middleware/token-rate-limiter.d.ts +37 -16
  214. package/dist/middleware/token-rate-limiter.d.ts.map +1 -1
  215. package/dist/middleware/token-rate-limiter.js +126 -54
  216. package/dist/middleware/token-rate-limiter.js.map +1 -1
  217. package/dist/oidc/account-finder.d.ts +5 -5
  218. package/dist/oidc/account-finder.d.ts.map +1 -1
  219. package/dist/oidc/account-finder.js +31 -23
  220. package/dist/oidc/account-finder.js.map +1 -1
  221. package/dist/oidc/adapter-factory.d.ts.map +1 -1
  222. package/dist/oidc/adapter-factory.js +83 -3
  223. package/dist/oidc/adapter-factory.js.map +1 -1
  224. package/dist/oidc/configuration.d.ts +3 -8
  225. package/dist/oidc/configuration.d.ts.map +1 -1
  226. package/dist/oidc/configuration.js +64 -50
  227. package/dist/oidc/configuration.js.map +1 -1
  228. package/dist/oidc/provider.d.ts.map +1 -1
  229. package/dist/oidc/provider.js +7 -0
  230. package/dist/oidc/provider.js.map +1 -1
  231. package/dist/oidc/redis-adapter.d.ts +2 -3
  232. package/dist/oidc/redis-adapter.d.ts.map +1 -1
  233. package/dist/oidc/redis-adapter.js +54 -46
  234. package/dist/oidc/redis-adapter.js.map +1 -1
  235. package/dist/organizations/branding-url.d.ts +17 -0
  236. package/dist/organizations/branding-url.d.ts.map +1 -0
  237. package/dist/organizations/branding-url.js +38 -0
  238. package/dist/organizations/branding-url.js.map +1 -0
  239. package/dist/organizations/index.d.ts +2 -2
  240. package/dist/organizations/index.d.ts.map +1 -1
  241. package/dist/organizations/index.js +1 -1
  242. package/dist/organizations/index.js.map +1 -1
  243. package/dist/organizations/repository.d.ts +22 -32
  244. package/dist/organizations/repository.d.ts.map +1 -1
  245. package/dist/organizations/repository.js +64 -45
  246. package/dist/organizations/repository.js.map +1 -1
  247. package/dist/organizations/service.d.ts +14 -46
  248. package/dist/organizations/service.d.ts.map +1 -1
  249. package/dist/organizations/service.js +60 -112
  250. package/dist/organizations/service.js.map +1 -1
  251. package/dist/organizations/types.d.ts +1 -15
  252. package/dist/organizations/types.d.ts.map +1 -1
  253. package/dist/organizations/validators.d.ts +41 -0
  254. package/dist/organizations/validators.d.ts.map +1 -0
  255. package/dist/organizations/validators.js +48 -0
  256. package/dist/organizations/validators.js.map +1 -0
  257. package/dist/portability/apply.d.ts +15 -0
  258. package/dist/portability/apply.d.ts.map +1 -0
  259. package/dist/portability/apply.js +274 -0
  260. package/dist/portability/apply.js.map +1 -0
  261. package/dist/portability/authorization.d.ts +30 -0
  262. package/dist/portability/authorization.d.ts.map +1 -0
  263. package/dist/portability/authorization.js +130 -0
  264. package/dist/portability/authorization.js.map +1 -0
  265. package/dist/portability/cleanup.d.ts +29 -0
  266. package/dist/portability/cleanup.d.ts.map +1 -0
  267. package/dist/portability/cleanup.js +167 -0
  268. package/dist/portability/cleanup.js.map +1 -0
  269. package/dist/portability/export.d.ts +18 -0
  270. package/dist/portability/export.d.ts.map +1 -0
  271. package/dist/portability/export.js +125 -0
  272. package/dist/portability/export.js.map +1 -0
  273. package/dist/portability/import-repository.d.ts +204 -0
  274. package/dist/portability/import-repository.d.ts.map +1 -0
  275. package/dist/portability/import-repository.js +347 -0
  276. package/dist/portability/import-repository.js.map +1 -0
  277. package/dist/portability/import-user-client-writers.d.ts +40 -0
  278. package/dist/portability/import-user-client-writers.d.ts.map +1 -0
  279. package/dist/portability/import-user-client-writers.js +168 -0
  280. package/dist/portability/import-user-client-writers.js.map +1 -0
  281. package/dist/portability/index.d.ts +8 -0
  282. package/dist/portability/index.d.ts.map +1 -0
  283. package/dist/portability/index.js +8 -0
  284. package/dist/portability/index.js.map +1 -0
  285. package/dist/portability/plan-support.d.ts +137 -0
  286. package/dist/portability/plan-support.d.ts.map +1 -0
  287. package/dist/portability/plan-support.js +336 -0
  288. package/dist/portability/plan-support.js.map +1 -0
  289. package/dist/portability/plan.d.ts +34 -0
  290. package/dist/portability/plan.d.ts.map +1 -0
  291. package/dist/portability/plan.js +450 -0
  292. package/dist/portability/plan.js.map +1 -0
  293. package/dist/portability/repository.d.ts +95 -0
  294. package/dist/portability/repository.d.ts.map +1 -0
  295. package/dist/portability/repository.js +388 -0
  296. package/dist/portability/repository.js.map +1 -0
  297. package/dist/portability/schema.d.ts +589 -0
  298. package/dist/portability/schema.d.ts.map +1 -0
  299. package/dist/portability/schema.js +458 -0
  300. package/dist/portability/schema.js.map +1 -0
  301. package/dist/portability/types.d.ts +381 -0
  302. package/dist/portability/types.d.ts.map +1 -0
  303. package/dist/portability/types.js +19 -0
  304. package/dist/portability/types.js.map +1 -0
  305. package/dist/rbac/errors.d.ts +4 -0
  306. package/dist/rbac/errors.d.ts.map +1 -1
  307. package/dist/rbac/errors.js +7 -0
  308. package/dist/rbac/errors.js.map +1 -1
  309. package/dist/rbac/index.d.ts +3 -3
  310. package/dist/rbac/index.d.ts.map +1 -1
  311. package/dist/rbac/index.js +3 -3
  312. package/dist/rbac/index.js.map +1 -1
  313. package/dist/rbac/mapping-repository.d.ts +84 -9
  314. package/dist/rbac/mapping-repository.d.ts.map +1 -1
  315. package/dist/rbac/mapping-repository.js +222 -55
  316. package/dist/rbac/mapping-repository.js.map +1 -1
  317. package/dist/rbac/permission-repository.d.ts +36 -19
  318. package/dist/rbac/permission-repository.d.ts.map +1 -1
  319. package/dist/rbac/permission-repository.js +93 -35
  320. package/dist/rbac/permission-repository.js.map +1 -1
  321. package/dist/rbac/permission-service.d.ts +18 -19
  322. package/dist/rbac/permission-service.d.ts.map +1 -1
  323. package/dist/rbac/permission-service.js +100 -57
  324. package/dist/rbac/permission-service.js.map +1 -1
  325. package/dist/rbac/role-repository.d.ts +29 -24
  326. package/dist/rbac/role-repository.d.ts.map +1 -1
  327. package/dist/rbac/role-repository.js +66 -43
  328. package/dist/rbac/role-repository.js.map +1 -1
  329. package/dist/rbac/role-service.d.ts +35 -32
  330. package/dist/rbac/role-service.d.ts.map +1 -1
  331. package/dist/rbac/role-service.js +210 -96
  332. package/dist/rbac/role-service.js.map +1 -1
  333. package/dist/rbac/slugs.d.ts +16 -27
  334. package/dist/rbac/slugs.d.ts.map +1 -1
  335. package/dist/rbac/slugs.js +36 -59
  336. package/dist/rbac/slugs.js.map +1 -1
  337. package/dist/rbac/types.d.ts +2 -4
  338. package/dist/rbac/types.d.ts.map +1 -1
  339. package/dist/rbac/types.js.map +1 -1
  340. package/dist/rbac/user-role-service.d.ts +23 -24
  341. package/dist/rbac/user-role-service.d.ts.map +1 -1
  342. package/dist/rbac/user-role-service.js +117 -61
  343. package/dist/rbac/user-role-service.js.map +1 -1
  344. package/dist/routes/applications.d.ts +4 -2
  345. package/dist/routes/applications.d.ts.map +1 -1
  346. package/dist/routes/applications.js +57 -19
  347. package/dist/routes/applications.js.map +1 -1
  348. package/dist/routes/audit.js +1 -1
  349. package/dist/routes/audit.js.map +1 -1
  350. package/dist/routes/branding.d.ts +3 -3
  351. package/dist/routes/branding.d.ts.map +1 -1
  352. package/dist/routes/branding.js +44 -16
  353. package/dist/routes/branding.js.map +1 -1
  354. package/dist/routes/bulk.d.ts +0 -5
  355. package/dist/routes/bulk.d.ts.map +1 -1
  356. package/dist/routes/bulk.js +3 -5
  357. package/dist/routes/bulk.js.map +1 -1
  358. package/dist/routes/clients.d.ts +4 -4
  359. package/dist/routes/clients.d.ts.map +1 -1
  360. package/dist/routes/clients.js +111 -52
  361. package/dist/routes/clients.js.map +1 -1
  362. package/dist/routes/config.d.ts +17 -12
  363. package/dist/routes/config.d.ts.map +1 -1
  364. package/dist/routes/config.js +165 -64
  365. package/dist/routes/config.js.map +1 -1
  366. package/dist/routes/custom-claims.d.ts.map +1 -1
  367. package/dist/routes/custom-claims.js +8 -2
  368. package/dist/routes/custom-claims.js.map +1 -1
  369. package/dist/routes/exports.d.ts +2 -1
  370. package/dist/routes/exports.d.ts.map +1 -1
  371. package/dist/routes/exports.js +57 -1
  372. package/dist/routes/exports.js.map +1 -1
  373. package/dist/routes/imports.d.ts +2 -2
  374. package/dist/routes/imports.d.ts.map +1 -1
  375. package/dist/routes/imports.js +95 -40
  376. package/dist/routes/imports.js.map +1 -1
  377. package/dist/routes/interactions.d.ts.map +1 -1
  378. package/dist/routes/interactions.js +16 -34
  379. package/dist/routes/interactions.js.map +1 -1
  380. package/dist/routes/invitation.d.ts.map +1 -1
  381. package/dist/routes/invitation.js +10 -23
  382. package/dist/routes/invitation.js.map +1 -1
  383. package/dist/routes/keys.d.ts.map +1 -1
  384. package/dist/routes/keys.js +26 -13
  385. package/dist/routes/keys.js.map +1 -1
  386. package/dist/routes/magic-link.d.ts.map +1 -1
  387. package/dist/routes/magic-link.js +5 -17
  388. package/dist/routes/magic-link.js.map +1 -1
  389. package/dist/routes/organizations.d.ts +1 -2
  390. package/dist/routes/organizations.d.ts.map +1 -1
  391. package/dist/routes/organizations.js +43 -104
  392. package/dist/routes/organizations.js.map +1 -1
  393. package/dist/routes/password-reset.d.ts.map +1 -1
  394. package/dist/routes/password-reset.js +13 -35
  395. package/dist/routes/password-reset.js.map +1 -1
  396. package/dist/routes/permissions.d.ts +1 -1
  397. package/dist/routes/permissions.d.ts.map +1 -1
  398. package/dist/routes/permissions.js +58 -17
  399. package/dist/routes/permissions.js.map +1 -1
  400. package/dist/routes/public-branding.d.ts +16 -0
  401. package/dist/routes/public-branding.d.ts.map +1 -0
  402. package/dist/routes/public-branding.js +52 -0
  403. package/dist/routes/public-branding.js.map +1 -0
  404. package/dist/routes/roles.d.ts +1 -1
  405. package/dist/routes/roles.d.ts.map +1 -1
  406. package/dist/routes/roles.js +63 -21
  407. package/dist/routes/roles.js.map +1 -1
  408. package/dist/routes/sessions.d.ts.map +1 -1
  409. package/dist/routes/sessions.js +23 -12
  410. package/dist/routes/sessions.js.map +1 -1
  411. package/dist/routes/two-factor.d.ts.map +1 -1
  412. package/dist/routes/two-factor.js +91 -38
  413. package/dist/routes/two-factor.js.map +1 -1
  414. package/dist/routes/user-roles.d.ts.map +1 -1
  415. package/dist/routes/user-roles.js +56 -9
  416. package/dist/routes/user-roles.js.map +1 -1
  417. package/dist/routes/users.d.ts +4 -8
  418. package/dist/routes/users.d.ts.map +1 -1
  419. package/dist/routes/users.js +108 -265
  420. package/dist/routes/users.js.map +1 -1
  421. package/dist/server.d.ts.map +1 -1
  422. package/dist/server.js +53 -37
  423. package/dist/server.js.map +1 -1
  424. package/dist/two-factor/errors.d.ts +9 -0
  425. package/dist/two-factor/errors.d.ts.map +1 -1
  426. package/dist/two-factor/errors.js +12 -0
  427. package/dist/two-factor/errors.js.map +1 -1
  428. package/dist/two-factor/index.d.ts +5 -4
  429. package/dist/two-factor/index.d.ts.map +1 -1
  430. package/dist/two-factor/index.js +4 -4
  431. package/dist/two-factor/index.js.map +1 -1
  432. package/dist/two-factor/repository.d.ts +17 -6
  433. package/dist/two-factor/repository.d.ts.map +1 -1
  434. package/dist/two-factor/repository.js +37 -9
  435. package/dist/two-factor/repository.js.map +1 -1
  436. package/dist/two-factor/service.d.ts.map +1 -1
  437. package/dist/two-factor/service.js +26 -19
  438. package/dist/two-factor/service.js.map +1 -1
  439. package/dist/two-factor/totp.d.ts +13 -2
  440. package/dist/two-factor/totp.d.ts.map +1 -1
  441. package/dist/two-factor/totp.js +20 -7
  442. package/dist/two-factor/totp.js.map +1 -1
  443. package/dist/two-factor/types.d.ts +3 -0
  444. package/dist/two-factor/types.d.ts.map +1 -1
  445. package/dist/two-factor/types.js +5 -0
  446. package/dist/two-factor/types.js.map +1 -1
  447. package/dist/users/gdpr.d.ts +2 -40
  448. package/dist/users/gdpr.d.ts.map +1 -1
  449. package/dist/users/gdpr.js +3 -110
  450. package/dist/users/gdpr.js.map +1 -1
  451. package/dist/users/index.d.ts +5 -4
  452. package/dist/users/index.d.ts.map +1 -1
  453. package/dist/users/index.js +5 -4
  454. package/dist/users/index.js.map +1 -1
  455. package/dist/users/repository.d.ts +49 -3
  456. package/dist/users/repository.d.ts.map +1 -1
  457. package/dist/users/repository.js +169 -18
  458. package/dist/users/repository.js.map +1 -1
  459. package/dist/users/service.d.ts +19 -51
  460. package/dist/users/service.d.ts.map +1 -1
  461. package/dist/users/service.js +72 -122
  462. package/dist/users/service.js.map +1 -1
  463. package/dist/users/types.d.ts +4 -5
  464. package/dist/users/types.d.ts.map +1 -1
  465. package/dist/users/types.js.map +1 -1
  466. package/dist/users/validators.d.ts +28 -0
  467. package/dist/users/validators.d.ts.map +1 -0
  468. package/dist/users/validators.js +25 -0
  469. package/dist/users/validators.js.map +1 -0
  470. package/locales/default/en/errors.json +2 -1
  471. package/migrations/024_application_client_correction.sql +39 -0
  472. package/migrations/025_record_deletion_lifecycle.sql +26 -0
  473. package/migrations/026_simplify_user_lifecycle.sql +33 -0
  474. package/migrations/027_branding_asset_size_limits.sql +19 -0
  475. package/migrations/028_totp_replay_protection.sql +17 -0
  476. package/migrations/029_public_session_identifiers.sql +16 -0
  477. package/migrations/030_global_configuration_catalog.sql +39 -0
  478. package/migrations/031_monotonic_updated_at.sql +28 -0
  479. package/package.json +2 -2
  480. package/dist/lib/data-import-plan.d.ts +0 -50
  481. package/dist/lib/data-import-plan.d.ts.map +0 -1
  482. package/dist/lib/data-import-plan.js +0 -235
  483. package/dist/lib/data-import-plan.js.map +0 -1
  484. package/dist/lib/data-import.d.ts +0 -276
  485. package/dist/lib/data-import.d.ts.map +0 -1
  486. package/dist/lib/data-import.js +0 -1694
  487. package/dist/lib/data-import.js.map +0 -1
@@ -12,16 +12,12 @@
12
12
  * GET /:userId — Get user by ID
13
13
  * PUT /:userId — Update user profile
14
14
  * POST /:userId/deactivate — Deactivate (active → inactive)
15
- * POST /:userId/reactivate — Reactivate (inactive → active)
16
- * POST /:userId/suspend — Suspend (active → suspended)
17
- * POST /:userId/unsuspend — Unsuspend (suspended → active)
18
- * POST /:userId/lock — Lock (active → locked)
19
- * POST /:userId/unlock — Unlock (locked → active)
15
+ * POST /:userId/activate — Activate (inactive → active)
20
16
  * POST /:userId/password — Set/change password
21
17
  * DELETE /:userId/password — Clear password (passwordless)
22
18
  * POST /:userId/verify-email — Mark email as verified
23
19
  * GET /:userId/export — GDPR data export (Article 20)
24
- * POST /:userId/purge — GDPR data purge (Article 17)
20
+ * DELETE /:userId — Delete user
25
21
  *
26
22
  * Error mapping:
27
23
  * UserNotFoundError → 404
@@ -32,7 +28,7 @@
32
28
  import Router from '@koa/router';
33
29
  import { z } from 'zod';
34
30
  import { renderInvitationEmail, sendInvitationEmail } from '../auth/email-service.js';
35
- import { insertInvitationToken, invalidateUserTokens } from '../auth/token-repository.js';
31
+ import { insertInvitationToken } from '../auth/token-repository.js';
36
32
  import { generateToken } from '../auth/tokens.js';
37
33
  import { config } from '../config/index.js';
38
34
  import { ADMIN_PERMISSIONS } from '../lib/admin-permissions.js';
@@ -41,83 +37,83 @@ import { afterDatabaseCommit, getPool } from '../lib/database.js';
41
37
  import { getEntityHistory } from '../lib/entity-history.js';
42
38
  import { checkIfMatch, setETagHeader } from '../lib/etag.js';
43
39
  import { guardSuperAdmin, SuperAdminProtectionError } from '../lib/super-admin-protection.js';
40
+ import { getSystemConfigNumber } from '../lib/system-config.js';
44
41
  import { requireAdminAuth } from '../middleware/admin-auth.js';
45
42
  import { requirePermission } from '../middleware/require-permission.js';
46
43
  import { requireUserOrganization } from '../middleware/require-user-organization.js';
44
+ import { requireExistingOrganization } from '../middleware/require-existing-organization.js';
47
45
  import { getOrganizationById } from '../organizations/service.js';
48
46
  import { UserNotFoundError, UserValidationError } from '../users/errors.js';
49
- import { exportUserData, purgeUserData } from '../users/gdpr.js';
47
+ import { exportUserData } from '../users/gdpr.js';
50
48
  import * as userService from '../users/service.js';
49
+ import { userBirthdateSchema, userCountrySchema, userEmailSchema, userGenderSchema, userLocaleSchema, userPhoneNumberSchema, userPostalCodeSchema, userProfileNameSchema, userProfileUrlSchema, userZoneinfoSchema, } from '../users/validators.js';
51
50
  // ---------------------------------------------------------------------------
52
51
  // Validation schemas
53
52
  // ---------------------------------------------------------------------------
54
53
  /** Schema for creating a new user */
55
54
  const createUserSchema = z.object({
56
- email: z.string().email().max(255),
55
+ email: userEmailSchema,
57
56
  password: z.string().min(8).max(128).optional(),
58
- givenName: z.string().max(255).optional(),
59
- familyName: z.string().max(255).optional(),
60
- middleName: z.string().max(255).optional(),
61
- nickname: z.string().max(255).optional(),
62
- preferredUsername: z.string().max(255).optional(),
63
- profileUrl: z.string().url().optional(),
64
- pictureUrl: z.string().url().optional(),
65
- websiteUrl: z.string().url().optional(),
66
- gender: z.string().max(50).optional(),
67
- birthdate: z
68
- .string()
69
- .regex(/^\d{4}-\d{2}-\d{2}$/)
70
- .optional(),
71
- zoneinfo: z.string().max(50).optional(),
72
- locale: z.string().max(10).optional(),
73
- phoneNumber: z.string().max(50).optional(),
57
+ givenName: userProfileNameSchema.optional(),
58
+ familyName: userProfileNameSchema.optional(),
59
+ middleName: userProfileNameSchema.optional(),
60
+ nickname: userProfileNameSchema.optional(),
61
+ preferredUsername: userProfileNameSchema.optional(),
62
+ profileUrl: userProfileUrlSchema.optional(),
63
+ pictureUrl: userProfileUrlSchema.optional(),
64
+ websiteUrl: userProfileUrlSchema.optional(),
65
+ gender: userGenderSchema.optional(),
66
+ birthdate: userBirthdateSchema.optional(),
67
+ zoneinfo: userZoneinfoSchema.optional(),
68
+ locale: userLocaleSchema.optional(),
69
+ phoneNumber: userPhoneNumberSchema.optional(),
74
70
  phoneNumberVerified: z.boolean().optional(),
75
71
  address: z
76
72
  .object({
77
73
  street: z.string().nullable().optional(),
78
- locality: z.string().max(255).nullable().optional(),
79
- region: z.string().max(255).nullable().optional(),
80
- postalCode: z.string().max(20).nullable().optional(),
81
- country: z.string().length(2).nullable().optional(),
74
+ locality: userProfileNameSchema.nullable().optional(),
75
+ region: userProfileNameSchema.nullable().optional(),
76
+ postalCode: userPostalCodeSchema.nullable().optional(),
77
+ country: userCountrySchema.nullable().optional(),
82
78
  })
83
79
  .optional(),
84
80
  });
85
81
  /** Schema for updating a user (all fields optional, nullable for clearing) */
86
- const updateUserSchema = z.object({
87
- givenName: z.string().max(255).nullable().optional(),
88
- familyName: z.string().max(255).nullable().optional(),
89
- middleName: z.string().max(255).nullable().optional(),
90
- nickname: z.string().max(255).nullable().optional(),
91
- preferredUsername: z.string().max(255).nullable().optional(),
92
- profileUrl: z.string().url().nullable().optional(),
93
- pictureUrl: z.string().url().nullable().optional(),
94
- websiteUrl: z.string().url().nullable().optional(),
95
- gender: z.string().max(50).nullable().optional(),
96
- birthdate: z
97
- .string()
98
- .regex(/^\d{4}-\d{2}-\d{2}$/)
99
- .nullable()
100
- .optional(),
101
- zoneinfo: z.string().max(50).nullable().optional(),
102
- locale: z.string().max(10).nullable().optional(),
103
- phoneNumber: z.string().max(50).nullable().optional(),
82
+ const updateUserSchema = z
83
+ .object({
84
+ givenName: userProfileNameSchema.nullable().optional(),
85
+ familyName: userProfileNameSchema.nullable().optional(),
86
+ middleName: userProfileNameSchema.nullable().optional(),
87
+ nickname: userProfileNameSchema.nullable().optional(),
88
+ preferredUsername: userProfileNameSchema.nullable().optional(),
89
+ profileUrl: userProfileUrlSchema.nullable().optional(),
90
+ pictureUrl: userProfileUrlSchema.nullable().optional(),
91
+ websiteUrl: userProfileUrlSchema.nullable().optional(),
92
+ gender: userGenderSchema.nullable().optional(),
93
+ birthdate: userBirthdateSchema.nullable().optional(),
94
+ zoneinfo: userZoneinfoSchema.nullable().optional(),
95
+ locale: userLocaleSchema.nullable().optional(),
96
+ phoneNumber: userPhoneNumberSchema.nullable().optional(),
104
97
  phoneNumberVerified: z.boolean().optional(),
105
98
  address: z
106
99
  .object({
107
100
  street: z.string().nullable().optional(),
108
- locality: z.string().max(255).nullable().optional(),
109
- region: z.string().max(255).nullable().optional(),
110
- postalCode: z.string().max(20).nullable().optional(),
111
- country: z.string().length(2).nullable().optional(),
101
+ locality: userProfileNameSchema.nullable().optional(),
102
+ region: userProfileNameSchema.nullable().optional(),
103
+ postalCode: userPostalCodeSchema.nullable().optional(),
104
+ country: userCountrySchema.nullable().optional(),
112
105
  })
113
106
  .nullable()
114
107
  .optional(),
108
+ })
109
+ .refine((value) => Object.keys(value).length > 0, {
110
+ message: 'At least one profile field must be provided',
115
111
  });
116
112
  /** Schema for listing users with pagination */
117
113
  const listUsersSchema = z.object({
118
114
  page: z.coerce.number().int().min(1).default(1),
119
115
  pageSize: z.coerce.number().int().min(1).max(100).default(20),
120
- status: z.enum(['active', 'inactive', 'suspended', 'locked']).optional(),
116
+ status: z.enum(['active', 'inactive', 'locked']).optional(),
121
117
  search: z.string().max(255).optional(),
122
118
  sortBy: z
123
119
  .enum(['email', 'given_name', 'family_name', 'created_at', 'last_login_at'])
@@ -128,7 +124,7 @@ const listUsersSchema = z.object({
128
124
  const listUsersCursorSchema = z.object({
129
125
  cursor: z.string().optional(),
130
126
  limit: z.coerce.number().int().min(1).max(100).default(25),
131
- status: z.enum(['active', 'inactive', 'suspended', 'locked']).optional(),
127
+ status: z.enum(['active', 'inactive', 'locked']).optional(),
132
128
  search: z.string().max(255).optional(),
133
129
  sortBy: z
134
130
  .enum(['email', 'given_name', 'family_name', 'created_at', 'last_login_at'])
@@ -139,13 +135,10 @@ const listUsersCursorSchema = z.object({
139
135
  const setPasswordSchema = z.object({
140
136
  password: z.string().min(8).max(128),
141
137
  });
142
- /** Schema for locking a user (reason required) */
143
- const lockUserSchema = z.object({
144
- reason: z.string().min(1).max(500),
145
- });
146
- /** Schema for suspending a user (reason optional) */
147
- const suspendUserSchema = z.object({
148
- reason: z.string().max(500).optional(),
138
+ /** Organization-qualified parameters accepted by user deletion. */
139
+ const identifierSchema = z.object({
140
+ orgId: z.string().uuid(),
141
+ userId: z.string().uuid(),
149
142
  });
150
143
  // ---------------------------------------------------------------------------
151
144
  // Error handler helper
@@ -181,8 +174,8 @@ function handleError(ctx, err) {
181
174
  *
182
175
  * All routes require admin authorization with granular permissions.
183
176
  * Users are always scoped to an organization via the :orgId URL parameter.
184
- * Destructive operations on the super-admin user are blocked by
185
- * guardSuperAdmin() checks.
177
+ * Operations that cannot preserve bootstrap-administrator availability are rejected at their
178
+ * owning service or repository boundary.
186
179
  *
187
180
  * Prefix: /api/admin/organizations/:orgId/users
188
181
  *
@@ -212,7 +205,7 @@ export function createUserRouter() {
212
205
  // -------------------------------------------------------------------------
213
206
  // GET / — List users (paginated)
214
207
  // -------------------------------------------------------------------------
215
- router.get('/', requirePermission(ADMIN_PERMISSIONS.USER_READ), async (ctx) => {
208
+ router.get('/', requirePermission(ADMIN_PERMISSIONS.USER_READ), requireExistingOrganization(), async (ctx) => {
216
209
  try {
217
210
  // Cursor-based pagination when `cursor` or `limit` param is present
218
211
  if (ctx.query.cursor !== undefined || ctx.query.limit !== undefined) {
@@ -273,7 +266,7 @@ export function createUserRouter() {
273
266
  // POST /:userId/deactivate — Deactivate user
274
267
  // Protected: super-admin user cannot be deactivated
275
268
  // -------------------------------------------------------------------------
276
- router.post('/:userId/deactivate', requirePermission(ADMIN_PERMISSIONS.USER_SUSPEND), requireUserOrganization(), async (ctx) => {
269
+ router.post('/:userId/deactivate', requirePermission(ADMIN_PERMISSIONS.USER_LIFECYCLE), requireUserOrganization(), async (ctx) => {
277
270
  try {
278
271
  await guardSuperAdmin(ctx.params.userId, 'deactivate');
279
272
  await userService.deactivateUser(ctx.params.userId);
@@ -284,65 +277,11 @@ export function createUserRouter() {
284
277
  }
285
278
  });
286
279
  // -------------------------------------------------------------------------
287
- // POST /:userId/reactivate — Reactivate user
288
- // -------------------------------------------------------------------------
289
- router.post('/:userId/reactivate', requirePermission(ADMIN_PERMISSIONS.USER_SUSPEND), requireUserOrganization(), async (ctx) => {
290
- try {
291
- await userService.reactivateUser(ctx.params.userId);
292
- ctx.status = 204;
293
- }
294
- catch (err) {
295
- handleError(ctx, err);
296
- }
297
- });
298
- // -------------------------------------------------------------------------
299
- // POST /:userId/suspend — Suspend user
300
- // Protected: super-admin user cannot be suspended
301
- // -------------------------------------------------------------------------
302
- router.post('/:userId/suspend', requirePermission(ADMIN_PERMISSIONS.USER_SUSPEND), requireUserOrganization(), async (ctx) => {
303
- try {
304
- await guardSuperAdmin(ctx.params.userId, 'suspend');
305
- const body = suspendUserSchema.parse(ctx.request.body ?? {});
306
- await userService.suspendUser(ctx.params.userId, body.reason);
307
- ctx.status = 204;
308
- }
309
- catch (err) {
310
- handleError(ctx, err);
311
- }
312
- });
313
- // -------------------------------------------------------------------------
314
- // POST /:userId/unsuspend — Unsuspend user
315
- // -------------------------------------------------------------------------
316
- router.post('/:userId/unsuspend', requirePermission(ADMIN_PERMISSIONS.USER_SUSPEND), requireUserOrganization(), async (ctx) => {
317
- try {
318
- await userService.unsuspendUser(ctx.params.userId);
319
- ctx.status = 204;
320
- }
321
- catch (err) {
322
- handleError(ctx, err);
323
- }
324
- });
325
- // -------------------------------------------------------------------------
326
- // POST /:userId/lock — Lock user
327
- // Protected: super-admin user cannot be locked
280
+ // POST /:userId/activate — Activate user
328
281
  // -------------------------------------------------------------------------
329
- router.post('/:userId/lock', requirePermission(ADMIN_PERMISSIONS.USER_SUSPEND), requireUserOrganization(), async (ctx) => {
282
+ router.post('/:userId/activate', requirePermission(ADMIN_PERMISSIONS.USER_LIFECYCLE), requireUserOrganization(), async (ctx) => {
330
283
  try {
331
- await guardSuperAdmin(ctx.params.userId, 'lock');
332
- const body = lockUserSchema.parse(ctx.request.body);
333
- await userService.lockUser(ctx.params.userId, body.reason);
334
- ctx.status = 204;
335
- }
336
- catch (err) {
337
- handleError(ctx, err);
338
- }
339
- });
340
- // -------------------------------------------------------------------------
341
- // POST /:userId/unlock — Unlock user
342
- // -------------------------------------------------------------------------
343
- router.post('/:userId/unlock', requirePermission(ADMIN_PERMISSIONS.USER_SUSPEND), requireUserOrganization(), async (ctx) => {
344
- try {
345
- await userService.unlockUser(ctx.params.userId);
284
+ await userService.activateUser(ctx.params.userId);
346
285
  ctx.status = 204;
347
286
  }
348
287
  catch (err) {
@@ -398,48 +337,16 @@ export function createUserRouter() {
398
337
  ctx.body = { data: exportData };
399
338
  });
400
339
  // -------------------------------------------------------------------------
401
- // POST /:userId/purge — GDPR data purge (Article 17)
402
- //
403
- // Requires X-Confirm-Purge: true header for safety.
404
- // Irreversibly anonymizes user data and deletes related records.
405
- // Protected: super-admin user cannot be purged
340
+ // DELETE /:userId — Delete user
406
341
  // -------------------------------------------------------------------------
407
- router.post('/:userId/purge', requirePermission(ADMIN_PERMISSIONS.USER_ARCHIVE), requireUserOrganization(), async (ctx) => {
408
- // Require explicit confirmation via header OR request body
409
- const confirmHeader = ctx.get('X-Confirm-Purge');
410
- const confirmBody = ctx.request.body?.confirmPurge;
411
- if (confirmHeader !== 'true' && confirmBody !== true) {
412
- ctx.status = 400;
413
- ctx.body = {
414
- error: 'Purge requires confirmation',
415
- message: 'Set X-Confirm-Purge: true header or send { "confirmPurge": true } in body',
416
- };
417
- return;
418
- }
419
- const user = await userService.getUserById(ctx.params.userId);
420
- if (!user) {
421
- return ctx.throw(404, 'User not found');
422
- }
342
+ router.delete('/:userId', requirePermission(ADMIN_PERMISSIONS.USER_DELETE), requireUserOrganization(), async (ctx) => {
423
343
  try {
424
- // Guard: super-admin user cannot be purged
425
- await guardSuperAdmin(ctx.params.userId, 'delete');
426
- // Use the admin user's ID as the actor for the audit trail
427
- const actorId = ctx.state.adminUser?.id ?? 'system';
428
- const result = await purgeUserData(user, actorId);
429
- ctx.body = { data: result };
344
+ identifierSchema.parse(ctx.params);
345
+ await userService.deleteUser(ctx.params.orgId, ctx.params.userId, ctx.state.adminUser?.id);
346
+ ctx.status = 204;
430
347
  }
431
348
  catch (err) {
432
- if (err instanceof SuperAdminProtectionError) {
433
- ctx.status = 403;
434
- ctx.body = { error: 'The requested operation is not permitted' };
435
- return;
436
- }
437
- if (err instanceof Error && err.message.includes('super-admin')) {
438
- ctx.status = 403;
439
- ctx.body = { error: 'The requested operation is not permitted' };
440
- return;
441
- }
442
- throw err;
349
+ handleError(ctx, err);
443
350
  }
444
351
  });
445
352
  // -------------------------------------------------------------------------
@@ -455,12 +362,13 @@ export function createUserRouter() {
455
362
  ctx.body = result;
456
363
  });
457
364
  // -------------------------------------------------------------------------
458
- // POST /invite — Send invitation to a new or existing user
365
+ // POST /invite — Invite a new user
459
366
  //
460
367
  // Enhanced invitation with optional personal message, role/claim
461
368
  // pre-assignment, and inviter tracking. Creates the user if they
462
369
  // don't exist, generates an invitation token with pre-assignment
463
- // details, and sends the invitation email.
370
+ // details, and sends the invitation email. Existing organization email
371
+ // addresses are rejected instead of silently resending an invitation.
464
372
  // -------------------------------------------------------------------------
465
373
  router.post('/invite', requirePermission(ADMIN_PERMISSIONS.USER_INVITE), async (ctx) => {
466
374
  try {
@@ -475,25 +383,30 @@ export function createUserRouter() {
475
383
  }
476
384
  // Validate referenced applicationIds, roleIds, claimDefinitionIds exist
477
385
  if (body.roles?.length || body.claims?.length) {
478
- await validatePreAssignments(orgId, body.roles, body.claims);
386
+ const preAssignmentErrors = await validatePreAssignments(body.roles, body.claims);
387
+ if (preAssignmentErrors.length > 0) {
388
+ ctx.status = 400;
389
+ ctx.body = { error: 'User request is invalid' };
390
+ return;
391
+ }
479
392
  }
480
- // Find or create the user
481
- let user = await userService.getUserByEmail(orgId, body.email);
482
- let created = false;
483
- if (!user) {
484
- user = await userService.createUser({
485
- organizationId: orgId,
486
- email: body.email,
487
- givenName: body.givenName,
488
- familyName: body.familyName,
489
- });
490
- created = true;
393
+ const existingUser = await userService.getUserByEmail(orgId, body.email);
394
+ if (existingUser) {
395
+ ctx.status = 409;
396
+ ctx.body = { error: 'User already exists in this organization' };
397
+ return;
491
398
  }
492
- // Invalidate any previous pending invitation tokens for this user
493
- await invalidateUserTokens('invitation_tokens', user.id);
399
+ const user = await userService.createUser({
400
+ organizationId: orgId,
401
+ email: body.email,
402
+ givenName: body.givenName,
403
+ familyName: body.familyName,
404
+ });
494
405
  // Generate a new invitation token
495
406
  const { plaintext, hash } = generateToken();
496
- const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days
407
+ // Existing invitations keep their absolute expiry; this policy applies to the new token only.
408
+ const invitationTtl = await getSystemConfigNumber('invitation_ttl');
409
+ const expiresAt = new Date(Date.now() + invitationTtl * 1000);
497
410
  // Build inviter display name
498
411
  const inviterName = adminUser.givenName
499
412
  ? adminUser.familyName
@@ -524,13 +437,7 @@ export function createUserRouter() {
524
437
  email: user.email,
525
438
  givenName: user.givenName,
526
439
  familyName: user.familyName,
527
- }, {
528
- id: org.id,
529
- slug: org.slug,
530
- brandingLogoUrl: org.brandingLogoUrl,
531
- brandingPrimaryColor: org.brandingPrimaryColor,
532
- brandingCompanyName: org.brandingCompanyName,
533
- }, inviteUrl, body.locale ?? org.defaultLocale ?? 'en', emailOptions));
440
+ }, org, inviteUrl, body.locale ?? org.defaultLocale ?? 'en', emailOptions));
534
441
  // Audit log the invitation
535
442
  writeAuditLog({
536
443
  organizationId: orgId,
@@ -545,12 +452,12 @@ export function createUserRouter() {
545
452
  preAssignedClaims: body.claims?.length ?? 0,
546
453
  },
547
454
  });
548
- ctx.status = created ? 201 : 200;
455
+ ctx.status = 201;
549
456
  ctx.body = {
550
457
  data: {
551
458
  userId: user.id,
552
459
  email: user.email,
553
- created,
460
+ created: true,
554
461
  invitationSent: true,
555
462
  expiresAt: expiresAt.toISOString(),
556
463
  },
@@ -595,13 +502,7 @@ export function createUserRouter() {
595
502
  if (body.personalMessage)
596
503
  emailOptions.personalMessage = body.personalMessage;
597
504
  emailOptions.inviterName = inviterName;
598
- const result = await renderInvitationEmail(previewUser, {
599
- id: org.id,
600
- slug: org.slug,
601
- brandingLogoUrl: org.brandingLogoUrl,
602
- brandingPrimaryColor: org.brandingPrimaryColor,
603
- brandingCompanyName: org.brandingCompanyName,
604
- },
505
+ const result = await renderInvitationEmail(previewUser, org,
605
506
  // Absolute preview URL — same trusted issuerBaseUrl prefix as the real invite.
606
507
  `${config.issuerBaseUrl}/${org.slug}/auth/accept-invite/PREVIEW_TOKEN`, body.locale ?? org.defaultLocale ?? 'en', emailOptions);
607
508
  ctx.body = { data: result };
@@ -650,30 +551,30 @@ const invitePreviewSchema = z.object({
650
551
  /**
651
552
  * Validate that referenced roles and claims exist in the database.
652
553
  *
653
- * Checks that all applicationIds are valid within the org, all roleIds
554
+ * Checks that all deployment-global applicationIds exist, all roleIds
654
555
  * belong to their specified application, and all claimDefinitionIds
655
- * belong to their specified application. Throws a ZodError-like 400
656
- * on validation failure.
556
+ * belong to their specified application.
657
557
  *
658
- * @param orgId - Organization ID for scoping
659
558
  * @param roles - Array of role pre-assignments to validate
660
559
  * @param claims - Array of claim pre-assignments to validate
661
- * @throws Error with 400 status if any reference is invalid
560
+ * @returns Validation errors; an empty array means every reference is valid
662
561
  */
663
- async function validatePreAssignments(orgId, roles, claims) {
562
+ async function validatePreAssignments(roles, claims) {
664
563
  const pool = getPool();
665
564
  const errors = [];
666
565
  // Collect unique applicationIds from both roles and claims
667
566
  const appIds = new Set();
668
567
  roles?.forEach((r) => appIds.add(r.applicationId));
669
568
  claims?.forEach((c) => appIds.add(c.applicationId));
670
- // Verify all applications exist within the org
569
+ // Applications are deployment-global definitions and are not organization-scoped.
671
570
  if (appIds.size > 0) {
672
- const appResult = await pool.query(`SELECT id FROM applications WHERE id = ANY($1) AND organization_id = $2`, [Array.from(appIds), orgId]);
571
+ const appResult = await pool.query(`SELECT id FROM applications WHERE id = ANY($1::uuid[])`, [
572
+ Array.from(appIds),
573
+ ]);
673
574
  const foundIds = new Set(appResult.rows.map((r) => r.id));
674
575
  for (const appId of appIds) {
675
576
  if (!foundIds.has(appId)) {
676
- errors.push(`Application ${appId} not found in this organization`);
577
+ errors.push(`Application ${appId} not found`);
677
578
  }
678
579
  }
679
580
  }
@@ -689,17 +590,13 @@ async function validatePreAssignments(orgId, roles, claims) {
689
590
  // Verify all claim definitions exist within their application
690
591
  if (claims?.length) {
691
592
  for (const claim of claims) {
692
- const result = await pool.query(`SELECT id FROM claim_definitions WHERE id = $1 AND application_id = $2`, [claim.claimDefinitionId, claim.applicationId]);
593
+ const result = await pool.query(`SELECT id FROM custom_claim_definitions WHERE id = $1 AND application_id = $2`, [claim.claimDefinitionId, claim.applicationId]);
693
594
  if (result.rows.length === 0) {
694
595
  errors.push(`Claim definition ${claim.claimDefinitionId} not found in application ${claim.applicationId}`);
695
596
  }
696
597
  }
697
598
  }
698
- if (errors.length > 0) {
699
- const err = new Error(`Pre-assignment validation failed: ${errors.join('; ')}`);
700
- err.status = 400;
701
- throw err;
702
- }
599
+ return errors;
703
600
  }
704
601
  // ---------------------------------------------------------------------------
705
602
  // Standalone user router (non-org-scoped)
@@ -748,7 +645,7 @@ export function createStandaloneUserRouter() {
748
645
  }
749
646
  });
750
647
  // POST /:userId/deactivate — Deactivate user
751
- router.post('/:userId/deactivate', requirePermission(ADMIN_PERMISSIONS.USER_SUSPEND), async (ctx) => {
648
+ router.post('/:userId/deactivate', requirePermission(ADMIN_PERMISSIONS.USER_LIFECYCLE), async (ctx) => {
752
649
  try {
753
650
  await guardSuperAdmin(ctx.params.userId, 'deactivate');
754
651
  await userService.deactivateUser(ctx.params.userId);
@@ -758,64 +655,10 @@ export function createStandaloneUserRouter() {
758
655
  handleError(ctx, err);
759
656
  }
760
657
  });
761
- // POST /:userId/reactivate — Reactivate user (inactive → active)
762
- router.post('/:userId/reactivate', requirePermission(ADMIN_PERMISSIONS.USER_SUSPEND), async (ctx) => {
763
- try {
764
- await userService.reactivateUser(ctx.params.userId);
765
- ctx.status = 204;
766
- }
767
- catch (err) {
768
- handleError(ctx, err);
769
- }
770
- });
771
- // POST /:userId/activate — Alias for reactivate (SPA compatibility)
772
- router.post('/:userId/activate', requirePermission(ADMIN_PERMISSIONS.USER_SUSPEND), async (ctx) => {
773
- try {
774
- await userService.reactivateUser(ctx.params.userId);
775
- ctx.status = 204;
776
- }
777
- catch (err) {
778
- handleError(ctx, err);
779
- }
780
- });
781
- // POST /:userId/suspend — Suspend user
782
- router.post('/:userId/suspend', requirePermission(ADMIN_PERMISSIONS.USER_SUSPEND), async (ctx) => {
783
- try {
784
- await guardSuperAdmin(ctx.params.userId, 'suspend');
785
- const body = suspendUserSchema.parse(ctx.request.body ?? {});
786
- await userService.suspendUser(ctx.params.userId, body.reason);
787
- ctx.status = 204;
788
- }
789
- catch (err) {
790
- handleError(ctx, err);
791
- }
792
- });
793
- // POST /:userId/unsuspend — Unsuspend user
794
- router.post('/:userId/unsuspend', requirePermission(ADMIN_PERMISSIONS.USER_SUSPEND), async (ctx) => {
795
- try {
796
- await userService.unsuspendUser(ctx.params.userId);
797
- ctx.status = 204;
798
- }
799
- catch (err) {
800
- handleError(ctx, err);
801
- }
802
- });
803
- // POST /:userId/lock — Lock user
804
- router.post('/:userId/lock', requirePermission(ADMIN_PERMISSIONS.USER_SUSPEND), async (ctx) => {
805
- try {
806
- await guardSuperAdmin(ctx.params.userId, 'lock');
807
- const body = lockUserSchema.parse(ctx.request.body);
808
- await userService.lockUser(ctx.params.userId, body.reason);
809
- ctx.status = 204;
810
- }
811
- catch (err) {
812
- handleError(ctx, err);
813
- }
814
- });
815
- // POST /:userId/unlock — Unlock user
816
- router.post('/:userId/unlock', requirePermission(ADMIN_PERMISSIONS.USER_SUSPEND), async (ctx) => {
658
+ // POST /:userId/activate — Activate user (inactive → active)
659
+ router.post('/:userId/activate', requirePermission(ADMIN_PERMISSIONS.USER_LIFECYCLE), async (ctx) => {
817
660
  try {
818
- await userService.unlockUser(ctx.params.userId);
661
+ await userService.activateUser(ctx.params.userId);
819
662
  ctx.status = 204;
820
663
  }
821
664
  catch (err) {