@portaidentity/server 1.7.3 → 1.9.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 (494) hide show
  1. package/CHANGELOG.md +28 -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/auth/token-repository.d.ts +9 -3
  44. package/dist/auth/token-repository.d.ts.map +1 -1
  45. package/dist/auth/token-repository.js +17 -8
  46. package/dist/auth/token-repository.js.map +1 -1
  47. package/dist/cli/commands/init.d.ts +1 -1
  48. package/dist/cli/commands/init.d.ts.map +1 -1
  49. package/dist/cli/commands/init.js +12 -12
  50. package/dist/cli/commands/init.js.map +1 -1
  51. package/dist/cli/error-handler.d.ts +3 -2
  52. package/dist/cli/error-handler.d.ts.map +1 -1
  53. package/dist/cli/error-handler.js +9 -2
  54. package/dist/cli/error-handler.js.map +1 -1
  55. package/dist/cli/index.d.ts +1 -1
  56. package/dist/cli/index.d.ts.map +1 -1
  57. package/dist/cli/index.js +1 -1
  58. package/dist/cli/index.js.map +1 -1
  59. package/dist/clients/index.d.ts +1 -1
  60. package/dist/clients/index.js +1 -1
  61. package/dist/clients/repository.d.ts +24 -0
  62. package/dist/clients/repository.d.ts.map +1 -1
  63. package/dist/clients/repository.js +39 -0
  64. package/dist/clients/repository.js.map +1 -1
  65. package/dist/clients/secret-repository.d.ts +35 -8
  66. package/dist/clients/secret-repository.d.ts.map +1 -1
  67. package/dist/clients/secret-repository.js +69 -16
  68. package/dist/clients/secret-repository.js.map +1 -1
  69. package/dist/clients/secret-service.d.ts +5 -9
  70. package/dist/clients/secret-service.d.ts.map +1 -1
  71. package/dist/clients/secret-service.js +13 -22
  72. package/dist/clients/secret-service.js.map +1 -1
  73. package/dist/clients/service.d.ts +10 -13
  74. package/dist/clients/service.d.ts.map +1 -1
  75. package/dist/clients/service.js +121 -42
  76. package/dist/clients/service.js.map +1 -1
  77. package/dist/clients/types.d.ts +14 -1
  78. package/dist/clients/types.d.ts.map +1 -1
  79. package/dist/clients/types.js +1 -0
  80. package/dist/clients/types.js.map +1 -1
  81. package/dist/clients/validators.d.ts +83 -1
  82. package/dist/clients/validators.d.ts.map +1 -1
  83. package/dist/clients/validators.js +110 -0
  84. package/dist/clients/validators.js.map +1 -1
  85. package/dist/config/index.d.ts +1 -0
  86. package/dist/config/index.d.ts.map +1 -1
  87. package/dist/config/index.js +1 -0
  88. package/dist/config/index.js.map +1 -1
  89. package/dist/config/schema.d.ts +1 -0
  90. package/dist/config/schema.d.ts.map +1 -1
  91. package/dist/config/schema.js +23 -1
  92. package/dist/config/schema.js.map +1 -1
  93. package/dist/custom-claims/repository.d.ts +14 -9
  94. package/dist/custom-claims/repository.d.ts.map +1 -1
  95. package/dist/custom-claims/repository.js +38 -11
  96. package/dist/custom-claims/repository.js.map +1 -1
  97. package/dist/custom-claims/service.d.ts +11 -6
  98. package/dist/custom-claims/service.d.ts.map +1 -1
  99. package/dist/custom-claims/service.js +43 -20
  100. package/dist/custom-claims/service.js.map +1 -1
  101. package/dist/index.js +8 -2
  102. package/dist/index.js.map +1 -1
  103. package/dist/lib/admin-permissions.d.ts +12 -7
  104. package/dist/lib/admin-permissions.d.ts.map +1 -1
  105. package/dist/lib/admin-permissions.js +26 -16
  106. package/dist/lib/admin-permissions.js.map +1 -1
  107. package/dist/lib/audit-log.d.ts +2 -2
  108. package/dist/lib/audit-log.d.ts.map +1 -1
  109. package/dist/lib/audit-log.js +1 -1
  110. package/dist/lib/audit-log.js.map +1 -1
  111. package/dist/lib/authority-revocation.d.ts +18 -0
  112. package/dist/lib/authority-revocation.d.ts.map +1 -0
  113. package/dist/lib/authority-revocation.js +37 -0
  114. package/dist/lib/authority-revocation.js.map +1 -0
  115. package/dist/lib/branding-assets.d.ts +35 -5
  116. package/dist/lib/branding-assets.d.ts.map +1 -1
  117. package/dist/lib/branding-assets.js +33 -26
  118. package/dist/lib/branding-assets.js.map +1 -1
  119. package/dist/lib/bulk-operations.d.ts +1 -1
  120. package/dist/lib/bulk-operations.d.ts.map +1 -1
  121. package/dist/lib/bulk-operations.js +1 -13
  122. package/dist/lib/bulk-operations.js.map +1 -1
  123. package/dist/lib/cursor.d.ts +2 -0
  124. package/dist/lib/cursor.d.ts.map +1 -1
  125. package/dist/lib/cursor.js +2 -7
  126. package/dist/lib/cursor.js.map +1 -1
  127. package/dist/lib/database.d.ts.map +1 -1
  128. package/dist/lib/database.js +5 -0
  129. package/dist/lib/database.js.map +1 -1
  130. package/dist/lib/deletion-cleanup.d.ts +43 -0
  131. package/dist/lib/deletion-cleanup.d.ts.map +1 -0
  132. package/dist/lib/deletion-cleanup.js +207 -0
  133. package/dist/lib/deletion-cleanup.js.map +1 -0
  134. package/dist/lib/image-validator.d.ts +3 -1
  135. package/dist/lib/image-validator.d.ts.map +1 -1
  136. package/dist/lib/image-validator.js +32 -19
  137. package/dist/lib/image-validator.js.map +1 -1
  138. package/dist/lib/log-format.d.ts +20 -0
  139. package/dist/lib/log-format.d.ts.map +1 -0
  140. package/dist/lib/log-format.js +22 -0
  141. package/dist/lib/log-format.js.map +1 -0
  142. package/dist/lib/logger.d.ts +3 -1
  143. package/dist/lib/logger.d.ts.map +1 -1
  144. package/dist/lib/logger.js +21 -4
  145. package/dist/lib/logger.js.map +1 -1
  146. package/dist/lib/redis.d.ts.map +1 -1
  147. package/dist/lib/redis.js +3 -0
  148. package/dist/lib/redis.js.map +1 -1
  149. package/dist/lib/session-tracking.d.ts +73 -11
  150. package/dist/lib/session-tracking.d.ts.map +1 -1
  151. package/dist/lib/session-tracking.js +76 -32
  152. package/dist/lib/session-tracking.js.map +1 -1
  153. package/dist/lib/signing-key-crypto.d.ts.map +1 -1
  154. package/dist/lib/signing-key-crypto.js +4 -5
  155. package/dist/lib/signing-key-crypto.js.map +1 -1
  156. package/dist/lib/signing-keys.d.ts +3 -4
  157. package/dist/lib/signing-keys.d.ts.map +1 -1
  158. package/dist/lib/signing-keys.js +66 -41
  159. package/dist/lib/signing-keys.js.map +1 -1
  160. package/dist/lib/super-admin-protection.d.ts +5 -5
  161. package/dist/lib/super-admin-protection.d.ts.map +1 -1
  162. package/dist/lib/super-admin-protection.js +6 -9
  163. package/dist/lib/super-admin-protection.js.map +1 -1
  164. package/dist/lib/system-config-catalog.d.ts +268 -0
  165. package/dist/lib/system-config-catalog.d.ts.map +1 -0
  166. package/dist/lib/system-config-catalog.js +90 -0
  167. package/dist/lib/system-config-catalog.js.map +1 -0
  168. package/dist/lib/system-config.d.ts +30 -33
  169. package/dist/lib/system-config.d.ts.map +1 -1
  170. package/dist/lib/system-config.js +78 -93
  171. package/dist/lib/system-config.js.map +1 -1
  172. package/dist/lib/with-timeout.d.ts +32 -0
  173. package/dist/lib/with-timeout.d.ts.map +1 -0
  174. package/dist/lib/with-timeout.js +45 -0
  175. package/dist/lib/with-timeout.js.map +1 -0
  176. package/dist/middleware/admin-auth.d.ts +6 -5
  177. package/dist/middleware/admin-auth.d.ts.map +1 -1
  178. package/dist/middleware/admin-auth.js +30 -20
  179. package/dist/middleware/admin-auth.js.map +1 -1
  180. package/dist/middleware/admin-cors.d.ts +3 -3
  181. package/dist/middleware/admin-cors.js +3 -3
  182. package/dist/middleware/admin-mutation-audit.d.ts +3 -2
  183. package/dist/middleware/admin-mutation-audit.d.ts.map +1 -1
  184. package/dist/middleware/admin-mutation-audit.js +12 -5
  185. package/dist/middleware/admin-mutation-audit.js.map +1 -1
  186. package/dist/middleware/admin-rate-limiter.d.ts +7 -8
  187. package/dist/middleware/admin-rate-limiter.d.ts.map +1 -1
  188. package/dist/middleware/admin-rate-limiter.js +7 -8
  189. package/dist/middleware/admin-rate-limiter.js.map +1 -1
  190. package/dist/middleware/client-secret-hash.d.ts +10 -29
  191. package/dist/middleware/client-secret-hash.d.ts.map +1 -1
  192. package/dist/middleware/client-secret-hash.js +161 -59
  193. package/dist/middleware/client-secret-hash.js.map +1 -1
  194. package/dist/middleware/health.d.ts +17 -0
  195. package/dist/middleware/health.d.ts.map +1 -1
  196. package/dist/middleware/health.js +25 -21
  197. package/dist/middleware/health.js.map +1 -1
  198. package/dist/middleware/ready.d.ts.map +1 -1
  199. package/dist/middleware/ready.js +1 -12
  200. package/dist/middleware/ready.js.map +1 -1
  201. package/dist/middleware/require-existing-organization.d.ts +18 -0
  202. package/dist/middleware/require-existing-organization.d.ts.map +1 -0
  203. package/dist/middleware/require-existing-organization.js +38 -0
  204. package/dist/middleware/require-existing-organization.js.map +1 -0
  205. package/dist/middleware/require-permission.d.ts.map +1 -1
  206. package/dist/middleware/require-permission.js +35 -1
  207. package/dist/middleware/require-permission.js.map +1 -1
  208. package/dist/middleware/require-user-organization.d.ts.map +1 -1
  209. package/dist/middleware/require-user-organization.js +4 -2
  210. package/dist/middleware/require-user-organization.js.map +1 -1
  211. package/dist/middleware/security-headers.d.ts +18 -0
  212. package/dist/middleware/security-headers.d.ts.map +1 -1
  213. package/dist/middleware/security-headers.js +32 -1
  214. package/dist/middleware/security-headers.js.map +1 -1
  215. package/dist/middleware/tenant-resolver.d.ts +1 -2
  216. package/dist/middleware/tenant-resolver.d.ts.map +1 -1
  217. package/dist/middleware/tenant-resolver.js +1 -5
  218. package/dist/middleware/tenant-resolver.js.map +1 -1
  219. package/dist/middleware/token-rate-limiter.d.ts +37 -16
  220. package/dist/middleware/token-rate-limiter.d.ts.map +1 -1
  221. package/dist/middleware/token-rate-limiter.js +126 -54
  222. package/dist/middleware/token-rate-limiter.js.map +1 -1
  223. package/dist/oidc/account-finder.d.ts +5 -5
  224. package/dist/oidc/account-finder.d.ts.map +1 -1
  225. package/dist/oidc/account-finder.js +31 -23
  226. package/dist/oidc/account-finder.js.map +1 -1
  227. package/dist/oidc/adapter-factory.d.ts.map +1 -1
  228. package/dist/oidc/adapter-factory.js +83 -3
  229. package/dist/oidc/adapter-factory.js.map +1 -1
  230. package/dist/oidc/configuration.d.ts +3 -8
  231. package/dist/oidc/configuration.d.ts.map +1 -1
  232. package/dist/oidc/configuration.js +67 -50
  233. package/dist/oidc/configuration.js.map +1 -1
  234. package/dist/oidc/provider.d.ts.map +1 -1
  235. package/dist/oidc/provider.js +7 -0
  236. package/dist/oidc/provider.js.map +1 -1
  237. package/dist/oidc/redis-adapter.d.ts +2 -3
  238. package/dist/oidc/redis-adapter.d.ts.map +1 -1
  239. package/dist/oidc/redis-adapter.js +54 -46
  240. package/dist/oidc/redis-adapter.js.map +1 -1
  241. package/dist/organizations/branding-url.d.ts +17 -0
  242. package/dist/organizations/branding-url.d.ts.map +1 -0
  243. package/dist/organizations/branding-url.js +38 -0
  244. package/dist/organizations/branding-url.js.map +1 -0
  245. package/dist/organizations/index.d.ts +2 -2
  246. package/dist/organizations/index.d.ts.map +1 -1
  247. package/dist/organizations/index.js +1 -1
  248. package/dist/organizations/index.js.map +1 -1
  249. package/dist/organizations/repository.d.ts +22 -32
  250. package/dist/organizations/repository.d.ts.map +1 -1
  251. package/dist/organizations/repository.js +64 -45
  252. package/dist/organizations/repository.js.map +1 -1
  253. package/dist/organizations/service.d.ts +14 -46
  254. package/dist/organizations/service.d.ts.map +1 -1
  255. package/dist/organizations/service.js +60 -112
  256. package/dist/organizations/service.js.map +1 -1
  257. package/dist/organizations/types.d.ts +1 -15
  258. package/dist/organizations/types.d.ts.map +1 -1
  259. package/dist/organizations/validators.d.ts +41 -0
  260. package/dist/organizations/validators.d.ts.map +1 -0
  261. package/dist/organizations/validators.js +48 -0
  262. package/dist/organizations/validators.js.map +1 -0
  263. package/dist/portability/apply.d.ts +15 -0
  264. package/dist/portability/apply.d.ts.map +1 -0
  265. package/dist/portability/apply.js +274 -0
  266. package/dist/portability/apply.js.map +1 -0
  267. package/dist/portability/authorization.d.ts +30 -0
  268. package/dist/portability/authorization.d.ts.map +1 -0
  269. package/dist/portability/authorization.js +130 -0
  270. package/dist/portability/authorization.js.map +1 -0
  271. package/dist/portability/cleanup.d.ts +29 -0
  272. package/dist/portability/cleanup.d.ts.map +1 -0
  273. package/dist/portability/cleanup.js +167 -0
  274. package/dist/portability/cleanup.js.map +1 -0
  275. package/dist/portability/export.d.ts +18 -0
  276. package/dist/portability/export.d.ts.map +1 -0
  277. package/dist/portability/export.js +125 -0
  278. package/dist/portability/export.js.map +1 -0
  279. package/dist/portability/import-repository.d.ts +204 -0
  280. package/dist/portability/import-repository.d.ts.map +1 -0
  281. package/dist/portability/import-repository.js +348 -0
  282. package/dist/portability/import-repository.js.map +1 -0
  283. package/dist/portability/import-user-client-writers.d.ts +40 -0
  284. package/dist/portability/import-user-client-writers.d.ts.map +1 -0
  285. package/dist/portability/import-user-client-writers.js +171 -0
  286. package/dist/portability/import-user-client-writers.js.map +1 -0
  287. package/dist/portability/index.d.ts +8 -0
  288. package/dist/portability/index.d.ts.map +1 -0
  289. package/dist/portability/index.js +8 -0
  290. package/dist/portability/index.js.map +1 -0
  291. package/dist/portability/plan-support.d.ts +137 -0
  292. package/dist/portability/plan-support.d.ts.map +1 -0
  293. package/dist/portability/plan-support.js +337 -0
  294. package/dist/portability/plan-support.js.map +1 -0
  295. package/dist/portability/plan.d.ts +34 -0
  296. package/dist/portability/plan.d.ts.map +1 -0
  297. package/dist/portability/plan.js +450 -0
  298. package/dist/portability/plan.js.map +1 -0
  299. package/dist/portability/repository.d.ts +95 -0
  300. package/dist/portability/repository.d.ts.map +1 -0
  301. package/dist/portability/repository.js +390 -0
  302. package/dist/portability/repository.js.map +1 -0
  303. package/dist/portability/schema.d.ts +591 -0
  304. package/dist/portability/schema.d.ts.map +1 -0
  305. package/dist/portability/schema.js +459 -0
  306. package/dist/portability/schema.js.map +1 -0
  307. package/dist/portability/types.d.ts +382 -0
  308. package/dist/portability/types.d.ts.map +1 -0
  309. package/dist/portability/types.js +19 -0
  310. package/dist/portability/types.js.map +1 -0
  311. package/dist/rbac/errors.d.ts +4 -0
  312. package/dist/rbac/errors.d.ts.map +1 -1
  313. package/dist/rbac/errors.js +7 -0
  314. package/dist/rbac/errors.js.map +1 -1
  315. package/dist/rbac/index.d.ts +3 -3
  316. package/dist/rbac/index.d.ts.map +1 -1
  317. package/dist/rbac/index.js +3 -3
  318. package/dist/rbac/index.js.map +1 -1
  319. package/dist/rbac/mapping-repository.d.ts +84 -9
  320. package/dist/rbac/mapping-repository.d.ts.map +1 -1
  321. package/dist/rbac/mapping-repository.js +222 -55
  322. package/dist/rbac/mapping-repository.js.map +1 -1
  323. package/dist/rbac/permission-repository.d.ts +36 -19
  324. package/dist/rbac/permission-repository.d.ts.map +1 -1
  325. package/dist/rbac/permission-repository.js +93 -35
  326. package/dist/rbac/permission-repository.js.map +1 -1
  327. package/dist/rbac/permission-service.d.ts +18 -19
  328. package/dist/rbac/permission-service.d.ts.map +1 -1
  329. package/dist/rbac/permission-service.js +100 -57
  330. package/dist/rbac/permission-service.js.map +1 -1
  331. package/dist/rbac/role-repository.d.ts +29 -24
  332. package/dist/rbac/role-repository.d.ts.map +1 -1
  333. package/dist/rbac/role-repository.js +66 -43
  334. package/dist/rbac/role-repository.js.map +1 -1
  335. package/dist/rbac/role-service.d.ts +35 -32
  336. package/dist/rbac/role-service.d.ts.map +1 -1
  337. package/dist/rbac/role-service.js +210 -96
  338. package/dist/rbac/role-service.js.map +1 -1
  339. package/dist/rbac/slugs.d.ts +16 -27
  340. package/dist/rbac/slugs.d.ts.map +1 -1
  341. package/dist/rbac/slugs.js +36 -59
  342. package/dist/rbac/slugs.js.map +1 -1
  343. package/dist/rbac/types.d.ts +2 -4
  344. package/dist/rbac/types.d.ts.map +1 -1
  345. package/dist/rbac/types.js.map +1 -1
  346. package/dist/rbac/user-role-service.d.ts +23 -24
  347. package/dist/rbac/user-role-service.d.ts.map +1 -1
  348. package/dist/rbac/user-role-service.js +117 -61
  349. package/dist/rbac/user-role-service.js.map +1 -1
  350. package/dist/routes/applications.d.ts +4 -2
  351. package/dist/routes/applications.d.ts.map +1 -1
  352. package/dist/routes/applications.js +57 -19
  353. package/dist/routes/applications.js.map +1 -1
  354. package/dist/routes/audit.js +1 -1
  355. package/dist/routes/audit.js.map +1 -1
  356. package/dist/routes/branding.d.ts +3 -3
  357. package/dist/routes/branding.d.ts.map +1 -1
  358. package/dist/routes/branding.js +44 -16
  359. package/dist/routes/branding.js.map +1 -1
  360. package/dist/routes/bulk.d.ts +0 -5
  361. package/dist/routes/bulk.d.ts.map +1 -1
  362. package/dist/routes/bulk.js +3 -5
  363. package/dist/routes/bulk.js.map +1 -1
  364. package/dist/routes/clients.d.ts +4 -4
  365. package/dist/routes/clients.d.ts.map +1 -1
  366. package/dist/routes/clients.js +113 -52
  367. package/dist/routes/clients.js.map +1 -1
  368. package/dist/routes/config.d.ts +17 -12
  369. package/dist/routes/config.d.ts.map +1 -1
  370. package/dist/routes/config.js +165 -64
  371. package/dist/routes/config.js.map +1 -1
  372. package/dist/routes/custom-claims.d.ts.map +1 -1
  373. package/dist/routes/custom-claims.js +8 -2
  374. package/dist/routes/custom-claims.js.map +1 -1
  375. package/dist/routes/exports.d.ts +2 -1
  376. package/dist/routes/exports.d.ts.map +1 -1
  377. package/dist/routes/exports.js +57 -1
  378. package/dist/routes/exports.js.map +1 -1
  379. package/dist/routes/imports.d.ts +2 -2
  380. package/dist/routes/imports.d.ts.map +1 -1
  381. package/dist/routes/imports.js +95 -40
  382. package/dist/routes/imports.js.map +1 -1
  383. package/dist/routes/interactions.d.ts.map +1 -1
  384. package/dist/routes/interactions.js +113 -87
  385. package/dist/routes/interactions.js.map +1 -1
  386. package/dist/routes/invitation.d.ts.map +1 -1
  387. package/dist/routes/invitation.js +28 -27
  388. package/dist/routes/invitation.js.map +1 -1
  389. package/dist/routes/keys.d.ts.map +1 -1
  390. package/dist/routes/keys.js +26 -13
  391. package/dist/routes/keys.js.map +1 -1
  392. package/dist/routes/magic-link.d.ts.map +1 -1
  393. package/dist/routes/magic-link.js +5 -17
  394. package/dist/routes/magic-link.js.map +1 -1
  395. package/dist/routes/organizations.d.ts +1 -2
  396. package/dist/routes/organizations.d.ts.map +1 -1
  397. package/dist/routes/organizations.js +43 -104
  398. package/dist/routes/organizations.js.map +1 -1
  399. package/dist/routes/password-reset.d.ts.map +1 -1
  400. package/dist/routes/password-reset.js +13 -35
  401. package/dist/routes/password-reset.js.map +1 -1
  402. package/dist/routes/permissions.d.ts +1 -1
  403. package/dist/routes/permissions.d.ts.map +1 -1
  404. package/dist/routes/permissions.js +58 -17
  405. package/dist/routes/permissions.js.map +1 -1
  406. package/dist/routes/public-branding.d.ts +16 -0
  407. package/dist/routes/public-branding.d.ts.map +1 -0
  408. package/dist/routes/public-branding.js +52 -0
  409. package/dist/routes/public-branding.js.map +1 -0
  410. package/dist/routes/roles.d.ts +1 -1
  411. package/dist/routes/roles.d.ts.map +1 -1
  412. package/dist/routes/roles.js +63 -21
  413. package/dist/routes/roles.js.map +1 -1
  414. package/dist/routes/sessions.d.ts.map +1 -1
  415. package/dist/routes/sessions.js +23 -12
  416. package/dist/routes/sessions.js.map +1 -1
  417. package/dist/routes/two-factor.d.ts.map +1 -1
  418. package/dist/routes/two-factor.js +91 -38
  419. package/dist/routes/two-factor.js.map +1 -1
  420. package/dist/routes/user-roles.d.ts.map +1 -1
  421. package/dist/routes/user-roles.js +56 -9
  422. package/dist/routes/user-roles.js.map +1 -1
  423. package/dist/routes/users.d.ts +4 -8
  424. package/dist/routes/users.d.ts.map +1 -1
  425. package/dist/routes/users.js +108 -265
  426. package/dist/routes/users.js.map +1 -1
  427. package/dist/server.d.ts.map +1 -1
  428. package/dist/server.js +53 -37
  429. package/dist/server.js.map +1 -1
  430. package/dist/two-factor/errors.d.ts +9 -0
  431. package/dist/two-factor/errors.d.ts.map +1 -1
  432. package/dist/two-factor/errors.js +12 -0
  433. package/dist/two-factor/errors.js.map +1 -1
  434. package/dist/two-factor/index.d.ts +5 -4
  435. package/dist/two-factor/index.d.ts.map +1 -1
  436. package/dist/two-factor/index.js +4 -4
  437. package/dist/two-factor/index.js.map +1 -1
  438. package/dist/two-factor/repository.d.ts +17 -6
  439. package/dist/two-factor/repository.d.ts.map +1 -1
  440. package/dist/two-factor/repository.js +37 -9
  441. package/dist/two-factor/repository.js.map +1 -1
  442. package/dist/two-factor/service.d.ts.map +1 -1
  443. package/dist/two-factor/service.js +26 -19
  444. package/dist/two-factor/service.js.map +1 -1
  445. package/dist/two-factor/totp.d.ts +13 -2
  446. package/dist/two-factor/totp.d.ts.map +1 -1
  447. package/dist/two-factor/totp.js +20 -7
  448. package/dist/two-factor/totp.js.map +1 -1
  449. package/dist/two-factor/types.d.ts +3 -0
  450. package/dist/two-factor/types.d.ts.map +1 -1
  451. package/dist/two-factor/types.js +5 -0
  452. package/dist/two-factor/types.js.map +1 -1
  453. package/dist/users/gdpr.d.ts +2 -40
  454. package/dist/users/gdpr.d.ts.map +1 -1
  455. package/dist/users/gdpr.js +3 -110
  456. package/dist/users/gdpr.js.map +1 -1
  457. package/dist/users/index.d.ts +5 -4
  458. package/dist/users/index.d.ts.map +1 -1
  459. package/dist/users/index.js +5 -4
  460. package/dist/users/index.js.map +1 -1
  461. package/dist/users/repository.d.ts +49 -3
  462. package/dist/users/repository.d.ts.map +1 -1
  463. package/dist/users/repository.js +169 -18
  464. package/dist/users/repository.js.map +1 -1
  465. package/dist/users/service.d.ts +19 -51
  466. package/dist/users/service.d.ts.map +1 -1
  467. package/dist/users/service.js +72 -122
  468. package/dist/users/service.js.map +1 -1
  469. package/dist/users/types.d.ts +4 -5
  470. package/dist/users/types.d.ts.map +1 -1
  471. package/dist/users/types.js.map +1 -1
  472. package/dist/users/validators.d.ts +28 -0
  473. package/dist/users/validators.d.ts.map +1 -0
  474. package/dist/users/validators.js +25 -0
  475. package/dist/users/validators.js.map +1 -0
  476. package/locales/default/en/errors.json +2 -1
  477. package/migrations/024_application_client_correction.sql +39 -0
  478. package/migrations/025_record_deletion_lifecycle.sql +26 -0
  479. package/migrations/026_simplify_user_lifecycle.sql +33 -0
  480. package/migrations/027_branding_asset_size_limits.sql +19 -0
  481. package/migrations/028_totp_replay_protection.sql +17 -0
  482. package/migrations/029_public_session_identifiers.sql +16 -0
  483. package/migrations/030_global_configuration_catalog.sql +39 -0
  484. package/migrations/031_monotonic_updated_at.sql +28 -0
  485. package/migrations/032_client_require_consent.sql +15 -0
  486. package/package.json +2 -2
  487. package/dist/lib/data-import-plan.d.ts +0 -50
  488. package/dist/lib/data-import-plan.d.ts.map +0 -1
  489. package/dist/lib/data-import-plan.js +0 -235
  490. package/dist/lib/data-import-plan.js.map +0 -1
  491. package/dist/lib/data-import.d.ts +0 -276
  492. package/dist/lib/data-import.d.ts.map +0 -1
  493. package/dist/lib/data-import.js +0 -1694
  494. package/dist/lib/data-import.js.map +0 -1
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Data import API routes.
3
3
  *
4
- * Provides admin endpoint for importing configuration from a JSON manifest.
5
- * Supports three modes: merge (skip existing), overwrite, and dry-run.
4
+ * Provides the Admin endpoint for previewing or atomically applying a strict portability manifest.
5
+ * Supports dry-run, keep-existing, and update-existing behavior.
6
6
  *
7
7
  * Route structure:
8
8
  * POST /api/admin/import — Import configuration manifest
@@ -10,21 +10,46 @@
10
10
  * @see 07-import-export-invitation.md
11
11
  */
12
12
  import Router from '@koa/router';
13
- import { z } from 'zod';
13
+ import bodyParser from 'koa-bodyparser';
14
14
  import { requireAdminAuth } from '../middleware/admin-auth.js';
15
15
  import { requirePermission } from '../middleware/require-permission.js';
16
16
  import { ADMIN_PERMISSIONS } from '../lib/admin-permissions.js';
17
- import { importData, importManifestSchema, ImportOperationError } from '../lib/data-import.js';
18
- // ---------------------------------------------------------------------------
19
- // Validation
20
- // ---------------------------------------------------------------------------
21
- const importRequestSchema = z
22
- .object({
23
- mode: z.enum(['merge', 'overwrite', 'dry-run']).default('dry-run'),
24
- organizationId: z.string().uuid().optional(),
25
- manifest: importManifestSchema,
26
- })
27
- .strict();
17
+ import { logger } from '../lib/logger.js';
18
+ import { applyPortabilityManifest } from '../portability/apply.js';
19
+ import { importManifestRequestSchema, PortabilityError, requirePortabilityAuthorization, } from '../portability/index.js';
20
+ import { buildPortabilityPlan } from '../portability/plan.js';
21
+ /** Route-owned JSON parser used only after authentication and the base import permission. */
22
+ const importManifestBodyParser = bodyParser({
23
+ enableTypes: ['json'],
24
+ jsonLimit: '64mb',
25
+ });
26
+ /** Return the public status carried by a body-parser error, when present. */
27
+ function bodyParserStatus(error) {
28
+ if (typeof error !== 'object' || error === null)
29
+ return undefined;
30
+ const status = Reflect.get(error, 'status');
31
+ return typeof status === 'number' ? status : undefined;
32
+ }
33
+ /** Parse one protected manifest body and map parser failures to fixed safe responses. */
34
+ async function parseImportManifestBody(ctx, next) {
35
+ try {
36
+ await importManifestBodyParser(ctx, next);
37
+ }
38
+ catch (error) {
39
+ const status = bodyParserStatus(error);
40
+ if (status === 413) {
41
+ ctx.status = 413;
42
+ ctx.body = { error: 'Import manifest is too large', code: 'import_manifest_too_large' };
43
+ return;
44
+ }
45
+ if (status === 400) {
46
+ ctx.status = 400;
47
+ ctx.body = { error: 'Invalid import manifest', code: 'import_manifest_invalid' };
48
+ return;
49
+ }
50
+ throw error;
51
+ }
52
+ }
28
53
  // ---------------------------------------------------------------------------
29
54
  // Router factory
30
55
  // ---------------------------------------------------------------------------
@@ -33,40 +58,70 @@ const importRequestSchema = z
33
58
  * @returns Configured Koa router
34
59
  */
35
60
  export function createImportRouter() {
36
- const router = new Router({ prefix: '/api/admin/import' });
37
- router.use(requireAdminAuth());
61
+ const router = new Router({ sensitive: false, strict: false });
38
62
  // -------------------------------------------------------------------------
39
63
  // POST / — Import configuration
40
64
  // -------------------------------------------------------------------------
41
- router.post('/', requirePermission(ADMIN_PERMISSIONS.IMPORT_WRITE), async (ctx) => {
42
- try {
43
- const body = importRequestSchema.parse(ctx.request.body);
44
- const actorId = ctx.state.adminUser?.id;
45
- const result = await importData(body.manifest, body.mode, actorId, body.organizationId);
46
- ctx.body = result;
65
+ router.post('/api/admin/import', async (ctx, next) => {
66
+ ctx.set('Cache-Control', 'no-store');
67
+ await next();
68
+ }, requireAdminAuth(), requirePermission(ADMIN_PERMISSIONS.IMPORT_WRITE), parseImportManifestBody, async (ctx) => {
69
+ const parsed = importManifestRequestSchema.safeParse(ctx.request.body);
70
+ if (!parsed.success) {
71
+ ctx.status = 400;
72
+ ctx.body = { error: 'Invalid import manifest', code: 'import_manifest_invalid' };
73
+ return;
47
74
  }
48
- catch (err) {
49
- if (err instanceof z.ZodError) {
50
- ctx.status = 400;
51
- ctx.body = { error: 'Import manifest is invalid', code: 'import_manifest_invalid' };
52
- return;
53
- }
54
- if (err instanceof ImportOperationError) {
55
- ctx.status = err.status;
56
- ctx.body = {
57
- error: 'Import request could not be completed',
58
- code: err.code,
59
- correlationId: err.correlationId,
60
- };
75
+ await requirePortabilityAuthorization('import', () => parsed.data.manifest.categories)(ctx, async () => {
76
+ await handleManifestImport(ctx, parsed.data);
77
+ });
78
+ });
79
+ return router;
80
+ }
81
+ /** Preview or apply a validated manifest and expose only fixed safe failures. */
82
+ async function handleManifestImport(ctx, request) {
83
+ try {
84
+ if (request.mode === 'dry-run') {
85
+ const result = await buildPortabilityPlan(request.manifest, request.mode);
86
+ if (result.errors.length > 0) {
87
+ ctx.status = 409;
88
+ ctx.body = { error: 'Import plan rejected', code: 'import_plan_rejected', result };
61
89
  return;
62
90
  }
63
- ctx.status = 503;
91
+ ctx.body = result;
92
+ return;
93
+ }
94
+ ctx.body = await applyPortabilityManifest(request.manifest, request.mode, portabilityActor(ctx));
95
+ }
96
+ catch (error) {
97
+ if (error instanceof PortabilityError && error.code === 'import_plan_rejected') {
98
+ ctx.status = 409;
64
99
  ctx.body = {
65
- error: 'Import request could not be completed',
66
- code: 'import_execution_failed',
100
+ error: 'Import plan rejected',
101
+ code: error.code,
102
+ result: error.result,
67
103
  };
104
+ return;
68
105
  }
69
- });
70
- return router;
106
+ const requestId = ctx.state.requestId;
107
+ logger.error({
108
+ operation: 'import',
109
+ code: 'import_execution_failed',
110
+ request_id: requestId,
111
+ mode: request.mode,
112
+ }, 'Portability import failed');
113
+ ctx.status = 503;
114
+ ctx.body = { error: 'Import failed', code: 'import_execution_failed', request_id: requestId };
115
+ }
116
+ }
117
+ /** Build the audit actor from the authenticated Admin identity. */
118
+ function portabilityActor(ctx) {
119
+ const adminUser = ctx.state.adminUser;
120
+ if (!adminUser)
121
+ throw new Error('Admin identity is unavailable');
122
+ return {
123
+ userId: adminUser.id,
124
+ controlPlaneOrganizationId: adminUser.organizationId,
125
+ };
71
126
  }
72
127
  //# sourceMappingURL=imports.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"imports.js","sourceRoot":"","sources":["../../src/routes/imports.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE/F,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E,MAAM,mBAAmB,GAAG,CAAC;KAC1B,MAAM,CAAC;IACN,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,WAAW,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;IAClE,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;IAC5C,QAAQ,EAAE,oBAAoB;CAC/B,CAAC;KACD,MAAM,EAAE,CAAC;AAEZ,8EAA8E;AAC9E,iBAAiB;AACjB,8EAA8E;AAE9E;;;GAGG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC,CAAC;IAE3D,MAAM,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAE/B,4EAA4E;IAC5E,gCAAgC;IAChC,4EAA4E;IAC5E,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,iBAAiB,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QAChF,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,mBAAmB,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACzD,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,CAAC;YAExC,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;YAExF,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC;QACpB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,CAAC,CAAC,QAAQ,EAAE,CAAC;gBAC9B,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;gBACjB,GAAG,CAAC,IAAI,GAAG,EAAE,KAAK,EAAE,4BAA4B,EAAE,IAAI,EAAE,yBAAyB,EAAE,CAAC;gBACpF,OAAO;YACT,CAAC;YACD,IAAI,GAAG,YAAY,oBAAoB,EAAE,CAAC;gBACxC,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;gBACxB,GAAG,CAAC,IAAI,GAAG;oBACT,KAAK,EAAE,uCAAuC;oBAC9C,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,aAAa,EAAE,GAAG,CAAC,aAAa;iBACjC,CAAC;gBACF,OAAO;YACT,CAAC;YACD,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;YACjB,GAAG,CAAC,IAAI,GAAG;gBACT,KAAK,EAAE,uCAAuC;gBAC9C,IAAI,EAAE,yBAAyB;aAChC,CAAC;QACJ,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"imports.js","sourceRoot":"","sources":["../../src/routes/imports.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"interactions.d.ts","sourceRoot":"","sources":["../../src/routes/interactions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"interactions.d.ts","sourceRoot":"","sources":["../../src/routes/interactions.ts"],"names":[],"mappings":"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"}
@@ -23,6 +23,7 @@ import Router from '@koa/router';
23
23
  import { generateCsrfToken, verifyCsrfToken, setCsrfCookie, getCsrfFromCookie, } from '../auth/csrf.js';
24
24
  import { checkRateLimit, resetRateLimit, buildLoginRateLimitKey, buildMagicLinkRateLimitKey, loadLoginRateLimitConfig, loadMagicLinkRateLimitConfig, } from '../auth/rate-limiter.js';
25
25
  import { sendOtpCodeEmail } from '../auth/email-service.js';
26
+ import { resolveEffectiveBranding } from '../auth/effective-branding.js';
26
27
  import { enqueueAccountRecovery } from '../auth/recovery-service.js';
27
28
  import { resolveLocale, getTranslationFunction } from '../auth/i18n.js';
28
29
  import { renderPage } from '../auth/template-engine.js';
@@ -61,13 +62,6 @@ const defaultEnumerationDependencies = {
61
62
  // ---------------------------------------------------------------------------
62
63
  // Helpers
63
64
  // ---------------------------------------------------------------------------
64
- /**
65
- * Build a branding context object from organization data.
66
- * Used to populate template context with org-specific branding.
67
- *
68
- * @param org - Organization with branding fields
69
- * @returns Branding context for templates
70
- */
71
65
  /**
72
66
  * Resolve the effective login methods for an OIDC provider Client.
73
67
  *
@@ -95,15 +89,6 @@ function resolveLoginMethodsFromOidcClient(client, org) {
95
89
  const normalized = Array.isArray(raw) ? raw : null;
96
90
  return resolveLoginMethods(org, { loginMethods: normalized });
97
91
  }
98
- function buildBrandingFromOrg(org) {
99
- return {
100
- logoUrl: org.brandingLogoUrl,
101
- faviconUrl: org.brandingFaviconUrl,
102
- primaryColor: org.brandingPrimaryColor ?? '#3B82F6',
103
- companyName: org.brandingCompanyName ?? org.name,
104
- customCss: org.brandingCustomCss,
105
- };
106
- }
107
92
  /**
108
93
  * Build a base template context from an interaction context.
109
94
  * Shared across all interaction route handlers to provide consistent
@@ -113,11 +98,11 @@ function buildBrandingFromOrg(org) {
113
98
  * @param locale - Resolved locale string
114
99
  * @param csrfToken - CSRF token for form protection
115
100
  * @param orgSlug - Organization slug for template resolution
116
- * @returns Base template context (without the `t` translation function)
101
+ * @returns Base template context (without the `t` translation function).
117
102
  */
118
- function buildBaseContext(ctx, locale, csrfToken, orgSlug) {
103
+ async function buildBaseContext(ctx, locale, csrfToken, orgSlug) {
119
104
  return {
120
- branding: buildBrandingFromOrg(ctx.state.organization),
105
+ branding: await resolveEffectiveBranding(ctx.state.organization),
121
106
  locale,
122
107
  csrfToken,
123
108
  orgSlug,
@@ -133,6 +118,7 @@ function buildBaseContext(ctx, locale, csrfToken, orgSlug) {
133
118
  * @param statusCode - HTTP status code (default: 200)
134
119
  */
135
120
  async function renderAndRespond(ctx, pageName, context, statusCode = 200, renderer = renderPage) {
121
+ ctx.state.brandingImageSources = context.branding.imageSources;
136
122
  const html = await renderer(pageName, context);
137
123
  ctx.status = statusCode;
138
124
  ctx.type = 'text/html';
@@ -408,7 +394,7 @@ async function showLogin(ctx, provider) {
408
394
  const showPassword = effectiveMethods.includes('password');
409
395
  const showMagicLink = effectiveMethods.includes('magic_link');
410
396
  const context = {
411
- ...buildBaseContext(ctx, locale, csrfToken, org.slug),
397
+ ...(await buildBaseContext(ctx, locale, csrfToken, org.slug)),
412
398
  t,
413
399
  interaction: {
414
400
  uid: interaction.uid,
@@ -451,7 +437,7 @@ async function renderMagicLinkSuccessPage(ctx, org) {
451
437
  const t = getTranslationFunction(locale, org.slug);
452
438
  const csrfToken = generateCsrfToken();
453
439
  const context = {
454
- branding: buildBrandingFromOrg(org),
440
+ branding: await resolveEffectiveBranding(org),
455
441
  locale,
456
442
  t,
457
443
  csrfToken,
@@ -577,8 +563,9 @@ async function processLogin(ctx, provider, dependencies) {
577
563
  email: user.email,
578
564
  },
579
565
  }, { mergeWithLastSubmission: true });
580
- // If method is email, auto-send the first OTP code
581
- if (twoFactorMethod === 'email' || (!user.twoFactorEnabled && !twoFactorMethod)) {
566
+ // Enrolled email users can receive their challenge immediately. Unenrolled users receive
567
+ // one only after choosing email during setup, so they are not sent duplicate or unwanted codes.
568
+ if (user.twoFactorEnabled && twoFactorMethod === 'email') {
582
569
  try {
583
570
  const otpCode = await sendOtpCode(user.id, user.email, org.id);
584
571
  // Send the OTP code via email (fire-and-forget)
@@ -587,13 +574,7 @@ async function processLogin(ctx, provider, dependencies) {
587
574
  email: user.email,
588
575
  givenName: user.givenName,
589
576
  familyName: user.familyName,
590
- }, {
591
- id: org.id,
592
- slug: org.slug,
593
- brandingLogoUrl: org.brandingLogoUrl,
594
- brandingPrimaryColor: org.brandingPrimaryColor,
595
- brandingCompanyName: org.brandingCompanyName,
596
- }, otpCode, 10, locale);
577
+ }, org, otpCode, 10, locale);
597
578
  }
598
579
  catch {
599
580
  logger.warn({ event: 'login-otp-delivery-failed' }, 'Initial OTP delivery failed');
@@ -737,7 +718,7 @@ async function handleSendMagicLink(ctx, provider, dependencies) {
737
718
  // Always render the "check your email" page — prevents user enumeration
738
719
  const csrfToken = dependencies.generateCsrfToken();
739
720
  const context = {
740
- ...buildBaseContext(ctx, locale, csrfToken, org.slug),
721
+ ...(await buildBaseContext(ctx, locale, csrfToken, org.slug)),
741
722
  t,
742
723
  email,
743
724
  loginUrl: `/interaction/${interaction.uid}`,
@@ -749,12 +730,82 @@ async function handleSendMagicLink(ctx, provider, dependencies) {
749
730
  await renderErrorPage(ctx, 'errors.interaction_expired');
750
731
  }
751
732
  }
733
+ /**
734
+ * Report whether the authorization request carries an explicit `prompt=consent`.
735
+ *
736
+ * @param params - Authorization request parameters from the interaction
737
+ * @returns `true` when the client explicitly requested consent
738
+ */
739
+ function requestedExplicitConsent(params) {
740
+ const prompt = typeof params.prompt === 'string' ? params.prompt : '';
741
+ return prompt.split(' ').includes('consent');
742
+ }
743
+ /**
744
+ * Report whether the interaction has no scope, claim, or resource left to grant.
745
+ *
746
+ * @param details - The interaction prompt details listing what is missing
747
+ * @returns `true` when nothing remains to consent to
748
+ */
749
+ function nothingMissing(details) {
750
+ const missingScopes = details.missingOIDCScope ?? [];
751
+ const missingClaims = details.missingOIDCClaims ?? [];
752
+ const missingResources = details.missingResourceScopes ?? {};
753
+ return ([...missingScopes].length === 0 &&
754
+ [...missingClaims].length === 0 &&
755
+ Object.values(missingResources).every((scopes) => [...scopes].length === 0));
756
+ }
757
+ /**
758
+ * Persist a consent grant covering the interaction's missing scopes and finish.
759
+ *
760
+ * Grants exactly what the provider listed as missing plus `offline_access` when
761
+ * requested (the provider treats it as a consent signal and may omit it from
762
+ * `missingOIDCScope`, but the grant must include it for a refresh token).
763
+ *
764
+ * @param ctx - Koa context with organization state
765
+ * @param provider - OIDC provider instance
766
+ * @param interaction - Current interaction details
767
+ * @param description - Audit description for the recorded grant
768
+ */
769
+ async function grantConsentAndFinish(ctx, provider, interaction, description) {
770
+ const params = interaction.params;
771
+ const details = interaction.prompt.details;
772
+ const clientId = params.client_id;
773
+ const grant = new provider.Grant({ accountId: interaction.session?.accountId, clientId });
774
+ if (details.missingOIDCScope) {
775
+ grant.addOIDCScope([...details.missingOIDCScope].join(' '));
776
+ }
777
+ if (details.missingOIDCClaims) {
778
+ grant.addOIDCClaims([...details.missingOIDCClaims]);
779
+ }
780
+ if (details.missingResourceScopes) {
781
+ for (const [indicator, scopes] of Object.entries(details.missingResourceScopes)) {
782
+ grant.addResourceScope(indicator, [...scopes].join(' '));
783
+ }
784
+ }
785
+ const requestedScope = params.scope ?? '';
786
+ if (requestedScope.includes('offline_access')) {
787
+ grant.addOIDCScope('offline_access');
788
+ }
789
+ const grantId = await grant.save();
790
+ writeAuditLog({
791
+ organizationId: ctx.state.organization.id,
792
+ userId: interaction.session?.accountId,
793
+ eventType: 'user.consent.granted',
794
+ eventCategory: 'authentication',
795
+ description,
796
+ });
797
+ await provider.interactionFinished(ctx.req, ctx.res, { consent: { grantId } }, { mergeWithLastSubmission: true });
798
+ }
752
799
  /**
753
800
  * Show the consent page for an OIDC interaction.
754
801
  *
755
- * If the client belongs to the same organization (first-party app),
756
- * auto-consents by immediately finishing the interaction. Otherwise,
757
- * renders the consent page with requested scopes and client info.
802
+ * The decision is trust-driven rather than organization-driven:
803
+ * - Nothing is missing → finish without rendering and reuse or create the
804
+ * grant. An explicit `prompt=consent` is ignored so a user who already
805
+ * consented is never asked again.
806
+ * - Something is missing → render the consent page when the client requires
807
+ * consent (`requireConsent`) or explicitly requested `prompt=consent`;
808
+ * otherwise auto-consent the trusted first-party client.
758
809
  *
759
810
  * @param ctx - Koa context with organization state
760
811
  * @param provider - OIDC provider instance
@@ -766,66 +817,40 @@ async function showConsent(ctx, provider) {
766
817
  // Resolve organization from the interaction's client_id
767
818
  await resolveOrganizationForInteraction(ctx, params.client_id, interaction.uid);
768
819
  const org = ctx.state.organization;
769
- // Resolve locale for the consent page
770
- const uiLocales = params.ui_locales;
771
- const acceptLanguage = ctx.get('Accept-Language') || undefined;
772
- const locale = await resolveLocale(uiLocales, acceptLanguage, org.defaultLocale);
773
- const t = getTranslationFunction(locale, org.slug);
774
- // Auto-consent for first-party apps: if the client metadata includes
775
- // the org ID matching the current tenant, skip the consent screen
776
820
  const clientId = params.client_id;
777
821
  const client = await provider.Client.find(clientId);
778
- if (client) {
779
- // Check if this is a first-party client (belongs to the same org)
780
- const clientOrgId = client.metadata()?.organizationId;
781
- if (clientOrgId && clientOrgId === org.id) {
782
- // Auto-consent: finish interaction immediately with consent grant
783
- const grant = new provider.Grant({ accountId: interaction.session?.accountId, clientId });
784
- // Use prompt.details to grant exactly what the provider requires
785
- const details = prompt.details;
786
- if (details.missingOIDCScope) {
787
- const scopes = details.missingOIDCScope;
788
- grant.addOIDCScope([...scopes].join(' '));
789
- }
790
- if (details.missingOIDCClaims) {
791
- const claims = details.missingOIDCClaims;
792
- grant.addOIDCClaims([...claims]);
793
- }
794
- if (details.missingResourceScopes) {
795
- for (const [indicator, scopes] of Object.entries(details.missingResourceScopes)) {
796
- grant.addResourceScope(indicator, [...scopes].join(' '));
797
- }
798
- }
799
- // Explicitly grant offline_access if requested — node-oidc-provider
800
- // treats it as a consent signal and may not include it in missingOIDCScope,
801
- // but the Grant must include it for a refresh_token to be issued.
802
- const requestedScope = params.scope ?? '';
803
- if (requestedScope.includes('offline_access')) {
804
- grant.addOIDCScope('offline_access');
805
- }
806
- const grantId = await grant.save();
807
- const result = {
808
- consent: { grantId },
809
- };
810
- writeAuditLog({
811
- organizationId: org.id,
812
- userId: interaction.session?.accountId,
813
- eventType: 'user.consent.granted',
814
- eventCategory: 'authentication',
815
- description: `Auto-consent granted for first-party client ${clientId}`,
816
- });
817
- await provider.interactionFinished(ctx.req, ctx.res, result, {
818
- mergeWithLastSubmission: true,
819
- });
822
+ const requiresConsent = client?.metadata()?.requireConsent === true;
823
+ const details = prompt.details;
824
+ // Nothing left to grant: finish without rendering, even under prompt=consent.
825
+ // Reuse the provider's resolved grant; creating a new grant here would
826
+ // replace it with an empty one and drop the scopes the user already
827
+ // approved, which would make the provider prompt again.
828
+ if (nothingMissing(details)) {
829
+ const existingGrantId = interaction.grantId;
830
+ if (existingGrantId !== undefined) {
831
+ await provider.interactionFinished(ctx.req, ctx.res, { consent: { grantId: existingGrantId } }, { mergeWithLastSubmission: true });
820
832
  return;
821
833
  }
834
+ await grantConsentAndFinish(ctx, provider, interaction, `Consent already granted for client ${clientId}`);
835
+ return;
836
+ }
837
+ // A new scope, claim, or resource is requested. Ask only when the client
838
+ // requires consent or explicitly requested it; trusted clients auto-consent.
839
+ const explicitConsent = requestedExplicitConsent(params);
840
+ if (!requiresConsent && !explicitConsent) {
841
+ await grantConsentAndFinish(ctx, provider, interaction, `Auto-consent granted for first-party client ${clientId}`);
842
+ return;
822
843
  }
823
- // Third-party client: show the consent page
844
+ // Resolve locale for the consent page
845
+ const uiLocales = params.ui_locales;
846
+ const acceptLanguage = ctx.get('Accept-Language') || undefined;
847
+ const locale = await resolveLocale(uiLocales, acceptLanguage, org.defaultLocale);
848
+ const t = getTranslationFunction(locale, org.slug);
824
849
  const csrfToken = generateCsrfToken();
825
850
  setCsrfCookie(ctx, csrfToken);
826
851
  const requestedScopes = (params.scope ?? '').split(' ').filter(Boolean);
827
852
  const context = {
828
- ...buildBaseContext(ctx, locale, csrfToken, org.slug),
853
+ ...(await buildBaseContext(ctx, locale, csrfToken, org.slug)),
829
854
  t,
830
855
  interaction: {
831
856
  uid: interaction.uid,
@@ -1008,7 +1033,7 @@ async function renderLoginWithError(ctx, provider, interaction, t, locale, email
1008
1033
  const showPassword = effectiveMethods.includes('password');
1009
1034
  const showMagicLink = effectiveMethods.includes('magic_link');
1010
1035
  const context = {
1011
- ...buildBaseContext(ctx, locale, csrfToken, org.slug),
1036
+ ...(await buildBaseContext(ctx, locale, csrfToken, org.slug)),
1012
1037
  t,
1013
1038
  interaction: {
1014
1039
  uid: interaction.uid,
@@ -1048,13 +1073,14 @@ async function renderErrorPage(ctx, errorKey) {
1048
1073
  const csrfToken = generateCsrfToken();
1049
1074
  const context = {
1050
1075
  branding: org
1051
- ? buildBrandingFromOrg(org)
1076
+ ? await resolveEffectiveBranding(org)
1052
1077
  : {
1053
1078
  logoUrl: null,
1054
1079
  faviconUrl: null,
1055
1080
  primaryColor: '#3B82F6',
1056
1081
  companyName: 'Porta',
1057
1082
  customCss: null,
1083
+ imageSources: [],
1058
1084
  },
1059
1085
  locale,
1060
1086
  t,