@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
@@ -34,8 +34,24 @@ export type LoginResponse = {
34
34
  };
35
35
  };
36
36
 
37
+ // Reason codes the login-handler is known to emit today (login-screen.tsx's
38
+ // reasonToKey maps every one of these to an i18n key). The server can also
39
+ // send a reason this client doesn't know about yet (rolling deploys, a new
40
+ // enforcement policy) — `string & {}` keeps that open while still giving
41
+ // autocomplete/typo-checking on the known set, instead of collapsing both
42
+ // to plain `string` (coding-standards: no magic strings without a union).
43
+ export type KnownLoginFailureReason =
44
+ | "invalid_credentials"
45
+ | "no_membership"
46
+ | "account_locked"
47
+ | "email_not_verified"
48
+ | "rate_limited"
49
+ | "invalid_body"
50
+ | "mfa_not_supported"
51
+ | "mfa_setup_required";
52
+
37
53
  export type LoginFailure = {
38
- readonly reason: string;
54
+ readonly reason: KnownLoginFailureReason | (string & {});
39
55
  readonly message?: string;
40
56
  readonly retryAfterSeconds?: number;
41
57
  };
@@ -45,13 +61,24 @@ export function csrfHeader(): Record<string, string> {
45
61
  return token !== undefined ? { [CSRF_HEADER_NAME]: token } : {};
46
62
  }
47
63
 
48
- // POST /api/auth/login. Erfolg → token + user; Fehler → strukturiertes
49
- // failure-objekt mit reason (invalid_credentials, account_locked,
50
- // no_membership, rate_limited). Das UI rendert darüber eine passende
51
- // Fehler-Meldung; der Server setzt Cookies bei 200 automatisch.
52
- export async function login(
53
- req: LoginRequest,
54
- ): Promise<{ ok: true; data: LoginResponse } | { ok: false; error: LoginFailure }> {
64
+ // LoginResult mirrors the two-step login contract auth-routes.ts owns: a
65
+ // straight success, an MFA challenge (auth-mfa mints the token), an
66
+ // MFA-setup-required block (enforcement policy blocks an unenrolled user),
67
+ // or a plain failure. One discriminated union, not `{ok}` booleans, because
68
+ // callers branch on 4 arms.
69
+ export type LoginResult =
70
+ | { readonly kind: "success"; readonly data: LoginResponse }
71
+ | { readonly kind: "mfa-challenge"; readonly challengeToken: string }
72
+ | { readonly kind: "mfa-setup-required"; readonly preauthSetupToken: string }
73
+ | { readonly kind: "failure"; readonly error: LoginFailure };
74
+
75
+ // POST /api/auth/login. Success → token + user; MFA-enrolled user → a
76
+ // challenge token the caller completes via auth-mfa's verify screen;
77
+ // unenrolled user blocked by enforcement policy → mfa-setup-required;
78
+ // otherwise a structured failure with reason (invalid_credentials,
79
+ // account_locked, no_membership, rate_limited). The server sets cookies
80
+ // automatically on any 200.
81
+ export async function login(req: LoginRequest): Promise<LoginResult> {
55
82
  const res = await fetch("/api/auth/login", {
56
83
  method: "POST",
57
84
  credentials: "same-origin",
@@ -59,13 +86,17 @@ export async function login(
59
86
  body: JSON.stringify(req),
60
87
  });
61
88
  if (res.status === 429) {
62
- return { ok: false, error: { reason: "rate_limited" } };
89
+ return { kind: "failure", error: { reason: "rate_limited" } };
63
90
  }
64
91
  // @cast-boundary engine-payload — HTTP-API contract, server-side schema-validated
65
92
  const body = (await res.json().catch(() => ({}))) as {
66
93
  isSuccess?: boolean;
67
94
  token?: string;
68
95
  user?: LoginResponse["user"];
96
+ mfaRequired?: boolean;
97
+ challengeToken?: string;
98
+ mfaSetupRequired?: boolean;
99
+ preauthSetupToken?: string;
69
100
  error?:
70
101
  | {
71
102
  code?: string;
@@ -74,19 +105,33 @@ export async function login(
74
105
  }
75
106
  | string;
76
107
  };
77
- if (body.isSuccess === true && body.token !== undefined && body.user !== undefined) {
78
- return { ok: true, data: { token: body.token, user: body.user } };
108
+ if (body.isSuccess === true) {
109
+ if (body.mfaRequired === true && typeof body.challengeToken === "string") {
110
+ return { kind: "mfa-challenge", challengeToken: body.challengeToken };
111
+ }
112
+ if (body.mfaSetupRequired === true) {
113
+ // Older servers / mounts may set the flag without minting a preauth token —
114
+ // surface an explicit failure (login-screen maps mfa_setup_required) instead
115
+ // of falling through to generic login_failed.
116
+ if (typeof body.preauthSetupToken === "string") {
117
+ return { kind: "mfa-setup-required", preauthSetupToken: body.preauthSetupToken };
118
+ }
119
+ return { kind: "failure", error: { reason: "mfa_setup_required" } };
120
+ }
121
+ if (body.token !== undefined && body.user !== undefined) {
122
+ return { kind: "success", data: { token: body.token, user: body.user } };
123
+ }
79
124
  }
80
125
  // Der Server schickt error entweder als string ("invalid_body") oder als
81
126
  // strukturiertes Objekt. Wir ziehen uns den sprechendsten Reason raus.
82
127
  const err = body.error;
83
128
  if (typeof err === "string") {
84
- return { ok: false, error: { reason: err } };
129
+ return { kind: "failure", error: { reason: err } };
85
130
  }
86
131
  const reason = err?.details?.reason ?? err?.code ?? "login_failed";
87
132
  const retry = err?.details?.retryAfterSeconds;
88
133
  return {
89
- ok: false,
134
+ kind: "failure",
90
135
  error: {
91
136
  reason,
92
137
  ...(err?.message !== undefined && { message: err.message }),
@@ -144,10 +189,10 @@ async function parseTokenFailure(res: Response): Promise<AuthTokenFailure> {
144
189
  return { reason };
145
190
  }
146
191
 
147
- // POST /api/auth/request-password-reset. 200 silent-success: auch wenn
148
- // die Email nicht existiert, sieht der caller `{ ok: true }` — kein
149
- // account-enumeration. Server triggert Mail nur intern wenn user
150
- // gefunden. 429 → typed rate-limit-Failure. 5xx → unknown-error.
192
+ // POST /api/auth/request-password-reset. 200 silent-success: even if the
193
+ // email doesn't exist, the caller sees `{ ok: true }` — no account
194
+ // enumeration. Server only triggers the mail internally when a user is
195
+ // found. 429 → typed rate-limit failure. 5xx → unknown-error.
151
196
  export async function requestPasswordReset(
152
197
  email: string,
153
198
  ): Promise<{ ok: true } | { ok: false; error: AuthTokenFailure }> {
@@ -181,7 +226,7 @@ export async function resetPassword(
181
226
  }
182
227
 
183
228
  // POST /api/auth/request-email-verification. Same silent-success
184
- // semantik wie request-password-reset. 429 → rate-limit-Failure.
229
+ // semantics as request-password-reset. 429 → rate-limit failure.
185
230
  export async function requestEmailVerification(
186
231
  email: string,
187
232
  ): Promise<{ ok: true } | { ok: false; error: AuthTokenFailure }> {
@@ -211,6 +256,39 @@ export async function verifyEmail(
211
256
  return { ok: false, error: await parseTokenFailure(res) };
212
257
  }
213
258
 
259
+ // POST /api/auth/request-account-unlock. Same silent-success semantics as
260
+ // request-password-reset. 429 → rate-limit failure.
261
+ export async function requestAccountUnlock(
262
+ email: string,
263
+ ): Promise<{ ok: true } | { ok: false; error: AuthTokenFailure }> {
264
+ const res = await fetch("/api/auth/request-account-unlock", {
265
+ method: "POST",
266
+ credentials: "same-origin",
267
+ headers: { "Content-Type": "application/json", ...csrfHeader() },
268
+ body: JSON.stringify({ email }),
269
+ });
270
+ if (res.ok) return { ok: true };
271
+ return { ok: false, error: await parseTokenFailure(res) };
272
+ }
273
+
274
+ // POST /api/auth/confirm-account-unlock. Auto-submitted by
275
+ // ConfirmAccountUnlockScreen after parsing `?token=...`. Server collapses
276
+ // every verify failure into `invalid_unlock_token` (anti-enumeration,
277
+ // parallel to reset/verify). Only clears the Redis lockout state, no
278
+ // entity write.
279
+ export async function confirmAccountUnlock(
280
+ token: string,
281
+ ): Promise<{ ok: true } | { ok: false; error: AuthTokenFailure }> {
282
+ const res = await fetch("/api/auth/confirm-account-unlock", {
283
+ method: "POST",
284
+ credentials: "same-origin",
285
+ headers: { "Content-Type": "application/json", ...csrfHeader() },
286
+ body: JSON.stringify({ token }),
287
+ });
288
+ if (res.ok) return { ok: true };
289
+ return { ok: false, error: await parseTokenFailure(res) };
290
+ }
291
+
214
292
  // POST /api/auth/signup-request. Always-200 (anti-enumeration; wir
215
293
  // sagen nicht ob die Email schon registriert ist). Server schickt
216
294
  // Activation-Mail an die Adresse — beim Klick auf den Link landet der
@@ -0,0 +1,30 @@
1
+ // @runtime client
2
+ // Pure form helpers shared by auth screens. Extracted so unit tests can
3
+ // pin password / rate-limit / redirect rules without mounting React.
4
+
5
+ export type PasswordPairIssue = "too_short" | "mismatch";
6
+
7
+ /** Client-side password + confirm check before calling signup/reset APIs. */
8
+ export function passwordPairIssue(
9
+ password: string,
10
+ confirmPassword: string,
11
+ minLength = 8,
12
+ ): PasswordPairIssue | null {
13
+ if (password.length < minLength) return "too_short";
14
+ if (password !== confirmPassword) return "mismatch";
15
+ return null;
16
+ }
17
+
18
+ /** Maps server `retryAfterSeconds` to whole minutes for i18n interpolation. */
19
+ export function retryAfterMinutes(retryAfterSeconds?: number): number | undefined {
20
+ if (retryAfterSeconds === undefined) return undefined;
21
+ return Math.ceil(retryAfterSeconds / 60);
22
+ }
23
+
24
+ /** Resolves post-login redirect: string template or function of tenantKey. */
25
+ export function resolveLoggedInHref(
26
+ href: string | ((args: { readonly tenantKey: string }) => string),
27
+ tenantKey: string,
28
+ ): string {
29
+ return typeof href === "function" ? href({ tenantKey }) : href;
30
+ }
@@ -9,15 +9,14 @@
9
9
  // (full-screen, zentriert, max-w-sm). Web-only;
10
10
  // eine Native-Variante landet bei Bedarf
11
11
  // daneben (z.B. SafeArea + ScrollView).
12
- // authButtonClass — Tailwind-Class für anchor-styled-as-button
13
- // (z.B. "Zum Login"-Link nach Reset-Success).
14
- // Nur dort, wo ein <a>-Tag rendert.
15
- // authMutedLinkClass — Subtle-Link-Style.
16
12
  // parseUrlToken — URL-Param-Helper (window.location.search).
13
+ //
14
+ // Link-Styles laufen über das Link-Primitive (variant="button"/"muted") —
15
+ // die früheren authButtonClass/authMutedLinkClass sind dorthin gewandert.
17
16
 
18
17
  import { usePrimitives } from "@cosmicdrift/kumiko-renderer";
19
- import { BareFormProvider, cn } from "@cosmicdrift/kumiko-renderer-web";
20
- import { createContext, type ReactNode, useContext } from "react";
18
+ import { BareFormProvider } from "@cosmicdrift/kumiko-renderer-web";
19
+ import { createContext, type ReactNode, useContext, useEffect, useState } from "react";
21
20
 
22
21
  // Wrappt die zentrierte Auth-Card in ihre Umgebung. Default = Fullscreen-
23
22
  // zentriert (Standalone-Auth-Page). Eine Apex-/Marketing-Chrome reicht über
@@ -80,20 +79,6 @@ export function AuthCard({ title, subtitle, children }: AuthCardProps): ReactNod
80
79
  return shell(card);
81
80
  }
82
81
 
83
- // Primary-button-Style für anchor-Tags die wie ein Button aussehen
84
- // (z.B. "Zum Login"-Link nach Reset-Success — kein <Button> weil <a>).
85
- export const authButtonClass = cn(
86
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors",
87
- "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
88
- "disabled:pointer-events-none disabled:opacity-50",
89
- "bg-primary text-primary-foreground shadow hover:bg-primary/90 h-9 px-4 py-2",
90
- );
91
-
92
- // Subtle-Link-Style (für "Zurück zum Login"-Anchors). Fixed margin/
93
- // alignment-classes lassen wir den Caller setzen — nur Farbe + hover.
94
- export const authMutedLinkClass =
95
- "text-sm text-muted-foreground hover:text-foreground underline-offset-4 hover:underline";
96
-
97
82
  // Liest `?<paramName>=<value>` aus der aktuellen URL — typisches
98
83
  // Pattern für Token-bearing Pages (reset, verify). Returnt "" wenn der
99
84
  // Browser nicht da ist (SSR-safety) oder der Parameter fehlt.
@@ -106,3 +91,20 @@ export function parseUrlToken(paramName = "token"): string {
106
91
  if (typeof window === "undefined") return "";
107
92
  return new URLSearchParams(window.location.search).get(paramName) ?? "";
108
93
  }
94
+
95
+ // Reads the magic-link token from `?<paramName>=` once on mount, then strips
96
+ // that param from the URL via history.replaceState so the single-use token
97
+ // doesn't linger in browser history / Referer (#774). An explicit `override`
98
+ // (server-injected token) short-circuits both the URL read and the scrub.
99
+ export function useUrlToken(override?: string, paramName = "token"): string {
100
+ const [token] = useState(() => override ?? parseUrlToken(paramName));
101
+ useEffect(() => {
102
+ if (override !== undefined) return;
103
+ if (typeof window === "undefined") return;
104
+ const url = new URL(window.location.href);
105
+ if (!url.searchParams.has(paramName)) return;
106
+ url.searchParams.delete(paramName);
107
+ window.history.replaceState(window.history.state, "", url.toString());
108
+ }, [override, paramName]);
109
+ return token;
110
+ }
@@ -9,38 +9,159 @@
9
9
  // (nur `{ children }`-Prop). Der Sample kann so einen eigenen Login-
10
10
  // Screen rein konfigurieren, ohne den Gate selbst ersetzen zu müssen.
11
11
 
12
- import type { ComponentType, ReactNode } from "react";
12
+ import { type ComponentType, type ReactNode, useEffect, useState } from "react";
13
13
  import { LoginScreen, type LoginScreenProps } from "./login-screen";
14
14
  import { SessionProvider, useSession } from "./session";
15
15
 
16
- export function makeAuthGate(
17
- LoginComponent: ComponentType<LoginScreenProps> = LoginScreen,
18
- loginProps?: LoginScreenProps,
19
- ): ComponentType<{ children: ReactNode }> {
20
- function AuthGate({ children }: { readonly children: ReactNode }): ReactNode {
21
- const { status } = useSession();
16
+ // Generic — NOT auth-mfa's MfaVerifyScreenProps directly, so this feature
17
+ // stays unaware of auth-mfa's concrete shape (same coupling direction as
18
+ // login.write.ts's mfaStatusChecker callback on the server side). Apps
19
+ // wire auth-mfa's MfaVerifyScreen in here via EmailPasswordClientOptions.
20
+ export type MfaVerifyComponentProps = {
21
+ readonly challengeToken: string;
22
+ readonly onSuccess?: () => void;
23
+ readonly onCancel?: () => void;
24
+ };
25
+
26
+ // Same coupling reasoning as MfaVerifyComponentProps above — generic, not
27
+ // auth-mfa's MfaSetupPreauthScreenProps directly. Apps wire auth-mfa's
28
+ // MfaSetupPreauthScreen in here via EmailPasswordClientOptions.
29
+ export type MfaSetupComponentProps = {
30
+ readonly preauthSetupToken: string;
31
+ readonly accountLabel: string;
32
+ readonly onSuccess?: () => void;
33
+ readonly onCancel?: () => void;
34
+ };
35
+
36
+ export type LoginRouteOptions = {
37
+ readonly loginScreen?: ComponentType<LoginScreenProps>;
38
+ readonly loginScreenProps?: LoginScreenProps;
39
+ readonly mfaVerifyScreen?: ComponentType<MfaVerifyComponentProps>;
40
+ readonly mfaSetupScreen?: ComponentType<MfaSetupComponentProps>;
41
+ /** Called once the session becomes authenticated. makeAuthGate ignores
42
+ * this (it renders `children` on its own authenticated branch instead);
43
+ * standalone routes — no parent gate, e.g. an anonymous apex/marketing
44
+ * surface — use it to redirect. */
45
+ readonly onAuthenticated?: () => void;
46
+ };
47
+
48
+ // The one sanctioned way to render a login flow that correctly completes
49
+ // an MFA challenge — makeAuthGate below is just this wrapped for the
50
+ // `{ children }` gate shape. Also exported directly for apps that render
51
+ // their own standalone login route outside any gate (createPublicSurface
52
+ // apex pages stack providers, not gates — see auth-mount.tsx recipes).
53
+ // Handing an app raw LoginScreen + telling it to hand-roll the challenge-
54
+ // token swap itself is exactly how kumiko-framework#266's login-time MFA
55
+ // step went missing in a real apex surface; this prevents the *most common*
56
+ // version of that mistake. MFA still only actually works once a consumer
57
+ // wires `mfaVerifyScreen` — omitting it is a silent no-MFA fallback, not
58
+ // something this route can catch on its own (auth-mfa is a separate,
59
+ // optional package).
60
+ export function createLoginRoute(
61
+ opts: LoginRouteOptions = {},
62
+ ): ComponentType<Record<string, never>> {
63
+ const LoginComponent = opts.loginScreen ?? LoginScreen;
64
+ const MfaVerifyComponent = opts.mfaVerifyScreen;
65
+ const MfaSetupComponent = opts.mfaSetupScreen;
66
+
67
+ function LoginRoute(): ReactNode {
68
+ const { status, refresh } = useSession();
69
+ const { onAuthenticated } = opts;
70
+ // Pending challenge-token from LoginScreen's onMfaChallenge. Lives here
71
+ // (not in SessionState) because it's a UI-only transition — the server
72
+ // never considers this session authenticated until verify succeeds.
73
+ const [challengeToken, setChallengeToken] = useState<string | null>(null);
74
+ const [setupRequest, setSetupRequest] = useState<{
75
+ readonly preauthSetupToken: string;
76
+ readonly accountLabel: string;
77
+ } | null>(null);
78
+
79
+ useEffect(() => {
80
+ if (status === "authenticated") onAuthenticated?.();
81
+ }, [status]);
82
+
22
83
  if (status === "loading") {
23
84
  return (
24
85
  <div className="min-h-screen flex items-center justify-center text-muted-foreground text-sm" />
25
86
  );
26
87
  }
27
- if (status === "unauthenticated") {
28
- return <LoginComponent {...loginProps} />;
88
+ // A standalone mount (no parent gate, no onAuthenticated wired — e.g. an
89
+ // apex/marketing surface that just places <LoginRoute /> directly) has no
90
+ // way to navigate away once authenticated. Blanking the page there is a
91
+ // regression against the old raw LoginScreen (which had no session check
92
+ // at all); fall through to the form instead. A route WITH onAuthenticated
93
+ // still returns null and trusts the effect above to redirect.
94
+ if (status === "authenticated" && onAuthenticated) return null;
95
+ if (challengeToken !== null && MfaVerifyComponent) {
96
+ return (
97
+ <MfaVerifyComponent
98
+ challengeToken={challengeToken}
99
+ onSuccess={() => setChallengeToken(null)}
100
+ onCancel={() => setChallengeToken(null)}
101
+ />
102
+ );
103
+ }
104
+ // Pending preauthSetupToken from LoginScreen's onMfaSetupRequired. Same
105
+ // reasoning as challengeToken — a UI-only transition, not session state.
106
+ if (setupRequest !== null && MfaSetupComponent) {
107
+ return (
108
+ <MfaSetupComponent
109
+ preauthSetupToken={setupRequest.preauthSetupToken}
110
+ accountLabel={setupRequest.accountLabel}
111
+ onSuccess={() => {
112
+ // MfaSetupPreauthScreen has no session to refresh itself with
113
+ // (it runs pre-auth) — the gate owns the session, so it refreshes.
114
+ // Keep setupRequest until refresh settles: clearing first would
115
+ // dump the user back on the login form with no retry if refresh
116
+ // rejects (cookie+JWT already minted server-side).
117
+ void refresh().then(
118
+ () => setSetupRequest(null),
119
+ () => {
120
+ // leave setupRequest set so the user can cancel/retry
121
+ },
122
+ );
123
+ }}
124
+ onCancel={() => setSetupRequest(null)}
125
+ />
126
+ );
29
127
  }
30
- return <>{children}</>;
128
+ return (
129
+ <LoginComponent
130
+ {...opts.loginScreenProps}
131
+ onMfaChallenge={
132
+ MfaVerifyComponent ? setChallengeToken : opts.loginScreenProps?.onMfaChallenge
133
+ }
134
+ onMfaSetupRequired={
135
+ MfaSetupComponent
136
+ ? (preauthSetupToken, accountLabel) =>
137
+ setSetupRequest({ preauthSetupToken, accountLabel })
138
+ : opts.loginScreenProps?.onMfaSetupRequired
139
+ }
140
+ />
141
+ );
142
+ }
143
+ return LoginRoute;
144
+ }
145
+
146
+ export function makeAuthGate(opts: LoginRouteOptions = {}): ComponentType<{
147
+ children: ReactNode;
148
+ }> {
149
+ const LoginRoute = createLoginRoute(opts);
150
+ function AuthGate({ children }: { readonly children: ReactNode }): ReactNode {
151
+ const { status } = useSession();
152
+ if (status === "authenticated") return <>{children}</>;
153
+ return <LoginRoute />;
31
154
  }
32
155
  return AuthGate;
33
156
  }
34
157
 
35
- /** SessionProvider + AuthGate als ein Gate — damit öffentliche Gates davor
36
- * (z.B. /rechner) den Session-Bootstrap nicht mounten. createKumikoApp
37
- * stackt providers außerhalb aller gates; SessionProvider darf deshalb
38
- * kein provider mehr sein. */
39
- export function makeSessionAuthGate(
40
- LoginComponent: ComponentType<LoginScreenProps> = LoginScreen,
41
- loginProps?: LoginScreenProps,
42
- ): ComponentType<{ children: ReactNode }> {
43
- const AuthGate = makeAuthGate(LoginComponent, loginProps);
158
+ // SessionProvider + AuthGate as one gate, so a public gate in front (e.g.
159
+ // /calculator) doesn't mount the session bootstrap — createKumikoApp stacks
160
+ // providers outside all gates, so SessionProvider can't be a provider anymore.
161
+ export function makeSessionAuthGate(opts: LoginRouteOptions = {}): ComponentType<{
162
+ children: ReactNode;
163
+ }> {
164
+ const AuthGate = makeAuthGate(opts);
44
165
  function SessionAuthGate({ children }: { readonly children: ReactNode }): ReactNode {
45
166
  return (
46
167
  <SessionProvider>
@@ -8,7 +8,11 @@
8
8
  import type { TranslationsByLocale } from "@cosmicdrift/kumiko-renderer";
9
9
  import type { ComponentType, ReactNode } from "react";
10
10
  import { defaultTranslations, mergeTranslations } from "../i18n";
11
- import { makeSessionAuthGate } from "./auth-gate";
11
+ import {
12
+ type MfaSetupComponentProps,
13
+ type MfaVerifyComponentProps,
14
+ makeSessionAuthGate,
15
+ } from "./auth-gate";
12
16
  import type { LoginScreenProps } from "./login-screen";
13
17
 
14
18
  export type EmailPasswordClientOptions = {
@@ -17,6 +21,17 @@ export type EmailPasswordClientOptions = {
17
21
  * eine eigene Komponente mit derselben Signatur reichen. */
18
22
  readonly loginScreen?: ComponentType<LoginScreenProps>;
19
23
  readonly loginScreenProps?: LoginScreenProps;
24
+ /** auth-mfa's MfaVerifyScreen, wired in from the app so this feature
25
+ * stays unaware of auth-mfa's concrete shape. When LoginScreen's
26
+ * onMfaChallenge fires, the gate swaps to this component instead of
27
+ * requiring the app to own that state itself. Apps not mounting
28
+ * auth-mfa simply don't pass this — LoginScreen's built-in fallback
29
+ * error covers that case. */
30
+ readonly mfaVerifyScreen?: ComponentType<MfaVerifyComponentProps>;
31
+ /** auth-mfa's MfaSetupPreauthScreen, wired in from the app the same way
32
+ * as mfaVerifyScreen above. Apps not mounting auth-mfa simply don't
33
+ * pass this. */
34
+ readonly mfaSetupScreen?: ComponentType<MfaSetupComponentProps>;
20
35
  /** Key-Overrides pro Locale. Wird mit den Default-Bundles (de/en)
21
36
  * aus `translations.ts` gemerged — jeder hier gesetzte Key gewinnt.
22
37
  * Für Branding ("Sign in" → "Login to Acme") oder weitere Sprachen
@@ -41,7 +56,14 @@ export function emailPasswordClient(
41
56
  return {
42
57
  name: "auth-email-password",
43
58
  providers: [],
44
- gates: [makeSessionAuthGate(options.loginScreen, options.loginScreenProps)],
59
+ gates: [
60
+ makeSessionAuthGate({
61
+ loginScreen: options.loginScreen,
62
+ loginScreenProps: options.loginScreenProps,
63
+ mfaVerifyScreen: options.mfaVerifyScreen,
64
+ mfaSetupScreen: options.mfaSetupScreen,
65
+ }),
66
+ ],
45
67
  translations,
46
68
  };
47
69
  }
@@ -0,0 +1,95 @@
1
+ // @runtime client
2
+ // ConfirmAccountUnlockScreen — reads `?token=...` from the URL, auto-posts
3
+ // it to /api/auth/confirm-account-unlock on mount, shows success/error.
4
+ // Idempotent server-side (clearLockoutState is a no-op when already
5
+ // unlocked), so no re-submit risk. Mirrors VerifyEmailScreen 1:1.
6
+ //
7
+ // useEffect with empty deps + a once-guard so React Strict Mode's double
8
+ // useEffect dispatch in dev fires the confirm call only once.
9
+
10
+ import { usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
11
+ import { type ReactNode, useEffect, useRef, useState } from "react";
12
+ import { confirmAccountUnlock } from "./auth-client";
13
+ import { AuthCard, useUrlToken } from "./auth-form-primitives";
14
+
15
+ export type ConfirmAccountUnlockScreenProps = {
16
+ readonly title?: string;
17
+ /** Override for the token from the URL. Default: parsed from `?token=...`. */
18
+ readonly token?: string;
19
+ /** href for the "go to sign in" link. Default "/login". */
20
+ readonly loginHref?: string;
21
+ };
22
+
23
+ type Status = "verifying" | "success" | "error" | "missing-token";
24
+
25
+ export function ConfirmAccountUnlockScreen({
26
+ title,
27
+ token: tokenProp,
28
+ loginHref = "/login",
29
+ }: ConfirmAccountUnlockScreenProps): ReactNode {
30
+ const t = useTranslation();
31
+ const { Link } = usePrimitives();
32
+ const token = useUrlToken(tokenProp);
33
+ const [status, setStatus] = useState<Status>(token === "" ? "missing-token" : "verifying");
34
+ const startedRef = useRef(false);
35
+
36
+ useEffect(() => {
37
+ if (status !== "verifying") return;
38
+ if (startedRef.current) return;
39
+ startedRef.current = true;
40
+ void (async () => {
41
+ const res = await confirmAccountUnlock(token);
42
+ setStatus(res.ok ? "success" : "error");
43
+ })();
44
+ }, [status, token]);
45
+
46
+ if (status === "missing-token") {
47
+ return (
48
+ <AuthCard title={title ?? t("auth.unlockAccount.errorTitle")}>
49
+ <div className="p-6 pt-0 flex flex-col gap-4">
50
+ <p className="text-sm text-muted-foreground">{t("auth.unlockAccount.missingToken")}</p>
51
+ <Link href={loginHref} variant="muted">
52
+ {t("auth.unlockAccount.goToLogin")}
53
+ </Link>
54
+ </div>
55
+ </AuthCard>
56
+ );
57
+ }
58
+
59
+ if (status === "verifying") {
60
+ return (
61
+ <AuthCard>
62
+ <div className="p-6">
63
+ <p className="text-sm text-muted-foreground" role="status">
64
+ {t("auth.unlockAccount.verifying")}
65
+ </p>
66
+ </div>
67
+ </AuthCard>
68
+ );
69
+ }
70
+
71
+ if (status === "success") {
72
+ return (
73
+ <AuthCard title={title ?? t("auth.unlockAccount.successTitle")}>
74
+ <div className="p-6 pt-0 flex flex-col gap-4">
75
+ <p className="text-sm text-muted-foreground">{t("auth.unlockAccount.successBody")}</p>
76
+ <Link href={loginHref} variant="button">
77
+ {t("auth.unlockAccount.goToLogin")}
78
+ </Link>
79
+ </div>
80
+ </AuthCard>
81
+ );
82
+ }
83
+
84
+ // status === "error"
85
+ return (
86
+ <AuthCard title={title ?? t("auth.unlockAccount.errorTitle")}>
87
+ <div className="p-6 pt-0 flex flex-col gap-4">
88
+ <p className="text-sm text-muted-foreground">{t("auth.unlockAccount.errorBody")}</p>
89
+ <Link href={loginHref} variant="muted">
90
+ {t("auth.unlockAccount.goToLogin")}
91
+ </Link>
92
+ </div>
93
+ </AuthCard>
94
+ );
95
+ }
@@ -12,7 +12,7 @@
12
12
  import { usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
13
13
  import { type FormEvent, type ReactNode, useState } from "react";
14
14
  import { requestPasswordReset } from "./auth-client";
15
- import { AuthCard, authMutedLinkClass } from "./auth-form-primitives";
15
+ import { AuthCard } from "./auth-form-primitives";
16
16
 
17
17
  export type ForgotPasswordScreenProps = {
18
18
  readonly title?: string;
@@ -28,7 +28,7 @@ export function ForgotPasswordScreen({
28
28
  loginHref = "/login",
29
29
  }: ForgotPasswordScreenProps): ReactNode {
30
30
  const t = useTranslation();
31
- const { Form, Field, Input, Button, Banner } = usePrimitives();
31
+ const { Form, Field, Input, Button, Banner, Link } = usePrimitives();
32
32
  const [email, setEmail] = useState("");
33
33
  const [submitting, setSubmitting] = useState(false);
34
34
  const [done, setDone] = useState(false);
@@ -77,9 +77,9 @@ export function ForgotPasswordScreen({
77
77
  <p className="font-medium text-foreground">{t("auth.forgotPassword.successTitle")}</p>
78
78
  <p className="mt-1">{t("auth.forgotPassword.successBody")}</p>
79
79
  </Banner>
80
- <a href={loginHref} className={authMutedLinkClass}>
80
+ <Link href={loginHref} variant="muted">
81
81
  {t("auth.forgotPassword.backToLogin")}
82
- </a>
82
+ </Link>
83
83
  </div>
84
84
  ) : (
85
85
  <div className="p-6 pt-0 flex flex-col gap-4">
@@ -101,9 +101,9 @@ export function ForgotPasswordScreen({
101
101
  {submitting ? t("auth.forgotPassword.submitting") : t("auth.forgotPassword.submit")}
102
102
  </Button>
103
103
  </Form>
104
- <a href={loginHref} className={`${authMutedLinkClass} self-center`}>
104
+ <Link href={loginHref} variant="muted" className="self-center">
105
105
  {t("auth.forgotPassword.backToLogin")}
106
- </a>
106
+ </Link>
107
107
  </div>
108
108
  )}
109
109
  </AuthCard>