@cosmicdrift/kumiko-bundled-features 1.0.0 → 2.0.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 (669) hide show
  1. package/package.json +41 -7
  2. package/src/__tests__/access-invalidation-consumer.integration.test.ts +279 -0
  3. package/src/__tests__/access-invalidation-stream.integration.test.ts +217 -0
  4. package/src/admin-shell/__tests__/admin-shell-security.integration.test.ts +200 -0
  5. package/src/admin-shell/__tests__/admin-shell.boot.test.ts +147 -0
  6. package/src/admin-shell/__tests__/overview-allowlist.test.ts +43 -0
  7. package/src/admin-shell/__tests__/overview-screens.boot.test.ts +35 -0
  8. package/src/admin-shell/constants.ts +11 -0
  9. package/src/admin-shell/feature.ts +135 -0
  10. package/src/admin-shell/i18n.ts +36 -0
  11. package/src/admin-shell/index.ts +17 -0
  12. package/src/admin-shell/overview-allowlist.ts +37 -0
  13. package/src/admin-shell/web/client-plugin.tsx +26 -0
  14. package/src/admin-shell/web/i18n.ts +16 -0
  15. package/src/admin-shell/web/index.ts +2 -0
  16. package/src/admin-shell/web/overview-layout.tsx +69 -0
  17. package/src/admin-shell/web/overview-query.ts +15 -0
  18. package/src/admin-shell/web/platform-overview-screen.tsx +90 -0
  19. package/src/admin-shell/web/tenant-overview-screen.tsx +90 -0
  20. package/src/agent-tools/__tests__/tool-catalog.test.ts +179 -0
  21. package/src/agent-tools/__tests__/tool-dispatch.integration.test.ts +100 -0
  22. package/src/agent-tools/__tests__/tool-dispatch.test.ts +126 -0
  23. package/src/agent-tools/index.ts +9 -0
  24. package/src/agent-tools/tool-catalog.ts +130 -0
  25. package/src/agent-tools/tool-dispatch.ts +82 -0
  26. package/src/agent-tools/types.ts +38 -0
  27. package/src/audit/__tests__/audit-screens.boot.test.ts +51 -0
  28. package/src/audit/__tests__/audit-security.integration.test.ts +201 -0
  29. package/src/audit/__tests__/audit.integration.test.ts +50 -25
  30. package/src/audit/constants.ts +9 -2
  31. package/src/audit/feature.ts +33 -1
  32. package/src/audit/handlers/details.query.ts +47 -0
  33. package/src/audit/handlers/list.query.ts +3 -2
  34. package/src/audit/i18n.ts +31 -0
  35. package/src/audit/web/audit-log-detail-screen.tsx +131 -0
  36. package/src/audit/web/audit-log-screen.tsx +250 -0
  37. package/src/audit/web/client-plugin.tsx +22 -0
  38. package/src/audit/web/i18n.ts +12 -0
  39. package/src/audit/web/index.ts +5 -0
  40. package/src/auth-email-password/__tests__/account-lockout-no-redis.integration.test.ts +4 -4
  41. package/src/auth-email-password/__tests__/account-lockout.integration.test.ts +4 -4
  42. package/src/auth-email-password/__tests__/account-unlock.integration.test.ts +283 -0
  43. package/src/auth-email-password/__tests__/auth-claims.integration.test.ts +10 -11
  44. package/src/auth-email-password/__tests__/auth-user-row.test.ts +36 -0
  45. package/src/auth-email-password/__tests__/auth.integration.test.ts +33 -6
  46. package/src/auth-email-password/__tests__/email-verification.integration.test.ts +9 -6
  47. package/src/auth-email-password/__tests__/feature-options.test.ts +33 -0
  48. package/src/auth-email-password/__tests__/identity-v3-login.integration.test.ts +3 -3
  49. package/src/auth-email-password/__tests__/invite-flow-kms.integration.test.ts +283 -0
  50. package/src/auth-email-password/__tests__/invite-flow.integration.test.ts +35 -1
  51. package/src/auth-email-password/__tests__/login-mfa-setup-required.integration.test.ts +129 -0
  52. package/src/auth-email-password/__tests__/multi-roles.integration.test.ts +1 -1
  53. package/src/auth-email-password/__tests__/password-policy.test.ts +15 -0
  54. package/src/auth-email-password/__tests__/password-reset.integration.test.ts +19 -6
  55. package/src/auth-email-password/__tests__/public-routes-rate-limit.integration.test.ts +3 -3
  56. package/src/auth-email-password/__tests__/seed-admin.integration.test.ts +1 -1
  57. package/src/auth-email-password/__tests__/self-registration-toggle.integration.test.ts +264 -0
  58. package/src/auth-email-password/__tests__/session-callbacks.integration.test.ts +20 -4
  59. package/src/auth-email-password/__tests__/session-strict-mode.integration.test.ts +7 -14
  60. package/src/auth-email-password/__tests__/signup-flow.integration.test.ts +91 -0
  61. package/src/auth-email-password/constants.ts +25 -0
  62. package/src/auth-email-password/email-templates.ts +32 -0
  63. package/src/auth-email-password/errors.ts +9 -0
  64. package/src/auth-email-password/feature.ts +59 -11
  65. package/src/auth-email-password/handlers/change-password.write.ts +14 -13
  66. package/src/auth-email-password/handlers/confirm-account-unlock.write.ts +59 -0
  67. package/src/auth-email-password/handlers/confirm-token-flow.ts +34 -105
  68. package/src/auth-email-password/handlers/invite-accept-with-login.write.ts +12 -5
  69. package/src/auth-email-password/handlers/invite-accept.write.ts +9 -2
  70. package/src/auth-email-password/handlers/invite-create.write.ts +2 -2
  71. package/src/auth-email-password/handlers/invite-signup-complete.write.ts +12 -4
  72. package/src/auth-email-password/handlers/login.write.ts +86 -16
  73. package/src/auth-email-password/handlers/logout.write.ts +4 -2
  74. package/src/auth-email-password/handlers/request-account-unlock.write.ts +38 -0
  75. package/src/auth-email-password/handlers/reset-password.write.ts +3 -2
  76. package/src/auth-email-password/handlers/self-registration-status.query.ts +23 -0
  77. package/src/auth-email-password/handlers/signup-confirm.write.ts +19 -11
  78. package/src/auth-email-password/handlers/signup-request.write.ts +22 -0
  79. package/src/auth-email-password/handlers/token-request-handler.ts +4 -2
  80. package/src/auth-email-password/i18n.ts +52 -0
  81. package/src/auth-email-password/index.ts +13 -2
  82. package/src/auth-email-password/lockout-store.ts +10 -5
  83. package/src/auth-email-password/password-policy.ts +2108 -0
  84. package/src/auth-email-password/seeding.ts +55 -27
  85. package/src/auth-email-password/self-registration-toggle.ts +25 -0
  86. package/src/auth-email-password/signed-token.ts +1 -0
  87. package/src/auth-email-password/unlock-token.ts +28 -0
  88. package/src/auth-email-password/web/__tests__/auth-client.test.ts +542 -0
  89. package/src/auth-email-password/web/__tests__/auth-form-logic.test.ts +44 -0
  90. package/src/auth-email-password/web/__tests__/auth-gate.test.tsx +194 -6
  91. package/src/auth-email-password/web/__tests__/client-plugin.test.tsx +34 -0
  92. package/src/auth-email-password/web/__tests__/confirm-account-unlock-screen.test.tsx +64 -0
  93. package/src/auth-email-password/web/__tests__/forgot-password-screen.test.tsx +5 -1
  94. package/src/auth-email-password/web/__tests__/invite-accept-screen.test.tsx +129 -2
  95. package/src/auth-email-password/web/__tests__/login-screen.test.tsx +149 -5
  96. package/src/auth-email-password/web/__tests__/request-account-unlock-screen.test.tsx +137 -0
  97. package/src/auth-email-password/web/__tests__/reset-password-screen.test.tsx +5 -1
  98. package/src/auth-email-password/web/__tests__/signup-complete-screen.test.tsx +134 -0
  99. package/src/auth-email-password/web/__tests__/signup-screen.test.tsx +93 -0
  100. package/src/auth-email-password/web/__tests__/test-utils.tsx +12 -2
  101. package/src/auth-email-password/web/__tests__/use-url-token.test.tsx +29 -0
  102. package/src/auth-email-password/web/__tests__/verify-email-screen.test.tsx +5 -1
  103. package/src/auth-email-password/web/auth-client.ts +96 -18
  104. package/src/auth-email-password/web/auth-form-logic.ts +30 -0
  105. package/src/auth-email-password/web/auth-form-primitives.tsx +22 -20
  106. package/src/auth-email-password/web/auth-gate.tsx +140 -19
  107. package/src/auth-email-password/web/client-plugin.ts +24 -2
  108. package/src/auth-email-password/web/confirm-account-unlock-screen.tsx +95 -0
  109. package/src/auth-email-password/web/forgot-password-screen.tsx +6 -6
  110. package/src/auth-email-password/web/index.ts +16 -4
  111. package/src/auth-email-password/web/invite-accept-screen.tsx +5 -5
  112. package/src/auth-email-password/web/login-screen.tsx +78 -22
  113. package/src/auth-email-password/web/request-account-unlock-screen.tsx +112 -0
  114. package/src/auth-email-password/web/reset-password-screen.tsx +15 -18
  115. package/src/auth-email-password/web/session.tsx +11 -6
  116. package/src/auth-email-password/web/signup-complete-screen.tsx +13 -15
  117. package/src/auth-email-password/web/signup-screen.tsx +8 -10
  118. package/src/auth-email-password/web/use-shell-user.ts +5 -2
  119. package/src/auth-email-password/web/user-menu.tsx +3 -1
  120. package/src/auth-email-password/web/verify-email-screen.tsx +10 -14
  121. package/src/auth-foundation/__tests__/feature.test.ts +46 -0
  122. package/src/auth-foundation/__tests__/session-store.test.ts +77 -0
  123. package/src/auth-foundation/__tests__/tenant-providers.test.ts +160 -0
  124. package/src/auth-foundation/__tests__/token-verifier.test.ts +150 -0
  125. package/src/auth-foundation/boot-checks.ts +150 -0
  126. package/src/auth-foundation/feature.ts +157 -0
  127. package/src/auth-foundation/index.ts +37 -0
  128. package/src/auth-foundation/resolve-anonymous-access.ts +62 -0
  129. package/src/auth-foundation/types.ts +170 -0
  130. package/src/auth-mfa/__tests__/base32.test.ts +28 -0
  131. package/src/auth-mfa/__tests__/disable-and-regenerate.integration.test.ts +265 -0
  132. package/src/auth-mfa/__tests__/enable-confirm-preauth.integration.test.ts +321 -0
  133. package/src/auth-mfa/__tests__/enable-flow.integration.test.ts +322 -0
  134. package/src/auth-mfa/__tests__/enable-start-preauth.integration.test.ts +237 -0
  135. package/src/auth-mfa/__tests__/errors-i18n-parity.test.ts +39 -0
  136. package/src/auth-mfa/__tests__/mfa-preauth-setup-token.test.ts +113 -0
  137. package/src/auth-mfa/__tests__/no-redis.integration.test.ts +209 -0
  138. package/src/auth-mfa/__tests__/pii-subject-encryption.integration.test.ts +139 -0
  139. package/src/auth-mfa/__tests__/reencrypt-job.integration.test.ts +247 -0
  140. package/src/auth-mfa/__tests__/session-auto-revoke.integration.test.ts +169 -0
  141. package/src/auth-mfa/__tests__/totp.test.ts +131 -0
  142. package/src/auth-mfa/__tests__/verify.integration.test.ts +319 -0
  143. package/src/auth-mfa/base32.ts +51 -0
  144. package/src/auth-mfa/config.ts +26 -0
  145. package/src/auth-mfa/constants.ts +45 -0
  146. package/src/auth-mfa/db/queries.ts +45 -0
  147. package/src/auth-mfa/errors.ts +66 -0
  148. package/src/auth-mfa/feature.ts +170 -0
  149. package/src/auth-mfa/handlers/disable.write.ts +67 -0
  150. package/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +200 -0
  151. package/src/auth-mfa/handlers/enable-confirm.write.ts +106 -0
  152. package/src/auth-mfa/handlers/enable-start-preauth.write.ts +87 -0
  153. package/src/auth-mfa/handlers/enable-start.write.ts +66 -0
  154. package/src/auth-mfa/handlers/reencrypt.job.ts +234 -0
  155. package/src/auth-mfa/handlers/regenerate-recovery.write.ts +77 -0
  156. package/src/auth-mfa/handlers/status.query.ts +23 -0
  157. package/src/auth-mfa/handlers/verify.write.ts +179 -0
  158. package/src/auth-mfa/i18n.ts +12 -0
  159. package/src/auth-mfa/index.ts +19 -0
  160. package/src/auth-mfa/mfa-challenge-token.ts +90 -0
  161. package/src/auth-mfa/mfa-preauth-setup-token.ts +86 -0
  162. package/src/auth-mfa/mfa-setup-token.ts +96 -0
  163. package/src/auth-mfa/mfa-status-checker.ts +84 -0
  164. package/src/auth-mfa/mfa-verify-attempts.ts +90 -0
  165. package/src/auth-mfa/otpauth-uri.ts +21 -0
  166. package/src/auth-mfa/recovery-codes.ts +54 -0
  167. package/src/auth-mfa/schema/user-mfa.ts +60 -0
  168. package/src/auth-mfa/testing.ts +5 -0
  169. package/src/auth-mfa/totp.ts +67 -0
  170. package/src/auth-mfa/verify-factor.ts +58 -0
  171. package/src/auth-mfa/web/__tests__/mfa-client.test.ts +265 -0
  172. package/src/auth-mfa/web/__tests__/mfa-enable-screen.test.tsx +182 -0
  173. package/src/auth-mfa/web/__tests__/mfa-setup-preauth-screen.test.tsx +379 -0
  174. package/src/auth-mfa/web/__tests__/mfa-verify-screen.test.tsx +104 -0
  175. package/src/auth-mfa/web/client-plugin.ts +37 -0
  176. package/src/auth-mfa/web/i18n.ts +136 -0
  177. package/src/auth-mfa/web/index.ts +27 -0
  178. package/src/auth-mfa/web/mfa-client.ts +184 -0
  179. package/src/auth-mfa/web/mfa-disable-dialog.tsx +69 -0
  180. package/src/auth-mfa/web/mfa-enable-screen.tsx +228 -0
  181. package/src/auth-mfa/web/mfa-recovery-codes-reveal.tsx +53 -0
  182. package/src/auth-mfa/web/mfa-regenerate-recovery-dialog.tsx +68 -0
  183. package/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +267 -0
  184. package/src/auth-mfa/web/mfa-verify-screen.tsx +117 -0
  185. package/src/auth-mfa/web/qrcode-browser.d.ts +12 -0
  186. package/src/auth-mfa-user-data/__tests__/hooks.integration.test.ts +134 -0
  187. package/src/auth-mfa-user-data/hooks.ts +53 -0
  188. package/src/auth-mfa-user-data/index.ts +21 -0
  189. package/src/billing-foundation/__tests__/billing-foundation.integration.test.ts +206 -2
  190. package/src/billing-foundation/__tests__/subscription-provider-contract.ts +93 -0
  191. package/src/billing-foundation/entities.ts +28 -4
  192. package/src/billing-foundation/events.ts +6 -2
  193. package/src/billing-foundation/feature.ts +9 -1
  194. package/src/billing-foundation/get-subscription-for-tenant.ts +16 -5
  195. package/src/billing-foundation/handlers/create-portal-session.write.ts +17 -1
  196. package/src/billing-foundation/handlers/list-subscriptions.query.ts +15 -1
  197. package/src/billing-foundation/handlers/process-event.write.ts +39 -3
  198. package/src/billing-foundation/tenant-destroy-hook.ts +46 -0
  199. package/src/cap-counter/__tests__/cap-list.boot.test.ts +27 -0
  200. package/src/cap-counter/__tests__/cap-list.security.integration.test.ts +53 -0
  201. package/src/cap-counter/__tests__/entity-ddl.test.ts +15 -0
  202. package/src/cap-counter/__tests__/with-cap-enforcement.integration.test.ts +8 -5
  203. package/src/cap-counter/constants.ts +2 -0
  204. package/src/cap-counter/enforce-cap.ts +15 -18
  205. package/src/cap-counter/entity.ts +3 -3
  206. package/src/cap-counter/feature.ts +28 -1
  207. package/src/cap-counter/i18n.ts +12 -0
  208. package/src/cap-counter/with-cap-enforcement.ts +2 -2
  209. package/src/channel-email/__tests__/pii-guard.test.ts +56 -0
  210. package/src/channel-email/email-channel.ts +2 -1
  211. package/src/channel-email/index.ts +1 -0
  212. package/src/channel-email/pii-guard.ts +39 -0
  213. package/src/compliance-profiles/__tests__/profile-picker.boot.test.ts +28 -0
  214. package/src/compliance-profiles/__tests__/profile-picker.security.integration.test.ts +91 -0
  215. package/src/compliance-profiles/constants.ts +15 -0
  216. package/src/compliance-profiles/feature.ts +22 -1
  217. package/src/compliance-profiles/handlers/set-profile.write.ts +2 -2
  218. package/src/compliance-profiles/i18n.ts +6 -0
  219. package/src/compliance-profiles/index.ts +6 -0
  220. package/src/compliance-profiles/web/client-plugin.tsx +22 -0
  221. package/src/compliance-profiles/web/compliance-profile-screen.tsx +152 -0
  222. package/src/compliance-profiles/web/i18n.ts +25 -0
  223. package/src/compliance-profiles/web/index.ts +6 -0
  224. package/src/config/__tests__/app-overrides.test.ts +11 -0
  225. package/src/config/__tests__/backing-secrets.integration.test.ts +22 -0
  226. package/src/config/__tests__/cascade.integration.test.ts +35 -0
  227. package/src/config/__tests__/config.integration.test.ts +71 -10
  228. package/src/config/__tests__/deserialize-value.test.ts +5 -8
  229. package/src/config/__tests__/inherited-redaction.integration.test.ts +4 -3
  230. package/src/config/__tests__/pii-encrypted.integration.test.ts +179 -0
  231. package/src/config/__tests__/reencrypt-job-concurrent-write-race.integration.test.ts +234 -0
  232. package/src/config/__tests__/reencrypt-job-kek-rotation.integration.test.ts +243 -0
  233. package/src/config/__tests__/write-helpers.test.ts +23 -0
  234. package/src/config/db/queries/resolver.ts +3 -3
  235. package/src/config/feature.ts +15 -8
  236. package/src/config/handlers/cascade.query.ts +2 -1
  237. package/src/config/handlers/reencrypt.job.ts +209 -0
  238. package/src/config/handlers/set.write.ts +55 -3
  239. package/src/config/handlers/values.query.ts +2 -1
  240. package/src/config/i18n.ts +9 -0
  241. package/src/config/index.ts +5 -4
  242. package/src/config/resolver.ts +75 -61
  243. package/src/config/table.ts +1 -1
  244. package/src/config/write-helpers.ts +22 -0
  245. package/src/crypto-shredding/__tests__/forget-subject.integration.test.ts +148 -0
  246. package/src/crypto-shredding/constants.ts +5 -0
  247. package/src/crypto-shredding/feature.ts +18 -0
  248. package/src/crypto-shredding/handlers/forget-subject.write.ts +91 -0
  249. package/src/crypto-shredding/index.ts +11 -0
  250. package/src/custom-fields/__tests__/audit-integration.integration.test.ts +1 -1
  251. package/src/custom-fields/__tests__/custom-fields.integration.test.ts +92 -53
  252. package/src/custom-fields/__tests__/field-access.integration.test.ts +25 -0
  253. package/src/custom-fields/__tests__/parse-serialized-field.test.ts +11 -3
  254. package/src/custom-fields/__tests__/retention.integration.test.ts +40 -0
  255. package/src/custom-fields/__tests__/user-data-rights.integration.test.ts +26 -79
  256. package/src/custom-fields/constants.ts +1 -0
  257. package/src/custom-fields/db/queries/user-data-rights.ts +0 -20
  258. package/src/custom-fields/entity.ts +1 -1
  259. package/src/custom-fields/events.ts +4 -3
  260. package/src/custom-fields/feature.ts +8 -0
  261. package/src/custom-fields/handlers/delete-system-field.write.ts +2 -1
  262. package/src/custom-fields/handlers/delete-tenant-field.write.ts +2 -1
  263. package/src/custom-fields/handlers/set-custom-field.write.ts +3 -44
  264. package/src/custom-fields/handlers/update-tenant-field.write.ts +0 -17
  265. package/src/custom-fields/lib/field-access.ts +8 -1
  266. package/src/custom-fields/lib/parse-serialized-field.ts +12 -4
  267. package/src/custom-fields/lib/value-schema.ts +1 -1
  268. package/src/custom-fields/run-retention.ts +9 -1
  269. package/src/custom-fields/schemas.ts +9 -9
  270. package/src/custom-fields/wire-for-entity.ts +110 -78
  271. package/src/custom-fields/wire-user-data-rights.ts +5 -54
  272. package/src/data-retention/__tests__/parse-override.test.ts +14 -13
  273. package/src/data-retention/__tests__/retention-cleanup.integration.test.ts +145 -8
  274. package/src/data-retention/feature.ts +3 -3
  275. package/src/data-retention/run-retention-cleanup.ts +102 -19
  276. package/src/delivery/__tests__/delivery-pii-kms.integration.test.ts +171 -0
  277. package/src/delivery/__tests__/delivery-screens.boot.test.ts +27 -0
  278. package/src/delivery/__tests__/delivery-security.integration.test.ts +106 -0
  279. package/src/delivery/constants.ts +13 -51
  280. package/src/delivery/feature.ts +29 -5
  281. package/src/delivery/handlers/log.query.ts +28 -7
  282. package/src/delivery/i18n.ts +6 -0
  283. package/src/delivery/index.ts +6 -1
  284. package/src/delivery/public-names.ts +36 -0
  285. package/src/delivery/tables.ts +3 -3
  286. package/src/delivery/web/client-plugin.tsx +20 -0
  287. package/src/delivery/web/delivery-log-screen.tsx +103 -0
  288. package/src/delivery/web/i18n.ts +25 -0
  289. package/src/delivery/web/index.ts +3 -0
  290. package/src/document-ingest-foundation/__tests__/feature.integration.test.ts +147 -0
  291. package/src/document-ingest-foundation/__tests__/feature.test.ts +105 -0
  292. package/src/document-ingest-foundation/__tests__/pages.test.ts +37 -0
  293. package/src/document-ingest-foundation/entity.ts +43 -0
  294. package/src/document-ingest-foundation/events.ts +25 -0
  295. package/src/document-ingest-foundation/feature.ts +126 -0
  296. package/src/document-ingest-foundation/index.ts +12 -0
  297. package/src/document-ingest-foundation/pages.ts +34 -0
  298. package/src/feature-toggles/__tests__/compose-tier-resolver.test.ts +304 -0
  299. package/src/feature-toggles/__tests__/feature-toggles.integration.test.ts +7 -6
  300. package/src/feature-toggles/__tests__/toggle-admin.boot.test.ts +31 -0
  301. package/src/feature-toggles/__tests__/toggle-admin.security.integration.test.ts +69 -0
  302. package/src/feature-toggles/compose-tier-resolver.ts +134 -0
  303. package/src/feature-toggles/constants.ts +14 -0
  304. package/src/feature-toggles/feature.ts +23 -3
  305. package/src/feature-toggles/global-feature-state-table.ts +20 -1
  306. package/src/feature-toggles/handlers/list.query.ts +1 -1
  307. package/src/feature-toggles/handlers/registered.query.ts +1 -1
  308. package/src/feature-toggles/i18n.ts +6 -0
  309. package/src/feature-toggles/index.ts +8 -0
  310. package/src/feature-toggles/toggle-runtime.ts +14 -0
  311. package/src/feature-toggles/web/client-plugin.tsx +22 -0
  312. package/src/feature-toggles/web/i18n.ts +29 -0
  313. package/src/feature-toggles/web/index.ts +6 -0
  314. package/src/feature-toggles/web/toggle-admin-screen.tsx +136 -0
  315. package/src/file-foundation/__tests__/feature.test.ts +5 -5
  316. package/src/file-foundation/__tests__/file-foundation.integration.test.ts +4 -3
  317. package/src/file-foundation/feature.ts +10 -11
  318. package/src/files-provider-s3/__tests__/s3-provider.integration.test.ts +27 -2
  319. package/src/files-provider-s3/s3-provider.ts +12 -1
  320. package/src/folders/__tests__/folders.integration.test.ts +3 -0
  321. package/src/folders/web/__tests__/folder-manager.test.tsx +43 -2
  322. package/src/folders/web/__tests__/tree.test.ts +8 -0
  323. package/src/folders/web/folder-manager.tsx +2 -3
  324. package/src/folders/web/i18n.ts +0 -2
  325. package/src/folders/web/tree.ts +13 -2
  326. package/src/folders-user-data/__tests__/hooks.integration.test.ts +1 -0
  327. package/src/folders-user-data/index.ts +4 -4
  328. package/src/inbound-mail-foundation/__tests__/account-state.test.ts +30 -0
  329. package/src/inbound-mail-foundation/__tests__/connect-routes.integration.test.ts +307 -0
  330. package/src/inbound-mail-foundation/__tests__/feature.test.ts +169 -0
  331. package/src/inbound-mail-foundation/__tests__/inbound-mail-foundation.integration.test.ts +644 -0
  332. package/src/inbound-mail-foundation/__tests__/inbound-mail-provider-contract.ts +111 -0
  333. package/src/inbound-mail-foundation/__tests__/retention.integration.test.ts +237 -0
  334. package/src/inbound-mail-foundation/__tests__/watch-supervisor.integration.test.ts +433 -0
  335. package/src/inbound-mail-foundation/aggregate-id.ts +40 -0
  336. package/src/inbound-mail-foundation/connect-routes.ts +230 -0
  337. package/src/inbound-mail-foundation/constants.ts +80 -0
  338. package/src/inbound-mail-foundation/db/queries/inbound-projections.ts +38 -0
  339. package/src/inbound-mail-foundation/entities.ts +172 -0
  340. package/src/inbound-mail-foundation/events.ts +147 -0
  341. package/src/inbound-mail-foundation/feature.ts +243 -0
  342. package/src/inbound-mail-foundation/handlers/account-state.ts +23 -0
  343. package/src/inbound-mail-foundation/handlers/connect-account.write.ts +112 -0
  344. package/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +69 -0
  345. package/src/inbound-mail-foundation/handlers/ingest-message.write.ts +333 -0
  346. package/src/inbound-mail-foundation/handlers/list-accounts.query.ts +44 -0
  347. package/src/inbound-mail-foundation/handlers/list-messages.query.ts +58 -0
  348. package/src/inbound-mail-foundation/handlers/scope-visibility.ts +14 -0
  349. package/src/inbound-mail-foundation/handlers/update-account.write.ts +78 -0
  350. package/src/inbound-mail-foundation/index.ts +97 -0
  351. package/src/inbound-mail-foundation/oauth-state.ts +103 -0
  352. package/src/inbound-mail-foundation/projection.ts +152 -0
  353. package/src/inbound-mail-foundation/provider-factory.ts +53 -0
  354. package/src/inbound-mail-foundation/retention-sweep.ts +168 -0
  355. package/src/inbound-mail-foundation/tenant-destroy-hook.ts +93 -0
  356. package/src/inbound-mail-foundation/types.ts +250 -0
  357. package/src/inbound-mail-foundation/watch-supervisor.ts +482 -0
  358. package/src/inbound-provider-imap/__tests__/feature.test.ts +115 -0
  359. package/src/inbound-provider-imap/__tests__/imap-foundation.integration.test.ts +302 -0
  360. package/src/inbound-provider-imap/__tests__/imap-live.integration.test.ts +206 -0
  361. package/src/inbound-provider-imap/__tests__/plugin-mocked.test.ts +358 -0
  362. package/src/inbound-provider-imap/credential-document.ts +51 -0
  363. package/src/inbound-provider-imap/feature.ts +283 -0
  364. package/src/inbound-provider-imap/imap-client.ts +158 -0
  365. package/src/inbound-provider-imap/index.ts +17 -0
  366. package/src/inbound-provider-inmemory/__tests__/feature.test.ts +52 -0
  367. package/src/inbound-provider-inmemory/feature.ts +154 -0
  368. package/src/inbound-provider-inmemory/index.ts +12 -0
  369. package/src/jobs/__tests__/entity-ddl.test.ts +15 -0
  370. package/src/jobs/__tests__/job-instant.test.ts +15 -0
  371. package/src/jobs/__tests__/job-system-user.integration.test.ts +4 -8
  372. package/src/jobs/__tests__/jobs-events.integration.test.ts +37 -1
  373. package/src/jobs/__tests__/jobs-pii-kms.integration.test.ts +120 -0
  374. package/src/jobs/__tests__/jobs-screens.boot.test.ts +36 -0
  375. package/src/jobs/__tests__/jobs-security.integration.test.ts +108 -0
  376. package/src/jobs/__tests__/reindex-entity-job.integration.test.ts +169 -0
  377. package/src/jobs/constants.ts +8 -1
  378. package/src/jobs/feature.ts +52 -8
  379. package/src/jobs/handlers/detail.query.ts +6 -0
  380. package/src/jobs/handlers/list.query.ts +8 -1
  381. package/src/jobs/handlers/projection-rebuild.job.ts +14 -4
  382. package/src/jobs/handlers/reindex-entity.job.ts +49 -0
  383. package/src/jobs/i18n.ts +37 -0
  384. package/src/jobs/job-instant.ts +9 -0
  385. package/src/jobs/job-run-table.ts +4 -4
  386. package/src/jobs/web/client-plugin.tsx +22 -0
  387. package/src/jobs/web/i18n.ts +12 -0
  388. package/src/jobs/web/index.ts +4 -0
  389. package/src/jobs/web/job-run-detail-screen.tsx +180 -0
  390. package/src/jobs/web/job-runs-screen.tsx +146 -0
  391. package/src/ledger/__tests__/ledger.integration.test.ts +8 -3
  392. package/src/ledger/handlers/confirm-schedule-period.write.ts +17 -4
  393. package/src/ledger/recurring.ts +17 -8
  394. package/src/ledger/web/index.ts +3 -2
  395. package/src/legal-pages/__tests__/legal-pages.integration.test.ts +55 -0
  396. package/src/legal-pages/feature.ts +24 -31
  397. package/src/legal-pages/index.ts +1 -0
  398. package/src/mail-foundation/__tests__/feature.test.ts +5 -6
  399. package/src/mail-foundation/__tests__/mail-foundation.integration.test.ts +4 -3
  400. package/src/mail-foundation/__tests__/mail-transport-contract.ts +58 -0
  401. package/src/mail-foundation/feature.ts +26 -24
  402. package/src/mail-transport-inmemory/__tests__/feature.test.ts +23 -0
  403. package/src/mail-transport-smtp/__tests__/feature.test.ts +65 -0
  404. package/src/managed-pages/__tests__/branding-coerce.test.ts +2 -6
  405. package/src/managed-pages/__tests__/i18n-surface.boot.test.ts +30 -0
  406. package/src/managed-pages/__tests__/managed-pages.integration.test.ts +137 -0
  407. package/src/managed-pages/feature.ts +45 -47
  408. package/src/managed-pages/handlers/branding.query.ts +5 -1
  409. package/src/managed-pages/handlers/by-tenant-published.query.ts +52 -0
  410. package/src/managed-pages/i18n.ts +66 -0
  411. package/src/managed-pages/screens/page-screens.ts +5 -1
  412. package/src/managed-pages/web/client-plugin.tsx +22 -0
  413. package/src/managed-pages/web/i18n.ts +24 -0
  414. package/src/managed-pages/web/index.ts +3 -0
  415. package/src/page-render/__tests__/layout.test.ts +63 -0
  416. package/src/page-render/__tests__/markdown.test.ts +29 -0
  417. package/src/page-render/branding.ts +4 -3
  418. package/src/page-render/index.ts +1 -1
  419. package/src/page-render/layout.ts +18 -3
  420. package/src/page-render/markdown.ts +1 -9
  421. package/src/page-render/web.ts +13 -0
  422. package/src/personal-access-tokens/__tests__/feature.test.ts +42 -0
  423. package/src/personal-access-tokens/__tests__/pat.integration.test.ts +239 -0
  424. package/src/personal-access-tokens/__tests__/scopes.test.ts +42 -0
  425. package/src/personal-access-tokens/constants.ts +33 -0
  426. package/src/personal-access-tokens/feature.ts +105 -0
  427. package/src/personal-access-tokens/handlers/available-scopes.query.ts +21 -0
  428. package/src/personal-access-tokens/handlers/create.write.ts +47 -0
  429. package/src/personal-access-tokens/handlers/list.query.ts +50 -0
  430. package/src/personal-access-tokens/handlers/revoke.write.ts +37 -0
  431. package/src/personal-access-tokens/hash.ts +17 -0
  432. package/src/personal-access-tokens/i18n.ts +5 -0
  433. package/src/personal-access-tokens/index.ts +31 -0
  434. package/src/personal-access-tokens/resolver.ts +58 -0
  435. package/src/personal-access-tokens/roles.ts +29 -0
  436. package/src/personal-access-tokens/schema/api-token.ts +63 -0
  437. package/src/personal-access-tokens/scopes.ts +43 -0
  438. package/src/personal-access-tokens/web/client-plugin.tsx +29 -0
  439. package/src/personal-access-tokens/web/i18n.ts +69 -0
  440. package/src/personal-access-tokens/web/index.ts +7 -0
  441. package/src/personal-access-tokens/web/pat-tokens-screen.tsx +265 -0
  442. package/src/readiness/__tests__/readiness.integration.test.ts +4 -3
  443. package/src/renderer-simple/__tests__/resolve-variables.test.ts +37 -0
  444. package/src/renderer-simple/resolve-variables.ts +3 -1
  445. package/src/secrets/__tests__/rotate.integration.test.ts +115 -18
  446. package/src/secrets/handlers/rotate.job.ts +77 -98
  447. package/src/secrets/secrets-context.ts +7 -38
  448. package/src/secrets/table.ts +7 -8
  449. package/src/seo/__tests__/llms-txt.test.ts +38 -0
  450. package/src/seo/__tests__/robots-txt.test.ts +18 -0
  451. package/src/seo/__tests__/schema-builders.test.ts +57 -0
  452. package/src/seo/__tests__/seo.integration.test.ts +350 -0
  453. package/src/seo/__tests__/sitemap.test.ts +38 -0
  454. package/src/seo/constants.ts +60 -0
  455. package/src/seo/feature.ts +316 -0
  456. package/src/seo/handlers/seo-config.query.ts +35 -0
  457. package/src/seo/index.ts +19 -0
  458. package/src/seo/llms-txt.ts +33 -0
  459. package/src/seo/robots-txt.ts +13 -0
  460. package/src/seo/schema-builders.ts +57 -0
  461. package/src/seo/sitemap.ts +33 -0
  462. package/src/sessions/__tests__/auto-revoke-binding.test.ts +71 -0
  463. package/src/sessions/__tests__/cleanup.integration.test.ts +2 -1
  464. package/src/sessions/__tests__/password-auto-revoke.integration.test.ts +11 -7
  465. package/src/sessions/__tests__/rebuild-survival.integration.test.ts +18 -7
  466. package/src/sessions/__tests__/session-store-registration.test.ts +21 -0
  467. package/src/sessions/__tests__/sessions-screens.boot.test.ts +55 -0
  468. package/src/sessions/__tests__/sessions.integration.test.ts +272 -20
  469. package/src/sessions/__tests__/test-helpers.ts +16 -3
  470. package/src/sessions/constants.ts +5 -0
  471. package/src/sessions/db/queries/cleanup.ts +2 -2
  472. package/src/sessions/feature.ts +175 -26
  473. package/src/sessions/handlers/detail.query.ts +36 -0
  474. package/src/sessions/handlers/list.query.ts +14 -9
  475. package/src/sessions/handlers/mine.query.ts +13 -8
  476. package/src/sessions/handlers/revoke-all-for-user.write.ts +51 -12
  477. package/src/sessions/handlers/revoke-all-others.write.ts +19 -1
  478. package/src/sessions/handlers/revoke.write.ts +13 -0
  479. package/src/sessions/i18n.ts +23 -0
  480. package/src/sessions/index.ts +9 -2
  481. package/src/sessions/schema/user-session.ts +8 -2
  482. package/src/sessions/session-callbacks.ts +99 -14
  483. package/src/sessions/session-checker-fail-open.test.ts +77 -0
  484. package/src/sessions/session-revoked-event.ts +29 -0
  485. package/src/sessions/testing.ts +14 -1
  486. package/src/shared/__tests__/chunked-entity-migration.test.ts +145 -0
  487. package/src/shared/chunked-entity-migration.ts +89 -0
  488. package/src/shared/decrypt-stored-pii.ts +26 -0
  489. package/src/shared/encrypt-for-direct-write.ts +23 -0
  490. package/src/shared/entities-of.ts +17 -0
  491. package/src/shared/grace-period.test.ts +26 -0
  492. package/src/shared/grace-period.ts +20 -0
  493. package/src/{auth-email-password/__tests__ → shared}/identity-v3-hash.test.ts +2 -2
  494. package/src/shared/index.ts +16 -0
  495. package/src/shared/map-with-concurrency.test.ts +104 -0
  496. package/src/shared/map-with-concurrency.ts +38 -0
  497. package/src/shared/password-hashing.test.ts +35 -0
  498. package/src/{auth-email-password → shared}/password-hashing.ts +11 -0
  499. package/src/shared/system-query.ts +5 -0
  500. package/src/{auth-email-password → shared}/token-burn-store.ts +1 -1
  501. package/src/subscription-mollie/__tests__/feature.test.ts +97 -0
  502. package/src/subscription-mollie/__tests__/mollie-foundation.integration.test.ts +12 -1
  503. package/src/subscription-mollie/__tests__/verify-webhook.test.ts +21 -0
  504. package/src/subscription-mollie/verify-webhook.ts +1 -0
  505. package/src/subscription-stripe/__tests__/feature.test.ts +127 -1
  506. package/src/subscription-stripe/__tests__/runtime.test.ts +14 -24
  507. package/src/subscription-stripe/__tests__/stripe-foundation.integration.test.ts +15 -5
  508. package/src/subscription-stripe/__tests__/verify-webhook.test.ts +20 -0
  509. package/src/subscription-stripe/feature.ts +2 -0
  510. package/src/subscription-stripe/verify-webhook.ts +3 -2
  511. package/src/tags/__tests__/schemas.test.ts +34 -0
  512. package/src/tags/__tests__/tags.integration.test.ts +12 -1
  513. package/src/tags/entity.ts +6 -1
  514. package/src/tags/feature.ts +2 -0
  515. package/src/tags/i18n.ts +5 -0
  516. package/src/tags/schemas.ts +9 -2
  517. package/src/tags/web/__tests__/tag-manager.test.tsx +333 -0
  518. package/src/tags/web/__tests__/tag-section.test.tsx +47 -1
  519. package/src/tags/web/tag-manager.tsx +1 -1
  520. package/src/tags/web/tag-section.tsx +6 -3
  521. package/src/template-resolver/testing.ts +2 -1
  522. package/src/tenant/__tests__/members-query-kms.integration.test.ts +98 -0
  523. package/src/tenant/__tests__/members-screens.boot.test.ts +51 -0
  524. package/src/tenant/__tests__/multi-tenant.integration.test.ts +1 -1
  525. package/src/tenant/__tests__/tenant-security.integration.test.ts +310 -0
  526. package/src/tenant/__tests__/tenant.integration.test.ts +8 -4
  527. package/src/tenant/constants.ts +5 -0
  528. package/src/tenant/feature.ts +19 -0
  529. package/src/tenant/handlers/active-tenant-ids.query.ts +8 -2
  530. package/src/tenant/handlers/cancel-invitation.write.ts +2 -2
  531. package/src/tenant/handlers/invitations.query.ts +21 -4
  532. package/src/tenant/handlers/members.query.ts +41 -6
  533. package/src/tenant/i18n.ts +28 -0
  534. package/src/tenant/index.ts +7 -2
  535. package/src/tenant/invitation-table.ts +3 -2
  536. package/src/tenant/schema/tenant.ts +27 -2
  537. package/src/tenant/screens.ts +3 -2
  538. package/src/tenant/seeding.ts +136 -3
  539. package/src/tenant/web/client-plugin.tsx +20 -0
  540. package/src/tenant/web/i18n.ts +43 -0
  541. package/src/tenant/web/index.ts +4 -0
  542. package/src/tenant/web/members-screen.tsx +270 -0
  543. package/src/tenant-lifecycle/__tests__/stages.test.ts +22 -0
  544. package/src/tenant-lifecycle/__tests__/tenant-lifecycle.integration.test.ts +471 -0
  545. package/src/tenant-lifecycle/boot-checks.ts +45 -0
  546. package/src/tenant-lifecycle/constants.ts +56 -0
  547. package/src/tenant-lifecycle/events.ts +46 -0
  548. package/src/tenant-lifecycle/feature.ts +112 -0
  549. package/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +79 -0
  550. package/src/tenant-lifecycle/handlers/request-destruction.write.ts +83 -0
  551. package/src/tenant-lifecycle/index.ts +7 -0
  552. package/src/tenant-lifecycle/lib/revoke-tenant-sessions.ts +16 -0
  553. package/src/tenant-lifecycle/lifecycle-gate.ts +65 -0
  554. package/src/tenant-lifecycle/run-tenant-destroy.ts +368 -0
  555. package/src/tenant-lifecycle/stages.ts +206 -0
  556. package/src/tenant-settings/__tests__/tenant-settings.integration.test.ts +115 -0
  557. package/src/tenant-settings/config.ts +44 -0
  558. package/src/tenant-settings/constants.ts +16 -0
  559. package/src/tenant-settings/feature.ts +49 -0
  560. package/src/tenant-settings/index.ts +8 -0
  561. package/src/tenant-settings/tenant-defaults.ts +67 -0
  562. package/src/text-content/web/__tests__/editor-read-only.test.tsx +33 -6
  563. package/src/text-content/web/__tests__/group-blocks.test.ts +28 -3
  564. package/src/text-content/web/client-plugin.tsx +126 -104
  565. package/src/tier-engine/__tests__/auto-default-tier.integration.test.ts +1 -1
  566. package/src/tier-engine/__tests__/resolver.integration.test.ts +1 -1
  567. package/src/tier-engine/__tests__/tier-engine.integration.test.ts +7 -4
  568. package/src/tier-engine/__tests__/trial-config-validation.test.ts +30 -0
  569. package/src/tier-engine/feature.ts +13 -6
  570. package/src/tier-engine/i18n.ts +8 -0
  571. package/src/tier-engine/web/tier-admin-screen.tsx +26 -19
  572. package/src/user/__tests__/stream-tenant-backfill.integration.test.ts +186 -0
  573. package/src/user/__tests__/user.integration.test.ts +26 -0
  574. package/src/user/constants.ts +1 -0
  575. package/src/user/db/queries/stream-tenant-backfill.ts +112 -0
  576. package/src/user/feature.ts +3 -0
  577. package/src/user/handlers/find-for-auth.query.ts +8 -6
  578. package/src/user/i18n.ts +20 -0
  579. package/src/user/index.ts +4 -0
  580. package/src/user/schema/user.ts +6 -2
  581. package/src/user/screens.ts +3 -15
  582. package/src/user/seeding.ts +8 -0
  583. package/src/user-data-rights/COMPLIANCE.md +21 -1
  584. package/src/user-data-rights/__tests__/anonymous-deletion-kms.integration.test.ts +145 -0
  585. package/src/user-data-rights/__tests__/anonymous-deletion.integration.test.ts +3 -0
  586. package/src/user-data-rights/__tests__/audit-log.integration.test.ts +67 -0
  587. package/src/user-data-rights/__tests__/boot-checks.test.ts +170 -0
  588. package/src/user-data-rights/__tests__/cross-data-matrix.integration.test.ts +4 -1
  589. package/src/user-data-rights/__tests__/download-by-token-resolver-trust.integration.test.ts +169 -0
  590. package/src/user-data-rights/__tests__/download.integration.test.ts +101 -33
  591. package/src/user-data-rights/__tests__/email-templates.test.ts +15 -0
  592. package/src/user-data-rights/__tests__/export-encrypted-fields.integration.test.ts +154 -0
  593. package/src/user-data-rights/__tests__/export-job-idempotency.integration.test.ts +2 -0
  594. package/src/user-data-rights/__tests__/export-job-list-cross-tenant.integration.test.ts +2 -0
  595. package/src/user-data-rights/__tests__/file-binary-forget-cleanup.integration.test.ts +4 -1
  596. package/src/user-data-rights/__tests__/file-binary-forget-failure.integration.test.ts +4 -1
  597. package/src/user-data-rights/__tests__/file-retention.integration.test.ts +4 -1
  598. package/src/user-data-rights/__tests__/file-storage-unification.integration.test.ts +4 -1
  599. package/src/user-data-rights/__tests__/forget-cleanup-hook-ordering.integration.test.ts +41 -62
  600. package/src/user-data-rights/__tests__/forget-hook-registry.integration.test.ts +110 -0
  601. package/src/user-data-rights/__tests__/inspector-screens.boot.test.ts +14 -0
  602. package/src/user-data-rights/__tests__/mail-default-bridge.integration.test.ts +19 -2
  603. package/src/user-data-rights/__tests__/read-users-rebuild-survives-lifecycle.integration.test.ts +25 -14
  604. package/src/user-data-rights/__tests__/request-cancel-deletion.integration.test.ts +2 -0
  605. package/src/user-data-rights/__tests__/request-deletion-callback.integration.test.ts +4 -1
  606. package/src/user-data-rights/__tests__/request-export.integration.test.ts +2 -0
  607. package/src/user-data-rights/__tests__/restriction-flow.integration.test.ts +172 -19
  608. package/src/user-data-rights/__tests__/run-export-jobs-cron-context.integration.test.ts +2 -0
  609. package/src/user-data-rights/__tests__/run-export-jobs.integration.test.ts +257 -26
  610. package/src/user-data-rights/__tests__/run-forget-cleanup.integration.test.ts +339 -5
  611. package/src/user-data-rights/__tests__/run-user-export.integration.test.ts +77 -1
  612. package/src/user-data-rights/__tests__/tenant-model-erasure.integration.test.ts +2 -0
  613. package/src/user-data-rights/__tests__/user-data-rights.integration.test.ts +9 -1
  614. package/src/user-data-rights/boot-checks.ts +70 -0
  615. package/src/user-data-rights/db/queries/__tests__/forget-cleanup.integration.test.ts +69 -0
  616. package/src/user-data-rights/db/queries/forget-cleanup.ts +9 -2
  617. package/src/user-data-rights/email-templates.ts +6 -6
  618. package/src/user-data-rights/feature.ts +162 -43
  619. package/src/user-data-rights/handlers/cancel-deletion.write.ts +8 -11
  620. package/src/user-data-rights/handlers/deletion-grace-period.ts +4 -1
  621. package/src/user-data-rights/handlers/download-by-job.query.ts +26 -18
  622. package/src/user-data-rights/handlers/download-by-token.query.ts +23 -15
  623. package/src/user-data-rights/handlers/lift-restriction.write.ts +44 -19
  624. package/src/user-data-rights/handlers/list-download-attempts.query.ts +1 -0
  625. package/src/user-data-rights/handlers/my-audit-log.query.ts +1 -0
  626. package/src/user-data-rights/handlers/request-deletion-by-email.write.ts +3 -1
  627. package/src/user-data-rights/handlers/restrict-account.write.ts +58 -7
  628. package/src/user-data-rights/handlers/run-forget-cleanup.write.ts +2 -0
  629. package/src/user-data-rights/i18n.ts +35 -32
  630. package/src/user-data-rights/lib/is-admin-actor.ts +19 -0
  631. package/src/user-data-rights/lib/storage-provider-resolver.ts +6 -5
  632. package/src/user-data-rights/lib/update-user-lifecycle.ts +8 -24
  633. package/src/user-data-rights/run-export-jobs.ts +55 -19
  634. package/src/user-data-rights/run-forget-cleanup.ts +112 -7
  635. package/src/user-data-rights/run-user-export.ts +70 -4
  636. package/src/user-data-rights/schema/download-token.ts +5 -3
  637. package/src/user-data-rights/schema/export-job.ts +2 -0
  638. package/src/user-data-rights/screens.ts +2 -1
  639. package/src/user-data-rights/web/__tests__/privacy-center-screen.test.tsx +1 -0
  640. package/src/user-data-rights/web/__tests__/public-deletion-gate.test.tsx +7 -1
  641. package/src/user-data-rights/web/client-plugin.tsx +6 -1
  642. package/src/user-data-rights/web/i18n.ts +30 -10
  643. package/src/user-data-rights/web/privacy-center-screen.tsx +5 -3
  644. package/src/user-data-rights-defaults/__tests__/bundled-entities.userdata-hooks.integration.test.ts +455 -0
  645. package/src/user-data-rights-defaults/__tests__/feature-mounted.test.ts +24 -0
  646. package/src/user-data-rights-defaults/__tests__/user-data-rights-defaults.integration.test.ts +33 -9
  647. package/src/user-data-rights-defaults/feature.ts +78 -1
  648. package/src/user-data-rights-defaults/hooks/api-token.userdata-hook.ts +37 -0
  649. package/src/user-data-rights-defaults/hooks/config-value.userdata-hook.ts +51 -0
  650. package/src/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.ts +37 -0
  651. package/src/user-data-rights-defaults/hooks/feature-mounted.ts +10 -0
  652. package/src/user-data-rights-defaults/hooks/in-app-message.userdata-hook.ts +38 -0
  653. package/src/user-data-rights-defaults/hooks/job-run.userdata-hook.ts +35 -0
  654. package/src/user-data-rights-defaults/hooks/notification-preference.userdata-hook.ts +54 -0
  655. package/src/user-data-rights-defaults/hooks/tenant-invitation.userdata-hook.ts +112 -0
  656. package/src/user-data-rights-defaults/hooks/user-session.userdata-hook.ts +42 -0
  657. package/src/user-data-rights-defaults/index.ts +26 -0
  658. package/src/user-profile/__tests__/change-email.integration.test.ts +7 -1
  659. package/src/user-profile/__tests__/profile-screen.test.tsx +5 -0
  660. package/src/user-profile/feature.ts +4 -1
  661. package/src/user-profile/handlers/change-email.write.ts +15 -10
  662. package/src/user-profile/web/profile-screen.tsx +15 -3
  663. package/src/auth-email-password/stream-tenant.ts +0 -31
  664. package/src/feature-toggles/db/queries/toggle-state.ts +0 -25
  665. package/src/legal-pages/security-headers.ts +0 -1
  666. package/src/sessions/schema/index.ts +0 -5
  667. package/src/tenant/schema/index.ts +0 -5
  668. package/src/user/schema/index.ts +0 -5
  669. /package/src/{auth-email-password → shared}/identity-v3-hash.ts +0 -0
@@ -1,12 +1,15 @@
1
+ import type { HandlerContext } from "@cosmicdrift/kumiko-framework/engine";
1
2
  import {
3
+ buildSessionRoles,
2
4
  createSystemUser,
3
5
  defineWriteHandler,
4
6
  type SessionUser,
5
- stripForbiddenMembershipRoles,
6
7
  type TenantId,
8
+ type WriteResult,
7
9
  } from "@cosmicdrift/kumiko-framework/engine";
8
10
  import { parseRoles } from "@cosmicdrift/kumiko-framework/utils";
9
11
  import { z } from "zod";
12
+ import { verifyDummyPassword, verifyPassword } from "../../shared";
10
13
  import { USER_STATUS, UserQueries } from "../../user";
11
14
  import { parseAuthUserRow } from "../auth-user-row";
12
15
  import {
@@ -21,7 +24,6 @@ import {
21
24
  noMembership,
22
25
  } from "../errors";
23
26
  import { clearLockoutState, getLockoutState, recordFailedAttempt } from "../lockout-store";
24
- import { verifyPassword } from "../password-hashing";
25
27
 
26
28
  export type LoginHandlerOptions = {
27
29
  // When true, a valid (email + password) login fails with email_not_verified
@@ -40,10 +42,36 @@ export type LoginHandlerOptions = {
40
42
  readonly maxFailedAttempts?: number;
41
43
  readonly lockoutDurationMinutes?: number;
42
44
  };
45
+ // Optional second-factor gate. auth-mfa (if mounted) wires this in at
46
+ // app-composition time. Deliberately generic here — auth-email-password
47
+ // must not import auth-mfa's config, only this shape. Called AFTER a
48
+ // successful password verification (never before — the point is to add
49
+ // a second factor to a real credential match, not to gate on identity
50
+ // alone).
51
+ readonly mfaStatusChecker?: (
52
+ ctx: HandlerContext,
53
+ userId: string,
54
+ tenantId: TenantId,
55
+ // Merged global+tenant roles, computed by this handler BEFORE the
56
+ // gate runs — needed for policy values like "admins" that key off role.
57
+ roles: readonly string[],
58
+ ) => Promise<
59
+ | { readonly required: false }
60
+ | { readonly required: true; readonly challengeToken: string }
61
+ | { readonly setupRequired: true; readonly preauthSetupToken: string }
62
+ >;
43
63
  };
44
64
 
45
65
  const SYSTEM_USER_ID = "00000000-0000-4000-8000-000000000000";
46
66
 
67
+ // Two possible success shapes: a straight login mints a session; a login
68
+ // gated by MFA hands back a challenge instead — auth-routes.ts branches on
69
+ // `kind` to decide whether to mint a JWT now or wait for /auth/mfa/verify.
70
+ type LoginResult =
71
+ | { readonly kind: "auth-session"; readonly session: SessionUser }
72
+ | { readonly kind: "mfa-challenge"; readonly challengeToken: string }
73
+ | { readonly kind: "mfa-setup-required"; readonly preauthSetupToken: string };
74
+
47
75
  // Login — unauthenticated entry point. The route is wired public (no JWT
48
76
  // middleware), synthesising a guest SessionUser for the handler's access
49
77
  // check. Everything inside the handler goes through ctx.queryAs(system, ...)
@@ -62,7 +90,7 @@ export function createLoginHandler(opts: LoginHandlerOptions = {}) {
62
90
  password: z.string().min(1),
63
91
  }),
64
92
  access: { roles: ["all"] },
65
- handler: async (event, ctx) => {
93
+ handler: async (event, ctx): Promise<WriteResult<LoginResult>> => {
66
94
  const systemUser = createSystemUser(SYSTEM_USER_ID);
67
95
 
68
96
  const found = parseAuthUserRow(
@@ -74,6 +102,9 @@ export function createLoginHandler(opts: LoginHandlerOptions = {}) {
74
102
  // Uniform response on any credential mismatch (no user, wrong password,
75
103
  // soft-deleted user) — prevents email enumeration.
76
104
  if (!found?.passwordHash || found.isDeleted) {
105
+ // Burn the same argon2 verify cost as the hit path so response
106
+ // latency doesn't reveal whether the email is registered (#774).
107
+ await verifyDummyPassword(event.payload.password);
77
108
  return invalidCredentials();
78
109
  }
79
110
 
@@ -82,6 +113,14 @@ export function createLoginHandler(opts: LoginHandlerOptions = {}) {
82
113
  // for a timing-oracle on the bcrypt verify). If Redis isn't wired,
83
114
  // lockout is silently skipped — login still works, brute-force
84
115
  // protection just degrades to the IP-rate-limiter at the edge.
116
+ //
117
+ // Deliberately fail-open here, unlike auth-mfa's enable-confirm-preauth
118
+ // (which fails closed without Redis): the secret guarded by THIS gate
119
+ // is a full password, not a 6-digit code — locking out every login
120
+ // app-wide because Redis is briefly unavailable is a self-inflicted
121
+ // outage across every tenant, for a backstop that the IP-rate-limiter
122
+ // still partially covers. auth-mfa's blast radius is one user's MFA
123
+ // enrollment, not global login availability — different tradeoff.
85
124
  if (ctx.redis) {
86
125
  const state = await getLockoutState(ctx.redis, found.id);
87
126
  if (state?.lockedUntil !== null && state?.lockedUntil !== undefined) {
@@ -107,6 +146,17 @@ export function createLoginHandler(opts: LoginHandlerOptions = {}) {
107
146
  return invalidCredentials();
108
147
  }
109
148
 
149
+ // Clear the lockout state as soon as the password is proven — the
150
+ // password itself is the thing this counter guards, and MFA (if
151
+ // gated below) has its own separate attempt-cap. Doing this before
152
+ // the MFA gate matters: without it, an MFA user who occasionally
153
+ // mistypes their password accumulates failures across otherwise-
154
+ // successful logins and eventually gets password-locked out even
155
+ // though every login they completed was legitimate.
156
+ if (ctx.redis) {
157
+ await clearLockoutState(ctx.redis, found.id);
158
+ }
159
+
110
160
  // Strict verification gate — runs AFTER password check so an attacker
111
161
  // probing "email_not_verified" needs valid credentials first. The
112
162
  // remaining enumeration surface is "valid-cred + unverified" → accepted
@@ -152,25 +202,45 @@ export function createLoginHandler(opts: LoginHandlerOptions = {}) {
152
202
  return noMembership();
153
203
  }
154
204
 
155
- // Clear the lockout state on success. DEL is idempotent, so no need
156
- // to gate on "was there a counter?" — skipping the Redis round-trip
157
- // entirely for users who never failed a login would optimise the hot
158
- // path, but the call is microseconds and the branch isn't free either.
159
- if (ctx.redis) {
160
- await clearLockoutState(ctx.redis, found.id);
161
- }
162
-
163
205
  // Globale Rollen aus user.roles + tenant-membership-roles mergen.
164
206
  // Globale Rollen (SystemAdmin etc.) bleiben so über alle tenants
165
207
  // gleich; tenant-spezifische Rollen (Admin, User) kommen aus der
166
208
  // membership. Dedupe via Set damit eine Rolle die in beiden Quellen
167
209
  // steht nicht doppelt im Session-Roles landet.
210
+ //
211
+ // Computed BEFORE the MFA gate (moved up from its original spot below
212
+ // baseSession) because the gate's "admins" enforcement policy needs
213
+ // the MERGED set — a SystemAdmin whose admin-ness lives only in
214
+ // globalRoles would be missed if only chosen.roles were passed.
168
215
  const globalRoles = parseRoles(found.roles ?? null);
169
- // Strip reserved roles from the membership portion only (globalRoles keeps
170
- // SystemAdmin) — read-time backstop against a rebuild-resurrected role.
171
- const mergedRoles = Array.from(
172
- new Set([...globalRoles, ...stripForbiddenMembershipRoles(chosen.roles)]),
173
- );
216
+ // buildSessionRoles calls stripForbiddenMembershipRoles to strip reserved
217
+ // only (globalRoles keeps SystemAdmin) — read-time backstop against a
218
+ // rebuild-resurrected role.
219
+ const mergedRoles = buildSessionRoles(globalRoles, chosen.roles);
220
+
221
+ // Second-factor gate. Runs AFTER password verification (correct
222
+ // credentials proven) and AFTER tenant resolution (need chosen.tenantId
223
+ // to scope the MFA-enabled check). Three outcomes: enrolled → mint a
224
+ // challenge instead of a session (/auth/mfa/verify completes the
225
+ // login); policy demands MFA but the user never enrolled → block with
226
+ // mfa-setup-required (no session, no challenge — see auth-mfa's
227
+ // config.ts for why this deliberately hard-blocks); neither → proceed.
228
+ if (opts.mfaStatusChecker) {
229
+ const mfaStatus = await opts.mfaStatusChecker(ctx, found.id, chosen.tenantId, mergedRoles);
230
+ if ("challengeToken" in mfaStatus) {
231
+ return {
232
+ isSuccess: true,
233
+ data: { kind: "mfa-challenge", challengeToken: mfaStatus.challengeToken },
234
+ };
235
+ }
236
+ if ("setupRequired" in mfaStatus) {
237
+ return {
238
+ isSuccess: true,
239
+ data: { kind: "mfa-setup-required", preauthSetupToken: mfaStatus.preauthSetupToken },
240
+ };
241
+ }
242
+ }
243
+
174
244
  const baseSession: SessionUser = {
175
245
  id: found.id,
176
246
  tenantId: chosen.tenantId,
@@ -1,4 +1,4 @@
1
- import { access, defineWriteHandler, pipeline } from "@cosmicdrift/kumiko-framework/engine";
1
+ import { access, defineWriteHandler, stepsPipeline } from "@cosmicdrift/kumiko-framework/engine";
2
2
  import { z } from "zod";
3
3
 
4
4
  // Logout — JWT is stateless, so server-side we only return OK. A future
@@ -8,5 +8,7 @@ export const logoutWrite = defineWriteHandler({
8
8
  name: "logout",
9
9
  schema: z.object({}),
10
10
  access: { roles: access.authenticated },
11
- perform: pipeline(({ r }) => [r.step.return({ isSuccess: true, data: { kind: "logged-out" } })]),
11
+ perform: stepsPipeline(({ r }) => [
12
+ r.step.return({ isSuccess: true, data: { kind: "logged-out" } }),
13
+ ]),
12
14
  });
@@ -0,0 +1,38 @@
1
+ import { AUTH_UNLOCK_DEFAULT_TTL_MINUTES, AuthErrors } from "../constants";
2
+ import { renderUnlockAccountEmail } from "../email-templates";
3
+ import { signUnlockToken } from "../unlock-token";
4
+ import {
5
+ createTokenRequestHandler,
6
+ type TokenRequestData,
7
+ type TokenRequestOptions,
8
+ } from "./token-request-handler";
9
+
10
+ const UNLOCK_NOTIFICATION_TYPE = "auth-email-password:account-unlock";
11
+
12
+ export type RequestAccountUnlockOptions = TokenRequestOptions;
13
+
14
+ // Public shape re-exported for callers that build custom routes on top of
15
+ // the dispatcher (bypassing the framework's auth-routes).
16
+ export type RequestUnlockData = TokenRequestData<"unlock-requested">;
17
+
18
+ // @wrapper-known semantic-alias
19
+ export function createRequestAccountUnlockHandler(opts: RequestAccountUnlockOptions) {
20
+ return createTokenRequestHandler(
21
+ {
22
+ handlerName: "request-account-unlock",
23
+ successKind: "unlock-requested",
24
+ defaultTtlMinutes: AUTH_UNLOCK_DEFAULT_TTL_MINUTES,
25
+ sign: signUnlockToken,
26
+ notConfiguredError: AuthErrors.unlockNotConfigured,
27
+ notConfiguredI18nKey: "auth.errors.unlockNotConfigured",
28
+ // No extra skip condition — any existing, non-deleted user can
29
+ // request an unlock link regardless of whether they're actually
30
+ // locked right now. Confirming just clears the (possibly already
31
+ // empty) lockout state — harmless no-op if they weren't locked.
32
+ extraSilentSkip: () => false,
33
+ notificationType: UNLOCK_NOTIFICATION_TYPE,
34
+ renderContent: renderUnlockAccountEmail,
35
+ },
36
+ opts,
37
+ );
38
+ }
@@ -1,9 +1,10 @@
1
1
  import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
2
2
  import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
3
3
  import { z } from "zod";
4
+ import { hashPassword } from "../../shared";
4
5
  import { AuthErrors } from "../constants";
5
6
  import { invalidResetToken } from "../errors";
6
- import { hashPassword } from "../password-hashing";
7
+ import { passwordSchema } from "../password-policy";
7
8
  import { verifyResetToken } from "../reset-token";
8
9
  import { runConfirmTokenFlow } from "./confirm-token-flow";
9
10
 
@@ -22,7 +23,7 @@ export function createResetPasswordHandler(opts: ResetPasswordOptions) {
22
23
  name: "reset-password",
23
24
  schema: z.object({
24
25
  token: z.string().min(1),
25
- newPassword: z.string().min(8).max(200),
26
+ newPassword: passwordSchema,
26
27
  }),
27
28
  access: { roles: ["all"] },
28
29
  handler: async (event, ctx) => {
@@ -0,0 +1,23 @@
1
+ import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { z } from "zod";
3
+ import { AUTH_SELF_REGISTRATION_FEATURE } from "../self-registration-toggle";
4
+
5
+ // Anonymous-readable status for the (unauthenticated) signup page: lets it
6
+ // hide its own link/form when an operator has flipped self-registration
7
+ // off, instead of collecting an email that signup-request will silently
8
+ // no-op on. Lives in auth-email-password itself (never toggleable) rather
9
+ // than on the auth-self-registration companion feature — the dispatcher's
10
+ // per-feature gate would otherwise make this query unreachable exactly when
11
+ // the toggle is off (same split as managed-pages' branding query vs.
12
+ // css-gate.ts).
13
+ export const selfRegistrationStatusQuery = defineQueryHandler({
14
+ name: "signup-registration-status",
15
+ schema: z.object({}),
16
+ access: { roles: ["anonymous", "User", "TenantAdmin", "SystemAdmin"] },
17
+ handler: async (_query, ctx) => {
18
+ // Same fail-open reasoning as signup-request.write.ts (#1468): only
19
+ // report disabled when the toggle feature is actually composed.
20
+ const gated = ctx.registry.getFeature(AUTH_SELF_REGISTRATION_FEATURE) !== undefined;
21
+ return { enabled: gated ? await ctx.hasFeature(AUTH_SELF_REGISTRATION_FEATURE) : true };
22
+ },
23
+ });
@@ -33,6 +33,7 @@ import { z } from "zod";
33
33
  // kumiko-lint-ignore cross-feature-import signup-confirm reads tenants.key for slug-uniqueness check (TOCTOU + DB-unique-index zusammen)
34
34
  import { tenantTable } from "../../tenant/schema/tenant";
35
35
  import { invalidSignupToken, signupEmailAlreadyRegistered } from "../errors";
36
+ import { passwordSchema } from "../password-policy";
36
37
  // kumiko-lint-ignore cross-feature-import provisioning needs cross-feature seeding helpers
37
38
  import { INITIAL_SIGNUP_ROLES, provisionSignupAccount } from "../seeding";
38
39
  import {
@@ -44,7 +45,7 @@ import {
44
45
 
45
46
  const SignupConfirmSchema = z.object({
46
47
  token: z.string().min(8),
47
- password: z.string().min(8).max(200),
48
+ password: passwordSchema,
48
49
  });
49
50
 
50
51
  // Mirror der login-handler-Shape (kind: "auth-session", session: SessionUser)
@@ -108,16 +109,23 @@ export function createSignupConfirmHandler() {
108
109
 
109
110
  let provisioned: { readonly userId: string; readonly tenantId: TenantId };
110
111
  try {
111
- provisioned = await provisionSignupAccount(dbConn, {
112
- email,
113
- password: event.payload.password,
114
- displayName,
115
- tenantId,
116
- tenantKey,
117
- // Tenant-Display-Name als Default = Email. User wechselt das im
118
- // Settings-Screen. Konzept "Tenant" leakt nicht in die Signup-UI.
119
- tenantName: email,
120
- });
112
+ provisioned = await provisionSignupAccount(
113
+ dbConn,
114
+ {
115
+ email,
116
+ password: event.payload.password,
117
+ displayName,
118
+ tenantId,
119
+ tenantKey,
120
+ // Tenant-Display-Name als Default = Email. User wechselt das im
121
+ // Settings-Screen. Konzept "Tenant" leakt nicht in die Signup-UI.
122
+ tenantName: email,
123
+ },
124
+ // #1463: seedTenant's postSave hooks (tier-engine's auto-default-
125
+ // tier, an app's auto-default-compliance) must fire on self-signup
126
+ // exactly like they do on the regular TenantHandlers.create path.
127
+ { registry: ctx.registry, context: ctx },
128
+ );
121
129
  } catch (err) {
122
130
  // Email hat bereits ein Konto — provisionSignupAccount ist create-only
123
131
  // (#365): sauberer User-Fehler, KEINE Session für den fremden Account.
@@ -31,6 +31,7 @@ import { AUTH_SIGNUP_DEFAULT_TTL_MINUTES } from "../constants";
31
31
  import type { AuthMailLocale } from "../email-templates";
32
32
  import { renderActivationEmail } from "../email-templates";
33
33
  import { dispatchMagicLinkMail } from "../magic-link-mail";
34
+ import { AUTH_SELF_REGISTRATION_FEATURE } from "../self-registration-toggle";
34
35
  import { getTokenForSignupEmail, normalizeEmail, storeSignupToken } from "../signup-token-store";
35
36
 
36
37
  const SIGNUP_NOTIFICATION_TYPE = "auth-email-password:signup-activation";
@@ -68,6 +69,27 @@ export function createSignupRequestHandler(opts: SignupRequestOptions) {
68
69
  schema: SignupRequestSchema,
69
70
  access: { roles: ["all"] },
70
71
  handler: async (event, ctx) => {
72
+ // Silent no-op when off, matching the route's own always-200
73
+ // anti-enumeration contract (registerTokenRequestRoute swallows every
74
+ // handler failure into `{isSuccess:true}` regardless) — no mail goes
75
+ // out, but the caller can't distinguish "disabled" from "unknown
76
+ // email" either way. The client-visible signal is the `status` query
77
+ // on auth-self-registration, which the signup page uses to hide its
78
+ // own link/form instead of collecting input that silently no-ops.
79
+ // Fail-open when the companion toggle feature isn't composed at all —
80
+ // ctx.hasFeature() is fail-closed for *unregistered* names the moment
81
+ // feature-toggles/tier-engine is wired, which silently kills
82
+ // self-signup for every existing app that mounts auth-email-password
83
+ // without also composing createAuthSelfRegistrationToggleFeature()
84
+ // (#1468). Only gate when the toggle actually exists.
85
+ const gated = ctx.registry.getFeature(AUTH_SELF_REGISTRATION_FEATURE) !== undefined;
86
+ if (gated && !(await ctx.hasFeature(AUTH_SELF_REGISTRATION_FEATURE))) {
87
+ // No email address here — this fires on every no-op request, PII
88
+ // must not accumulate in logs just for the "was this toggled off"
89
+ // signal a support ticket needs.
90
+ ctx.log?.info("signup-request skipped: self-registration disabled");
91
+ return { isSuccess: true, data: { kind: "no-op" } };
92
+ }
71
93
  if (!ctx.redis) {
72
94
  return writeFailure(
73
95
  new InternalError({
@@ -125,7 +125,9 @@ export function createTokenRequestHandler<TName extends string, TSuccessKind ext
125
125
  renderContent: spec.renderContent,
126
126
  },
127
127
  {
128
- email: user.email,
128
+ // Row value is ciphertext with an active KMS; the byte-exact
129
+ // lookup above guarantees the payload IS the stored plaintext.
130
+ email: event.payload.email,
129
131
  appUrl: opts.appUrl,
130
132
  token,
131
133
  expiresAt: expiresAt.toString(),
@@ -136,7 +138,7 @@ export function createTokenRequestHandler<TName extends string, TSuccessKind ext
136
138
 
137
139
  const data: TokenRequestData<TSuccessKind> = {
138
140
  kind: spec.successKind,
139
- email: user.email,
141
+ email: event.payload.email,
140
142
  token,
141
143
  expiresAt: expiresAt.toString(),
142
144
  };
@@ -18,6 +18,7 @@ export const defaultTranslations: TranslationsByLocale = {
18
18
  "auth.login.submit": "Einloggen",
19
19
  "auth.login.submitting": "…",
20
20
  "auth.login.forgotPassword": "Passwort vergessen?",
21
+ "auth.login.unlockAccount": "Konto entsperren?",
21
22
  "auth.login.resendVerification": "Bestätigungs-Mail erneut senden",
22
23
  "auth.login.resendSuccess": "Wir haben dir eine neue Bestätigungs-Mail geschickt.",
23
24
  "auth.login.resendRateLimited": "Bitte warte kurz und versuche es erneut.",
@@ -32,9 +33,15 @@ export const defaultTranslations: TranslationsByLocale = {
32
33
  "auth.errors.rateLimited": "Zu viele Login-Versuche. Bitte kurz warten.",
33
34
  "auth.errors.invalidBody": "Ungültige Eingabe.",
34
35
  "auth.errors.loginFailed": "Login fehlgeschlagen.",
36
+ "auth.errors.mfaNotSupported":
37
+ "Diese App unterstützt keine Zwei-Faktor-Bestätigung. Bitte an den Support wenden.",
38
+ "auth.errors.mfaSetupRequired":
39
+ "Zwei-Faktor-Authentifizierung erforderlich. Bitte an den Administrator wenden.",
35
40
  "auth.errors.invalidResetToken":
36
41
  "Der Link ist ungültig oder abgelaufen. Bitte fordere einen neuen an.",
37
42
  "auth.errors.invalidVerificationToken": "Der Bestätigungs-Link ist ungültig oder abgelaufen.",
43
+ "auth.errors.invalidUnlockToken":
44
+ "Der Entsperren-Link ist ungültig oder abgelaufen. Bitte fordere einen neuen an.",
38
45
  "auth.errors.invalidSignupToken":
39
46
  "Der Aktivierungs-Link ist ungültig oder abgelaufen. Bitte fordere einen neuen an.",
40
47
  "auth.errors.signupEmailAlreadyRegistered":
@@ -72,6 +79,26 @@ export const defaultTranslations: TranslationsByLocale = {
72
79
  "Der Link ist ungültig oder abgelaufen. Bitte fordere eine neue Bestätigungs-Mail an.",
73
80
  "auth.verifyEmail.goToLogin": "Zum Login",
74
81
  "auth.verifyEmail.missingToken": "Der Bestätigungs-Link enthält keinen Token.",
82
+ "auth.requestUnlock.title": "Konto entsperren",
83
+ "auth.requestUnlock.intro":
84
+ "Gib deine E-Mail-Adresse ein. Falls dein Konto gesperrt ist, schicken wir dir einen Entsperren-Link.",
85
+ "auth.requestUnlock.email": "E-Mail",
86
+ "auth.requestUnlock.submit": "Link anfordern",
87
+ "auth.requestUnlock.submitting": "…",
88
+ "auth.requestUnlock.successTitle": "Mail gesendet",
89
+ "auth.requestUnlock.successBody":
90
+ "Falls die E-Mail in unserem System existiert und gesperrt ist, ist eine Nachricht mit einem Entsperren-Link unterwegs. Bitte schau in deinen Posteingang.",
91
+ "auth.requestUnlock.backToLogin": "Zurück zum Login",
92
+ "auth.requestUnlock.rateLimited": "Zu viele Anfragen. Neuer Versuch in {minutes} Minuten.",
93
+ "auth.unlockAccount.verifying": "Konto wird entsperrt …",
94
+ "auth.unlockAccount.successTitle": "Konto entsperrt",
95
+ "auth.unlockAccount.successBody":
96
+ "Dein Konto ist wieder entsperrt. Du kannst dich jetzt anmelden.",
97
+ "auth.unlockAccount.errorTitle": "Entsperren fehlgeschlagen",
98
+ "auth.unlockAccount.errorBody":
99
+ "Der Link ist ungültig oder abgelaufen. Bitte fordere einen neuen Entsperren-Link an.",
100
+ "auth.unlockAccount.goToLogin": "Zum Login",
101
+ "auth.unlockAccount.missingToken": "Der Entsperren-Link enthält keinen Token.",
75
102
  "auth.signup.title": "Account erstellen",
76
103
  "auth.signup.intro":
77
104
  "Gib deine E-Mail-Adresse ein. Wir schicken dir einen Aktivierungs-Link, mit dem du dein Passwort setzt.",
@@ -120,6 +147,7 @@ export const defaultTranslations: TranslationsByLocale = {
120
147
  "auth.login.submit": "Sign in",
121
148
  "auth.login.submitting": "…",
122
149
  "auth.login.forgotPassword": "Forgot password?",
150
+ "auth.login.unlockAccount": "Unlock account?",
123
151
  "auth.login.resendVerification": "Send verification email again",
124
152
  "auth.login.resendSuccess": "We've sent you a new verification email.",
125
153
  "auth.login.resendRateLimited": "Please wait a moment and try again.",
@@ -134,8 +162,14 @@ export const defaultTranslations: TranslationsByLocale = {
134
162
  "auth.errors.rateLimited": "Too many login attempts. Please wait briefly.",
135
163
  "auth.errors.invalidBody": "Invalid input.",
136
164
  "auth.errors.loginFailed": "Login failed.",
165
+ "auth.errors.mfaNotSupported":
166
+ "This app doesn't support two-factor verification. Please contact support.",
167
+ "auth.errors.mfaSetupRequired":
168
+ "Two-factor authentication required. Please contact your administrator.",
137
169
  "auth.errors.invalidResetToken": "Link is invalid or expired. Please request a new one.",
138
170
  "auth.errors.invalidVerificationToken": "Verification link is invalid or expired.",
171
+ "auth.errors.invalidUnlockToken":
172
+ "Unlock link is invalid or expired. Please request a new one.",
139
173
  "auth.errors.invalidSignupToken":
140
174
  "Activation link is invalid or expired. Please request a new one.",
141
175
  "auth.errors.signupEmailAlreadyRegistered":
@@ -172,6 +206,24 @@ export const defaultTranslations: TranslationsByLocale = {
172
206
  "Link is invalid or expired. Please request a new verification email.",
173
207
  "auth.verifyEmail.goToLogin": "Go to sign in",
174
208
  "auth.verifyEmail.missingToken": "Verification link is missing a token.",
209
+ "auth.requestUnlock.title": "Unlock account",
210
+ "auth.requestUnlock.intro":
211
+ "Enter your email address. If your account is locked, we'll send you an unlock link.",
212
+ "auth.requestUnlock.email": "Email",
213
+ "auth.requestUnlock.submit": "Request link",
214
+ "auth.requestUnlock.submitting": "…",
215
+ "auth.requestUnlock.successTitle": "Email sent",
216
+ "auth.requestUnlock.successBody":
217
+ "If that email exists in our system and is locked, a message with an unlock link is on its way. Please check your inbox.",
218
+ "auth.requestUnlock.backToLogin": "Back to sign in",
219
+ "auth.requestUnlock.rateLimited": "Too many requests. Try again in {minutes} minutes.",
220
+ "auth.unlockAccount.verifying": "Unlocking account …",
221
+ "auth.unlockAccount.successTitle": "Account unlocked",
222
+ "auth.unlockAccount.successBody": "Your account is unlocked again. You can sign in now.",
223
+ "auth.unlockAccount.errorTitle": "Unlock failed",
224
+ "auth.unlockAccount.errorBody": "Link is invalid or expired. Please request a new unlock link.",
225
+ "auth.unlockAccount.goToLogin": "Go to sign in",
226
+ "auth.unlockAccount.missingToken": "Unlock link is missing a token.",
175
227
  "auth.signup.title": "Create account",
176
228
  "auth.signup.intro":
177
229
  "Enter your email. We'll send you an activation link to set your password.",
@@ -1,7 +1,13 @@
1
1
  // Convention paths for the auth pages (reset/verify/signup/invite); apps build
2
2
  // their magic-link appUrls from these. All mail now goes through delivery.
3
+
4
+ // Kept as a public re-export (not just internal call sites) — this feature's
5
+ // barrel is where consumers of the published package have always found
6
+ // hashPassword/verifyPassword; removing it is a breaking change that needs
7
+ // its own changeset + deliberate deprecation window, not a silent drop.
8
+ export { hashPassword, verifyPassword } from "../shared/password-hashing";
3
9
  export { type AuthPaths, DEFAULT_AUTH_PATHS, makeAuthPaths } from "./auth-paths";
4
- export { AUTH_EMAIL_PASSWORD_FEATURE, AuthErrors, AuthHandlers } from "./constants";
10
+ export { AUTH_EMAIL_PASSWORD_FEATURE, AuthErrors, AuthHandlers, AuthQueries } from "./constants";
5
11
  // Renderers for the auth mails. All four magic-link flows (reset, verify,
6
12
  // signup-activation, invite) emit structured AuthMailContent through delivery
7
13
  // (ctx.notify).
@@ -16,10 +22,12 @@ export {
16
22
  renderActivationEmail,
17
23
  renderInviteEmail,
18
24
  renderResetPasswordEmail,
25
+ renderUnlockAccountEmail,
19
26
  renderVerifyEmail,
20
27
  } from "./email-templates";
21
28
  export type {
22
29
  AccountLockoutOptions,
30
+ AccountUnlockOptions,
23
31
  AuthEmailPasswordOptions,
24
32
  EmailVerificationOptions,
25
33
  InviteOptions,
@@ -27,7 +35,10 @@ export type {
27
35
  SignupOptions,
28
36
  } from "./feature";
29
37
  export { authEmailPasswordEnvSchema, createAuthEmailPasswordFeature } from "./feature";
30
- export { hashPassword, verifyPassword } from "./password-hashing";
38
+ export {
39
+ AUTH_SELF_REGISTRATION_FEATURE,
40
+ createAuthSelfRegistrationToggleFeature,
41
+ } from "./self-registration-toggle";
31
42
  // Generic HMAC-signed single-purpose token helpers. Re-exported damit
32
43
  // app-spezifische out-of-band-Flows (subscriber-confirm, magic-links,
33
44
  // invite-tokens) denselben battle-tested signer/verifier nutzen können
@@ -31,9 +31,13 @@ export type LockoutState = {
31
31
  // Consequence of the monotonic counter: once a user has been locked, the
32
32
  // NEXT wrong password after the lock expires re-locks immediately — the
33
33
  // INCR still returns a value ≥ threshold, so the SET NX re-arms the lock.
34
- // A successful login is the only way to reset the streak. Intentional:
35
- // brute-force resistance favours strictness over UX, and a legitimate user
36
- // who hit the lock once can just log in correctly to clear it.
34
+ // A successful login is one way to reset the streak; the other is the
35
+ // account-unlock magic-link flow (#1266, see
36
+ // handlers/confirm-account-unlock.write.ts), a deliberate escape hatch for
37
+ // a legitimate user who can't currently produce the right password (e.g.
38
+ // they forgot it too) but can prove mailbox ownership. Intentional:
39
+ // brute-force resistance favours strictness over UX for anonymous login
40
+ // attempts, while the unlock flow keeps the DoS from being permanent.
37
41
  const COUNT_KEY_PREFIX = "kumiko:auth:lockout:count:";
38
42
  const UNTIL_KEY_PREFIX = "kumiko:auth:lockout:until:";
39
43
 
@@ -111,8 +115,9 @@ export async function recordFailedAttempt(
111
115
  return { failureCount: count, lockedUntil };
112
116
  }
113
117
 
114
- // Called on successful login. Idempotent — deleting missing keys is a no-op.
115
- // The ONLY path that resets the counter; intentional.
118
+ // Called on successful login, and on a confirmed account-unlock token
119
+ // (#1266). Idempotent — deleting missing keys is a no-op, so a replayed
120
+ // unlock link just re-clears harmlessly.
116
121
  export async function clearLockoutState(redis: Redis, userId: string): Promise<void> {
117
122
  await redis.del(countKey(userId), untilKey(userId));
118
123
  }