@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,267 @@
1
+ // @runtime client
2
+ // Pre-auth twin of MfaEnableScreen: a user blocked at login by MFA
3
+ // enforcement and not yet enrolled lands here straight out of the login
4
+ // flow — there is no session yet. Identity comes entirely from the
5
+ // preauthSetupToken login()'s mfa-setup-required result carries (see
6
+ // auth-client.ts's LoginResult). Calls startMfaSetupPreauth/confirmMfa-
7
+ // SetupPreauth directly via fetch (mfa-client.ts) — no useSession(), no
8
+ // useDispatcher(), both assume a real session that doesn't exist here.
9
+ //
10
+ // Session contract: a successful confirm mints cookie+JWT server-side, but
11
+ // THIS component does not refresh the session itself (unlike MfaVerify-
12
+ // Screen, which calls session.refresh() internally). onSuccess fires right
13
+ // after confirm — the caller (e.g. auth-gate's LoginRoute, which already
14
+ // has useSession() in scope) must call refresh() there, otherwise the app
15
+ // stays "unauthenticated" until the next reload.
16
+
17
+ import { usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
18
+ // qrcode's package.json#browser remap avoids Node-only deps (yargs/pngjs)
19
+ // for bundlers that honor it — Metro doesn't, hence the explicit subpath.
20
+ import QRCode from "qrcode/lib/browser";
21
+ import { type FormEvent, type ReactNode, useState } from "react";
22
+ // kumiko-lint-ignore cross-feature-import client-only component, the feature's server barrel has no web/ re-export
23
+ import { AuthCard } from "../../auth-email-password/web";
24
+ import { confirmMfaSetupPreauth, startMfaSetupPreauth } from "./mfa-client";
25
+
26
+ type SetupState = {
27
+ readonly setupToken: string;
28
+ readonly secretParam: string;
29
+ readonly recoveryCodes: readonly string[];
30
+ readonly qrSvg: string;
31
+ };
32
+
33
+ // otpauth:// URIs put the secret in the query string — extract it for the
34
+ // manual-entry fallback (authenticator apps without a camera / desktop use).
35
+ function extractSecret(otpauthUri: string): string {
36
+ const query = otpauthUri.split("?")[1] ?? "";
37
+ return new URLSearchParams(query).get("secret") ?? "";
38
+ }
39
+
40
+ function reasonToKey(reason: string): string {
41
+ switch (reason) {
42
+ case "invalid_totp_code":
43
+ return "auth.mfa.errors.invalidCode";
44
+ case "mfa_already_enabled":
45
+ return "auth.mfa.errors.mfaAlreadyEnabled";
46
+ case "invalid_setup_token":
47
+ return "auth.mfa.errors.invalidSetupToken";
48
+ case "invalid_challenge_token":
49
+ return "auth.mfa.errors.challengeExpired";
50
+ case "too_many_attempts":
51
+ return "auth.mfa.errors.tooManyAttempts";
52
+ case "rate_limited":
53
+ return "auth.errors.rateLimited";
54
+ default:
55
+ return "auth.mfa.errors.setupFailed";
56
+ }
57
+ }
58
+
59
+ export type MfaSetupPreauthScreenProps = {
60
+ readonly preauthSetupToken: string;
61
+ /** Account label for the otpauth:// URI (typically the email typed at
62
+ * login) — without a session there's no session.user.email to derive it from. */
63
+ readonly accountLabel: string;
64
+ readonly title?: string;
65
+ readonly subtitle?: ReactNode;
66
+ /** Fired once confirm succeeds — the caller owns session refresh, see the
67
+ * file-level comment above. */
68
+ readonly onSuccess?: () => void;
69
+ readonly onCancel?: () => void;
70
+ };
71
+
72
+ export function MfaSetupPreauthScreen({
73
+ preauthSetupToken,
74
+ accountLabel,
75
+ title,
76
+ subtitle,
77
+ onSuccess,
78
+ onCancel,
79
+ }: MfaSetupPreauthScreenProps): ReactNode {
80
+ const t = useTranslation();
81
+ const { Button, Banner, Field, Form, Input, Section } = usePrimitives();
82
+
83
+ const [setup, setSetup] = useState<SetupState | null>(null);
84
+ const [acknowledged, setAcknowledged] = useState(false);
85
+ const [code, setCode] = useState("");
86
+ const [busy, setBusy] = useState(false);
87
+ const [error, setError] = useState<string | null>(null);
88
+ const [retryAfterSeconds, setRetryAfterSeconds] = useState<number | null>(null);
89
+
90
+ const startSetup = async (): Promise<void> => {
91
+ setBusy(true);
92
+ setError(null);
93
+ setRetryAfterSeconds(null);
94
+ try {
95
+ const res = await startMfaSetupPreauth(preauthSetupToken, accountLabel);
96
+ if (res.kind !== "success") {
97
+ setError(res.error.reason);
98
+ setRetryAfterSeconds(res.error.retryAfterSeconds ?? null);
99
+ return;
100
+ }
101
+ // errorCorrectionLevel "H" (~30% redundancy) — more resilient to
102
+ // camera/lighting issues than the default, no downside for a code
103
+ // this short-lived.
104
+ const qrSvg = await QRCode.toString(res.data.otpauthUri, {
105
+ type: "svg",
106
+ errorCorrectionLevel: "H",
107
+ });
108
+ setSetup({
109
+ setupToken: res.data.setupToken,
110
+ secretParam: extractSecret(res.data.otpauthUri),
111
+ recoveryCodes: res.data.recoveryCodes,
112
+ qrSvg,
113
+ });
114
+ setAcknowledged(false);
115
+ setCode("");
116
+ } catch {
117
+ setError("setup_failed");
118
+ } finally {
119
+ setBusy(false);
120
+ }
121
+ };
122
+
123
+ const confirmSetup = async (): Promise<void> => {
124
+ if (!setup) return;
125
+ setBusy(true);
126
+ setError(null);
127
+ setRetryAfterSeconds(null);
128
+ try {
129
+ const res = await confirmMfaSetupPreauth(setup.setupToken, code);
130
+ if (res.kind !== "success") {
131
+ setError(res.error.reason);
132
+ setRetryAfterSeconds(res.error.retryAfterSeconds ?? null);
133
+ // An expired or already-burned setupToken can never succeed again —
134
+ // drop back to the intro so the "start setup" button reappears,
135
+ // otherwise the user is stuck on this screen with no way forward
136
+ // short of a full reload (which loses the preauthSetupToken).
137
+ if (
138
+ res.error.reason === "invalid_setup_token" ||
139
+ res.error.reason === "invalid_challenge_token"
140
+ ) {
141
+ setSetup(null);
142
+ setAcknowledged(false);
143
+ setCode("");
144
+ }
145
+ return;
146
+ }
147
+ onSuccess?.();
148
+ } catch {
149
+ setError("setup_failed");
150
+ } finally {
151
+ setBusy(false);
152
+ }
153
+ };
154
+
155
+ const onSubmit = (e?: FormEvent): void => {
156
+ e?.preventDefault();
157
+ void confirmSetup();
158
+ };
159
+
160
+ return (
161
+ <AuthCard
162
+ title={title ?? t("auth.mfa.setup.title")}
163
+ subtitle={subtitle ?? t("auth.mfa.setup.subtitle")}
164
+ >
165
+ <div className="p-6 pt-0 flex flex-col gap-4">
166
+ {error !== null ? (
167
+ <Banner variant="error">
168
+ {error === "too_many_attempts" && retryAfterSeconds !== null
169
+ ? t("auth.mfa.errors.tooManyAttemptsWithSeconds", { seconds: retryAfterSeconds })
170
+ : t(reasonToKey(error))}
171
+ </Banner>
172
+ ) : null}
173
+
174
+ {!setup && (
175
+ <Section
176
+ testId="mfa-setup-preauth-intro"
177
+ actions={
178
+ <Button
179
+ variant="primary"
180
+ onClick={() => void startSetup()}
181
+ loading={busy}
182
+ disabled={busy}
183
+ >
184
+ {t("auth.mfa.setup.start")}
185
+ </Button>
186
+ }
187
+ >
188
+ <span className="text-sm text-muted-foreground">{t("auth.mfa.setup.intro")}</span>
189
+ </Section>
190
+ )}
191
+
192
+ {setup && (
193
+ <Form onSubmit={onSubmit}>
194
+ <Section
195
+ testId="mfa-setup-preauth-setup"
196
+ actions={
197
+ <Button
198
+ type="submit"
199
+ variant="primary"
200
+ loading={busy}
201
+ disabled={busy || !acknowledged || code.length !== 6}
202
+ >
203
+ {t("auth.mfa.setup.confirm")}
204
+ </Button>
205
+ }
206
+ >
207
+ <div className="flex flex-col items-center gap-2 text-center">
208
+ <span className="text-sm font-semibold">{t("auth.mfa.enable.scanTitle")}</span>
209
+ {/* qrcode's own SVG string output, not user input — safe to inline */}
210
+ <div
211
+ className="h-40 w-40"
212
+ // biome-ignore lint/security/noDangerouslySetInnerHtml: qrcode-generated SVG, no user input
213
+ dangerouslySetInnerHTML={{ __html: setup.qrSvg }}
214
+ />
215
+ <span className="text-xs text-muted-foreground">
216
+ {t("auth.mfa.enable.manualEntry")}
217
+ </span>
218
+ <code className="inline-block break-all rounded bg-muted px-3 py-2 font-mono text-sm">
219
+ {setup.secretParam}
220
+ </code>
221
+ </div>
222
+
223
+ <div className="flex flex-col gap-2">
224
+ <span className="text-sm font-semibold">{t("auth.mfa.enable.recoveryTitle")}</span>
225
+ <span className="text-xs text-muted-foreground">
226
+ {t("auth.mfa.enable.recoveryHint")}
227
+ </span>
228
+ <code className="block whitespace-pre-wrap break-all rounded bg-muted px-3 py-2 font-mono text-sm">
229
+ {setup.recoveryCodes.join("\n")}
230
+ </code>
231
+ <Field id="mfa-setup-preauth-ack" label={t("auth.mfa.enable.acknowledge")}>
232
+ <Input
233
+ kind="boolean"
234
+ id="mfa-setup-preauth-ack"
235
+ name="mfa-setup-preauth-ack"
236
+ value={acknowledged}
237
+ onChange={setAcknowledged}
238
+ />
239
+ </Field>
240
+ </div>
241
+
242
+ <Field id="mfa-setup-preauth-code" label={t("auth.mfa.enable.code")} required>
243
+ <Input
244
+ kind="text"
245
+ id="mfa-setup-preauth-code"
246
+ name="mfa-setup-preauth-code"
247
+ value={code}
248
+ onChange={setCode}
249
+ disabled={busy || !acknowledged}
250
+ autoComplete="one-time-code"
251
+ />
252
+ </Field>
253
+ </Section>
254
+ </Form>
255
+ )}
256
+
257
+ {onCancel ? (
258
+ <div className="flex justify-center">
259
+ <Button type="button" variant="secondary" onClick={onCancel} disabled={busy}>
260
+ {t("auth.mfa.verify.backToLogin")}
261
+ </Button>
262
+ </div>
263
+ ) : null}
264
+ </div>
265
+ </AuthCard>
266
+ );
267
+ }
@@ -0,0 +1,117 @@
1
+ // @runtime client
2
+ // Login-Challenge-Step, gerendert wenn LoginScreen's onMfaChallenge feuert
3
+ // (/auth/login antwortete mfaRequired). Stilistisch wie login-screen.tsx:
4
+ // useState + direkter fetch-Call, kein Dispatcher — es existiert noch kein
5
+ // JWT. Nach Erfolg ruft es session.refresh() statt eines Hard-Reloads, weil
6
+ // der Aufrufer (typisch ein Wizard-State im App-Root) sofort neu rendern
7
+ // will, sobald status:"authenticated" ankommt.
8
+
9
+ import { usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
10
+ import { type FormEvent, type ReactNode, useState } from "react";
11
+ // kumiko-lint-ignore cross-feature-import client-only barrel, the feature's server barrel has no web/ re-export
12
+ import { AuthCard, useSession } from "../../auth-email-password/web";
13
+ import { verifyMfaChallenge } from "./mfa-client";
14
+
15
+ export type MfaVerifyScreenProps = {
16
+ readonly challengeToken: string;
17
+ readonly title?: string;
18
+ readonly subtitle?: ReactNode;
19
+ readonly submitLabel?: string;
20
+ /** Called after the server confirms the code and the session state has
21
+ * refreshed. Optional — apps that swap this screen back out purely on
22
+ * session.status changing to "authenticated" don't need it. */
23
+ readonly onSuccess?: () => void;
24
+ // Some failures (challenge_expired, too_many_attempts) have no retry path —
25
+ // the challengeToken is dead. Without this, the host has no way back to
26
+ // login short of a full page reload. Optional: apps that always mount
27
+ // this screen fresh per challenge can omit it.
28
+ readonly onCancel?: () => void;
29
+ };
30
+
31
+ function reasonToKey(reason: string): string {
32
+ switch (reason) {
33
+ case "invalid_totp_code":
34
+ case "invalid_recovery_code":
35
+ case "invalid_code":
36
+ return "auth.mfa.errors.invalidCode";
37
+ case "challenge_expired":
38
+ case "invalid_challenge_token":
39
+ return "auth.mfa.errors.challengeExpired";
40
+ case "too_many_attempts":
41
+ return "auth.mfa.errors.tooManyAttempts";
42
+ case "rate_limited":
43
+ return "auth.errors.rateLimited";
44
+ default:
45
+ return "auth.mfa.errors.verifyFailed";
46
+ }
47
+ }
48
+
49
+ export function MfaVerifyScreen({
50
+ challengeToken,
51
+ title,
52
+ subtitle,
53
+ submitLabel,
54
+ onSuccess,
55
+ onCancel,
56
+ }: MfaVerifyScreenProps): ReactNode {
57
+ const t = useTranslation();
58
+ const { Form, Field, Input, Button, Banner } = usePrimitives();
59
+ const session = useSession();
60
+ const [code, setCode] = useState("");
61
+ const [submitting, setSubmitting] = useState(false);
62
+ const [error, setError] = useState<string | null>(null);
63
+
64
+ const doSubmit = async (): Promise<void> => {
65
+ setSubmitting(true);
66
+ setError(null);
67
+ const res = await verifyMfaChallenge(challengeToken, code);
68
+ if (res.kind === "success") {
69
+ await session.refresh();
70
+ setSubmitting(false);
71
+ onSuccess?.();
72
+ return;
73
+ }
74
+ setSubmitting(false);
75
+ setError(res.error.reason);
76
+ };
77
+
78
+ const onSubmit = (e?: FormEvent): void => {
79
+ e?.preventDefault();
80
+ void doSubmit();
81
+ };
82
+
83
+ return (
84
+ <AuthCard
85
+ title={title ?? t("auth.mfa.verify.title")}
86
+ subtitle={subtitle ?? t("auth.mfa.verify.subtitle")}
87
+ >
88
+ <div className="p-6 pt-0 flex flex-col gap-4">
89
+ <Form onSubmit={onSubmit}>
90
+ <Field id="mfa-verify-code" label={t("auth.mfa.verify.code")} required>
91
+ <Input
92
+ kind="text"
93
+ id="mfa-verify-code"
94
+ name="mfa-verify-code"
95
+ value={code}
96
+ onChange={setCode}
97
+ disabled={submitting}
98
+ required
99
+ autoComplete="one-time-code"
100
+ />
101
+ </Field>
102
+ {error !== null ? <Banner variant="error">{t(reasonToKey(error))}</Banner> : null}
103
+ <Button type="submit" loading={submitting} disabled={submitting}>
104
+ {submitting
105
+ ? t("auth.mfa.verify.submitting")
106
+ : (submitLabel ?? t("auth.mfa.verify.submit"))}
107
+ </Button>
108
+ {onCancel ? (
109
+ <Button type="button" variant="secondary" onClick={onCancel} disabled={submitting}>
110
+ {t("auth.mfa.verify.backToLogin")}
111
+ </Button>
112
+ ) : null}
113
+ </Form>
114
+ </div>
115
+ </AuthCard>
116
+ );
117
+ }
@@ -0,0 +1,12 @@
1
+ // @types/qrcode only declares the main "qrcode" entry, not the browser-only
2
+ // subpath (see mfa-enable-screen.tsx's import — the main entry pulls in
3
+ // Node-only deps like yargs/pngjs via qrcode's own package.json#browser
4
+ // remap, which Metro doesn't honor). Same runtime shape, just re-typed.
5
+ // `export *` never re-exports a default — `import QRCode from "..."`
6
+ // resolving here relies on the consuming app's esModuleInterop/
7
+ // allowSyntheticDefaultImports (which synthesizes a default from the
8
+ // namespace). Copy this file into apps that need it, but note that
9
+ // requirement — without it, the default import comes back undefined.
10
+ declare module "qrcode/lib/browser" {
11
+ export * from "qrcode";
12
+ }
@@ -0,0 +1,134 @@
1
+ // userMfaExportHook/userMfaDeleteHook — GDPR export must never surface the
2
+ // TOTP secret or recovery codes, and forget must actually remove the row
3
+ // (not just leave it dangling for a future projection rebuild to resurrect).
4
+
5
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
6
+ import { configureEntityFieldEncryption } from "@cosmicdrift/kumiko-framework/db";
7
+ import {
8
+ createTestUser,
9
+ setupTestStack,
10
+ type TestStack,
11
+ unsafeCreateEntityTable,
12
+ unsafePushTables,
13
+ } from "@cosmicdrift/kumiko-framework/stack";
14
+ import { createTestEnvelopeCipher } from "@cosmicdrift/kumiko-framework/testing";
15
+ import { base32Decode } from "../../auth-mfa/base32";
16
+ import { AuthMfaHandlers } from "../../auth-mfa/constants";
17
+ import { createAuthMfaFeature } from "../../auth-mfa/feature";
18
+ import { userMfaEntity } from "../../auth-mfa/schema/user-mfa";
19
+ import { currentTotpCode } from "../../auth-mfa/totp";
20
+ import { createConfigFeature } from "../../config";
21
+ import { createConfigResolver } from "../../config/resolver";
22
+ import { configValuesTable } from "../../config/table";
23
+ import { createTenantFeature } from "../../tenant";
24
+ import { createUserFeature } from "../../user/feature";
25
+ import { userEntity } from "../../user/schema/user";
26
+ import { userMfaDeleteHook, userMfaExportHook } from "../hooks";
27
+
28
+ let stack: TestStack;
29
+
30
+ const SETUP_TOKEN_SECRET = "test-setup-token-secret-do-not-use-in-prod";
31
+
32
+ beforeAll(async () => {
33
+ const encryption = createTestEnvelopeCipher();
34
+ configureEntityFieldEncryption(encryption);
35
+ const resolver = createConfigResolver({ cipher: encryption });
36
+ stack = await setupTestStack({
37
+ features: [
38
+ createConfigFeature(),
39
+ createUserFeature(),
40
+ createTenantFeature(),
41
+ createAuthMfaFeature({
42
+ setupTokenSecret: SETUP_TOKEN_SECRET,
43
+ issuer: "Kumiko Test",
44
+ challengeTokenSecret: "test-mfa-challenge-secret-at-least-32-bytes!!",
45
+ }),
46
+ ],
47
+ extraContext: { configResolver: resolver, configEncryption: encryption },
48
+ });
49
+ await unsafeCreateEntityTable(stack.db, userEntity);
50
+ await unsafeCreateEntityTable(stack.db, userMfaEntity);
51
+ await unsafePushTables(stack.db, { configValuesTable });
52
+ });
53
+
54
+ afterAll(async () => {
55
+ await stack.cleanup();
56
+ });
57
+
58
+ async function enrollUser(userId: string) {
59
+ const user = createTestUser({ id: userId, roles: ["User"] });
60
+ const start = await stack.http.writeOk<{ setupToken: string; otpauthUri: string }>(
61
+ AuthMfaHandlers.enableStart,
62
+ { accountLabel: `${userId}@example.com` },
63
+ user,
64
+ );
65
+ const secretParam = new URLSearchParams(start.otpauthUri.split("?")[1]).get("secret") ?? "";
66
+ const secret = base32Decode(secretParam);
67
+ await stack.http.writeOk(
68
+ AuthMfaHandlers.enableConfirm,
69
+ { setupToken: start.setupToken, code: currentTotpCode(secret) },
70
+ user,
71
+ );
72
+ return user;
73
+ }
74
+
75
+ describe("userMfaExportHook / userMfaDeleteHook", () => {
76
+ test("export confirms enrollment without leaking the TOTP secret or recovery codes", async () => {
77
+ const user = await enrollUser("export-1");
78
+
79
+ const snippet = await userMfaExportHook({
80
+ db: stack.db,
81
+ registry: stack.registry,
82
+ tenantId: user.tenantId,
83
+ userId: user.id,
84
+ });
85
+
86
+ expect(snippet).not.toBeNull();
87
+ expect(snippet?.rows).toHaveLength(1);
88
+ const row = snippet?.rows[0] as Record<string, unknown>;
89
+ expect(row["enrolled"]).toBe(true);
90
+ expect(JSON.stringify(row)).not.toContain("totpSecret");
91
+ expect(JSON.stringify(row)).not.toContain("recoveryCodes");
92
+ });
93
+
94
+ test("export returns null for a user who never enrolled", async () => {
95
+ const user = createTestUser({ id: "never-enrolled", roles: ["User"] });
96
+ const snippet = await userMfaExportHook({
97
+ db: stack.db,
98
+ registry: stack.registry,
99
+ tenantId: user.tenantId,
100
+ userId: user.id,
101
+ });
102
+ expect(snippet).toBeNull();
103
+ });
104
+
105
+ test("delete hard-removes the row via forget (rebuild-safe)", async () => {
106
+ const user = await enrollUser("delete-1");
107
+
108
+ const before = await userMfaExportHook({
109
+ db: stack.db,
110
+ registry: stack.registry,
111
+ tenantId: user.tenantId,
112
+ userId: user.id,
113
+ });
114
+ expect(before).not.toBeNull();
115
+
116
+ await userMfaDeleteHook(
117
+ {
118
+ db: stack.db,
119
+ registry: stack.registry,
120
+ tenantId: user.tenantId,
121
+ userId: user.id,
122
+ },
123
+ "delete",
124
+ );
125
+
126
+ const after = await userMfaExportHook({
127
+ db: stack.db,
128
+ registry: stack.registry,
129
+ tenantId: user.tenantId,
130
+ userId: user.id,
131
+ });
132
+ expect(after).toBeNull();
133
+ });
134
+ });
@@ -0,0 +1,53 @@
1
+ // EXT_USER_DATA hooks for userMfa (GDPR Art. 15/17/20). Both totpSecret and
2
+ // recoveryCodes are marked userOwned in the schema for exactly this: on
3
+ // forget, the row must actually disappear — a leftover TOTP secret or
4
+ // recovery-code hash under an anonymized user is still a live credential
5
+ // nobody can rotate. Mirrors folders-user-data/hooks.ts's executor-based
6
+ // delete (rebuild-safe: forget replays via the event, a raw DELETE would be
7
+ // resurrected by a projection rebuild).
8
+
9
+ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
10
+ import { createEventStoreExecutor, createTenantDb } from "@cosmicdrift/kumiko-framework/db";
11
+ import {
12
+ createSystemUser,
13
+ type UserDataDeleteHook,
14
+ type UserDataExportHook,
15
+ } from "@cosmicdrift/kumiko-framework/engine";
16
+ import { userMfaEntity, userMfaTable } from "../auth-mfa";
17
+
18
+ const executor = createEventStoreExecutor(userMfaTable, userMfaEntity, {
19
+ entityName: "user-mfa",
20
+ });
21
+
22
+ // Never includes totpSecret/recoveryCodes — secret-derived credential
23
+ // material, not portable personal data (same reasoning as api-token's
24
+ // tokenHash exclusion in user-data-rights-defaults). Just confirms
25
+ // enrollment + when, which is what a data subject actually needs to see.
26
+ export const userMfaExportHook: UserDataExportHook = async (ctx) => {
27
+ const rows = await selectMany<{ id: string; enabledAt: unknown }>(ctx.db, userMfaTable, {
28
+ userId: ctx.userId,
29
+ tenantId: ctx.tenantId,
30
+ });
31
+ if (rows.length === 0) return null;
32
+ return {
33
+ entity: "user-mfa",
34
+ rows: rows.map((r) => ({ enrolled: true, enabledAt: String(r.enabledAt ?? "") })),
35
+ };
36
+ };
37
+
38
+ // Always a real hard-delete regardless of the incoming strategy — a 2FA
39
+ // secret can't be anonymized, and userOwned already means this row belongs
40
+ // to exactly one user (never shared tenant data).
41
+ export const userMfaDeleteHook: UserDataDeleteHook = async (ctx) => {
42
+ const rows = await selectMany<{ id: string }>(ctx.db, userMfaTable, {
43
+ userId: ctx.userId,
44
+ tenantId: ctx.tenantId,
45
+ });
46
+ // skip: nothing enrolled for this user — no row to erase
47
+ if (rows.length === 0) return;
48
+ const systemUser = createSystemUser(ctx.tenantId);
49
+ const tdb = createTenantDb(ctx.db, ctx.tenantId, "system");
50
+ for (const row of rows) {
51
+ await executor.forget({ id: row.id }, systemUser, tdb);
52
+ }
53
+ };
@@ -0,0 +1,21 @@
1
+ // Provides the EXT_USER_DATA export/delete hooks for the `user-mfa` entity as
2
+ // a standalone feature — mount it alongside `auth-mfa` + `user-data-rights`
3
+ // when an app needs 2FA enrollment in its GDPR export/forget pipeline. Kept
4
+ // separate from `auth-mfa` (which only requires "user"/"config") so auth-mfa
5
+ // consumers without the user-data-rights stack don't pull a hard dependency.
6
+ // Mirrors folders-user-data.
7
+
8
+ import { defineFeature, EXT_USER_DATA } from "@cosmicdrift/kumiko-framework/engine";
9
+ import { userMfaDeleteHook, userMfaExportHook } from "./hooks";
10
+
11
+ export const authMfaUserDataFeature = defineFeature("auth-mfa-user-data", (r) => {
12
+ r.describe(
13
+ "GDPR (Art. 20 export / Art. 17 erasure) coverage for the `auth-mfa` feature's `user-mfa` entity. Mounts the EXT_USER_DATA export + delete hooks so 2FA enrollment status is included in the user-data export bundle and a user's TOTP secret + recovery codes are hard-deleted (via executor.forget, rebuild-safe) on a data-subject erasure request. Kept separate from `auth-mfa` so consumers without the user-data-rights pipeline don't pull a hard dependency — requires `user-data-rights`, optionalRequires `auth-mfa`.",
14
+ );
15
+ r.requires("user-data-rights");
16
+ r.optionalRequires("auth-mfa");
17
+ r.useExtension(EXT_USER_DATA, "user-mfa", {
18
+ export: userMfaExportHook,
19
+ delete: userMfaDeleteHook,
20
+ });
21
+ });