@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
@@ -23,12 +23,12 @@ import {
23
23
  import { createConfigFeature } from "../../config/feature";
24
24
  import { createConfigResolver } from "../../config/resolver";
25
25
  import { configValuesTable } from "../../config/table";
26
+ import { verifyPassword } from "../../shared";
26
27
  import { createTenantFeature } from "../../tenant/feature";
27
28
  import { tenantMembershipsTable } from "../../tenant/membership-table";
28
29
  import { tenantEntity, tenantTable } from "../../tenant/schema/tenant";
29
30
  import { createUserFeature } from "../../user/feature";
30
31
  import { userEntity, userTable } from "../../user/schema/user";
31
- import { verifyPassword } from "../password-hashing";
32
32
  import { seedAdmin } from "../testing";
33
33
 
34
34
  let stack: TestStack;
@@ -0,0 +1,264 @@
1
+ // Runtime on/off switch for self-signup (#self-registration-toggle). Pins:
2
+ // 1. Default (no override row) → signup-request works.
3
+ // 2. runtime.apply(off) → signup-request still 200 (silent no-op, anti-enumeration), no mail sent.
4
+ // 3. runtime.apply(on) → works again.
5
+ // Mirrors samples/recipes/feature-toggles' runtime.apply() pattern — flips the
6
+ // in-memory snapshot directly instead of going through the set-handler/HTTP,
7
+ // since that wiring is already covered by feature-toggles' own tests.
8
+
9
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
10
+ import {
11
+ createFeatureTogglesFeature,
12
+ GlobalFeatureToggleRuntime,
13
+ globalFeatureStateTable,
14
+ } from "@cosmicdrift/kumiko-bundled-features/feature-toggles";
15
+ import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
16
+ import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
17
+ import {
18
+ setupTestStack,
19
+ type TestStack,
20
+ unsafeCreateEntityTable,
21
+ unsafePushTables,
22
+ } from "@cosmicdrift/kumiko-framework/stack";
23
+ import { createLateBoundHolder } from "@cosmicdrift/kumiko-framework/testing";
24
+ import { createChannelEmailFeature, createInMemoryTransport } from "../../channel-email";
25
+ import { createConfigFeature } from "../../config";
26
+ import { createConfigResolver } from "../../config/resolver";
27
+ import { configValuesTable } from "../../config/table";
28
+ import { createDeliveryFeature, createDeliveryTestContext } from "../../delivery";
29
+ import { notificationPreferencesTable } from "../../delivery/tables";
30
+ import { createRendererFoundationFeature } from "../../renderer-foundation/feature";
31
+ import { createRendererSimpleFeature, simpleRenderer } from "../../renderer-simple";
32
+ import { createTemplateResolverFeature } from "../../template-resolver/feature";
33
+ import { createTenantFeature } from "../../tenant";
34
+ import { tenantMembershipsTable } from "../../tenant/membership-table";
35
+ import { tenantEntity } from "../../tenant/schema/tenant";
36
+ import { createUserFeature } from "../../user/feature";
37
+ import { userEntity, userTable } from "../../user/schema/user";
38
+ import { AuthHandlers } from "../constants";
39
+ import { createAuthEmailPasswordFeature } from "../feature";
40
+ import {
41
+ AUTH_SELF_REGISTRATION_FEATURE,
42
+ createAuthSelfRegistrationToggleFeature,
43
+ } from "../self-registration-toggle";
44
+
45
+ const APP_ACTIVATION_URL = "https://app.example.com/signup/complete";
46
+ const emailTransport = createInMemoryTransport();
47
+
48
+ let stack: TestStack;
49
+ let runtime: GlobalFeatureToggleRuntime;
50
+
51
+ beforeAll(async () => {
52
+ let effective: () => ReadonlySet<string> = () => new Set();
53
+ const runtimeHolder = createLateBoundHolder<GlobalFeatureToggleRuntime>("runtime");
54
+
55
+ stack = await setupTestStack({
56
+ features: [
57
+ createConfigFeature(),
58
+ createUserFeature(),
59
+ createTenantFeature(),
60
+ createTemplateResolverFeature(),
61
+ createRendererFoundationFeature(),
62
+ createDeliveryFeature(),
63
+ createRendererSimpleFeature(),
64
+ createChannelEmailFeature({
65
+ transport: emailTransport,
66
+ renderer: simpleRenderer,
67
+ resolveEmail: async () => "unused@test.local",
68
+ }),
69
+ createAuthEmailPasswordFeature({
70
+ signup: { tokenTtlMinutes: 60, appUrl: APP_ACTIVATION_URL },
71
+ }),
72
+ createAuthSelfRegistrationToggleFeature(),
73
+ createFeatureTogglesFeature({ getRuntime: () => runtimeHolder.get() }),
74
+ ],
75
+ effectiveFeatures: () => effective(),
76
+ extraContext: (deps) => ({
77
+ ...createDeliveryTestContext(deps),
78
+ configResolver: createConfigResolver(),
79
+ }),
80
+ systemHooks: [],
81
+ anonymousAccess: { defaultTenantId: SYSTEM_TENANT_ID },
82
+ authConfig: {
83
+ membershipQuery: "tenant:query:memberships",
84
+ loginHandler: AuthHandlers.login,
85
+ signup: {
86
+ requestHandler: AuthHandlers.signupRequest,
87
+ confirmHandler: AuthHandlers.signupConfirm,
88
+ },
89
+ },
90
+ });
91
+
92
+ await unsafeCreateEntityTable(stack.db, userEntity);
93
+ await unsafeCreateEntityTable(stack.db, tenantEntity);
94
+ // globalFeatureStateTable is auto-provisioned by setupTestStack — no
95
+ // manual push needed (that used to mask a missing r.storeTable()
96
+ // registration entirely).
97
+ await unsafePushTables(stack.db, {
98
+ configValuesTable,
99
+ tenantMembershipsTable,
100
+ notificationPreferencesTable,
101
+ });
102
+
103
+ runtime = new GlobalFeatureToggleRuntime(stack.db, stack.registry);
104
+ await runtime.initialize();
105
+ effective = runtime.effectiveFeatures;
106
+ runtimeHolder.set(runtime);
107
+ });
108
+
109
+ afterAll(async () => {
110
+ await stack.cleanup();
111
+ });
112
+
113
+ beforeEach(async () => {
114
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
115
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${globalFeatureStateTable.tableName}"`);
116
+ await runtime.refresh();
117
+ emailTransport.sent.length = 0;
118
+ const allKeys = await stack.redis.redis.keys("signup:*");
119
+ if (allKeys.length > 0) await stack.redis.redis.del(...allKeys);
120
+ });
121
+
122
+ async function postSignupRequest(email: string): Promise<Response> {
123
+ return stack.http.raw("POST", "/api/auth/signup-request", { email });
124
+ }
125
+
126
+ describe("auth-self-registration toggle", () => {
127
+ test("default (no override) → signup-request works", async () => {
128
+ const res = await postSignupRequest("alice@example.com");
129
+ expect(res.status).toBe(200);
130
+ expect(emailTransport.sent).toHaveLength(1);
131
+ });
132
+
133
+ test("runtime off → signup-request still 200 (anti-enumeration silent-success) but no mail sent", async () => {
134
+ runtime.apply(AUTH_SELF_REGISTRATION_FEATURE, false);
135
+
136
+ const res = await postSignupRequest("blocked@example.com");
137
+ expect(res.status).toBe(200);
138
+ expect(emailTransport.sent).toHaveLength(0);
139
+ });
140
+
141
+ test("runtime back on → mail sends again", async () => {
142
+ runtime.apply(AUTH_SELF_REGISTRATION_FEATURE, false);
143
+ await postSignupRequest("still-blocked@example.com");
144
+ expect(emailTransport.sent).toHaveLength(0);
145
+
146
+ runtime.apply(AUTH_SELF_REGISTRATION_FEATURE, true);
147
+ const res = await postSignupRequest("reenabled@example.com");
148
+ expect(res.status).toBe(200);
149
+ expect(emailTransport.sent).toHaveLength(1);
150
+ });
151
+
152
+ test("status query reflects the runtime flip", async () => {
153
+ const onRes = await stack.http.raw("POST", "/api/query", {
154
+ type: "auth-email-password:query:signup-registration-status",
155
+ payload: {},
156
+ });
157
+ expect((await onRes.json()) as { data?: { enabled: boolean } }).toMatchObject({
158
+ data: { enabled: true },
159
+ });
160
+
161
+ runtime.apply(AUTH_SELF_REGISTRATION_FEATURE, false);
162
+ const offRes = await stack.http.raw("POST", "/api/query", {
163
+ type: "auth-email-password:query:signup-registration-status",
164
+ payload: {},
165
+ });
166
+ expect((await offRes.json()) as { data?: { enabled: boolean } }).toMatchObject({
167
+ data: { enabled: false },
168
+ });
169
+ });
170
+ });
171
+
172
+ // Regression for Finding 1 (#1468): the suite above always composes the
173
+ // companion toggle feature alongside feature-toggles. An app that mounts
174
+ // createFeatureTogglesFeature + a real effectiveFeatures resolver but
175
+ // forgets (or never knew to) compose createAuthSelfRegistrationToggleFeature
176
+ // must not go silently signup-dead — ctx.hasFeature() is checked against a
177
+ // feature name the registry never registered.
178
+ describe("auth-self-registration toggle — companion feature NOT composed", () => {
179
+ const bareEmailTransport = createInMemoryTransport();
180
+ let bareStack: TestStack;
181
+ let bareRuntime: GlobalFeatureToggleRuntime;
182
+
183
+ beforeAll(async () => {
184
+ const runtimeHolder = createLateBoundHolder<GlobalFeatureToggleRuntime>("bare-runtime");
185
+ let effective: () => ReadonlySet<string> = () => new Set();
186
+
187
+ bareStack = await setupTestStack({
188
+ features: [
189
+ createConfigFeature(),
190
+ createUserFeature(),
191
+ createTenantFeature(),
192
+ createTemplateResolverFeature(),
193
+ createRendererFoundationFeature(),
194
+ createDeliveryFeature(),
195
+ createRendererSimpleFeature(),
196
+ createChannelEmailFeature({
197
+ transport: bareEmailTransport,
198
+ renderer: simpleRenderer,
199
+ resolveEmail: async () => "unused@test.local",
200
+ }),
201
+ createAuthEmailPasswordFeature({
202
+ signup: { tokenTtlMinutes: 60, appUrl: APP_ACTIVATION_URL },
203
+ }),
204
+ // Deliberately NO createAuthSelfRegistrationToggleFeature().
205
+ createFeatureTogglesFeature({ getRuntime: () => runtimeHolder.get() }),
206
+ ],
207
+ effectiveFeatures: () => effective(),
208
+ extraContext: (deps) => ({
209
+ ...createDeliveryTestContext(deps),
210
+ configResolver: createConfigResolver(),
211
+ }),
212
+ systemHooks: [],
213
+ anonymousAccess: { defaultTenantId: SYSTEM_TENANT_ID },
214
+ authConfig: {
215
+ membershipQuery: "tenant:query:memberships",
216
+ loginHandler: AuthHandlers.login,
217
+ signup: {
218
+ requestHandler: AuthHandlers.signupRequest,
219
+ confirmHandler: AuthHandlers.signupConfirm,
220
+ },
221
+ },
222
+ });
223
+
224
+ await unsafeCreateEntityTable(bareStack.db, userEntity);
225
+ await unsafeCreateEntityTable(bareStack.db, tenantEntity);
226
+ await unsafePushTables(bareStack.db, {
227
+ configValuesTable,
228
+ tenantMembershipsTable,
229
+ notificationPreferencesTable,
230
+ });
231
+
232
+ bareRuntime = new GlobalFeatureToggleRuntime(bareStack.db, bareStack.registry);
233
+ await bareRuntime.initialize();
234
+ effective = bareRuntime.effectiveFeatures;
235
+ runtimeHolder.set(bareRuntime);
236
+ });
237
+
238
+ afterAll(async () => {
239
+ await bareStack.cleanup();
240
+ });
241
+
242
+ // Fixed (#1468): ctx.hasFeature() is fail-closed for a name nothing ever
243
+ // registered — signup-request and the status query now only gate on the
244
+ // toggle when registry.getFeature(AUTH_SELF_REGISTRATION_FEATURE) proves
245
+ // the companion feature is actually composed, so an app that mounts
246
+ // auth-email-password without it keeps self-signup working.
247
+ test("signup-request still sends mail when the companion toggle feature was never composed", async () => {
248
+ const res = await bareStack.http.raw("POST", "/api/auth/signup-request", {
249
+ email: "not-toggle-aware@example.com",
250
+ });
251
+ expect(res.status).toBe(200);
252
+ expect(bareEmailTransport.sent).toHaveLength(1);
253
+ });
254
+
255
+ test("signup-registration-status reports enabled:true with no toggle feature mounted", async () => {
256
+ const res = await bareStack.http.raw("POST", "/api/query", {
257
+ type: "auth-email-password:query:signup-registration-status",
258
+ payload: {},
259
+ });
260
+ expect((await res.json()) as { data?: { enabled: boolean } }).toMatchObject({
261
+ data: { enabled: true },
262
+ });
263
+ });
264
+ });
@@ -1,7 +1,6 @@
1
1
  import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
2
2
  import { randomBytes } from "node:crypto";
3
3
  import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
4
- import { createEncryptionProvider } from "@cosmicdrift/kumiko-framework/db";
5
4
  import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
6
5
  import {
7
6
  setupTestStack,
@@ -10,10 +9,12 @@ import {
10
9
  unsafeCreateEntityTable,
11
10
  unsafePushTables,
12
11
  } from "@cosmicdrift/kumiko-framework/stack";
12
+ import { createTestEnvelopeCipher } from "@cosmicdrift/kumiko-framework/testing";
13
13
  import * as jose from "jose";
14
14
  import { createConfigFeature } from "../../config";
15
15
  import { createConfigResolver } from "../../config/resolver";
16
16
  import { configValuesTable } from "../../config/table";
17
+ import { hashPassword } from "../../shared";
17
18
  import { createTenantFeature } from "../../tenant";
18
19
  import { tenantMembershipsTable } from "../../tenant/membership-table";
19
20
  import { tenantEntity } from "../../tenant/schema/tenant";
@@ -23,7 +24,6 @@ import { createUserFeature } from "../../user/feature";
23
24
  import { userEntity, userTable } from "../../user/schema/user";
24
25
  import { AuthErrors, AuthHandlers } from "../constants";
25
26
  import { createAuthEmailPasswordFeature } from "../feature";
26
- import { hashPassword } from "../password-hashing";
27
27
 
28
28
  // In-memory fake of a real sessions-store — just enough to observe that the
29
29
  // framework calls the callbacks at the right moments and threads the sid back
@@ -90,8 +90,8 @@ const TENANT_A: TenantId = "00000000-0000-4000-8000-0000000000a1";
90
90
  const TENANT_B: TenantId = "00000000-0000-4000-8000-0000000000b1";
91
91
 
92
92
  beforeAll(async () => {
93
- const encryption = createEncryptionProvider(encryptionKey);
94
- const resolver = createConfigResolver({ encryption });
93
+ const encryption = createTestEnvelopeCipher(encryptionKey);
94
+ const resolver = createConfigResolver({ cipher: encryption });
95
95
 
96
96
  store = createFakeStore();
97
97
  sidStream = deterministicSids();
@@ -120,6 +120,22 @@ beforeAll(async () => {
120
120
  await unsafeCreateEntityTable(stack.db, userEntity);
121
121
  await unsafeCreateEntityTable(stack.db, tenantEntity);
122
122
  await unsafePushTables(stack.db, { configValuesTable, tenantMembershipsTable });
123
+
124
+ // Prime request-helper's per-user sid cache for systemAdmin (the seed
125
+ // actor every test's seedUser() call authenticates as) here, once,
126
+ // before any test's observation window opens — otherwise whichever
127
+ // test happens to call seedUser() first would observe an extra
128
+ // store.created entry for seed plumbing, not the auth-route behavior
129
+ // under test (#1452: authHeader() now mints one sid per user.id, not
130
+ // one per call, so this only ever fires here).
131
+ await seedUser({
132
+ email: "warmup-seed-actor@example.com",
133
+ password: "warmup-password-not-asserted-on",
134
+ tenants: [],
135
+ });
136
+ store.live.clear();
137
+ store.created.length = 0;
138
+ store.revoked.length = 0;
123
139
  });
124
140
 
125
141
  afterAll(async () => {
@@ -1,8 +1,6 @@
1
- // Tests the `sessionStrictMode` flag on AuthRoutesConfig. When enabled, a
2
- // JWT that arrives WITHOUT a `jti` is rejected at the middleware — useful
3
- // after a rolling deploy has been emitting sids longer than the JWT TTL,
4
- // so legacy stateless tokens are expected to have expired. Default false
5
- // keeps pre-upgrade tokens working; this suite flips it on and asserts.
1
+ // Pins the default sessionChecker behavior: a JWT that arrives WITHOUT a
2
+ // `jti` is rejected at the middleware. There is no relaxed mode — any app
3
+ // that wires sessionChecker gets this for free.
6
4
 
7
5
  import { afterAll, beforeAll, describe, expect, test } from "bun:test";
8
6
  import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
@@ -21,12 +19,8 @@ let stack: TestStack;
21
19
  const TENANT: TenantId = testTenantId(1);
22
20
  const userId = TestUsers.systemAdmin.id;
23
21
 
24
- // Stub checker that always accepts. The strictMode branch runs BEFORE the
25
- // checker is even consulted (no jti → nothing to check), so the stub never
26
- // fires in the strict-mode path. It's present to satisfy the framework's
27
- // "you wired sessionChecker, so we'll run it if we have an sid" contract.
28
- // Accepts the full AuthSessionChecker signature (sid + expectedUserId)
29
- // even though it doesn't use the args.
22
+ // Stub checker that always accepts. Accepts the full AuthSessionChecker
23
+ // signature (sid + expectedUserId) even though it doesn't use the args.
30
24
  async function stubChecker(_sid: string, _expectedUserId: string): Promise<"live"> {
31
25
  return "live";
32
26
  }
@@ -42,7 +36,6 @@ beforeAll(async () => {
42
36
  authConfig: {
43
37
  membershipQuery: "tenant:query:memberships",
44
38
  sessionChecker: stubChecker,
45
- sessionStrictMode: true,
46
39
  },
47
40
  });
48
41
  });
@@ -51,7 +44,7 @@ afterAll(async () => {
51
44
  await stack.cleanup();
52
45
  });
53
46
 
54
- describe("sessionStrictMode: sidless JWTs are rejected", () => {
47
+ describe("sessionChecker wired: sidless JWTs are rejected", () => {
55
48
  test("JWT without jti → 401 with reason=no_sid", async () => {
56
49
  // Hand-signed JWT that carries id + tenantId + roles but NO jti. The
57
50
  // standard testing request-helper signs JWTs the same way on user
@@ -93,7 +86,7 @@ describe("sessionStrictMode: sidless JWTs are rejected", () => {
93
86
  expect(res.status).not.toBe(401);
94
87
  // Narrow the "not 401" to exclude other 4xx middleware errors too.
95
88
  // A 403 from access-layer or a 400 from shape-validation wouldn't
96
- // come from sessionStrictMode, but either would be a different code
89
+ // come from the sid gate, but either would be a different code
97
90
  // path than the one we're testing — flag them if they surface.
98
91
  expect(res.status).not.toBe(403);
99
92
  expect(res.status).not.toBe(400);
@@ -26,6 +26,8 @@
26
26
 
27
27
  import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
28
28
  import { asRawClient, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
29
+ import { buildEntityTable } from "@cosmicdrift/kumiko-framework/db";
30
+ import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
29
31
  import {
30
32
  setupTestStack,
31
33
  type TestStack,
@@ -44,11 +46,53 @@ import { createTemplateResolverFeature } from "../../template-resolver/feature";
44
46
  import { createTenantFeature } from "../../tenant";
45
47
  import { tenantMembershipsTable } from "../../tenant/membership-table";
46
48
  import { tenantEntity, tenantTable } from "../../tenant/schema/tenant";
49
+ // kumiko-lint-ignore cross-feature-import regression-proof for #1463: seedTenant
50
+ // must fire tier-engine's entity postSave hook on self-signup, not just on the
51
+ // TenantHandlers.create HTTP path.
52
+ import { tierAssignmentEntity } from "../../tier-engine/entity";
53
+ import { createTierEngineFeature } from "../../tier-engine/feature";
47
54
  import { createUserFeature } from "../../user/feature";
48
55
  import { userEntity, userTable } from "../../user/schema/user";
49
56
  import { AuthErrors, AuthHandlers } from "../constants";
50
57
  import { createAuthEmailPasswordFeature } from "../feature";
51
58
 
59
+ const tierAssignmentTable = buildEntityTable("tier-assignment", tierAssignmentEntity);
60
+
61
+ // Regression-proof for #1478: provisionSignupAccount only threaded `hooks`
62
+ // into seedTenant, leaving seedUserWithPassword's `user`-entity and
63
+ // seedTenantMembership's `tenantMembership`-entity postSave hooks unfired
64
+ // on self-signup — same bug class as #1463, just for the other two
65
+ // entities in the same call. This vendor feature's hook is the user-side
66
+ // analog of tier-engine's tenant hook above.
67
+ const userPostSaveFired = { count: 0 };
68
+ const userHookVendorFeature = defineFeature("user-hook-vendor-test", (r) => {
69
+ r.requires("user");
70
+ r.hook("postSave", { allOf: "user" }, async (ctx) => {
71
+ if (ctx.isNew) userPostSaveFired.count++;
72
+ });
73
+ });
74
+
75
+ // Regression-proof for #1478/#1526: seedTenantMembership must fire the
76
+ // `tenantMembership` entity's postSave hooks (e.g. a Welcome-notification
77
+ // hook) on self-signup too — pins the `fireEntityPostSave` call added at
78
+ // seeding.ts's seedTenantMembership, not just the `user`/`tenant` ones.
79
+ // #1566: also record ctx._tenantId so a shallow-only rescope regresses.
80
+ const tenantMembershipPostSaveFired = { count: 0, seenTenantIds: [] as string[] };
81
+ const tenantMembershipHookVendorFeature = defineFeature(
82
+ "tenant-membership-hook-vendor-test",
83
+ (r) => {
84
+ r.requires("tenant");
85
+ r.hook("postSave", { allOf: "tenant-membership" }, async (result, context) => {
86
+ if (result.isNew) {
87
+ tenantMembershipPostSaveFired.count++;
88
+ if (context._tenantId !== undefined) {
89
+ tenantMembershipPostSaveFired.seenTenantIds.push(String(context._tenantId));
90
+ }
91
+ }
92
+ });
93
+ },
94
+ );
95
+
52
96
  const APP_ACTIVATION_URL = "https://app.example.com/signup/complete";
53
97
 
54
98
  // Activation mails now go through delivery (ctx.notify → channel-email). The
@@ -78,6 +122,14 @@ beforeAll(async () => {
78
122
  createAuthEmailPasswordFeature({
79
123
  signup: { tokenTtlMinutes: 60, appUrl: APP_ACTIVATION_URL },
80
124
  }),
125
+ // Regression-proof for #1463: seedTenant must fire the entity postSave
126
+ // hook on self-signup too, not just on the TenantHandlers.create path.
127
+ createTierEngineFeature({
128
+ defaultTier: "free",
129
+ tierMap: { free: { features: [], caps: {} } },
130
+ }),
131
+ userHookVendorFeature,
132
+ tenantMembershipHookVendorFeature,
81
133
  ],
82
134
  extraContext: (deps) => ({
83
135
  ...createDeliveryTestContext(deps),
@@ -102,6 +154,7 @@ beforeAll(async () => {
102
154
  configValuesTable,
103
155
  tenantMembershipsTable,
104
156
  notificationPreferencesTable,
157
+ tierAssignmentTable,
105
158
  });
106
159
  });
107
160
 
@@ -112,8 +165,12 @@ afterAll(async () => {
112
165
  beforeEach(async () => {
113
166
  await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
114
167
  await asRawClient(stack.db).unsafe(`DELETE FROM "${tenantMembershipsTable.tableName}"`);
168
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${tierAssignmentTable.tableName}"`);
115
169
  await asRawClient(stack.db).unsafe(`DELETE FROM "${tenantTable.tableName}"`);
116
170
  emailTransport.sent.length = 0;
171
+ userPostSaveFired.count = 0;
172
+ tenantMembershipPostSaveFired.count = 0;
173
+ tenantMembershipPostSaveFired.seenTenantIds = [];
117
174
  // Redis-cleanup damit Resend-Tests keine state-leaks haben.
118
175
  const allKeys = await stack.redis.redis.keys("signup:*");
119
176
  if (allKeys.length > 0) await stack.redis.redis.del(...allKeys);
@@ -220,6 +277,28 @@ describe("POST /api/auth/signup-confirm", () => {
220
277
  expect(JSON.parse(rolesRaw) as string[]).toContain("Admin");
221
278
  }
222
279
 
280
+ // #1463 regression: seedTenant fires the tenant entity's postSave
281
+ // hooks — tier-engine's auto-default-tier hook must run on self-signup
282
+ // exactly like it does on the regular TenantHandlers.create HTTP path.
283
+ const tierRows = await selectMany(stack.db, tierAssignmentTable, {
284
+ tenantId: body.user?.tenantId ?? "",
285
+ });
286
+ expect(tierRows).toHaveLength(1);
287
+ expect(tierRows[0]?.["tier"]).toBe("free");
288
+
289
+ // #1478 regression: seedUserWithPassword (called from
290
+ // provisionSignupAccount) fires the `user` entity's postSave hooks too —
291
+ // before this fix, only seedTenant's hooks param was wired through.
292
+ expect(userPostSaveFired.count).toBe(1);
293
+
294
+ // #1478/#1526 regression: seedTenantMembership (called from
295
+ // provisionSignupAccount) fires the `tenant-membership` entity's postSave
296
+ // hooks too — before this fix, only seedTenant's hooks param was wired.
297
+ expect(tenantMembershipPostSaveFired.count).toBe(1);
298
+ // #1566: rescope must put the new tenant on the hook context, not the
299
+ // anonymous/system caller's tenantId.
300
+ expect(tenantMembershipPostSaveFired.seenTenantIds).toEqual([body.user?.tenantId ?? ""]);
301
+
223
302
  // Authority-Beweis: Login mit dem gesetzten Password funktioniert.
224
303
  const loginRes = await postLogin(email, password);
225
304
  expect(loginRes.status).toBe(200);
@@ -320,4 +399,16 @@ describe("POST /api/auth/signup-confirm", () => {
320
399
  const loginAttacker = await postLogin(email, attackerPassword);
321
400
  expect(loginAttacker.status).not.toBe(200);
322
401
  });
402
+
403
+ test("common password → 400 (schema rejects breach-list password) (#1340)", async () => {
404
+ const email = "common-signup@example.com";
405
+ const token = await requestSignup(email);
406
+
407
+ const confirmRes = await postSignupConfirm(token, "password1");
408
+ expect(confirmRes.status).toBe(400);
409
+
410
+ // No user created — the rejected confirm never landed.
411
+ const userRows = await selectMany(stack.db, userTable, { email });
412
+ expect(userRows).toHaveLength(0);
413
+ });
323
414
  });
@@ -6,6 +6,10 @@
6
6
  // der server-side Zugriff (handlers, dispatcher) erhalten.
7
7
  export const AUTH_EMAIL_PASSWORD_FEATURE = "auth-email-password" as const;
8
8
 
9
+ // Minimum length for reset/verify hmacSecret — mirrors the ≥32-char
10
+ // JWT_SECRET env check (HMAC-SHA256 key material).
11
+ export const MIN_HMAC_SECRET_LENGTH = 32;
12
+
9
13
  // Qualified handler names. Non-CRUD handlers, no entity prefix.
10
14
  export const AuthHandlers = {
11
15
  login: "auth-email-password:write:login",
@@ -15,6 +19,12 @@ export const AuthHandlers = {
15
19
  resetPassword: "auth-email-password:write:reset-password",
16
20
  requestEmailVerification: "auth-email-password:write:request-email-verification",
17
21
  verifyEmail: "auth-email-password:write:verify-email",
22
+ // Account-Unlock Magic-Link (fixes the lockout-DoS where the monotonic
23
+ // failure-counter re-locks immediately after expiry, #1266). Mirrors
24
+ // reset: request mints an HMAC token + mails it, confirm clears the
25
+ // Redis lockout state.
26
+ requestAccountUnlock: "auth-email-password:write:request-account-unlock",
27
+ confirmAccountUnlock: "auth-email-password:write:confirm-account-unlock",
18
28
  // Magic-Link Self-Signup (Pre-Activation-Token-Pattern). request mintet
19
29
  // einen opaken Random-Token, speichert ihn bidirektional in Redis und
20
30
  // sendet eine Aktivierungs-Mail. confirm löst den Token ein und legt
@@ -36,6 +46,12 @@ export const AuthHandlers = {
36
46
  inviteCancel: "auth-email-password:write:invite-cancel",
37
47
  } as const;
38
48
 
49
+ // Qualified query names. Anonymous-readable status so the (unauthenticated)
50
+ // signup page can decide whether to show its own link/form.
51
+ export const AuthQueries = {
52
+ signupRegistrationStatus: "auth-email-password:query:signup-registration-status",
53
+ } as const;
54
+
39
55
  // Error codes — kept intentionally generic so clients can't distinguish
40
56
  // "email doesn't exist" from "password wrong". Both surface as invalid_credentials.
41
57
  // Soft-deleted users also collapse into invalid_credentials to avoid enumeration.
@@ -55,6 +71,11 @@ export const AuthErrors = {
55
71
  invalidVerificationToken: "invalid_verification_token",
56
72
  verificationNotConfigured: "email_verification_not_configured",
57
73
  emailNotVerified: "email_not_verified",
74
+ // Account-Unlock: mirrors the reset-token handling — every verify
75
+ // failure (malformed/bad-signature/expired) collapses to this single
76
+ // code so a probing client can't distinguish tampered from stale.
77
+ invalidUnlockToken: "invalid_unlock_token",
78
+ unlockNotConfigured: "account_unlock_not_configured",
58
79
  // Self-Signup: alle confirm-Failures (unbekannter Token, schon
59
80
  // konsumiert, abgelaufen) collapsen auf diesen Code — gleicher
60
81
  // anti-enumeration-Trade-off wie reset/verify.
@@ -99,6 +120,10 @@ export const AuthErrors = {
99
120
  export const AUTH_LOCKOUT_DEFAULT_MAX_FAILED_ATTEMPTS = 5;
100
121
  export const AUTH_LOCKOUT_DEFAULT_DURATION_MINUTES = 15;
101
122
 
123
+ // Account-unlock token TTL — same as reset (15min): short-lived, the user
124
+ // clicks it right after hitting the lockout screen.
125
+ export const AUTH_UNLOCK_DEFAULT_TTL_MINUTES = 15;
126
+
102
127
  export const AUTH_RESET_DEFAULT_TTL_MINUTES = 15;
103
128
  // Verification tokens live longer by default because the user may not be
104
129
  // at their computer the moment they sign up — 24h covers "verify after
@@ -73,6 +73,14 @@ const STRINGS = {
73
73
  inviteExpiry: (when: string) => `Der Link läuft am ${when} ab.`,
74
74
  inviteIgnore:
75
75
  "Falls du diese Einladung nicht erwartet hast, kannst du diese E-Mail ignorieren.",
76
+ unlockSubject: (app: string) => `${app} — Konto entsperren`,
77
+ unlockGreeting: "Hallo,",
78
+ unlockIntro: (app: string) =>
79
+ `dein ${app}-Konto wurde nach mehreren fehlgeschlagenen Anmeldeversuchen vorübergehend gesperrt. Klicke auf den folgenden Link, um es sofort zu entsperren:`,
80
+ unlockButton: "Konto entsperren",
81
+ unlockExpiry: (when: string) => `Der Link läuft am ${when} ab.`,
82
+ unlockIgnore:
83
+ "Falls du diese Sperre nicht ausgelöst hast, kannst du diese E-Mail ignorieren — die Sperre läuft von selbst ab.",
76
84
  },
77
85
  en: {
78
86
  resetSubject: (app: string) => `${app} — Reset your password`,
@@ -105,6 +113,14 @@ const STRINGS = {
105
113
  inviteButton: "Accept invitation",
106
114
  inviteExpiry: (when: string) => `The link expires on ${when}.`,
107
115
  inviteIgnore: "If you weren't expecting this invitation, you can ignore this email.",
116
+ unlockSubject: (app: string) => `${app} — Unlock your account`,
117
+ unlockGreeting: "Hi,",
118
+ unlockIntro: (app: string) =>
119
+ `your ${app} account was temporarily locked after several failed sign-in attempts. Click the link below to unlock it immediately:`,
120
+ unlockButton: "Unlock account",
121
+ unlockExpiry: (when: string) => `The link expires on ${when}.`,
122
+ unlockIgnore:
123
+ "If you didn't trigger this lock, you can ignore this email — the lock expires on its own.",
108
124
  },
109
125
  } as const;
110
126
 
@@ -151,6 +167,22 @@ export function renderResetPasswordEmail(args: RenderTokenContentArgs): AuthMail
151
167
  });
152
168
  }
153
169
 
170
+ export function renderUnlockAccountEmail(args: RenderTokenContentArgs): AuthMailContent {
171
+ const locale = args.locale ?? "en";
172
+ const appName = args.appName ?? (locale === "de" ? "Konto" : "Account");
173
+ const t = STRINGS[locale];
174
+ return tokenMailContent({
175
+ subject: t.unlockSubject(appName),
176
+ header: t.unlockButton,
177
+ greeting: t.unlockGreeting,
178
+ intro: t.unlockIntro(appName),
179
+ buttonLabel: t.unlockButton,
180
+ buttonUrl: args.url,
181
+ expiry: t.unlockExpiry(formatExpiry(args.expiresAt)),
182
+ ignore: t.unlockIgnore,
183
+ });
184
+ }
185
+
154
186
  export function renderVerifyEmail(args: RenderTokenContentArgs): AuthMailContent {
155
187
  const locale = args.locale ?? "en";
156
188
  const appName = args.appName ?? (locale === "de" ? "Konto" : "Account");
@@ -37,6 +37,15 @@ export function invalidResetToken() {
37
37
  );
38
38
  }
39
39
 
40
+ // @wrapper-known error-helper
41
+ export function invalidUnlockToken() {
42
+ return writeFailure(
43
+ new UnprocessableError(AuthErrors.invalidUnlockToken, {
44
+ i18nKey: "auth.errors.invalidUnlockToken",
45
+ }),
46
+ );
47
+ }
48
+
40
49
  // @wrapper-known error-helper
41
50
  export function invalidVerificationToken() {
42
51
  return writeFailure(