@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
@@ -0,0 +1,379 @@
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
+ import {
3
+ createStaticLocaleResolver,
4
+ LocaleProvider,
5
+ PrimitivesProvider,
6
+ } from "@cosmicdrift/kumiko-renderer";
7
+ import { defaultPrimitives } from "@cosmicdrift/kumiko-renderer-web";
8
+ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
9
+ import type { ReactNode } from "react";
10
+ import { defaultTranslations } from "../i18n";
11
+
12
+ mock.module("qrcode/lib/browser", () => ({
13
+ default: { toString: mock(async () => "<svg></svg>") },
14
+ }));
15
+
16
+ const { MfaSetupPreauthScreen } = await import("../mfa-setup-preauth-screen");
17
+
18
+ function Wrapper({ children }: { readonly children: ReactNode }): ReactNode {
19
+ return (
20
+ <PrimitivesProvider value={defaultPrimitives}>
21
+ <LocaleProvider
22
+ resolver={createStaticLocaleResolver({ locale: "de" })}
23
+ fallbackBundles={[defaultTranslations]}
24
+ >
25
+ {children}
26
+ </LocaleProvider>
27
+ </PrimitivesProvider>
28
+ );
29
+ }
30
+
31
+ function jsonResponse(body: unknown, status = 200): Response {
32
+ return new Response(JSON.stringify(body), { status });
33
+ }
34
+
35
+ const startBody = {
36
+ isSuccess: true,
37
+ setupToken: "setup-token-1",
38
+ otpauthUri: "otpauth://totp/App:user@example.com?secret=ABCD1234&issuer=App",
39
+ recoveryCodes: ["code-1", "code-2"],
40
+ };
41
+ const confirmBody = {
42
+ isSuccess: true,
43
+ token: "t",
44
+ user: { id: "u1", tenantId: "t1", roles: [] },
45
+ };
46
+
47
+ beforeEach(() => {
48
+ globalThis.fetch = mock(async (input: RequestInfo | URL) => {
49
+ const url = String(input);
50
+ if (url.includes("preauth-enable-start")) return jsonResponse(startBody);
51
+ if (url.includes("preauth-confirm")) return jsonResponse(confirmBody);
52
+ return jsonResponse({ isSuccess: false, error: "unexpected_url" }, 500);
53
+ }) as unknown as typeof fetch;
54
+ });
55
+
56
+ describe("MfaSetupPreauthScreen", () => {
57
+ test("rendert Titel + Start-Button, kein Setup-Bereich vor dem Start", () => {
58
+ render(
59
+ <Wrapper>
60
+ <MfaSetupPreauthScreen preauthSetupToken="preauth-1" accountLabel="user@example.com" />
61
+ </Wrapper>,
62
+ );
63
+ expect(screen.getByText("Zwei-Faktor-Authentifizierung erforderlich")).toBeTruthy();
64
+ expect(screen.getByRole("button", { name: "Einrichtung starten" })).toBeTruthy();
65
+ expect(screen.queryByLabelText(/^Code aus der Authenticator-App/)).toBeNull();
66
+ });
67
+
68
+ test("Start-Klick → postet preauthSetupToken+accountLabel, danach QR + Recovery-Codes", async () => {
69
+ const fetchMock = mock(async () => jsonResponse(startBody));
70
+ globalThis.fetch = fetchMock as unknown as typeof fetch;
71
+ render(
72
+ <Wrapper>
73
+ <MfaSetupPreauthScreen preauthSetupToken="preauth-1" accountLabel="user@example.com" />
74
+ </Wrapper>,
75
+ );
76
+
77
+ fireEvent.click(screen.getByRole("button", { name: "Einrichtung starten" }));
78
+
79
+ await waitFor(() => {
80
+ expect(fetchMock).toHaveBeenCalledWith(
81
+ "/api/auth/mfa/preauth-enable-start",
82
+ expect.objectContaining({
83
+ method: "POST",
84
+ body: JSON.stringify({
85
+ preauthSetupToken: "preauth-1",
86
+ accountLabel: "user@example.com",
87
+ }),
88
+ }),
89
+ );
90
+ });
91
+ await waitFor(() => {
92
+ expect(screen.getByText("ABCD1234")).toBeTruthy();
93
+ });
94
+ expect(screen.getByText(/code-1/)).toBeTruthy();
95
+ expect(screen.getByText(/code-2/)).toBeTruthy();
96
+ });
97
+
98
+ test("Bestätigen-Button bleibt disabled bis Recovery-Codes bestätigt + 6-stelliger Code eingegeben", async () => {
99
+ render(
100
+ <Wrapper>
101
+ <MfaSetupPreauthScreen preauthSetupToken="preauth-1" accountLabel="user@example.com" />
102
+ </Wrapper>,
103
+ );
104
+ fireEvent.click(screen.getByRole("button", { name: "Einrichtung starten" }));
105
+ await waitFor(() => {
106
+ expect(screen.getByText("ABCD1234")).toBeTruthy();
107
+ });
108
+
109
+ const confirm = screen.getByRole("button", {
110
+ name: "Einrichtung abschließen",
111
+ }) as HTMLButtonElement;
112
+ expect(confirm.disabled).toBe(true);
113
+
114
+ fireEvent.click(screen.getByLabelText("Ich habe die Recovery-Codes gespeichert."));
115
+ expect(confirm.disabled).toBe(true);
116
+
117
+ fireEvent.change(screen.getByLabelText(/^Code aus der Authenticator-App/), {
118
+ target: { value: "123456" },
119
+ });
120
+ expect(confirm.disabled).toBe(false);
121
+ });
122
+
123
+ test("Bestätigen → postet setupToken+code, danach onSuccess", async () => {
124
+ const onSuccess = mock(() => {});
125
+ render(
126
+ <Wrapper>
127
+ <MfaSetupPreauthScreen
128
+ preauthSetupToken="preauth-1"
129
+ accountLabel="user@example.com"
130
+ onSuccess={onSuccess}
131
+ />
132
+ </Wrapper>,
133
+ );
134
+ fireEvent.click(screen.getByRole("button", { name: "Einrichtung starten" }));
135
+ await waitFor(() => {
136
+ expect(screen.getByText("ABCD1234")).toBeTruthy();
137
+ });
138
+ fireEvent.click(screen.getByLabelText("Ich habe die Recovery-Codes gespeichert."));
139
+ fireEvent.change(screen.getByLabelText(/^Code aus der Authenticator-App/), {
140
+ target: { value: "123456" },
141
+ });
142
+
143
+ const fetchMock = globalThis.fetch as unknown as ReturnType<typeof mock>;
144
+ fireEvent.click(screen.getByRole("button", { name: "Einrichtung abschließen" }));
145
+
146
+ await waitFor(() => {
147
+ expect(fetchMock).toHaveBeenCalledWith(
148
+ "/api/auth/mfa/preauth-confirm",
149
+ expect.objectContaining({
150
+ method: "POST",
151
+ body: JSON.stringify({ setupToken: "setup-token-1", code: "123456" }),
152
+ }),
153
+ );
154
+ });
155
+ await waitFor(() => {
156
+ expect(onSuccess).toHaveBeenCalled();
157
+ });
158
+ });
159
+
160
+ test("enable-start schlägt fehl → übersetztes Fehler-Banner, kein Setup-Bereich", async () => {
161
+ globalThis.fetch = mock(async () =>
162
+ jsonResponse({ isSuccess: false, error: "mfa_already_enabled" }),
163
+ ) as unknown as typeof fetch;
164
+ render(
165
+ <Wrapper>
166
+ <MfaSetupPreauthScreen preauthSetupToken="preauth-1" accountLabel="user@example.com" />
167
+ </Wrapper>,
168
+ );
169
+
170
+ fireEvent.click(screen.getByRole("button", { name: "Einrichtung starten" }));
171
+
172
+ await waitFor(() => {
173
+ expect(screen.getByText("Zwei-Faktor-Authentifizierung ist bereits aktiv.")).toBeTruthy();
174
+ });
175
+ expect(screen.queryByText("ABCD1234")).toBeNull();
176
+ });
177
+
178
+ test("onCancel gesetzt → 'Zurück zum Login'-Button vorhanden, klick ruft onCancel", () => {
179
+ const onCancel = mock(() => {});
180
+ render(
181
+ <Wrapper>
182
+ <MfaSetupPreauthScreen
183
+ preauthSetupToken="preauth-1"
184
+ accountLabel="user@example.com"
185
+ onCancel={onCancel}
186
+ />
187
+ </Wrapper>,
188
+ );
189
+
190
+ fireEvent.click(screen.getByRole("button", { name: "Zurück zum Login" }));
191
+ expect(onCancel).toHaveBeenCalled();
192
+ });
193
+
194
+ test("ohne onCancel → kein 'Zurück zum Login'-Button", () => {
195
+ render(
196
+ <Wrapper>
197
+ <MfaSetupPreauthScreen preauthSetupToken="preauth-1" accountLabel="user@example.com" />
198
+ </Wrapper>,
199
+ );
200
+ expect(screen.queryByRole("button", { name: "Zurück zum Login" })).toBeNull();
201
+ });
202
+
203
+ test("confirm schlägt mit invalid_totp_code fehl → übersetztes Banner, onSuccess NICHT gefeuert, Formular weiter bedienbar", async () => {
204
+ const onSuccess = mock(() => {});
205
+ globalThis.fetch = mock(async (input: RequestInfo | URL) => {
206
+ const url = String(input);
207
+ if (url.includes("preauth-enable-start")) return jsonResponse(startBody);
208
+ if (url.includes("preauth-confirm")) {
209
+ return jsonResponse({ isSuccess: false, error: "invalid_totp_code" });
210
+ }
211
+ return jsonResponse({ isSuccess: false, error: "unexpected_url" }, 500);
212
+ }) as unknown as typeof fetch;
213
+
214
+ render(
215
+ <Wrapper>
216
+ <MfaSetupPreauthScreen
217
+ preauthSetupToken="preauth-1"
218
+ accountLabel="user@example.com"
219
+ onSuccess={onSuccess}
220
+ />
221
+ </Wrapper>,
222
+ );
223
+ fireEvent.click(screen.getByRole("button", { name: "Einrichtung starten" }));
224
+ await waitFor(() => {
225
+ expect(screen.getByText("ABCD1234")).toBeTruthy();
226
+ });
227
+ fireEvent.click(screen.getByLabelText("Ich habe die Recovery-Codes gespeichert."));
228
+ fireEvent.change(screen.getByLabelText(/^Code aus der Authenticator-App/), {
229
+ target: { value: "000000" },
230
+ });
231
+ fireEvent.click(screen.getByRole("button", { name: "Einrichtung abschließen" }));
232
+
233
+ await waitFor(() => {
234
+ expect(screen.getByText("Ungültiger Code. Bitte erneut versuchen.")).toBeTruthy();
235
+ });
236
+ expect(onSuccess).not.toHaveBeenCalled();
237
+ // Form stays usable — QR/recovery section is still there, a retry is possible.
238
+ expect(screen.getByText("ABCD1234")).toBeTruthy();
239
+ const confirm = screen.getByRole("button", {
240
+ name: "Einrichtung abschließen",
241
+ }) as HTMLButtonElement;
242
+ expect(confirm.disabled).toBe(false);
243
+ });
244
+
245
+ // #1470: the banner told the user to "start over", but setup stayed
246
+ // non-null — the intro block (with the only "start setup" button) never
247
+ // came back, leaving the user stuck without a reload (which loses the
248
+ // preauthSetupToken entirely).
249
+ test("confirm schlägt mit invalid_setup_token fehl → Neustart-Pfad: Start-Button kommt zurück (#1470)", async () => {
250
+ globalThis.fetch = mock(async (input: RequestInfo | URL) => {
251
+ const url = String(input);
252
+ if (url.includes("preauth-enable-start")) return jsonResponse(startBody);
253
+ if (url.includes("preauth-confirm")) {
254
+ return jsonResponse({ isSuccess: false, error: "invalid_setup_token" });
255
+ }
256
+ return jsonResponse({ isSuccess: false, error: "unexpected_url" }, 500);
257
+ }) as unknown as typeof fetch;
258
+
259
+ render(
260
+ <Wrapper>
261
+ <MfaSetupPreauthScreen preauthSetupToken="preauth-1" accountLabel="user@example.com" />
262
+ </Wrapper>,
263
+ );
264
+ fireEvent.click(screen.getByRole("button", { name: "Einrichtung starten" }));
265
+ await waitFor(() => {
266
+ expect(screen.getByText("ABCD1234")).toBeTruthy();
267
+ });
268
+ fireEvent.click(screen.getByLabelText("Ich habe die Recovery-Codes gespeichert."));
269
+ fireEvent.change(screen.getByLabelText(/^Code aus der Authenticator-App/), {
270
+ target: { value: "123456" },
271
+ });
272
+ fireEvent.click(screen.getByRole("button", { name: "Einrichtung abschließen" }));
273
+
274
+ await waitFor(() => {
275
+ expect(
276
+ screen.getByText("Die Einrichtung ist abgelaufen. Bitte erneut starten."),
277
+ ).toBeTruthy();
278
+ });
279
+ // The actual fix: intro re-appears with a working start button, the QR/
280
+ // recovery-codes section from the dead setup is gone.
281
+ expect(screen.getByRole("button", { name: "Einrichtung starten" })).toBeTruthy();
282
+ expect(screen.queryByText("ABCD1234")).toBeNull();
283
+ });
284
+
285
+ test("confirm schlägt mit invalid_challenge_token fehl → auch Neustart-Pfad (#1470)", async () => {
286
+ globalThis.fetch = mock(async (input: RequestInfo | URL) => {
287
+ const url = String(input);
288
+ if (url.includes("preauth-enable-start")) return jsonResponse(startBody);
289
+ if (url.includes("preauth-confirm")) {
290
+ return jsonResponse({ isSuccess: false, error: "invalid_challenge_token" });
291
+ }
292
+ return jsonResponse({ isSuccess: false, error: "unexpected_url" }, 500);
293
+ }) as unknown as typeof fetch;
294
+
295
+ render(
296
+ <Wrapper>
297
+ <MfaSetupPreauthScreen preauthSetupToken="preauth-1" accountLabel="user@example.com" />
298
+ </Wrapper>,
299
+ );
300
+ fireEvent.click(screen.getByRole("button", { name: "Einrichtung starten" }));
301
+ await waitFor(() => {
302
+ expect(screen.getByText("ABCD1234")).toBeTruthy();
303
+ });
304
+ fireEvent.click(screen.getByLabelText("Ich habe die Recovery-Codes gespeichert."));
305
+ fireEvent.change(screen.getByLabelText(/^Code aus der Authenticator-App/), {
306
+ target: { value: "123456" },
307
+ });
308
+ fireEvent.click(screen.getByRole("button", { name: "Einrichtung abschließen" }));
309
+
310
+ await waitFor(() => {
311
+ expect(screen.getByRole("button", { name: "Einrichtung starten" })).toBeTruthy();
312
+ });
313
+ });
314
+
315
+ // kumiko-framework#1470: confirmMfaSetupPreauth already extracts
316
+ // retryAfterSeconds from error.details (see mfa-client.test.ts), but the
317
+ // screen used to drop it and show a generic "too many attempts" banner
318
+ // with no wait-time. Pins that the seconds actually reach the UI.
319
+ test("confirm schlägt mit too_many_attempts fehl → Banner zeigt retryAfterSeconds (#1470)", async () => {
320
+ globalThis.fetch = mock(async (input: RequestInfo | URL) => {
321
+ const url = String(input);
322
+ if (url.includes("preauth-enable-start")) return jsonResponse(startBody);
323
+ if (url.includes("preauth-confirm")) {
324
+ return jsonResponse({
325
+ isSuccess: false,
326
+ error: { code: "too_many_attempts", details: { retryAfterSeconds: 42 } },
327
+ });
328
+ }
329
+ return jsonResponse({ isSuccess: false, error: "unexpected_url" }, 500);
330
+ }) as unknown as typeof fetch;
331
+
332
+ render(
333
+ <Wrapper>
334
+ <MfaSetupPreauthScreen preauthSetupToken="preauth-1" accountLabel="user@example.com" />
335
+ </Wrapper>,
336
+ );
337
+ fireEvent.click(screen.getByRole("button", { name: "Einrichtung starten" }));
338
+ await waitFor(() => {
339
+ expect(screen.getByText("ABCD1234")).toBeTruthy();
340
+ });
341
+ fireEvent.click(screen.getByLabelText("Ich habe die Recovery-Codes gespeichert."));
342
+ fireEvent.change(screen.getByLabelText(/^Code aus der Authenticator-App/), {
343
+ target: { value: "123456" },
344
+ });
345
+ fireEvent.click(screen.getByRole("button", { name: "Einrichtung abschließen" }));
346
+
347
+ await waitFor(() => {
348
+ expect(
349
+ screen.getByText(
350
+ "Zu viele Fehlversuche. Bitte in 42 Sekunden erneut versuchen oder neu einloggen.",
351
+ ),
352
+ ).toBeTruthy();
353
+ });
354
+ });
355
+
356
+ // kumiko-framework#1470: server validates z.string().length(6) — a 7th
357
+ // pasted digit must not make the button look submittable, it would only
358
+ // 400 as invalid_body.
359
+ test("7-stelliger Code hält den Bestätigen-Button disabled", async () => {
360
+ render(
361
+ <Wrapper>
362
+ <MfaSetupPreauthScreen preauthSetupToken="preauth-1" accountLabel="user@example.com" />
363
+ </Wrapper>,
364
+ );
365
+ fireEvent.click(screen.getByRole("button", { name: "Einrichtung starten" }));
366
+ await waitFor(() => {
367
+ expect(screen.getByText("ABCD1234")).toBeTruthy();
368
+ });
369
+ fireEvent.click(screen.getByLabelText("Ich habe die Recovery-Codes gespeichert."));
370
+ fireEvent.change(screen.getByLabelText(/^Code aus der Authenticator-App/), {
371
+ target: { value: "1234567" },
372
+ });
373
+
374
+ const confirm = screen.getByRole("button", {
375
+ name: "Einrichtung abschließen",
376
+ }) as HTMLButtonElement;
377
+ expect(confirm.disabled).toBe(true);
378
+ });
379
+ });
@@ -0,0 +1,104 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
+ import {
3
+ createStaticLocaleResolver,
4
+ LocaleProvider,
5
+ PrimitivesProvider,
6
+ } from "@cosmicdrift/kumiko-renderer";
7
+ import { defaultPrimitives } from "@cosmicdrift/kumiko-renderer-web";
8
+ import { fireEvent, type RenderResult, render, screen, waitFor } from "@testing-library/react";
9
+ import type { ReactElement } from "react";
10
+ import { makeSessionApi } from "../../../auth-email-password/web/__tests__/test-utils";
11
+ import { SessionContext } from "../../../auth-email-password/web/session";
12
+ import { defaultTranslations } from "../i18n";
13
+ import { MfaVerifyScreen } from "../mfa-verify-screen";
14
+
15
+ function renderScreen(ui: ReactElement, session = makeSessionApi()): RenderResult {
16
+ return render(
17
+ <PrimitivesProvider value={defaultPrimitives}>
18
+ <LocaleProvider
19
+ resolver={createStaticLocaleResolver({ locale: "de" })}
20
+ fallbackBundles={[defaultTranslations]}
21
+ >
22
+ <SessionContext.Provider value={session}>{ui}</SessionContext.Provider>
23
+ </LocaleProvider>
24
+ </PrimitivesProvider>,
25
+ );
26
+ }
27
+
28
+ const originalFetch = globalThis.fetch;
29
+
30
+ beforeEach(() => {
31
+ globalThis.fetch = mock(
32
+ async () => new Response(null, { status: 200 }),
33
+ ) as unknown as typeof fetch;
34
+ });
35
+ afterEach(() => {
36
+ globalThis.fetch = originalFetch;
37
+ });
38
+
39
+ describe("MfaVerifyScreen", () => {
40
+ test("rendert Titel + Code-Feld + Submit-Button (de)", () => {
41
+ void renderScreen(<MfaVerifyScreen challengeToken="challenge-1" />);
42
+ expect(screen.getByText("Zwei-Faktor-Bestätigung")).toBeTruthy();
43
+ expect(screen.getByLabelText(/^Code/)).toBeTruthy();
44
+ expect(screen.getByRole("button", { name: "Bestätigen" })).toBeTruthy();
45
+ });
46
+
47
+ test("richtiger Code → ruft session.refresh() + onSuccess()", async () => {
48
+ globalThis.fetch = mock(
49
+ async () =>
50
+ new Response(
51
+ JSON.stringify({
52
+ isSuccess: true,
53
+ token: "t",
54
+ user: { id: "u1", tenantId: "t1", roles: [] },
55
+ }),
56
+ { status: 200 },
57
+ ),
58
+ ) as unknown as typeof fetch;
59
+ const session = makeSessionApi();
60
+ const onSuccess = mock(() => {});
61
+ void renderScreen(
62
+ <MfaVerifyScreen challengeToken="challenge-1" onSuccess={onSuccess} />,
63
+ session,
64
+ );
65
+
66
+ fireEvent.change(screen.getByLabelText(/^Code/), { target: { value: "123456" } });
67
+ fireEvent.click(screen.getByRole("button", { name: "Bestätigen" }));
68
+
69
+ await waitFor(() => {
70
+ expect(session.refresh).toHaveBeenCalled();
71
+ expect(onSuccess).toHaveBeenCalled();
72
+ });
73
+ });
74
+
75
+ test("falscher Code (invalid_totp_code) → Banner mit übersetztem Text", async () => {
76
+ globalThis.fetch = mock(
77
+ async () =>
78
+ new Response(JSON.stringify({ isSuccess: false, error: "invalid_totp_code" }), {
79
+ status: 200,
80
+ }),
81
+ ) as unknown as typeof fetch;
82
+ void renderScreen(<MfaVerifyScreen challengeToken="challenge-1" />);
83
+
84
+ fireEvent.change(screen.getByLabelText(/^Code/), { target: { value: "000000" } });
85
+ fireEvent.click(screen.getByRole("button", { name: "Bestätigen" }));
86
+
87
+ await waitFor(() => {
88
+ expect(screen.getByText("Ungültiger Code. Bitte erneut versuchen.")).toBeTruthy();
89
+ });
90
+ });
91
+
92
+ test("onCancel gesetzt → 'Zurück zum Login'-Button vorhanden, klick ruft onCancel", () => {
93
+ const onCancel = mock(() => {});
94
+ void renderScreen(<MfaVerifyScreen challengeToken="challenge-1" onCancel={onCancel} />);
95
+
96
+ fireEvent.click(screen.getByRole("button", { name: "Zurück zum Login" }));
97
+ expect(onCancel).toHaveBeenCalled();
98
+ });
99
+
100
+ test("ohne onCancel → kein 'Zurück zum Login'-Button", () => {
101
+ void renderScreen(<MfaVerifyScreen challengeToken="challenge-1" />);
102
+ expect(screen.queryByRole("button", { name: "Zurück zum Login" })).toBeNull();
103
+ });
104
+ });
@@ -0,0 +1,37 @@
1
+ // @runtime client
2
+ // Client-Feature-Factory für auth-mfa — mirrors emailPasswordClient()'s
3
+ // shape so apps register both the same way:
4
+ // createKumikoApp({ clientFeatures: [emailPasswordClient(), authMfaClient()] }).
5
+ // No gates: MfaVerifyScreen isn't a route gate, it's a state swap wired
6
+ // via EmailPasswordClientOptions.mfaVerifyScreen. This factory merges the
7
+ // default de/en translations and maps MFA_ENABLE_SCREEN_ID to
8
+ // MfaEnableScreen (same "components" convention as personal-access-tokens).
9
+
10
+ import type { TranslationsByLocale } from "@cosmicdrift/kumiko-renderer";
11
+ import type { ComponentType, ReactNode } from "react";
12
+ import { MFA_ENABLE_SCREEN_ID } from "../constants";
13
+ import { defaultTranslations, mergeTranslations } from "./i18n";
14
+ import { MfaEnableScreen } from "./mfa-enable-screen";
15
+
16
+ export type AuthMfaClientOptions = {
17
+ /** Key-Overrides pro Locale, gemerged mit den Default-Bundles (de/en). */
18
+ readonly translations?: TranslationsByLocale;
19
+ };
20
+
21
+ export type AuthMfaClientFeature = {
22
+ readonly name: "auth-mfa";
23
+ readonly providers: readonly ComponentType<{ children: ReactNode }>[];
24
+ readonly gates: readonly ComponentType<{ children: ReactNode }>[];
25
+ readonly translations: TranslationsByLocale;
26
+ readonly components: Readonly<Record<string, ComponentType>>;
27
+ };
28
+
29
+ export function authMfaClient(options: AuthMfaClientOptions = {}): AuthMfaClientFeature {
30
+ return {
31
+ name: "auth-mfa",
32
+ providers: [],
33
+ gates: [],
34
+ translations: mergeTranslations(defaultTranslations, options.translations ?? {}),
35
+ components: { [MFA_ENABLE_SCREEN_ID]: MfaEnableScreen },
36
+ };
37
+ }
@@ -0,0 +1,136 @@
1
+ // @runtime client
2
+ // Default-Bundles für die auth-mfa Feature-UI. Merged in mit
3
+ // authMfaClient() analog zu emailPasswordClient() aus auth-email-password.
4
+ // Keys folgen `auth.mfa.<area>.<slug>`.
5
+
6
+ import type { TranslationsByLocale } from "@cosmicdrift/kumiko-renderer";
7
+
8
+ export const defaultTranslations: TranslationsByLocale = {
9
+ de: {
10
+ "screen:auth-mfa-enable.title": "Zwei-Faktor-Authentifizierung",
11
+ "auth.mfa.verify.title": "Zwei-Faktor-Bestätigung",
12
+ "auth.mfa.verify.subtitle": "Gib den 6-stelligen Code aus deiner Authenticator-App ein.",
13
+ "auth.mfa.verify.code": "Code",
14
+ "auth.mfa.verify.submit": "Bestätigen",
15
+ "auth.mfa.verify.submitting": "…",
16
+ "auth.mfa.verify.backToLogin": "Zurück zum Login",
17
+ "auth.mfa.errors.invalidCode": "Ungültiger Code. Bitte erneut versuchen.",
18
+ "auth.mfa.errors.invalidTotpCode": "Ungültiger Code. Bitte erneut versuchen.",
19
+ "auth.mfa.errors.invalidChallengeToken":
20
+ "Die Anmeldung ist abgelaufen. Bitte erneut einloggen.",
21
+ "auth.mfa.errors.challengeExpired": "Die Anmeldung ist abgelaufen. Bitte erneut einloggen.",
22
+ "auth.mfa.errors.tooManyAttempts": "Zu viele Fehlversuche. Bitte erneut einloggen.",
23
+ "auth.mfa.errors.tooManyAttemptsWithSeconds":
24
+ "Zu viele Fehlversuche. Bitte in {seconds} Sekunden erneut versuchen oder neu einloggen.",
25
+ "auth.mfa.errors.verifyFailed": "Bestätigung fehlgeschlagen.",
26
+ "auth.mfa.errors.mfaAlreadyEnabled": "Zwei-Faktor-Authentifizierung ist bereits aktiv.",
27
+ "auth.mfa.errors.mfaNotEnabled": "Zwei-Faktor-Authentifizierung ist nicht aktiv.",
28
+ "auth.mfa.errors.invalidSetupToken": "Die Einrichtung ist abgelaufen. Bitte erneut starten.",
29
+ "auth.mfa.errors.setupFailed": "Einrichtung fehlgeschlagen. Bitte erneut versuchen.",
30
+ "auth.mfa.errors.invalidRecoveryCode": "Ungültiger Recovery-Code.",
31
+ "auth.mfa.enable.title": "Zwei-Faktor-Authentifizierung",
32
+ "auth.mfa.enable.intro":
33
+ "Schütze dein Konto zusätzlich mit einer Authenticator-App (z.B. Google Authenticator, 1Password).",
34
+ "auth.mfa.enable.start": "Einrichtung starten",
35
+ "auth.mfa.enable.scanTitle": "QR-Code scannen",
36
+ "auth.mfa.enable.manualEntry": "Oder manuell eingeben:",
37
+ "auth.mfa.enable.recoveryTitle": "Recovery-Codes",
38
+ "auth.mfa.enable.recoveryHint":
39
+ "Speichere diese Codes an einem sicheren Ort. Sie werden nur dieses eine Mal angezeigt und erlauben dir den Zugriff, falls du dein Gerät verlierst.",
40
+ "auth.mfa.enable.acknowledge": "Ich habe die Recovery-Codes gespeichert.",
41
+ "auth.mfa.enable.code": "Code aus der Authenticator-App",
42
+ "auth.mfa.enable.cancel": "Abbrechen",
43
+ "auth.mfa.enable.confirm": "Aktivieren",
44
+ "auth.mfa.enable.success": "Zwei-Faktor-Authentifizierung ist jetzt aktiv.",
45
+ "auth.mfa.setup.title": "Zwei-Faktor-Authentifizierung erforderlich",
46
+ "auth.mfa.setup.subtitle":
47
+ "Dein Konto verlangt Zwei-Faktor-Authentifizierung. Richte sie jetzt ein, um dich anzumelden.",
48
+ "auth.mfa.setup.intro":
49
+ "Scanne den QR-Code mit einer Authenticator-App (z.B. Google Authenticator, 1Password).",
50
+ "auth.mfa.setup.start": "Einrichtung starten",
51
+ "auth.mfa.setup.confirm": "Einrichtung abschließen",
52
+ "auth.mfa.disable.title": "Zwei-Faktor-Authentifizierung deaktivieren",
53
+ "auth.mfa.disable.description":
54
+ "Bestätige mit einem Code aus deiner Authenticator-App oder einem Recovery-Code. Dein Konto ist danach nur noch durch dein Passwort geschützt.",
55
+ "auth.mfa.disable.code": "Code aus der Authenticator-App oder Recovery-Code",
56
+ "auth.mfa.disable.confirm": "Deaktivieren",
57
+ "auth.mfa.disable.cancel": "Abbrechen",
58
+ "auth.mfa.disable.trigger": "Zwei-Faktor-Authentifizierung deaktivieren",
59
+ "auth.mfa.regenerate.title": "Neue Recovery-Codes erzeugen",
60
+ "auth.mfa.regenerate.description":
61
+ "Bestätige mit einem Code aus deiner Authenticator-App. Alle bisherigen Recovery-Codes werden sofort ungültig.",
62
+ "auth.mfa.regenerate.code": "Code aus der Authenticator-App",
63
+ "auth.mfa.regenerate.confirm": "Neu erzeugen",
64
+ "auth.mfa.regenerate.cancel": "Abbrechen",
65
+ "auth.mfa.regenerate.trigger": "Neue Recovery-Codes erzeugen",
66
+ "auth.mfa.regenerate.newCodesTitle": "Deine neuen Recovery-Codes",
67
+ "auth.mfa.regenerate.newCodesHint":
68
+ "Speichere diese Codes an einem sicheren Ort. Die alten Codes funktionieren ab sofort nicht mehr.",
69
+ "auth.mfa.regenerate.acknowledge": "Ich habe die neuen Recovery-Codes gespeichert.",
70
+ "auth.mfa.regenerate.done": "Fertig",
71
+ },
72
+ en: {
73
+ "screen:auth-mfa-enable.title": "Two-factor authentication",
74
+ "auth.mfa.verify.title": "Two-factor verification",
75
+ "auth.mfa.verify.subtitle": "Enter the 6-digit code from your authenticator app.",
76
+ "auth.mfa.verify.code": "Code",
77
+ "auth.mfa.verify.submit": "Verify",
78
+ "auth.mfa.verify.submitting": "…",
79
+ "auth.mfa.verify.backToLogin": "Back to login",
80
+ "auth.mfa.errors.invalidCode": "Invalid code. Please try again.",
81
+ "auth.mfa.errors.invalidTotpCode": "Invalid code. Please try again.",
82
+ "auth.mfa.errors.invalidChallengeToken": "Your sign-in has expired. Please sign in again.",
83
+ "auth.mfa.errors.challengeExpired": "Your sign-in has expired. Please sign in again.",
84
+ "auth.mfa.errors.tooManyAttempts": "Too many failed attempts. Please sign in again.",
85
+ "auth.mfa.errors.tooManyAttemptsWithSeconds":
86
+ "Too many failed attempts. Try again in {seconds} seconds, or sign in again.",
87
+ "auth.mfa.errors.verifyFailed": "Verification failed.",
88
+ "auth.mfa.errors.mfaAlreadyEnabled": "Two-factor authentication is already enabled.",
89
+ "auth.mfa.errors.mfaNotEnabled": "Two-factor authentication is not enabled.",
90
+ "auth.mfa.errors.invalidSetupToken": "Setup expired. Please start again.",
91
+ "auth.mfa.errors.setupFailed": "Setup failed. Please try again.",
92
+ "auth.mfa.errors.invalidRecoveryCode": "Invalid recovery code.",
93
+ "auth.mfa.enable.title": "Two-factor authentication",
94
+ "auth.mfa.enable.intro":
95
+ "Add an extra layer of protection with an authenticator app (e.g. Google Authenticator, 1Password).",
96
+ "auth.mfa.enable.start": "Start setup",
97
+ "auth.mfa.enable.scanTitle": "Scan the QR code",
98
+ "auth.mfa.enable.manualEntry": "Or enter manually:",
99
+ "auth.mfa.enable.recoveryTitle": "Recovery codes",
100
+ "auth.mfa.enable.recoveryHint":
101
+ "Save these codes somewhere safe. They're shown only this once and let you back in if you lose your device.",
102
+ "auth.mfa.enable.acknowledge": "I've saved my recovery codes.",
103
+ "auth.mfa.enable.code": "Code from your authenticator app",
104
+ "auth.mfa.enable.cancel": "Cancel",
105
+ "auth.mfa.enable.confirm": "Enable",
106
+ "auth.mfa.enable.success": "Two-factor authentication is now enabled.",
107
+ "auth.mfa.setup.title": "Two-factor authentication required",
108
+ "auth.mfa.setup.subtitle":
109
+ "Your account requires two-factor authentication. Set it up now to sign in.",
110
+ "auth.mfa.setup.intro":
111
+ "Scan the QR code with an authenticator app (e.g. Google Authenticator, 1Password).",
112
+ "auth.mfa.setup.start": "Start setup",
113
+ "auth.mfa.setup.confirm": "Complete setup",
114
+ "auth.mfa.disable.title": "Disable two-factor authentication",
115
+ "auth.mfa.disable.description":
116
+ "Confirm with a code from your authenticator app or a recovery code. Your account will then be protected by your password alone.",
117
+ "auth.mfa.disable.code": "Code from your authenticator app or a recovery code",
118
+ "auth.mfa.disable.confirm": "Disable",
119
+ "auth.mfa.disable.cancel": "Cancel",
120
+ "auth.mfa.disable.trigger": "Disable two-factor authentication",
121
+ "auth.mfa.regenerate.title": "Generate new recovery codes",
122
+ "auth.mfa.regenerate.description":
123
+ "Confirm with a code from your authenticator app. All existing recovery codes stop working immediately.",
124
+ "auth.mfa.regenerate.code": "Code from your authenticator app",
125
+ "auth.mfa.regenerate.confirm": "Generate new codes",
126
+ "auth.mfa.regenerate.cancel": "Cancel",
127
+ "auth.mfa.regenerate.trigger": "Generate new recovery codes",
128
+ "auth.mfa.regenerate.newCodesTitle": "Your new recovery codes",
129
+ "auth.mfa.regenerate.newCodesHint":
130
+ "Save these codes somewhere safe. The old codes stop working immediately.",
131
+ "auth.mfa.regenerate.acknowledge": "I've saved my new recovery codes.",
132
+ "auth.mfa.regenerate.done": "Done",
133
+ },
134
+ };
135
+
136
+ export { mergeTranslations } from "@cosmicdrift/kumiko-renderer";