@cosmicdrift/kumiko-bundled-features 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (669) hide show
  1. package/package.json +41 -7
  2. package/src/__tests__/access-invalidation-consumer.integration.test.ts +279 -0
  3. package/src/__tests__/access-invalidation-stream.integration.test.ts +217 -0
  4. package/src/admin-shell/__tests__/admin-shell-security.integration.test.ts +200 -0
  5. package/src/admin-shell/__tests__/admin-shell.boot.test.ts +147 -0
  6. package/src/admin-shell/__tests__/overview-allowlist.test.ts +43 -0
  7. package/src/admin-shell/__tests__/overview-screens.boot.test.ts +35 -0
  8. package/src/admin-shell/constants.ts +11 -0
  9. package/src/admin-shell/feature.ts +135 -0
  10. package/src/admin-shell/i18n.ts +36 -0
  11. package/src/admin-shell/index.ts +17 -0
  12. package/src/admin-shell/overview-allowlist.ts +37 -0
  13. package/src/admin-shell/web/client-plugin.tsx +26 -0
  14. package/src/admin-shell/web/i18n.ts +16 -0
  15. package/src/admin-shell/web/index.ts +2 -0
  16. package/src/admin-shell/web/overview-layout.tsx +69 -0
  17. package/src/admin-shell/web/overview-query.ts +15 -0
  18. package/src/admin-shell/web/platform-overview-screen.tsx +90 -0
  19. package/src/admin-shell/web/tenant-overview-screen.tsx +90 -0
  20. package/src/agent-tools/__tests__/tool-catalog.test.ts +179 -0
  21. package/src/agent-tools/__tests__/tool-dispatch.integration.test.ts +100 -0
  22. package/src/agent-tools/__tests__/tool-dispatch.test.ts +126 -0
  23. package/src/agent-tools/index.ts +9 -0
  24. package/src/agent-tools/tool-catalog.ts +130 -0
  25. package/src/agent-tools/tool-dispatch.ts +82 -0
  26. package/src/agent-tools/types.ts +38 -0
  27. package/src/audit/__tests__/audit-screens.boot.test.ts +51 -0
  28. package/src/audit/__tests__/audit-security.integration.test.ts +201 -0
  29. package/src/audit/__tests__/audit.integration.test.ts +50 -25
  30. package/src/audit/constants.ts +9 -2
  31. package/src/audit/feature.ts +33 -1
  32. package/src/audit/handlers/details.query.ts +47 -0
  33. package/src/audit/handlers/list.query.ts +3 -2
  34. package/src/audit/i18n.ts +31 -0
  35. package/src/audit/web/audit-log-detail-screen.tsx +131 -0
  36. package/src/audit/web/audit-log-screen.tsx +250 -0
  37. package/src/audit/web/client-plugin.tsx +22 -0
  38. package/src/audit/web/i18n.ts +12 -0
  39. package/src/audit/web/index.ts +5 -0
  40. package/src/auth-email-password/__tests__/account-lockout-no-redis.integration.test.ts +4 -4
  41. package/src/auth-email-password/__tests__/account-lockout.integration.test.ts +4 -4
  42. package/src/auth-email-password/__tests__/account-unlock.integration.test.ts +283 -0
  43. package/src/auth-email-password/__tests__/auth-claims.integration.test.ts +10 -11
  44. package/src/auth-email-password/__tests__/auth-user-row.test.ts +36 -0
  45. package/src/auth-email-password/__tests__/auth.integration.test.ts +33 -6
  46. package/src/auth-email-password/__tests__/email-verification.integration.test.ts +9 -6
  47. package/src/auth-email-password/__tests__/feature-options.test.ts +33 -0
  48. package/src/auth-email-password/__tests__/identity-v3-login.integration.test.ts +3 -3
  49. package/src/auth-email-password/__tests__/invite-flow-kms.integration.test.ts +283 -0
  50. package/src/auth-email-password/__tests__/invite-flow.integration.test.ts +35 -1
  51. package/src/auth-email-password/__tests__/login-mfa-setup-required.integration.test.ts +129 -0
  52. package/src/auth-email-password/__tests__/multi-roles.integration.test.ts +1 -1
  53. package/src/auth-email-password/__tests__/password-policy.test.ts +15 -0
  54. package/src/auth-email-password/__tests__/password-reset.integration.test.ts +19 -6
  55. package/src/auth-email-password/__tests__/public-routes-rate-limit.integration.test.ts +3 -3
  56. package/src/auth-email-password/__tests__/seed-admin.integration.test.ts +1 -1
  57. package/src/auth-email-password/__tests__/self-registration-toggle.integration.test.ts +264 -0
  58. package/src/auth-email-password/__tests__/session-callbacks.integration.test.ts +20 -4
  59. package/src/auth-email-password/__tests__/session-strict-mode.integration.test.ts +7 -14
  60. package/src/auth-email-password/__tests__/signup-flow.integration.test.ts +91 -0
  61. package/src/auth-email-password/constants.ts +25 -0
  62. package/src/auth-email-password/email-templates.ts +32 -0
  63. package/src/auth-email-password/errors.ts +9 -0
  64. package/src/auth-email-password/feature.ts +59 -11
  65. package/src/auth-email-password/handlers/change-password.write.ts +14 -13
  66. package/src/auth-email-password/handlers/confirm-account-unlock.write.ts +59 -0
  67. package/src/auth-email-password/handlers/confirm-token-flow.ts +34 -105
  68. package/src/auth-email-password/handlers/invite-accept-with-login.write.ts +12 -5
  69. package/src/auth-email-password/handlers/invite-accept.write.ts +9 -2
  70. package/src/auth-email-password/handlers/invite-create.write.ts +2 -2
  71. package/src/auth-email-password/handlers/invite-signup-complete.write.ts +12 -4
  72. package/src/auth-email-password/handlers/login.write.ts +86 -16
  73. package/src/auth-email-password/handlers/logout.write.ts +4 -2
  74. package/src/auth-email-password/handlers/request-account-unlock.write.ts +38 -0
  75. package/src/auth-email-password/handlers/reset-password.write.ts +3 -2
  76. package/src/auth-email-password/handlers/self-registration-status.query.ts +23 -0
  77. package/src/auth-email-password/handlers/signup-confirm.write.ts +19 -11
  78. package/src/auth-email-password/handlers/signup-request.write.ts +22 -0
  79. package/src/auth-email-password/handlers/token-request-handler.ts +4 -2
  80. package/src/auth-email-password/i18n.ts +52 -0
  81. package/src/auth-email-password/index.ts +13 -2
  82. package/src/auth-email-password/lockout-store.ts +10 -5
  83. package/src/auth-email-password/password-policy.ts +2108 -0
  84. package/src/auth-email-password/seeding.ts +55 -27
  85. package/src/auth-email-password/self-registration-toggle.ts +25 -0
  86. package/src/auth-email-password/signed-token.ts +1 -0
  87. package/src/auth-email-password/unlock-token.ts +28 -0
  88. package/src/auth-email-password/web/__tests__/auth-client.test.ts +542 -0
  89. package/src/auth-email-password/web/__tests__/auth-form-logic.test.ts +44 -0
  90. package/src/auth-email-password/web/__tests__/auth-gate.test.tsx +194 -6
  91. package/src/auth-email-password/web/__tests__/client-plugin.test.tsx +34 -0
  92. package/src/auth-email-password/web/__tests__/confirm-account-unlock-screen.test.tsx +64 -0
  93. package/src/auth-email-password/web/__tests__/forgot-password-screen.test.tsx +5 -1
  94. package/src/auth-email-password/web/__tests__/invite-accept-screen.test.tsx +129 -2
  95. package/src/auth-email-password/web/__tests__/login-screen.test.tsx +149 -5
  96. package/src/auth-email-password/web/__tests__/request-account-unlock-screen.test.tsx +137 -0
  97. package/src/auth-email-password/web/__tests__/reset-password-screen.test.tsx +5 -1
  98. package/src/auth-email-password/web/__tests__/signup-complete-screen.test.tsx +134 -0
  99. package/src/auth-email-password/web/__tests__/signup-screen.test.tsx +93 -0
  100. package/src/auth-email-password/web/__tests__/test-utils.tsx +12 -2
  101. package/src/auth-email-password/web/__tests__/use-url-token.test.tsx +29 -0
  102. package/src/auth-email-password/web/__tests__/verify-email-screen.test.tsx +5 -1
  103. package/src/auth-email-password/web/auth-client.ts +96 -18
  104. package/src/auth-email-password/web/auth-form-logic.ts +30 -0
  105. package/src/auth-email-password/web/auth-form-primitives.tsx +22 -20
  106. package/src/auth-email-password/web/auth-gate.tsx +140 -19
  107. package/src/auth-email-password/web/client-plugin.ts +24 -2
  108. package/src/auth-email-password/web/confirm-account-unlock-screen.tsx +95 -0
  109. package/src/auth-email-password/web/forgot-password-screen.tsx +6 -6
  110. package/src/auth-email-password/web/index.ts +16 -4
  111. package/src/auth-email-password/web/invite-accept-screen.tsx +5 -5
  112. package/src/auth-email-password/web/login-screen.tsx +78 -22
  113. package/src/auth-email-password/web/request-account-unlock-screen.tsx +112 -0
  114. package/src/auth-email-password/web/reset-password-screen.tsx +15 -18
  115. package/src/auth-email-password/web/session.tsx +11 -6
  116. package/src/auth-email-password/web/signup-complete-screen.tsx +13 -15
  117. package/src/auth-email-password/web/signup-screen.tsx +8 -10
  118. package/src/auth-email-password/web/use-shell-user.ts +5 -2
  119. package/src/auth-email-password/web/user-menu.tsx +3 -1
  120. package/src/auth-email-password/web/verify-email-screen.tsx +10 -14
  121. package/src/auth-foundation/__tests__/feature.test.ts +46 -0
  122. package/src/auth-foundation/__tests__/session-store.test.ts +77 -0
  123. package/src/auth-foundation/__tests__/tenant-providers.test.ts +160 -0
  124. package/src/auth-foundation/__tests__/token-verifier.test.ts +150 -0
  125. package/src/auth-foundation/boot-checks.ts +150 -0
  126. package/src/auth-foundation/feature.ts +157 -0
  127. package/src/auth-foundation/index.ts +37 -0
  128. package/src/auth-foundation/resolve-anonymous-access.ts +62 -0
  129. package/src/auth-foundation/types.ts +170 -0
  130. package/src/auth-mfa/__tests__/base32.test.ts +28 -0
  131. package/src/auth-mfa/__tests__/disable-and-regenerate.integration.test.ts +265 -0
  132. package/src/auth-mfa/__tests__/enable-confirm-preauth.integration.test.ts +321 -0
  133. package/src/auth-mfa/__tests__/enable-flow.integration.test.ts +322 -0
  134. package/src/auth-mfa/__tests__/enable-start-preauth.integration.test.ts +237 -0
  135. package/src/auth-mfa/__tests__/errors-i18n-parity.test.ts +39 -0
  136. package/src/auth-mfa/__tests__/mfa-preauth-setup-token.test.ts +113 -0
  137. package/src/auth-mfa/__tests__/no-redis.integration.test.ts +209 -0
  138. package/src/auth-mfa/__tests__/pii-subject-encryption.integration.test.ts +139 -0
  139. package/src/auth-mfa/__tests__/reencrypt-job.integration.test.ts +247 -0
  140. package/src/auth-mfa/__tests__/session-auto-revoke.integration.test.ts +169 -0
  141. package/src/auth-mfa/__tests__/totp.test.ts +131 -0
  142. package/src/auth-mfa/__tests__/verify.integration.test.ts +319 -0
  143. package/src/auth-mfa/base32.ts +51 -0
  144. package/src/auth-mfa/config.ts +26 -0
  145. package/src/auth-mfa/constants.ts +45 -0
  146. package/src/auth-mfa/db/queries.ts +45 -0
  147. package/src/auth-mfa/errors.ts +66 -0
  148. package/src/auth-mfa/feature.ts +170 -0
  149. package/src/auth-mfa/handlers/disable.write.ts +67 -0
  150. package/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +200 -0
  151. package/src/auth-mfa/handlers/enable-confirm.write.ts +106 -0
  152. package/src/auth-mfa/handlers/enable-start-preauth.write.ts +87 -0
  153. package/src/auth-mfa/handlers/enable-start.write.ts +66 -0
  154. package/src/auth-mfa/handlers/reencrypt.job.ts +234 -0
  155. package/src/auth-mfa/handlers/regenerate-recovery.write.ts +77 -0
  156. package/src/auth-mfa/handlers/status.query.ts +23 -0
  157. package/src/auth-mfa/handlers/verify.write.ts +179 -0
  158. package/src/auth-mfa/i18n.ts +12 -0
  159. package/src/auth-mfa/index.ts +19 -0
  160. package/src/auth-mfa/mfa-challenge-token.ts +90 -0
  161. package/src/auth-mfa/mfa-preauth-setup-token.ts +86 -0
  162. package/src/auth-mfa/mfa-setup-token.ts +96 -0
  163. package/src/auth-mfa/mfa-status-checker.ts +84 -0
  164. package/src/auth-mfa/mfa-verify-attempts.ts +90 -0
  165. package/src/auth-mfa/otpauth-uri.ts +21 -0
  166. package/src/auth-mfa/recovery-codes.ts +54 -0
  167. package/src/auth-mfa/schema/user-mfa.ts +60 -0
  168. package/src/auth-mfa/testing.ts +5 -0
  169. package/src/auth-mfa/totp.ts +67 -0
  170. package/src/auth-mfa/verify-factor.ts +58 -0
  171. package/src/auth-mfa/web/__tests__/mfa-client.test.ts +265 -0
  172. package/src/auth-mfa/web/__tests__/mfa-enable-screen.test.tsx +182 -0
  173. package/src/auth-mfa/web/__tests__/mfa-setup-preauth-screen.test.tsx +379 -0
  174. package/src/auth-mfa/web/__tests__/mfa-verify-screen.test.tsx +104 -0
  175. package/src/auth-mfa/web/client-plugin.ts +37 -0
  176. package/src/auth-mfa/web/i18n.ts +136 -0
  177. package/src/auth-mfa/web/index.ts +27 -0
  178. package/src/auth-mfa/web/mfa-client.ts +184 -0
  179. package/src/auth-mfa/web/mfa-disable-dialog.tsx +69 -0
  180. package/src/auth-mfa/web/mfa-enable-screen.tsx +228 -0
  181. package/src/auth-mfa/web/mfa-recovery-codes-reveal.tsx +53 -0
  182. package/src/auth-mfa/web/mfa-regenerate-recovery-dialog.tsx +68 -0
  183. package/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +267 -0
  184. package/src/auth-mfa/web/mfa-verify-screen.tsx +117 -0
  185. package/src/auth-mfa/web/qrcode-browser.d.ts +12 -0
  186. package/src/auth-mfa-user-data/__tests__/hooks.integration.test.ts +134 -0
  187. package/src/auth-mfa-user-data/hooks.ts +53 -0
  188. package/src/auth-mfa-user-data/index.ts +21 -0
  189. package/src/billing-foundation/__tests__/billing-foundation.integration.test.ts +206 -2
  190. package/src/billing-foundation/__tests__/subscription-provider-contract.ts +93 -0
  191. package/src/billing-foundation/entities.ts +28 -4
  192. package/src/billing-foundation/events.ts +6 -2
  193. package/src/billing-foundation/feature.ts +9 -1
  194. package/src/billing-foundation/get-subscription-for-tenant.ts +16 -5
  195. package/src/billing-foundation/handlers/create-portal-session.write.ts +17 -1
  196. package/src/billing-foundation/handlers/list-subscriptions.query.ts +15 -1
  197. package/src/billing-foundation/handlers/process-event.write.ts +39 -3
  198. package/src/billing-foundation/tenant-destroy-hook.ts +46 -0
  199. package/src/cap-counter/__tests__/cap-list.boot.test.ts +27 -0
  200. package/src/cap-counter/__tests__/cap-list.security.integration.test.ts +53 -0
  201. package/src/cap-counter/__tests__/entity-ddl.test.ts +15 -0
  202. package/src/cap-counter/__tests__/with-cap-enforcement.integration.test.ts +8 -5
  203. package/src/cap-counter/constants.ts +2 -0
  204. package/src/cap-counter/enforce-cap.ts +15 -18
  205. package/src/cap-counter/entity.ts +3 -3
  206. package/src/cap-counter/feature.ts +28 -1
  207. package/src/cap-counter/i18n.ts +12 -0
  208. package/src/cap-counter/with-cap-enforcement.ts +2 -2
  209. package/src/channel-email/__tests__/pii-guard.test.ts +56 -0
  210. package/src/channel-email/email-channel.ts +2 -1
  211. package/src/channel-email/index.ts +1 -0
  212. package/src/channel-email/pii-guard.ts +39 -0
  213. package/src/compliance-profiles/__tests__/profile-picker.boot.test.ts +28 -0
  214. package/src/compliance-profiles/__tests__/profile-picker.security.integration.test.ts +91 -0
  215. package/src/compliance-profiles/constants.ts +15 -0
  216. package/src/compliance-profiles/feature.ts +22 -1
  217. package/src/compliance-profiles/handlers/set-profile.write.ts +2 -2
  218. package/src/compliance-profiles/i18n.ts +6 -0
  219. package/src/compliance-profiles/index.ts +6 -0
  220. package/src/compliance-profiles/web/client-plugin.tsx +22 -0
  221. package/src/compliance-profiles/web/compliance-profile-screen.tsx +152 -0
  222. package/src/compliance-profiles/web/i18n.ts +25 -0
  223. package/src/compliance-profiles/web/index.ts +6 -0
  224. package/src/config/__tests__/app-overrides.test.ts +11 -0
  225. package/src/config/__tests__/backing-secrets.integration.test.ts +22 -0
  226. package/src/config/__tests__/cascade.integration.test.ts +35 -0
  227. package/src/config/__tests__/config.integration.test.ts +71 -10
  228. package/src/config/__tests__/deserialize-value.test.ts +5 -8
  229. package/src/config/__tests__/inherited-redaction.integration.test.ts +4 -3
  230. package/src/config/__tests__/pii-encrypted.integration.test.ts +179 -0
  231. package/src/config/__tests__/reencrypt-job-concurrent-write-race.integration.test.ts +234 -0
  232. package/src/config/__tests__/reencrypt-job-kek-rotation.integration.test.ts +243 -0
  233. package/src/config/__tests__/write-helpers.test.ts +23 -0
  234. package/src/config/db/queries/resolver.ts +3 -3
  235. package/src/config/feature.ts +15 -8
  236. package/src/config/handlers/cascade.query.ts +2 -1
  237. package/src/config/handlers/reencrypt.job.ts +209 -0
  238. package/src/config/handlers/set.write.ts +55 -3
  239. package/src/config/handlers/values.query.ts +2 -1
  240. package/src/config/i18n.ts +9 -0
  241. package/src/config/index.ts +5 -4
  242. package/src/config/resolver.ts +75 -61
  243. package/src/config/table.ts +1 -1
  244. package/src/config/write-helpers.ts +22 -0
  245. package/src/crypto-shredding/__tests__/forget-subject.integration.test.ts +148 -0
  246. package/src/crypto-shredding/constants.ts +5 -0
  247. package/src/crypto-shredding/feature.ts +18 -0
  248. package/src/crypto-shredding/handlers/forget-subject.write.ts +91 -0
  249. package/src/crypto-shredding/index.ts +11 -0
  250. package/src/custom-fields/__tests__/audit-integration.integration.test.ts +1 -1
  251. package/src/custom-fields/__tests__/custom-fields.integration.test.ts +92 -53
  252. package/src/custom-fields/__tests__/field-access.integration.test.ts +25 -0
  253. package/src/custom-fields/__tests__/parse-serialized-field.test.ts +11 -3
  254. package/src/custom-fields/__tests__/retention.integration.test.ts +40 -0
  255. package/src/custom-fields/__tests__/user-data-rights.integration.test.ts +26 -79
  256. package/src/custom-fields/constants.ts +1 -0
  257. package/src/custom-fields/db/queries/user-data-rights.ts +0 -20
  258. package/src/custom-fields/entity.ts +1 -1
  259. package/src/custom-fields/events.ts +4 -3
  260. package/src/custom-fields/feature.ts +8 -0
  261. package/src/custom-fields/handlers/delete-system-field.write.ts +2 -1
  262. package/src/custom-fields/handlers/delete-tenant-field.write.ts +2 -1
  263. package/src/custom-fields/handlers/set-custom-field.write.ts +3 -44
  264. package/src/custom-fields/handlers/update-tenant-field.write.ts +0 -17
  265. package/src/custom-fields/lib/field-access.ts +8 -1
  266. package/src/custom-fields/lib/parse-serialized-field.ts +12 -4
  267. package/src/custom-fields/lib/value-schema.ts +1 -1
  268. package/src/custom-fields/run-retention.ts +9 -1
  269. package/src/custom-fields/schemas.ts +9 -9
  270. package/src/custom-fields/wire-for-entity.ts +110 -78
  271. package/src/custom-fields/wire-user-data-rights.ts +5 -54
  272. package/src/data-retention/__tests__/parse-override.test.ts +14 -13
  273. package/src/data-retention/__tests__/retention-cleanup.integration.test.ts +145 -8
  274. package/src/data-retention/feature.ts +3 -3
  275. package/src/data-retention/run-retention-cleanup.ts +102 -19
  276. package/src/delivery/__tests__/delivery-pii-kms.integration.test.ts +171 -0
  277. package/src/delivery/__tests__/delivery-screens.boot.test.ts +27 -0
  278. package/src/delivery/__tests__/delivery-security.integration.test.ts +106 -0
  279. package/src/delivery/constants.ts +13 -51
  280. package/src/delivery/feature.ts +29 -5
  281. package/src/delivery/handlers/log.query.ts +28 -7
  282. package/src/delivery/i18n.ts +6 -0
  283. package/src/delivery/index.ts +6 -1
  284. package/src/delivery/public-names.ts +36 -0
  285. package/src/delivery/tables.ts +3 -3
  286. package/src/delivery/web/client-plugin.tsx +20 -0
  287. package/src/delivery/web/delivery-log-screen.tsx +103 -0
  288. package/src/delivery/web/i18n.ts +25 -0
  289. package/src/delivery/web/index.ts +3 -0
  290. package/src/document-ingest-foundation/__tests__/feature.integration.test.ts +147 -0
  291. package/src/document-ingest-foundation/__tests__/feature.test.ts +105 -0
  292. package/src/document-ingest-foundation/__tests__/pages.test.ts +37 -0
  293. package/src/document-ingest-foundation/entity.ts +43 -0
  294. package/src/document-ingest-foundation/events.ts +25 -0
  295. package/src/document-ingest-foundation/feature.ts +126 -0
  296. package/src/document-ingest-foundation/index.ts +12 -0
  297. package/src/document-ingest-foundation/pages.ts +34 -0
  298. package/src/feature-toggles/__tests__/compose-tier-resolver.test.ts +304 -0
  299. package/src/feature-toggles/__tests__/feature-toggles.integration.test.ts +7 -6
  300. package/src/feature-toggles/__tests__/toggle-admin.boot.test.ts +31 -0
  301. package/src/feature-toggles/__tests__/toggle-admin.security.integration.test.ts +69 -0
  302. package/src/feature-toggles/compose-tier-resolver.ts +134 -0
  303. package/src/feature-toggles/constants.ts +14 -0
  304. package/src/feature-toggles/feature.ts +23 -3
  305. package/src/feature-toggles/global-feature-state-table.ts +20 -1
  306. package/src/feature-toggles/handlers/list.query.ts +1 -1
  307. package/src/feature-toggles/handlers/registered.query.ts +1 -1
  308. package/src/feature-toggles/i18n.ts +6 -0
  309. package/src/feature-toggles/index.ts +8 -0
  310. package/src/feature-toggles/toggle-runtime.ts +14 -0
  311. package/src/feature-toggles/web/client-plugin.tsx +22 -0
  312. package/src/feature-toggles/web/i18n.ts +29 -0
  313. package/src/feature-toggles/web/index.ts +6 -0
  314. package/src/feature-toggles/web/toggle-admin-screen.tsx +136 -0
  315. package/src/file-foundation/__tests__/feature.test.ts +5 -5
  316. package/src/file-foundation/__tests__/file-foundation.integration.test.ts +4 -3
  317. package/src/file-foundation/feature.ts +10 -11
  318. package/src/files-provider-s3/__tests__/s3-provider.integration.test.ts +27 -2
  319. package/src/files-provider-s3/s3-provider.ts +12 -1
  320. package/src/folders/__tests__/folders.integration.test.ts +3 -0
  321. package/src/folders/web/__tests__/folder-manager.test.tsx +43 -2
  322. package/src/folders/web/__tests__/tree.test.ts +8 -0
  323. package/src/folders/web/folder-manager.tsx +2 -3
  324. package/src/folders/web/i18n.ts +0 -2
  325. package/src/folders/web/tree.ts +13 -2
  326. package/src/folders-user-data/__tests__/hooks.integration.test.ts +1 -0
  327. package/src/folders-user-data/index.ts +4 -4
  328. package/src/inbound-mail-foundation/__tests__/account-state.test.ts +30 -0
  329. package/src/inbound-mail-foundation/__tests__/connect-routes.integration.test.ts +307 -0
  330. package/src/inbound-mail-foundation/__tests__/feature.test.ts +169 -0
  331. package/src/inbound-mail-foundation/__tests__/inbound-mail-foundation.integration.test.ts +644 -0
  332. package/src/inbound-mail-foundation/__tests__/inbound-mail-provider-contract.ts +111 -0
  333. package/src/inbound-mail-foundation/__tests__/retention.integration.test.ts +237 -0
  334. package/src/inbound-mail-foundation/__tests__/watch-supervisor.integration.test.ts +433 -0
  335. package/src/inbound-mail-foundation/aggregate-id.ts +40 -0
  336. package/src/inbound-mail-foundation/connect-routes.ts +230 -0
  337. package/src/inbound-mail-foundation/constants.ts +80 -0
  338. package/src/inbound-mail-foundation/db/queries/inbound-projections.ts +38 -0
  339. package/src/inbound-mail-foundation/entities.ts +172 -0
  340. package/src/inbound-mail-foundation/events.ts +147 -0
  341. package/src/inbound-mail-foundation/feature.ts +243 -0
  342. package/src/inbound-mail-foundation/handlers/account-state.ts +23 -0
  343. package/src/inbound-mail-foundation/handlers/connect-account.write.ts +112 -0
  344. package/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +69 -0
  345. package/src/inbound-mail-foundation/handlers/ingest-message.write.ts +333 -0
  346. package/src/inbound-mail-foundation/handlers/list-accounts.query.ts +44 -0
  347. package/src/inbound-mail-foundation/handlers/list-messages.query.ts +58 -0
  348. package/src/inbound-mail-foundation/handlers/scope-visibility.ts +14 -0
  349. package/src/inbound-mail-foundation/handlers/update-account.write.ts +78 -0
  350. package/src/inbound-mail-foundation/index.ts +97 -0
  351. package/src/inbound-mail-foundation/oauth-state.ts +103 -0
  352. package/src/inbound-mail-foundation/projection.ts +152 -0
  353. package/src/inbound-mail-foundation/provider-factory.ts +53 -0
  354. package/src/inbound-mail-foundation/retention-sweep.ts +168 -0
  355. package/src/inbound-mail-foundation/tenant-destroy-hook.ts +93 -0
  356. package/src/inbound-mail-foundation/types.ts +250 -0
  357. package/src/inbound-mail-foundation/watch-supervisor.ts +482 -0
  358. package/src/inbound-provider-imap/__tests__/feature.test.ts +115 -0
  359. package/src/inbound-provider-imap/__tests__/imap-foundation.integration.test.ts +302 -0
  360. package/src/inbound-provider-imap/__tests__/imap-live.integration.test.ts +206 -0
  361. package/src/inbound-provider-imap/__tests__/plugin-mocked.test.ts +358 -0
  362. package/src/inbound-provider-imap/credential-document.ts +51 -0
  363. package/src/inbound-provider-imap/feature.ts +283 -0
  364. package/src/inbound-provider-imap/imap-client.ts +158 -0
  365. package/src/inbound-provider-imap/index.ts +17 -0
  366. package/src/inbound-provider-inmemory/__tests__/feature.test.ts +52 -0
  367. package/src/inbound-provider-inmemory/feature.ts +154 -0
  368. package/src/inbound-provider-inmemory/index.ts +12 -0
  369. package/src/jobs/__tests__/entity-ddl.test.ts +15 -0
  370. package/src/jobs/__tests__/job-instant.test.ts +15 -0
  371. package/src/jobs/__tests__/job-system-user.integration.test.ts +4 -8
  372. package/src/jobs/__tests__/jobs-events.integration.test.ts +37 -1
  373. package/src/jobs/__tests__/jobs-pii-kms.integration.test.ts +120 -0
  374. package/src/jobs/__tests__/jobs-screens.boot.test.ts +36 -0
  375. package/src/jobs/__tests__/jobs-security.integration.test.ts +108 -0
  376. package/src/jobs/__tests__/reindex-entity-job.integration.test.ts +169 -0
  377. package/src/jobs/constants.ts +8 -1
  378. package/src/jobs/feature.ts +52 -8
  379. package/src/jobs/handlers/detail.query.ts +6 -0
  380. package/src/jobs/handlers/list.query.ts +8 -1
  381. package/src/jobs/handlers/projection-rebuild.job.ts +14 -4
  382. package/src/jobs/handlers/reindex-entity.job.ts +49 -0
  383. package/src/jobs/i18n.ts +37 -0
  384. package/src/jobs/job-instant.ts +9 -0
  385. package/src/jobs/job-run-table.ts +4 -4
  386. package/src/jobs/web/client-plugin.tsx +22 -0
  387. package/src/jobs/web/i18n.ts +12 -0
  388. package/src/jobs/web/index.ts +4 -0
  389. package/src/jobs/web/job-run-detail-screen.tsx +180 -0
  390. package/src/jobs/web/job-runs-screen.tsx +146 -0
  391. package/src/ledger/__tests__/ledger.integration.test.ts +8 -3
  392. package/src/ledger/handlers/confirm-schedule-period.write.ts +17 -4
  393. package/src/ledger/recurring.ts +17 -8
  394. package/src/ledger/web/index.ts +3 -2
  395. package/src/legal-pages/__tests__/legal-pages.integration.test.ts +55 -0
  396. package/src/legal-pages/feature.ts +24 -31
  397. package/src/legal-pages/index.ts +1 -0
  398. package/src/mail-foundation/__tests__/feature.test.ts +5 -6
  399. package/src/mail-foundation/__tests__/mail-foundation.integration.test.ts +4 -3
  400. package/src/mail-foundation/__tests__/mail-transport-contract.ts +58 -0
  401. package/src/mail-foundation/feature.ts +26 -24
  402. package/src/mail-transport-inmemory/__tests__/feature.test.ts +23 -0
  403. package/src/mail-transport-smtp/__tests__/feature.test.ts +65 -0
  404. package/src/managed-pages/__tests__/branding-coerce.test.ts +2 -6
  405. package/src/managed-pages/__tests__/i18n-surface.boot.test.ts +30 -0
  406. package/src/managed-pages/__tests__/managed-pages.integration.test.ts +137 -0
  407. package/src/managed-pages/feature.ts +45 -47
  408. package/src/managed-pages/handlers/branding.query.ts +5 -1
  409. package/src/managed-pages/handlers/by-tenant-published.query.ts +52 -0
  410. package/src/managed-pages/i18n.ts +66 -0
  411. package/src/managed-pages/screens/page-screens.ts +5 -1
  412. package/src/managed-pages/web/client-plugin.tsx +22 -0
  413. package/src/managed-pages/web/i18n.ts +24 -0
  414. package/src/managed-pages/web/index.ts +3 -0
  415. package/src/page-render/__tests__/layout.test.ts +63 -0
  416. package/src/page-render/__tests__/markdown.test.ts +29 -0
  417. package/src/page-render/branding.ts +4 -3
  418. package/src/page-render/index.ts +1 -1
  419. package/src/page-render/layout.ts +18 -3
  420. package/src/page-render/markdown.ts +1 -9
  421. package/src/page-render/web.ts +13 -0
  422. package/src/personal-access-tokens/__tests__/feature.test.ts +42 -0
  423. package/src/personal-access-tokens/__tests__/pat.integration.test.ts +239 -0
  424. package/src/personal-access-tokens/__tests__/scopes.test.ts +42 -0
  425. package/src/personal-access-tokens/constants.ts +33 -0
  426. package/src/personal-access-tokens/feature.ts +105 -0
  427. package/src/personal-access-tokens/handlers/available-scopes.query.ts +21 -0
  428. package/src/personal-access-tokens/handlers/create.write.ts +47 -0
  429. package/src/personal-access-tokens/handlers/list.query.ts +50 -0
  430. package/src/personal-access-tokens/handlers/revoke.write.ts +37 -0
  431. package/src/personal-access-tokens/hash.ts +17 -0
  432. package/src/personal-access-tokens/i18n.ts +5 -0
  433. package/src/personal-access-tokens/index.ts +31 -0
  434. package/src/personal-access-tokens/resolver.ts +58 -0
  435. package/src/personal-access-tokens/roles.ts +29 -0
  436. package/src/personal-access-tokens/schema/api-token.ts +63 -0
  437. package/src/personal-access-tokens/scopes.ts +43 -0
  438. package/src/personal-access-tokens/web/client-plugin.tsx +29 -0
  439. package/src/personal-access-tokens/web/i18n.ts +69 -0
  440. package/src/personal-access-tokens/web/index.ts +7 -0
  441. package/src/personal-access-tokens/web/pat-tokens-screen.tsx +265 -0
  442. package/src/readiness/__tests__/readiness.integration.test.ts +4 -3
  443. package/src/renderer-simple/__tests__/resolve-variables.test.ts +37 -0
  444. package/src/renderer-simple/resolve-variables.ts +3 -1
  445. package/src/secrets/__tests__/rotate.integration.test.ts +115 -18
  446. package/src/secrets/handlers/rotate.job.ts +77 -98
  447. package/src/secrets/secrets-context.ts +7 -38
  448. package/src/secrets/table.ts +7 -8
  449. package/src/seo/__tests__/llms-txt.test.ts +38 -0
  450. package/src/seo/__tests__/robots-txt.test.ts +18 -0
  451. package/src/seo/__tests__/schema-builders.test.ts +57 -0
  452. package/src/seo/__tests__/seo.integration.test.ts +350 -0
  453. package/src/seo/__tests__/sitemap.test.ts +38 -0
  454. package/src/seo/constants.ts +60 -0
  455. package/src/seo/feature.ts +316 -0
  456. package/src/seo/handlers/seo-config.query.ts +35 -0
  457. package/src/seo/index.ts +19 -0
  458. package/src/seo/llms-txt.ts +33 -0
  459. package/src/seo/robots-txt.ts +13 -0
  460. package/src/seo/schema-builders.ts +57 -0
  461. package/src/seo/sitemap.ts +33 -0
  462. package/src/sessions/__tests__/auto-revoke-binding.test.ts +71 -0
  463. package/src/sessions/__tests__/cleanup.integration.test.ts +2 -1
  464. package/src/sessions/__tests__/password-auto-revoke.integration.test.ts +11 -7
  465. package/src/sessions/__tests__/rebuild-survival.integration.test.ts +18 -7
  466. package/src/sessions/__tests__/session-store-registration.test.ts +21 -0
  467. package/src/sessions/__tests__/sessions-screens.boot.test.ts +55 -0
  468. package/src/sessions/__tests__/sessions.integration.test.ts +272 -20
  469. package/src/sessions/__tests__/test-helpers.ts +16 -3
  470. package/src/sessions/constants.ts +5 -0
  471. package/src/sessions/db/queries/cleanup.ts +2 -2
  472. package/src/sessions/feature.ts +175 -26
  473. package/src/sessions/handlers/detail.query.ts +36 -0
  474. package/src/sessions/handlers/list.query.ts +14 -9
  475. package/src/sessions/handlers/mine.query.ts +13 -8
  476. package/src/sessions/handlers/revoke-all-for-user.write.ts +51 -12
  477. package/src/sessions/handlers/revoke-all-others.write.ts +19 -1
  478. package/src/sessions/handlers/revoke.write.ts +13 -0
  479. package/src/sessions/i18n.ts +23 -0
  480. package/src/sessions/index.ts +9 -2
  481. package/src/sessions/schema/user-session.ts +8 -2
  482. package/src/sessions/session-callbacks.ts +99 -14
  483. package/src/sessions/session-checker-fail-open.test.ts +77 -0
  484. package/src/sessions/session-revoked-event.ts +29 -0
  485. package/src/sessions/testing.ts +14 -1
  486. package/src/shared/__tests__/chunked-entity-migration.test.ts +145 -0
  487. package/src/shared/chunked-entity-migration.ts +89 -0
  488. package/src/shared/decrypt-stored-pii.ts +26 -0
  489. package/src/shared/encrypt-for-direct-write.ts +23 -0
  490. package/src/shared/entities-of.ts +17 -0
  491. package/src/shared/grace-period.test.ts +26 -0
  492. package/src/shared/grace-period.ts +20 -0
  493. package/src/{auth-email-password/__tests__ → shared}/identity-v3-hash.test.ts +2 -2
  494. package/src/shared/index.ts +16 -0
  495. package/src/shared/map-with-concurrency.test.ts +104 -0
  496. package/src/shared/map-with-concurrency.ts +38 -0
  497. package/src/shared/password-hashing.test.ts +35 -0
  498. package/src/{auth-email-password → shared}/password-hashing.ts +11 -0
  499. package/src/shared/system-query.ts +5 -0
  500. package/src/{auth-email-password → shared}/token-burn-store.ts +1 -1
  501. package/src/subscription-mollie/__tests__/feature.test.ts +97 -0
  502. package/src/subscription-mollie/__tests__/mollie-foundation.integration.test.ts +12 -1
  503. package/src/subscription-mollie/__tests__/verify-webhook.test.ts +21 -0
  504. package/src/subscription-mollie/verify-webhook.ts +1 -0
  505. package/src/subscription-stripe/__tests__/feature.test.ts +127 -1
  506. package/src/subscription-stripe/__tests__/runtime.test.ts +14 -24
  507. package/src/subscription-stripe/__tests__/stripe-foundation.integration.test.ts +15 -5
  508. package/src/subscription-stripe/__tests__/verify-webhook.test.ts +20 -0
  509. package/src/subscription-stripe/feature.ts +2 -0
  510. package/src/subscription-stripe/verify-webhook.ts +3 -2
  511. package/src/tags/__tests__/schemas.test.ts +34 -0
  512. package/src/tags/__tests__/tags.integration.test.ts +12 -1
  513. package/src/tags/entity.ts +6 -1
  514. package/src/tags/feature.ts +2 -0
  515. package/src/tags/i18n.ts +5 -0
  516. package/src/tags/schemas.ts +9 -2
  517. package/src/tags/web/__tests__/tag-manager.test.tsx +333 -0
  518. package/src/tags/web/__tests__/tag-section.test.tsx +47 -1
  519. package/src/tags/web/tag-manager.tsx +1 -1
  520. package/src/tags/web/tag-section.tsx +6 -3
  521. package/src/template-resolver/testing.ts +2 -1
  522. package/src/tenant/__tests__/members-query-kms.integration.test.ts +98 -0
  523. package/src/tenant/__tests__/members-screens.boot.test.ts +51 -0
  524. package/src/tenant/__tests__/multi-tenant.integration.test.ts +1 -1
  525. package/src/tenant/__tests__/tenant-security.integration.test.ts +310 -0
  526. package/src/tenant/__tests__/tenant.integration.test.ts +8 -4
  527. package/src/tenant/constants.ts +5 -0
  528. package/src/tenant/feature.ts +19 -0
  529. package/src/tenant/handlers/active-tenant-ids.query.ts +8 -2
  530. package/src/tenant/handlers/cancel-invitation.write.ts +2 -2
  531. package/src/tenant/handlers/invitations.query.ts +21 -4
  532. package/src/tenant/handlers/members.query.ts +41 -6
  533. package/src/tenant/i18n.ts +28 -0
  534. package/src/tenant/index.ts +7 -2
  535. package/src/tenant/invitation-table.ts +3 -2
  536. package/src/tenant/schema/tenant.ts +27 -2
  537. package/src/tenant/screens.ts +3 -2
  538. package/src/tenant/seeding.ts +136 -3
  539. package/src/tenant/web/client-plugin.tsx +20 -0
  540. package/src/tenant/web/i18n.ts +43 -0
  541. package/src/tenant/web/index.ts +4 -0
  542. package/src/tenant/web/members-screen.tsx +270 -0
  543. package/src/tenant-lifecycle/__tests__/stages.test.ts +22 -0
  544. package/src/tenant-lifecycle/__tests__/tenant-lifecycle.integration.test.ts +471 -0
  545. package/src/tenant-lifecycle/boot-checks.ts +45 -0
  546. package/src/tenant-lifecycle/constants.ts +56 -0
  547. package/src/tenant-lifecycle/events.ts +46 -0
  548. package/src/tenant-lifecycle/feature.ts +112 -0
  549. package/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +79 -0
  550. package/src/tenant-lifecycle/handlers/request-destruction.write.ts +83 -0
  551. package/src/tenant-lifecycle/index.ts +7 -0
  552. package/src/tenant-lifecycle/lib/revoke-tenant-sessions.ts +16 -0
  553. package/src/tenant-lifecycle/lifecycle-gate.ts +65 -0
  554. package/src/tenant-lifecycle/run-tenant-destroy.ts +368 -0
  555. package/src/tenant-lifecycle/stages.ts +206 -0
  556. package/src/tenant-settings/__tests__/tenant-settings.integration.test.ts +115 -0
  557. package/src/tenant-settings/config.ts +44 -0
  558. package/src/tenant-settings/constants.ts +16 -0
  559. package/src/tenant-settings/feature.ts +49 -0
  560. package/src/tenant-settings/index.ts +8 -0
  561. package/src/tenant-settings/tenant-defaults.ts +67 -0
  562. package/src/text-content/web/__tests__/editor-read-only.test.tsx +33 -6
  563. package/src/text-content/web/__tests__/group-blocks.test.ts +28 -3
  564. package/src/text-content/web/client-plugin.tsx +126 -104
  565. package/src/tier-engine/__tests__/auto-default-tier.integration.test.ts +1 -1
  566. package/src/tier-engine/__tests__/resolver.integration.test.ts +1 -1
  567. package/src/tier-engine/__tests__/tier-engine.integration.test.ts +7 -4
  568. package/src/tier-engine/__tests__/trial-config-validation.test.ts +30 -0
  569. package/src/tier-engine/feature.ts +13 -6
  570. package/src/tier-engine/i18n.ts +8 -0
  571. package/src/tier-engine/web/tier-admin-screen.tsx +26 -19
  572. package/src/user/__tests__/stream-tenant-backfill.integration.test.ts +186 -0
  573. package/src/user/__tests__/user.integration.test.ts +26 -0
  574. package/src/user/constants.ts +1 -0
  575. package/src/user/db/queries/stream-tenant-backfill.ts +112 -0
  576. package/src/user/feature.ts +3 -0
  577. package/src/user/handlers/find-for-auth.query.ts +8 -6
  578. package/src/user/i18n.ts +20 -0
  579. package/src/user/index.ts +4 -0
  580. package/src/user/schema/user.ts +6 -2
  581. package/src/user/screens.ts +3 -15
  582. package/src/user/seeding.ts +8 -0
  583. package/src/user-data-rights/COMPLIANCE.md +21 -1
  584. package/src/user-data-rights/__tests__/anonymous-deletion-kms.integration.test.ts +145 -0
  585. package/src/user-data-rights/__tests__/anonymous-deletion.integration.test.ts +3 -0
  586. package/src/user-data-rights/__tests__/audit-log.integration.test.ts +67 -0
  587. package/src/user-data-rights/__tests__/boot-checks.test.ts +170 -0
  588. package/src/user-data-rights/__tests__/cross-data-matrix.integration.test.ts +4 -1
  589. package/src/user-data-rights/__tests__/download-by-token-resolver-trust.integration.test.ts +169 -0
  590. package/src/user-data-rights/__tests__/download.integration.test.ts +101 -33
  591. package/src/user-data-rights/__tests__/email-templates.test.ts +15 -0
  592. package/src/user-data-rights/__tests__/export-encrypted-fields.integration.test.ts +154 -0
  593. package/src/user-data-rights/__tests__/export-job-idempotency.integration.test.ts +2 -0
  594. package/src/user-data-rights/__tests__/export-job-list-cross-tenant.integration.test.ts +2 -0
  595. package/src/user-data-rights/__tests__/file-binary-forget-cleanup.integration.test.ts +4 -1
  596. package/src/user-data-rights/__tests__/file-binary-forget-failure.integration.test.ts +4 -1
  597. package/src/user-data-rights/__tests__/file-retention.integration.test.ts +4 -1
  598. package/src/user-data-rights/__tests__/file-storage-unification.integration.test.ts +4 -1
  599. package/src/user-data-rights/__tests__/forget-cleanup-hook-ordering.integration.test.ts +41 -62
  600. package/src/user-data-rights/__tests__/forget-hook-registry.integration.test.ts +110 -0
  601. package/src/user-data-rights/__tests__/inspector-screens.boot.test.ts +14 -0
  602. package/src/user-data-rights/__tests__/mail-default-bridge.integration.test.ts +19 -2
  603. package/src/user-data-rights/__tests__/read-users-rebuild-survives-lifecycle.integration.test.ts +25 -14
  604. package/src/user-data-rights/__tests__/request-cancel-deletion.integration.test.ts +2 -0
  605. package/src/user-data-rights/__tests__/request-deletion-callback.integration.test.ts +4 -1
  606. package/src/user-data-rights/__tests__/request-export.integration.test.ts +2 -0
  607. package/src/user-data-rights/__tests__/restriction-flow.integration.test.ts +172 -19
  608. package/src/user-data-rights/__tests__/run-export-jobs-cron-context.integration.test.ts +2 -0
  609. package/src/user-data-rights/__tests__/run-export-jobs.integration.test.ts +257 -26
  610. package/src/user-data-rights/__tests__/run-forget-cleanup.integration.test.ts +339 -5
  611. package/src/user-data-rights/__tests__/run-user-export.integration.test.ts +77 -1
  612. package/src/user-data-rights/__tests__/tenant-model-erasure.integration.test.ts +2 -0
  613. package/src/user-data-rights/__tests__/user-data-rights.integration.test.ts +9 -1
  614. package/src/user-data-rights/boot-checks.ts +70 -0
  615. package/src/user-data-rights/db/queries/__tests__/forget-cleanup.integration.test.ts +69 -0
  616. package/src/user-data-rights/db/queries/forget-cleanup.ts +9 -2
  617. package/src/user-data-rights/email-templates.ts +6 -6
  618. package/src/user-data-rights/feature.ts +162 -43
  619. package/src/user-data-rights/handlers/cancel-deletion.write.ts +8 -11
  620. package/src/user-data-rights/handlers/deletion-grace-period.ts +4 -1
  621. package/src/user-data-rights/handlers/download-by-job.query.ts +26 -18
  622. package/src/user-data-rights/handlers/download-by-token.query.ts +23 -15
  623. package/src/user-data-rights/handlers/lift-restriction.write.ts +44 -19
  624. package/src/user-data-rights/handlers/list-download-attempts.query.ts +1 -0
  625. package/src/user-data-rights/handlers/my-audit-log.query.ts +1 -0
  626. package/src/user-data-rights/handlers/request-deletion-by-email.write.ts +3 -1
  627. package/src/user-data-rights/handlers/restrict-account.write.ts +58 -7
  628. package/src/user-data-rights/handlers/run-forget-cleanup.write.ts +2 -0
  629. package/src/user-data-rights/i18n.ts +35 -32
  630. package/src/user-data-rights/lib/is-admin-actor.ts +19 -0
  631. package/src/user-data-rights/lib/storage-provider-resolver.ts +6 -5
  632. package/src/user-data-rights/lib/update-user-lifecycle.ts +8 -24
  633. package/src/user-data-rights/run-export-jobs.ts +55 -19
  634. package/src/user-data-rights/run-forget-cleanup.ts +112 -7
  635. package/src/user-data-rights/run-user-export.ts +70 -4
  636. package/src/user-data-rights/schema/download-token.ts +5 -3
  637. package/src/user-data-rights/schema/export-job.ts +2 -0
  638. package/src/user-data-rights/screens.ts +2 -1
  639. package/src/user-data-rights/web/__tests__/privacy-center-screen.test.tsx +1 -0
  640. package/src/user-data-rights/web/__tests__/public-deletion-gate.test.tsx +7 -1
  641. package/src/user-data-rights/web/client-plugin.tsx +6 -1
  642. package/src/user-data-rights/web/i18n.ts +30 -10
  643. package/src/user-data-rights/web/privacy-center-screen.tsx +5 -3
  644. package/src/user-data-rights-defaults/__tests__/bundled-entities.userdata-hooks.integration.test.ts +455 -0
  645. package/src/user-data-rights-defaults/__tests__/feature-mounted.test.ts +24 -0
  646. package/src/user-data-rights-defaults/__tests__/user-data-rights-defaults.integration.test.ts +33 -9
  647. package/src/user-data-rights-defaults/feature.ts +78 -1
  648. package/src/user-data-rights-defaults/hooks/api-token.userdata-hook.ts +37 -0
  649. package/src/user-data-rights-defaults/hooks/config-value.userdata-hook.ts +51 -0
  650. package/src/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.ts +37 -0
  651. package/src/user-data-rights-defaults/hooks/feature-mounted.ts +10 -0
  652. package/src/user-data-rights-defaults/hooks/in-app-message.userdata-hook.ts +38 -0
  653. package/src/user-data-rights-defaults/hooks/job-run.userdata-hook.ts +35 -0
  654. package/src/user-data-rights-defaults/hooks/notification-preference.userdata-hook.ts +54 -0
  655. package/src/user-data-rights-defaults/hooks/tenant-invitation.userdata-hook.ts +112 -0
  656. package/src/user-data-rights-defaults/hooks/user-session.userdata-hook.ts +42 -0
  657. package/src/user-data-rights-defaults/index.ts +26 -0
  658. package/src/user-profile/__tests__/change-email.integration.test.ts +7 -1
  659. package/src/user-profile/__tests__/profile-screen.test.tsx +5 -0
  660. package/src/user-profile/feature.ts +4 -1
  661. package/src/user-profile/handlers/change-email.write.ts +15 -10
  662. package/src/user-profile/web/profile-screen.tsx +15 -3
  663. package/src/auth-email-password/stream-tenant.ts +0 -31
  664. package/src/feature-toggles/db/queries/toggle-state.ts +0 -25
  665. package/src/legal-pages/security-headers.ts +0 -1
  666. package/src/sessions/schema/index.ts +0 -5
  667. package/src/tenant/schema/index.ts +0 -5
  668. package/src/user/schema/index.ts +0 -5
  669. /package/src/{auth-email-password → shared}/identity-v3-hash.ts +0 -0
@@ -1,29 +1,77 @@
1
+ import {
2
+ EXT_SESSION_STORE,
3
+ type SessionStore,
4
+ type SessionStoreProvider,
5
+ } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
1
6
  import { buildEntityTableMeta } from "@cosmicdrift/kumiko-framework/db";
2
- import { defineFeature, type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
7
+ import {
8
+ access,
9
+ defineFeature,
10
+ type FeatureDefinition,
11
+ } from "@cosmicdrift/kumiko-framework/engine";
12
+ import { SESSION_DETAIL_SCREEN_ID, SESSION_LIST_SCREEN_ID, SessionQueries } from "./constants";
3
13
  import { cleanupJob } from "./handlers/cleanup.job";
14
+ import { detailQuery } from "./handlers/detail.query";
4
15
  import { listQuery } from "./handlers/list.query";
5
16
  import { mineQuery } from "./handlers/mine.query";
6
17
  import { revokeWrite } from "./handlers/revoke.write";
7
18
  import { revokeAllForUserWrite } from "./handlers/revoke-all-for-user.write";
8
19
  import { revokeAllOthersWrite } from "./handlers/revoke-all-others.write";
20
+ import { SESSIONS_I18N } from "./i18n";
9
21
  import { userSessionEntity } from "./schema/user-session";
10
- import type { SessionMassRevoker } from "./session-callbacks";
22
+ import {
23
+ createSessionCallbacks,
24
+ type SessionAllOthersRevoker,
25
+ type SessionMassRevoker,
26
+ } from "./session-callbacks";
27
+ import { SESSION_REVOKED_EVENT_SHORT, sessionRevokedSchema } from "./session-revoked-event";
11
28
 
12
29
  export type SessionsFeatureOptions = {
13
- // When wired, a successful update on the `user` entity that changes the
14
- // `passwordHash` column triggers a mass-revoke of every live session for
15
- // that user. Industry-standard "password-change signs you out everywhere"
16
- // flow, including the session that did the change itself — the client has
30
+ // A successful update on the `user` entity that changes the `passwordHash`
31
+ // column triggers a mass-revoke of every live session for that user.
32
+ // Industry-standard "password-change signs you out everywhere" flow,
33
+ // including the session that did the change itself — the client has
17
34
  // to re-login after a password change.
18
35
  //
19
36
  // Runs as an afterCommit postSave hook: the password-change commits first,
20
37
  // then the sessions are revoked. Best-effort — if the mass-revoker throws,
21
38
  // the password change is NOT rolled back (a password change with a stale
22
39
  // session still wins over a user-visible error on the change itself).
40
+ //
41
+ // Default: run{Prod,Dev}App bind their own sessionMassRevoker via
42
+ // `bindAutoRevokeOnPasswordChange` (secure-by-default). Set this option
43
+ // only to supply a custom revoker — an explicit value wins over the
44
+ // runtime binding.
23
45
  readonly autoRevokeOnPasswordChange?: SessionMassRevoker;
46
+ // Session JWT / store_user_sessions TTL. Was previously auth.sessions.expiresInMs
47
+ // (#1372) — lives on the sessions feature now that sessionStore is the wiring.
48
+ readonly expiresInMs?: number;
24
49
  };
25
50
 
26
- // The sessions feature registers the read_user_sessions table (as an
51
+ export type BindAutoRevokeOnPasswordChange = (revoker: SessionMassRevoker) => void;
52
+ export type BindRevokeAllOtherSessions = (revoker: SessionAllOthersRevoker) => void;
53
+
54
+ // Reads the late-bind setter off a mounted sessions feature's exports.
55
+ // run{Prod,Dev}App call it once the DB connection is concrete — the feature
56
+ // itself is constructed in app run-config long before a db exists, so the
57
+ // revoker can't be a constructor argument.
58
+ export function bindAutoRevokeFromFeature(
59
+ feature: FeatureDefinition,
60
+ ): BindAutoRevokeOnPasswordChange | undefined {
61
+ const exports = feature.exports;
62
+ if (exports && typeof exports === "object" && "bindAutoRevokeOnPasswordChange" in exports) {
63
+ const { bindAutoRevokeOnPasswordChange } = exports as {
64
+ bindAutoRevokeOnPasswordChange: unknown;
65
+ };
66
+ if (typeof bindAutoRevokeOnPasswordChange === "function") {
67
+ // @cast-boundary exports-walk — feature.exports is untyped by design
68
+ return bindAutoRevokeOnPasswordChange as BindAutoRevokeOnPasswordChange;
69
+ }
70
+ }
71
+ return undefined;
72
+ }
73
+
74
+ // The sessions feature registers the store_user_sessions table (as an
27
75
  // unmanaged direct-write store, NOT an r.entity — see below) and the three
28
76
  // user-facing handlers (mine/revoke/revoke-all-others). It intentionally does NOT
29
77
  // export a sessionCreator/sessionRevoker here — those are produced by
@@ -41,7 +89,7 @@ export type SessionsFeatureOptions = {
41
89
  export function createSessionsFeature(options?: SessionsFeatureOptions): FeatureDefinition {
42
90
  return defineFeature("sessions", (r) => {
43
91
  r.describe(
44
- "Tracks signed-in clients in the `read_user_sessions` table (one row per JWT, keyed by the `sid`/`jti` claim) and exposes handlers for `mine` (list your sessions), `revoke`, and `revokeAllOthers`. Session creation and revocation on the hot auth path are handled by `createSessionCallbacks()`, wired into `buildServer({ auth: { ... } })` outside the dispatcher; the feature also ships a manual-trigger cleanup job for pruning expired rows and an optional `autoRevokeOnPasswordChange` hook that mass-revokes all sessions for a user whenever their `passwordHash` changes.",
92
+ "Tracks signed-in clients in the `store_user_sessions` table (one row per JWT, keyed by the `sid`/`jti` claim) and exposes handlers for `mine` (list your sessions), `revoke`, and `revokeAllOthers`. Session creation and revocation on the hot auth path are handled by `createSessionCallbacks()`, wired into `buildServer({ auth: { ... } })` outside the dispatcher; the same callbacks are also registered as an auth-foundation sessionStore provider, resolvable generically via `resolveSessionStore()`. The feature also ships a manual-trigger cleanup job for pruning expired rows and an optional `autoRevokeOnPasswordChange` hook that mass-revokes all sessions for a user whenever their `passwordHash` changes.",
45
93
  );
46
94
  r.uiHints({
47
95
  displayLabel: "Sessions · Server-side Logout",
@@ -51,20 +99,56 @@ export function createSessionsFeature(options?: SessionsFeatureOptions): Feature
51
99
  // sessionChecker reads read_users on every authenticated request (status
52
100
  // gate for locked accounts) — make that a boot-time dependency so a
53
101
  // sessions-without-user wiring fails validateBoot instead of 500ing live.
54
- r.requires("user");
55
- // read_user_sessions is a hot-path direct-write store: sessionCreator
102
+ // auth-foundation owns EXT_SESSION_STORE, which the useExtension below
103
+ // registers against.
104
+ r.requires("user", "auth-foundation");
105
+ // store_user_sessions is a hot-path direct-write store: sessionCreator
56
106
  // inserts and the revoke handlers update rows WITHOUT emitting lifecycle
57
107
  // events (the row columns ARE the audit trail). Registering it as
58
108
  // r.entity would make it a rebuildable implicit projection whose replay
59
109
  // finds zero session events and swaps an empty shadow over the live
60
110
  // table — wiping every active session on the next projection rebuild
61
- // (#498/#494). r.unmanagedTable keeps the migration DDL but opts the
111
+ // (#498/#494). r.storeTable keeps the migration DDL but opts the
62
112
  // table out of implicit rebuild, like jobs/channel-in-app/feature-toggles
63
113
  // which are direct-write stores too.
64
- r.unmanagedTable(buildEntityTableMeta("user-session", userSessionEntity), {
114
+ r.storeTable(buildEntityTableMeta("user-session", userSessionEntity, { source: "unmanaged" }), {
65
115
  reason: "read_side.user_sessions_direct_write",
116
+ // sessionCreator encrypts ip/userAgent via encryptForDirectWrite (#820).
117
+ piiEncryptedOnWrite: true,
66
118
  });
67
119
 
120
+ // Self-registers as auth-foundation's sessionStore provider (#1371) —
121
+ // wraps the same createSessionCallbacks() used by the manual
122
+ // buildServer({ auth: { ... } }) wiring above; a future issue (#1372)
123
+ // removes that manual wiring once the middleware resolves generically
124
+ // via resolveSessionStore().
125
+ r.useExtension(EXT_SESSION_STORE, "default", {
126
+ build: (deps): SessionStore => {
127
+ const callbacks = createSessionCallbacks({
128
+ db: deps.db,
129
+ ...(options?.expiresInMs !== undefined && { expiresInMs: options.expiresInMs }),
130
+ });
131
+ return {
132
+ creator: callbacks.sessionCreator,
133
+ revoker: callbacks.sessionRevoker,
134
+ checker: callbacks.sessionChecker,
135
+ massRevoker: callbacks.sessionMassRevoker,
136
+ revokeAllOthers: callbacks.sessionRevokeAllOthers,
137
+ };
138
+ },
139
+ } satisfies SessionStoreProvider);
140
+
141
+ // Custom domain-event for cross-instance access-invalidation (#1559).
142
+ // r.defineEvent registers the schema so ctx.unsafeAppendEvent (revoke,
143
+ // revoke-all-others) enforces it at append time. revoke-all-for-user
144
+ // appends via the low-level append() instead (needs to anchor on
145
+ // SYSTEM_TENANT_ID — see that handler for why) — append() does NOT
146
+ // consult the registry, so that callsite parses against
147
+ // sessionRevokedSchema explicitly, same guarantee via a different path.
148
+ // No projection: the payload IS the read, consumed directly off the
149
+ // event-store NOTIFY (#1560).
150
+ r.defineEvent(SESSION_REVOKED_EVENT_SHORT, sessionRevokedSchema);
151
+
68
152
  const handlers = {
69
153
  revoke: r.writeHandler(revokeWrite),
70
154
  revokeAllOthers: r.writeHandler(revokeAllOthersWrite),
@@ -75,6 +159,7 @@ export function createSessionsFeature(options?: SessionsFeatureOptions): Feature
75
159
  const queries = {
76
160
  mine: r.queryHandler(mineQuery),
77
161
  list: r.queryHandler(listQuery),
162
+ detail: r.queryHandler(detailQuery),
78
163
  };
79
164
 
80
165
  // Retention: chunked DELETE of expired/revoked rows. Manual trigger
@@ -93,20 +178,84 @@ export function createSessionsFeature(options?: SessionsFeatureOptions): Feature
93
178
  // "the handler didn't touch this column", which is exactly the signal
94
179
  // we want to skip on. Works for both direct user:update calls and any
95
180
  // other handler that happens to write the column.
96
- const autoRevoke = options?.autoRevokeOnPasswordChange;
97
- if (autoRevoke) {
98
- r.entityHook("postSave", "user", async (ctx) => {
99
- // skip: brand-new user, no sessions can possibly exist yet. The
100
- // initial passwordHash on a user:create would trip the second guard
101
- // otherwise — every registration would do a mass-revoke roundtrip
102
- // for a user who literally has no rows in user_sessions.
103
- if (ctx.isNew) return;
104
- // skip: handler didn't touch passwordHash, nothing to revoke
105
- if (ctx.changes["passwordHash"] === undefined) return;
106
- await autoRevoke(String(ctx.id));
107
- });
108
- }
181
+ let autoRevoke = options?.autoRevokeOnPasswordChange;
182
+ r.hook("postSave", { allOf: "user" }, async (ctx) => {
183
+ // skip: nothing bound — stateless-JWT deployments without a runtime
184
+ // that calls bindAutoRevokeOnPasswordChange keep the old behavior.
185
+ if (!autoRevoke) return;
186
+ // skip: brand-new user, no sessions can possibly exist yet. The
187
+ // initial passwordHash on a user:create would trip the second guard
188
+ // otherwise — every registration would do a mass-revoke roundtrip
189
+ // for a user who literally has no rows in user_sessions.
190
+ if (ctx.isNew) return;
191
+ // skip: handler didn't touch passwordHash, nothing to revoke
192
+ if (ctx.changes["passwordHash"] === undefined) return;
193
+ await autoRevoke(String(ctx.id));
194
+ });
195
+
196
+ const bindAutoRevokeOnPasswordChange: BindAutoRevokeOnPasswordChange = (revoker) => {
197
+ // explicit constructor option wins over the runtime binding
198
+ autoRevoke ??= revoker;
199
+ };
200
+
201
+ r.translations({ keys: SESSIONS_I18N });
202
+
203
+ const listAccess = { roles: access.admin };
204
+
205
+ r.screen({
206
+ id: SESSION_LIST_SCREEN_ID,
207
+ type: "projectionList",
208
+ query: SessionQueries.list,
209
+ columns: [
210
+ { field: "id", label: "sessions.list.col.id" },
211
+ { field: "userId", label: "sessions.list.col.userId" },
212
+ { field: "createdAt", label: "sessions.list.col.createdAt" },
213
+ { field: "expiresAt", label: "sessions.list.col.expiresAt" },
214
+ { field: "revokedAt", label: "sessions.list.col.revokedAt" },
215
+ ],
216
+ rowActions: [
217
+ {
218
+ kind: "navigate",
219
+ id: "open",
220
+ label: "sessions.list.action.open",
221
+ screen: SESSION_DETAIL_SCREEN_ID,
222
+ entityId: "id",
223
+ rowClick: true,
224
+ },
225
+ ],
226
+ access: listAccess,
227
+ });
228
+ r.screen({
229
+ id: SESSION_DETAIL_SCREEN_ID,
230
+ type: "projectionDetail",
231
+ query: SessionQueries.detail,
232
+ listScreenId: SESSION_LIST_SCREEN_ID,
233
+ layout: {
234
+ sections: [
235
+ {
236
+ fields: ["id", "userId", "createdAt", "expiresAt", "revokedAt", "ip", "userAgent"],
237
+ },
238
+ ],
239
+ },
240
+ fieldLabels: {
241
+ id: "sessions.detail.field.id",
242
+ userId: "sessions.detail.field.userId",
243
+ createdAt: "sessions.detail.field.createdAt",
244
+ expiresAt: "sessions.detail.field.expiresAt",
245
+ revokedAt: "sessions.detail.field.revokedAt",
246
+ ip: "sessions.detail.field.ip",
247
+ userAgent: "sessions.detail.field.userAgent",
248
+ },
249
+ access: listAccess,
250
+ });
251
+ r.nav({
252
+ id: "session-list",
253
+ label: "sessions:nav.sessionList",
254
+ icon: "list",
255
+ screen: "sessions:screen:session-list",
256
+ order: 10,
257
+ });
109
258
 
110
- return { handlers, queries };
259
+ return { handlers, queries, bindAutoRevokeOnPasswordChange };
111
260
  });
112
261
  }
@@ -0,0 +1,36 @@
1
+ import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
+ import { access, defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { z } from "zod";
4
+ import { decryptStoredPii } from "../../shared";
5
+ import { userSessionTable } from "../schema/user-session";
6
+
7
+ // Admin single-session inspector — mirrors list.query's decrypt handling for
8
+ // the one-row case. ctx.db (TenantDb) applies tenant-scoping automatically.
9
+ export const detailQuery = defineQueryHandler({
10
+ name: "user-session:detail",
11
+ schema: z.object({ id: z.uuid() }),
12
+ access: { roles: access.admin },
13
+ handler: async (query, ctx) => {
14
+ const row = await fetchOne<{
15
+ id: string;
16
+ userId: string;
17
+ createdAt: unknown;
18
+ expiresAt: unknown;
19
+ revokedAt: unknown;
20
+ ip: string | null;
21
+ userAgent: string | null;
22
+ }>(ctx.db, userSessionTable, { id: query.payload.id });
23
+ if (!row) return null;
24
+ return {
25
+ id: row.id,
26
+ userId: row.userId,
27
+ createdAt: row.createdAt,
28
+ expiresAt: row.expiresAt,
29
+ revokedAt: row.revokedAt,
30
+ ip: row.ip ? await decryptStoredPii(row.ip, "ip", "sessions:detail") : row.ip,
31
+ userAgent: row.userAgent
32
+ ? await decryptStoredPii(row.userAgent, "userAgent", "sessions:detail")
33
+ : row.userAgent,
34
+ };
35
+ },
36
+ });
@@ -1,6 +1,7 @@
1
1
  import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { access, defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { z } from "zod";
4
+ import { decryptStoredPii } from "../../shared";
4
5
  import { userSessionTable } from "../schema/user-session";
5
6
 
6
7
  // Admin view of every session in the active tenant. ctx.db (TenantDb)
@@ -22,14 +23,18 @@ export const listQuery = defineQueryHandler({
22
23
  }>(ctx.db, userSessionTable, undefined, {
23
24
  orderBy: { col: "createdAt", direction: "desc" },
24
25
  });
25
- return rows.map((r) => ({
26
- id: r.id,
27
- userId: r.userId,
28
- createdAt: r.createdAt,
29
- expiresAt: r.expiresAt,
30
- revokedAt: r.revokedAt,
31
- ip: r.ip,
32
- userAgent: r.userAgent,
33
- }));
26
+ return Promise.all(
27
+ rows.map(async (r) => ({
28
+ id: r.id,
29
+ userId: r.userId,
30
+ createdAt: r.createdAt,
31
+ expiresAt: r.expiresAt,
32
+ revokedAt: r.revokedAt,
33
+ ip: r.ip ? await decryptStoredPii(r.ip, "ip", "sessions:list") : r.ip,
34
+ userAgent: r.userAgent
35
+ ? await decryptStoredPii(r.userAgent, "userAgent", "sessions:list")
36
+ : r.userAgent,
37
+ })),
38
+ );
34
39
  },
35
40
  });
@@ -1,6 +1,7 @@
1
1
  import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { z } from "zod";
4
+ import { decryptStoredPii } from "../../shared";
4
5
  import { userSessionTable } from "../schema/user-session";
5
6
 
6
7
  // "My live sessions" — the backing data for a devices/sessions UI. Returns
@@ -26,13 +27,17 @@ export const mineQuery = defineQueryHandler({
26
27
  },
27
28
  );
28
29
  const currentSid = query.user.sid;
29
- return rows.map((r) => ({
30
- id: r.id,
31
- createdAt: r.createdAt,
32
- expiresAt: r.expiresAt,
33
- ip: r.ip,
34
- userAgent: r.userAgent,
35
- current: currentSid === r.id,
36
- }));
30
+ return Promise.all(
31
+ rows.map(async (r) => ({
32
+ id: r.id,
33
+ createdAt: r.createdAt,
34
+ expiresAt: r.expiresAt,
35
+ ip: r.ip ? await decryptStoredPii(r.ip, "ip", "sessions:mine") : r.ip,
36
+ userAgent: r.userAgent
37
+ ? await decryptStoredPii(r.userAgent, "userAgent", "sessions:mine")
38
+ : r.userAgent,
39
+ current: currentSid === r.id,
40
+ })),
41
+ );
37
42
  },
38
43
  });
@@ -1,21 +1,28 @@
1
1
  import { updateMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
- import { access, defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { access, defineWriteHandler, SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { append } from "@cosmicdrift/kumiko-framework/event-store";
4
+ import { generateId } from "@cosmicdrift/kumiko-framework/utils";
3
5
  import { Temporal } from "temporal-polyfill";
4
6
  import { z } from "zod";
5
7
  import { userSessionTable } from "../schema/user-session";
8
+ import {
9
+ SESSION_REVOKED_AGGREGATE_TYPE,
10
+ SESSION_REVOKED_EVENT_QN,
11
+ sessionRevokedSchema,
12
+ } from "../session-revoked-event";
6
13
 
7
14
  // Mass-revoke ALL live sessions for a target user — privileged-only.
8
- // Used by user-data-rights:restrict-account zur Account-Freeze
9
- // (DSGVO Art. 18) sowie potenziell anderen ops-flows ("ban user",
10
- // "compromised account"). Im Gegensatz zu revoke-all-others wird
11
- // die ggf. aufrufende Session ebenfalls revoked — Caller ist System
12
- // (cron/operator/cross-feature), nicht der Endnutzer selbst.
15
+ // Used by user-data-rights:restrict-account for account-freeze (DSGVO
16
+ // Art. 18) and potentially other ops flows ("ban user", "compromised
17
+ // account"). Unlike revoke-all-others, the caller's own session (if any)
18
+ // is revoked too — the caller is System (cron/operator/cross-feature),
19
+ // not the end-user themselves.
13
20
  //
14
- // Tenant-scope: das userSession-Schema persistiert tenantId pro Row
15
- // (User kann mehrere Sessions in mehreren Tenants haben). Wir
16
- // revoken cross-tenant, weil "Account-Restriction" eine globale
17
- // User-Aussage ist (Forget-Pfad ist auch global, sieht User-Entity-
18
- // special-Doc). UPDATE filtert nur auf userId.
21
+ // Tenant-scope: the userSession schema persists tenantId per row (a user
22
+ // can have sessions in multiple tenants). We revoke cross-tenant because
23
+ // "account restriction" is a global user-level statement (the forget path
24
+ // is global too, see the User-Entity special doc). The UPDATE filters
25
+ // only on userId.
19
26
  export const revokeAllForUserWrite = defineWriteHandler({
20
27
  name: "user-session:revoke-all-for-user",
21
28
  schema: z.object({
@@ -23,13 +30,45 @@ export const revokeAllForUserWrite = defineWriteHandler({
23
30
  }),
24
31
  access: { roles: access.privileged },
25
32
  handler: async (event, ctx) => {
26
- const updated = await updateMany(
33
+ const updated = await updateMany<{ id: string }>(
27
34
  ctx.db.raw,
28
35
  userSessionTable,
29
36
  { revokedAt: Temporal.Now.instant() },
30
37
  { userId: event.payload.userId, revokedAt: null },
31
38
  );
32
39
 
40
+ // Lightweight append alongside the direct-write above (#1559) — see
41
+ // revoke.write.ts for why this isn't a lifecycle event on
42
+ // store_user_sessions. Uses the low-level append() (not
43
+ // ctx.unsafeAppendEvent) because this handler is deliberately
44
+ // cross-tenant: the privileged caller's own tenantId (what
45
+ // ctx.unsafeAppendEvent would bind to) has no relationship to the
46
+ // target user's session tenant(s). Anchored on SYSTEM_TENANT_ID
47
+ // instead, same as the jobs feature's cross-tenant run-lifecycle
48
+ // events (job-run-logger.ts) — a fresh aggregate per call, so no
49
+ // predecessor/version_conflict concerns.
50
+ //
51
+ // append() itself does NOT validate against the r.defineEvent-
52
+ // registered schema (that guarantee only comes from going through
53
+ // ctx.unsafeAppendEvent's dispatcher path). Mirrors job-run-logger.ts:
54
+ // parse explicitly before the raw append so a shape drift fails loudly
55
+ // here instead of landing unvalidated on the events table.
56
+ if (updated.length > 0) {
57
+ const payload = sessionRevokedSchema.parse({
58
+ userId: event.payload.userId,
59
+ sessionIds: updated.map((row) => row.id),
60
+ });
61
+ await append(ctx.db.raw, {
62
+ aggregateId: generateId(),
63
+ aggregateType: SESSION_REVOKED_AGGREGATE_TYPE,
64
+ tenantId: SYSTEM_TENANT_ID,
65
+ expectedVersion: 0,
66
+ type: SESSION_REVOKED_EVENT_QN,
67
+ payload,
68
+ metadata: { userId: event.user.id },
69
+ });
70
+ }
71
+
33
72
  return {
34
73
  isSuccess: true as const,
35
74
  data: { count: updated.length, userId: event.payload.userId },
@@ -1,10 +1,12 @@
1
1
  import { updateMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
4
+ import { generateId } from "@cosmicdrift/kumiko-framework/utils";
4
5
  import { Temporal } from "temporal-polyfill";
5
6
  import { z } from "zod";
6
7
  import { SessionErrors } from "../constants";
7
8
  import { userSessionTable } from "../schema/user-session";
9
+ import { SESSION_REVOKED_AGGREGATE_TYPE, SESSION_REVOKED_EVENT_QN } from "../session-revoked-event";
8
10
 
9
11
  // "Sign out everywhere else" — keep the caller's current session, kill all
10
12
  // other live sessions for this user. Requires `user.sid` so "keep current"
@@ -25,13 +27,29 @@ export const revokeAllOthersWrite = defineWriteHandler({
25
27
  );
26
28
  }
27
29
 
28
- const updated = await updateMany(
30
+ const updated = await updateMany<{ id: string }>(
29
31
  ctx.db,
30
32
  userSessionTable,
31
33
  { revokedAt: Temporal.Now.instant() },
32
34
  { userId: event.user.id, revokedAt: null, id: { ne: keepSid } },
33
35
  );
34
36
 
37
+ // Lightweight append alongside the direct-write above (#1559) — see
38
+ // revoke.write.ts for why this isn't a lifecycle event on
39
+ // store_user_sessions. Skipped when nothing was revoked (no other
40
+ // live sessions) — a spurious invalidation would be the failure mode.
41
+ if (updated.length > 0) {
42
+ await ctx.unsafeAppendEvent({
43
+ aggregateId: generateId(),
44
+ aggregateType: SESSION_REVOKED_AGGREGATE_TYPE,
45
+ type: SESSION_REVOKED_EVENT_QN,
46
+ payload: {
47
+ userId: event.user.id,
48
+ sessionIds: updated.map((row) => row.id),
49
+ },
50
+ });
51
+ }
52
+
35
53
  return { isSuccess: true, data: { count: updated.length } };
36
54
  },
37
55
  });
@@ -1,10 +1,12 @@
1
1
  import { fetchOne, updateMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
4
+ import { generateId } from "@cosmicdrift/kumiko-framework/utils";
4
5
  import { Temporal } from "temporal-polyfill";
5
6
  import { z } from "zod";
6
7
  import { SessionErrors } from "../constants";
7
8
  import { userSessionTable } from "../schema/user-session";
9
+ import { SESSION_REVOKED_AGGREGATE_TYPE, SESSION_REVOKED_EVENT_QN } from "../session-revoked-event";
8
10
 
9
11
  // Revoke a single session by id (= JWT jti). Three distinguishable outcomes:
10
12
  //
@@ -36,6 +38,17 @@ export const revokeWrite = defineWriteHandler({
36
38
  );
37
39
 
38
40
  if (updated.length > 0) {
41
+ // Lightweight append alongside the direct-write above (#1559) — NOT a
42
+ // lifecycle event for store_user_sessions (that table stays an
43
+ // unmanaged direct-write store, see feature.ts). Own aggregate id per
44
+ // revoke, no predecessor to satisfy, no version_conflict against a
45
+ // concurrent revoke on a different sid.
46
+ await ctx.unsafeAppendEvent({
47
+ aggregateId: generateId(),
48
+ aggregateType: SESSION_REVOKED_AGGREGATE_TYPE,
49
+ type: SESSION_REVOKED_EVENT_QN,
50
+ payload: { userId: event.user.id, sessionIds: [event.payload.id] },
51
+ });
39
52
  return { isSuccess: true, data: { id: event.payload.id } };
40
53
  }
41
54
 
@@ -0,0 +1,23 @@
1
+ // @runtime client
2
+ // Server + client i18n for the sessions operator screens.
3
+
4
+ type LocalizedString = { readonly de: string; readonly en: string };
5
+
6
+ export const SESSIONS_I18N: Readonly<Record<string, LocalizedString>> = {
7
+ "screen:session-list.title": { de: "Sitzungen", en: "Sessions" },
8
+ "screen:session-detail.title": { de: "Sitzung", en: "Session" },
9
+ "sessions:nav.sessionList": { de: "Sitzungen", en: "Sessions" },
10
+ "sessions.list.col.id": { de: "ID", en: "ID" },
11
+ "sessions.list.col.userId": { de: "User-ID", en: "User ID" },
12
+ "sessions.list.col.createdAt": { de: "Erstellt", en: "Created" },
13
+ "sessions.list.col.expiresAt": { de: "Läuft ab", en: "Expires" },
14
+ "sessions.list.col.revokedAt": { de: "Widerrufen", en: "Revoked" },
15
+ "sessions.list.action.open": { de: "Details", en: "Details" },
16
+ "sessions.detail.field.id": { de: "ID", en: "ID" },
17
+ "sessions.detail.field.userId": { de: "User-ID", en: "User ID" },
18
+ "sessions.detail.field.createdAt": { de: "Erstellt", en: "Created" },
19
+ "sessions.detail.field.expiresAt": { de: "Läuft ab", en: "Expires" },
20
+ "sessions.detail.field.revokedAt": { de: "Widerrufen", en: "Revoked" },
21
+ "sessions.detail.field.ip": { de: "IP-Adresse", en: "IP address" },
22
+ "sessions.detail.field.userAgent": { de: "User-Agent", en: "User agent" },
23
+ };
@@ -6,8 +6,8 @@ export {
6
6
  SessionHandlers,
7
7
  SessionQueries,
8
8
  } from "./constants";
9
- export type { SessionsFeatureOptions } from "./feature";
10
- export { createSessionsFeature } from "./feature";
9
+ export type { BindAutoRevokeOnPasswordChange, SessionsFeatureOptions } from "./feature";
10
+ export { bindAutoRevokeFromFeature, createSessionsFeature } from "./feature";
11
11
  export { userSessionEntity, userSessionTable } from "./schema/user-session";
12
12
  export type {
13
13
  SessionCallbacks,
@@ -15,3 +15,10 @@ export type {
15
15
  SessionMassRevoker,
16
16
  } from "./session-callbacks";
17
17
  export { createSessionCallbacks } from "./session-callbacks";
18
+ export type { SessionRevokedPayload } from "./session-revoked-event";
19
+ export {
20
+ SESSION_REVOKED_AGGREGATE_TYPE,
21
+ SESSION_REVOKED_EVENT_QN,
22
+ SESSION_REVOKED_EVENT_SHORT,
23
+ sessionRevokedSchema,
24
+ } from "./session-revoked-event";
@@ -20,7 +20,7 @@ export const userSessionEntity = createEntity({
20
20
  // Entity-Key ist "user-session" (mit Dash), toTableName's snake-case-
21
21
  // Transform käme auf "read_user-sessions" → kein valides SQL ohne Quoting.
22
22
  // Deshalb expliziter Override auf den read_-konformen Namen.
23
- table: "read_user_sessions",
23
+ table: "store_user_sessions",
24
24
  // sid-as-PK: the sessionCreator callback generates the UUID, returns it to
25
25
  // the framework; the framework stamps it as `jti`; here the same value is
26
26
  // the row primary key. Single source of truth for the identifier.
@@ -53,13 +53,17 @@ export const userSessionEntity = createEntity({
53
53
  revokedAt: createTimestampField({
54
54
  access: { write: access.privileged },
55
55
  }),
56
+ // Subject is the session's user, not the session row — a user-forget
57
+ // must shred these, so the key hangs off userId (sid-self would orphan).
56
58
  ip: createTextField({
57
59
  maxLength: 64,
58
60
  access: { write: access.privileged },
61
+ userOwned: { ownerField: "userId" },
59
62
  }),
60
63
  userAgent: createTextField({
61
64
  maxLength: 512,
62
65
  access: { write: access.privileged },
66
+ userOwned: { ownerField: "userId" },
63
67
  }),
64
68
  },
65
69
  });
@@ -70,4 +74,6 @@ export const userSessionEntity = createEntity({
70
74
  // handlers write it directly via ctx.db; the meta carries no executor-only
71
75
  // brand so those writes stay legal. See feature.ts for the rebuild-exclusion
72
76
  // rationale (#494/#498).
73
- export const userSessionTable = buildEntityTableMeta("user-session", userSessionEntity);
77
+ export const userSessionTable = buildEntityTableMeta("user-session", userSessionEntity, {
78
+ source: "unmanaged",
79
+ });