@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.
- package/package.json +41 -7
- package/src/__tests__/access-invalidation-consumer.integration.test.ts +279 -0
- package/src/__tests__/access-invalidation-stream.integration.test.ts +217 -0
- package/src/admin-shell/__tests__/admin-shell-security.integration.test.ts +200 -0
- package/src/admin-shell/__tests__/admin-shell.boot.test.ts +147 -0
- package/src/admin-shell/__tests__/overview-allowlist.test.ts +43 -0
- package/src/admin-shell/__tests__/overview-screens.boot.test.ts +35 -0
- package/src/admin-shell/constants.ts +11 -0
- package/src/admin-shell/feature.ts +135 -0
- package/src/admin-shell/i18n.ts +36 -0
- package/src/admin-shell/index.ts +17 -0
- package/src/admin-shell/overview-allowlist.ts +37 -0
- package/src/admin-shell/web/client-plugin.tsx +26 -0
- package/src/admin-shell/web/i18n.ts +16 -0
- package/src/admin-shell/web/index.ts +2 -0
- package/src/admin-shell/web/overview-layout.tsx +69 -0
- package/src/admin-shell/web/overview-query.ts +15 -0
- package/src/admin-shell/web/platform-overview-screen.tsx +90 -0
- package/src/admin-shell/web/tenant-overview-screen.tsx +90 -0
- package/src/agent-tools/__tests__/tool-catalog.test.ts +179 -0
- package/src/agent-tools/__tests__/tool-dispatch.integration.test.ts +100 -0
- package/src/agent-tools/__tests__/tool-dispatch.test.ts +126 -0
- package/src/agent-tools/index.ts +9 -0
- package/src/agent-tools/tool-catalog.ts +130 -0
- package/src/agent-tools/tool-dispatch.ts +82 -0
- package/src/agent-tools/types.ts +38 -0
- package/src/audit/__tests__/audit-screens.boot.test.ts +51 -0
- package/src/audit/__tests__/audit-security.integration.test.ts +201 -0
- package/src/audit/__tests__/audit.integration.test.ts +50 -25
- package/src/audit/constants.ts +9 -2
- package/src/audit/feature.ts +33 -1
- package/src/audit/handlers/details.query.ts +47 -0
- package/src/audit/handlers/list.query.ts +3 -2
- package/src/audit/i18n.ts +31 -0
- package/src/audit/web/audit-log-detail-screen.tsx +131 -0
- package/src/audit/web/audit-log-screen.tsx +250 -0
- package/src/audit/web/client-plugin.tsx +22 -0
- package/src/audit/web/i18n.ts +12 -0
- package/src/audit/web/index.ts +5 -0
- package/src/auth-email-password/__tests__/account-lockout-no-redis.integration.test.ts +4 -4
- package/src/auth-email-password/__tests__/account-lockout.integration.test.ts +4 -4
- package/src/auth-email-password/__tests__/account-unlock.integration.test.ts +283 -0
- package/src/auth-email-password/__tests__/auth-claims.integration.test.ts +10 -11
- package/src/auth-email-password/__tests__/auth-user-row.test.ts +36 -0
- package/src/auth-email-password/__tests__/auth.integration.test.ts +33 -6
- package/src/auth-email-password/__tests__/email-verification.integration.test.ts +9 -6
- package/src/auth-email-password/__tests__/feature-options.test.ts +33 -0
- package/src/auth-email-password/__tests__/identity-v3-login.integration.test.ts +3 -3
- package/src/auth-email-password/__tests__/invite-flow-kms.integration.test.ts +283 -0
- package/src/auth-email-password/__tests__/invite-flow.integration.test.ts +35 -1
- package/src/auth-email-password/__tests__/login-mfa-setup-required.integration.test.ts +129 -0
- package/src/auth-email-password/__tests__/multi-roles.integration.test.ts +1 -1
- package/src/auth-email-password/__tests__/password-policy.test.ts +15 -0
- package/src/auth-email-password/__tests__/password-reset.integration.test.ts +19 -6
- package/src/auth-email-password/__tests__/public-routes-rate-limit.integration.test.ts +3 -3
- package/src/auth-email-password/__tests__/seed-admin.integration.test.ts +1 -1
- package/src/auth-email-password/__tests__/self-registration-toggle.integration.test.ts +264 -0
- package/src/auth-email-password/__tests__/session-callbacks.integration.test.ts +20 -4
- package/src/auth-email-password/__tests__/session-strict-mode.integration.test.ts +7 -14
- package/src/auth-email-password/__tests__/signup-flow.integration.test.ts +91 -0
- package/src/auth-email-password/constants.ts +25 -0
- package/src/auth-email-password/email-templates.ts +32 -0
- package/src/auth-email-password/errors.ts +9 -0
- package/src/auth-email-password/feature.ts +59 -11
- package/src/auth-email-password/handlers/change-password.write.ts +14 -13
- package/src/auth-email-password/handlers/confirm-account-unlock.write.ts +59 -0
- package/src/auth-email-password/handlers/confirm-token-flow.ts +34 -105
- package/src/auth-email-password/handlers/invite-accept-with-login.write.ts +12 -5
- package/src/auth-email-password/handlers/invite-accept.write.ts +9 -2
- package/src/auth-email-password/handlers/invite-create.write.ts +2 -2
- package/src/auth-email-password/handlers/invite-signup-complete.write.ts +12 -4
- package/src/auth-email-password/handlers/login.write.ts +86 -16
- package/src/auth-email-password/handlers/logout.write.ts +4 -2
- package/src/auth-email-password/handlers/request-account-unlock.write.ts +38 -0
- package/src/auth-email-password/handlers/reset-password.write.ts +3 -2
- package/src/auth-email-password/handlers/self-registration-status.query.ts +23 -0
- package/src/auth-email-password/handlers/signup-confirm.write.ts +19 -11
- package/src/auth-email-password/handlers/signup-request.write.ts +22 -0
- package/src/auth-email-password/handlers/token-request-handler.ts +4 -2
- package/src/auth-email-password/i18n.ts +52 -0
- package/src/auth-email-password/index.ts +13 -2
- package/src/auth-email-password/lockout-store.ts +10 -5
- package/src/auth-email-password/password-policy.ts +2108 -0
- package/src/auth-email-password/seeding.ts +55 -27
- package/src/auth-email-password/self-registration-toggle.ts +25 -0
- package/src/auth-email-password/signed-token.ts +1 -0
- package/src/auth-email-password/unlock-token.ts +28 -0
- package/src/auth-email-password/web/__tests__/auth-client.test.ts +542 -0
- package/src/auth-email-password/web/__tests__/auth-form-logic.test.ts +44 -0
- package/src/auth-email-password/web/__tests__/auth-gate.test.tsx +194 -6
- package/src/auth-email-password/web/__tests__/client-plugin.test.tsx +34 -0
- package/src/auth-email-password/web/__tests__/confirm-account-unlock-screen.test.tsx +64 -0
- package/src/auth-email-password/web/__tests__/forgot-password-screen.test.tsx +5 -1
- package/src/auth-email-password/web/__tests__/invite-accept-screen.test.tsx +129 -2
- package/src/auth-email-password/web/__tests__/login-screen.test.tsx +149 -5
- package/src/auth-email-password/web/__tests__/request-account-unlock-screen.test.tsx +137 -0
- package/src/auth-email-password/web/__tests__/reset-password-screen.test.tsx +5 -1
- package/src/auth-email-password/web/__tests__/signup-complete-screen.test.tsx +134 -0
- package/src/auth-email-password/web/__tests__/signup-screen.test.tsx +93 -0
- package/src/auth-email-password/web/__tests__/test-utils.tsx +12 -2
- package/src/auth-email-password/web/__tests__/use-url-token.test.tsx +29 -0
- package/src/auth-email-password/web/__tests__/verify-email-screen.test.tsx +5 -1
- package/src/auth-email-password/web/auth-client.ts +96 -18
- package/src/auth-email-password/web/auth-form-logic.ts +30 -0
- package/src/auth-email-password/web/auth-form-primitives.tsx +22 -20
- package/src/auth-email-password/web/auth-gate.tsx +140 -19
- package/src/auth-email-password/web/client-plugin.ts +24 -2
- package/src/auth-email-password/web/confirm-account-unlock-screen.tsx +95 -0
- package/src/auth-email-password/web/forgot-password-screen.tsx +6 -6
- package/src/auth-email-password/web/index.ts +16 -4
- package/src/auth-email-password/web/invite-accept-screen.tsx +5 -5
- package/src/auth-email-password/web/login-screen.tsx +78 -22
- package/src/auth-email-password/web/request-account-unlock-screen.tsx +112 -0
- package/src/auth-email-password/web/reset-password-screen.tsx +15 -18
- package/src/auth-email-password/web/session.tsx +11 -6
- package/src/auth-email-password/web/signup-complete-screen.tsx +13 -15
- package/src/auth-email-password/web/signup-screen.tsx +8 -10
- package/src/auth-email-password/web/use-shell-user.ts +5 -2
- package/src/auth-email-password/web/user-menu.tsx +3 -1
- package/src/auth-email-password/web/verify-email-screen.tsx +10 -14
- package/src/auth-foundation/__tests__/feature.test.ts +46 -0
- package/src/auth-foundation/__tests__/session-store.test.ts +77 -0
- package/src/auth-foundation/__tests__/tenant-providers.test.ts +160 -0
- package/src/auth-foundation/__tests__/token-verifier.test.ts +150 -0
- package/src/auth-foundation/boot-checks.ts +150 -0
- package/src/auth-foundation/feature.ts +157 -0
- package/src/auth-foundation/index.ts +37 -0
- package/src/auth-foundation/resolve-anonymous-access.ts +62 -0
- package/src/auth-foundation/types.ts +170 -0
- package/src/auth-mfa/__tests__/base32.test.ts +28 -0
- package/src/auth-mfa/__tests__/disable-and-regenerate.integration.test.ts +265 -0
- package/src/auth-mfa/__tests__/enable-confirm-preauth.integration.test.ts +321 -0
- package/src/auth-mfa/__tests__/enable-flow.integration.test.ts +322 -0
- package/src/auth-mfa/__tests__/enable-start-preauth.integration.test.ts +237 -0
- package/src/auth-mfa/__tests__/errors-i18n-parity.test.ts +39 -0
- package/src/auth-mfa/__tests__/mfa-preauth-setup-token.test.ts +113 -0
- package/src/auth-mfa/__tests__/no-redis.integration.test.ts +209 -0
- package/src/auth-mfa/__tests__/pii-subject-encryption.integration.test.ts +139 -0
- package/src/auth-mfa/__tests__/reencrypt-job.integration.test.ts +247 -0
- package/src/auth-mfa/__tests__/session-auto-revoke.integration.test.ts +169 -0
- package/src/auth-mfa/__tests__/totp.test.ts +131 -0
- package/src/auth-mfa/__tests__/verify.integration.test.ts +319 -0
- package/src/auth-mfa/base32.ts +51 -0
- package/src/auth-mfa/config.ts +26 -0
- package/src/auth-mfa/constants.ts +45 -0
- package/src/auth-mfa/db/queries.ts +45 -0
- package/src/auth-mfa/errors.ts +66 -0
- package/src/auth-mfa/feature.ts +170 -0
- package/src/auth-mfa/handlers/disable.write.ts +67 -0
- package/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +200 -0
- package/src/auth-mfa/handlers/enable-confirm.write.ts +106 -0
- package/src/auth-mfa/handlers/enable-start-preauth.write.ts +87 -0
- package/src/auth-mfa/handlers/enable-start.write.ts +66 -0
- package/src/auth-mfa/handlers/reencrypt.job.ts +234 -0
- package/src/auth-mfa/handlers/regenerate-recovery.write.ts +77 -0
- package/src/auth-mfa/handlers/status.query.ts +23 -0
- package/src/auth-mfa/handlers/verify.write.ts +179 -0
- package/src/auth-mfa/i18n.ts +12 -0
- package/src/auth-mfa/index.ts +19 -0
- package/src/auth-mfa/mfa-challenge-token.ts +90 -0
- package/src/auth-mfa/mfa-preauth-setup-token.ts +86 -0
- package/src/auth-mfa/mfa-setup-token.ts +96 -0
- package/src/auth-mfa/mfa-status-checker.ts +84 -0
- package/src/auth-mfa/mfa-verify-attempts.ts +90 -0
- package/src/auth-mfa/otpauth-uri.ts +21 -0
- package/src/auth-mfa/recovery-codes.ts +54 -0
- package/src/auth-mfa/schema/user-mfa.ts +60 -0
- package/src/auth-mfa/testing.ts +5 -0
- package/src/auth-mfa/totp.ts +67 -0
- package/src/auth-mfa/verify-factor.ts +58 -0
- package/src/auth-mfa/web/__tests__/mfa-client.test.ts +265 -0
- package/src/auth-mfa/web/__tests__/mfa-enable-screen.test.tsx +182 -0
- package/src/auth-mfa/web/__tests__/mfa-setup-preauth-screen.test.tsx +379 -0
- package/src/auth-mfa/web/__tests__/mfa-verify-screen.test.tsx +104 -0
- package/src/auth-mfa/web/client-plugin.ts +37 -0
- package/src/auth-mfa/web/i18n.ts +136 -0
- package/src/auth-mfa/web/index.ts +27 -0
- package/src/auth-mfa/web/mfa-client.ts +184 -0
- package/src/auth-mfa/web/mfa-disable-dialog.tsx +69 -0
- package/src/auth-mfa/web/mfa-enable-screen.tsx +228 -0
- package/src/auth-mfa/web/mfa-recovery-codes-reveal.tsx +53 -0
- package/src/auth-mfa/web/mfa-regenerate-recovery-dialog.tsx +68 -0
- package/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +267 -0
- package/src/auth-mfa/web/mfa-verify-screen.tsx +117 -0
- package/src/auth-mfa/web/qrcode-browser.d.ts +12 -0
- package/src/auth-mfa-user-data/__tests__/hooks.integration.test.ts +134 -0
- package/src/auth-mfa-user-data/hooks.ts +53 -0
- package/src/auth-mfa-user-data/index.ts +21 -0
- package/src/billing-foundation/__tests__/billing-foundation.integration.test.ts +206 -2
- package/src/billing-foundation/__tests__/subscription-provider-contract.ts +93 -0
- package/src/billing-foundation/entities.ts +28 -4
- package/src/billing-foundation/events.ts +6 -2
- package/src/billing-foundation/feature.ts +9 -1
- package/src/billing-foundation/get-subscription-for-tenant.ts +16 -5
- package/src/billing-foundation/handlers/create-portal-session.write.ts +17 -1
- package/src/billing-foundation/handlers/list-subscriptions.query.ts +15 -1
- package/src/billing-foundation/handlers/process-event.write.ts +39 -3
- package/src/billing-foundation/tenant-destroy-hook.ts +46 -0
- package/src/cap-counter/__tests__/cap-list.boot.test.ts +27 -0
- package/src/cap-counter/__tests__/cap-list.security.integration.test.ts +53 -0
- package/src/cap-counter/__tests__/entity-ddl.test.ts +15 -0
- package/src/cap-counter/__tests__/with-cap-enforcement.integration.test.ts +8 -5
- package/src/cap-counter/constants.ts +2 -0
- package/src/cap-counter/enforce-cap.ts +15 -18
- package/src/cap-counter/entity.ts +3 -3
- package/src/cap-counter/feature.ts +28 -1
- package/src/cap-counter/i18n.ts +12 -0
- package/src/cap-counter/with-cap-enforcement.ts +2 -2
- package/src/channel-email/__tests__/pii-guard.test.ts +56 -0
- package/src/channel-email/email-channel.ts +2 -1
- package/src/channel-email/index.ts +1 -0
- package/src/channel-email/pii-guard.ts +39 -0
- package/src/compliance-profiles/__tests__/profile-picker.boot.test.ts +28 -0
- package/src/compliance-profiles/__tests__/profile-picker.security.integration.test.ts +91 -0
- package/src/compliance-profiles/constants.ts +15 -0
- package/src/compliance-profiles/feature.ts +22 -1
- package/src/compliance-profiles/handlers/set-profile.write.ts +2 -2
- package/src/compliance-profiles/i18n.ts +6 -0
- package/src/compliance-profiles/index.ts +6 -0
- package/src/compliance-profiles/web/client-plugin.tsx +22 -0
- package/src/compliance-profiles/web/compliance-profile-screen.tsx +152 -0
- package/src/compliance-profiles/web/i18n.ts +25 -0
- package/src/compliance-profiles/web/index.ts +6 -0
- package/src/config/__tests__/app-overrides.test.ts +11 -0
- package/src/config/__tests__/backing-secrets.integration.test.ts +22 -0
- package/src/config/__tests__/cascade.integration.test.ts +35 -0
- package/src/config/__tests__/config.integration.test.ts +71 -10
- package/src/config/__tests__/deserialize-value.test.ts +5 -8
- package/src/config/__tests__/inherited-redaction.integration.test.ts +4 -3
- package/src/config/__tests__/pii-encrypted.integration.test.ts +179 -0
- package/src/config/__tests__/reencrypt-job-concurrent-write-race.integration.test.ts +234 -0
- package/src/config/__tests__/reencrypt-job-kek-rotation.integration.test.ts +243 -0
- package/src/config/__tests__/write-helpers.test.ts +23 -0
- package/src/config/db/queries/resolver.ts +3 -3
- package/src/config/feature.ts +15 -8
- package/src/config/handlers/cascade.query.ts +2 -1
- package/src/config/handlers/reencrypt.job.ts +209 -0
- package/src/config/handlers/set.write.ts +55 -3
- package/src/config/handlers/values.query.ts +2 -1
- package/src/config/i18n.ts +9 -0
- package/src/config/index.ts +5 -4
- package/src/config/resolver.ts +75 -61
- package/src/config/table.ts +1 -1
- package/src/config/write-helpers.ts +22 -0
- package/src/crypto-shredding/__tests__/forget-subject.integration.test.ts +148 -0
- package/src/crypto-shredding/constants.ts +5 -0
- package/src/crypto-shredding/feature.ts +18 -0
- package/src/crypto-shredding/handlers/forget-subject.write.ts +91 -0
- package/src/crypto-shredding/index.ts +11 -0
- package/src/custom-fields/__tests__/audit-integration.integration.test.ts +1 -1
- package/src/custom-fields/__tests__/custom-fields.integration.test.ts +92 -53
- package/src/custom-fields/__tests__/field-access.integration.test.ts +25 -0
- package/src/custom-fields/__tests__/parse-serialized-field.test.ts +11 -3
- package/src/custom-fields/__tests__/retention.integration.test.ts +40 -0
- package/src/custom-fields/__tests__/user-data-rights.integration.test.ts +26 -79
- package/src/custom-fields/constants.ts +1 -0
- package/src/custom-fields/db/queries/user-data-rights.ts +0 -20
- package/src/custom-fields/entity.ts +1 -1
- package/src/custom-fields/events.ts +4 -3
- package/src/custom-fields/feature.ts +8 -0
- package/src/custom-fields/handlers/delete-system-field.write.ts +2 -1
- package/src/custom-fields/handlers/delete-tenant-field.write.ts +2 -1
- package/src/custom-fields/handlers/set-custom-field.write.ts +3 -44
- package/src/custom-fields/handlers/update-tenant-field.write.ts +0 -17
- package/src/custom-fields/lib/field-access.ts +8 -1
- package/src/custom-fields/lib/parse-serialized-field.ts +12 -4
- package/src/custom-fields/lib/value-schema.ts +1 -1
- package/src/custom-fields/run-retention.ts +9 -1
- package/src/custom-fields/schemas.ts +9 -9
- package/src/custom-fields/wire-for-entity.ts +110 -78
- package/src/custom-fields/wire-user-data-rights.ts +5 -54
- package/src/data-retention/__tests__/parse-override.test.ts +14 -13
- package/src/data-retention/__tests__/retention-cleanup.integration.test.ts +145 -8
- package/src/data-retention/feature.ts +3 -3
- package/src/data-retention/run-retention-cleanup.ts +102 -19
- package/src/delivery/__tests__/delivery-pii-kms.integration.test.ts +171 -0
- package/src/delivery/__tests__/delivery-screens.boot.test.ts +27 -0
- package/src/delivery/__tests__/delivery-security.integration.test.ts +106 -0
- package/src/delivery/constants.ts +13 -51
- package/src/delivery/feature.ts +29 -5
- package/src/delivery/handlers/log.query.ts +28 -7
- package/src/delivery/i18n.ts +6 -0
- package/src/delivery/index.ts +6 -1
- package/src/delivery/public-names.ts +36 -0
- package/src/delivery/tables.ts +3 -3
- package/src/delivery/web/client-plugin.tsx +20 -0
- package/src/delivery/web/delivery-log-screen.tsx +103 -0
- package/src/delivery/web/i18n.ts +25 -0
- package/src/delivery/web/index.ts +3 -0
- package/src/document-ingest-foundation/__tests__/feature.integration.test.ts +147 -0
- package/src/document-ingest-foundation/__tests__/feature.test.ts +105 -0
- package/src/document-ingest-foundation/__tests__/pages.test.ts +37 -0
- package/src/document-ingest-foundation/entity.ts +43 -0
- package/src/document-ingest-foundation/events.ts +25 -0
- package/src/document-ingest-foundation/feature.ts +126 -0
- package/src/document-ingest-foundation/index.ts +12 -0
- package/src/document-ingest-foundation/pages.ts +34 -0
- package/src/feature-toggles/__tests__/compose-tier-resolver.test.ts +304 -0
- package/src/feature-toggles/__tests__/feature-toggles.integration.test.ts +7 -6
- package/src/feature-toggles/__tests__/toggle-admin.boot.test.ts +31 -0
- package/src/feature-toggles/__tests__/toggle-admin.security.integration.test.ts +69 -0
- package/src/feature-toggles/compose-tier-resolver.ts +134 -0
- package/src/feature-toggles/constants.ts +14 -0
- package/src/feature-toggles/feature.ts +23 -3
- package/src/feature-toggles/global-feature-state-table.ts +20 -1
- package/src/feature-toggles/handlers/list.query.ts +1 -1
- package/src/feature-toggles/handlers/registered.query.ts +1 -1
- package/src/feature-toggles/i18n.ts +6 -0
- package/src/feature-toggles/index.ts +8 -0
- package/src/feature-toggles/toggle-runtime.ts +14 -0
- package/src/feature-toggles/web/client-plugin.tsx +22 -0
- package/src/feature-toggles/web/i18n.ts +29 -0
- package/src/feature-toggles/web/index.ts +6 -0
- package/src/feature-toggles/web/toggle-admin-screen.tsx +136 -0
- package/src/file-foundation/__tests__/feature.test.ts +5 -5
- package/src/file-foundation/__tests__/file-foundation.integration.test.ts +4 -3
- package/src/file-foundation/feature.ts +10 -11
- package/src/files-provider-s3/__tests__/s3-provider.integration.test.ts +27 -2
- package/src/files-provider-s3/s3-provider.ts +12 -1
- package/src/folders/__tests__/folders.integration.test.ts +3 -0
- package/src/folders/web/__tests__/folder-manager.test.tsx +43 -2
- package/src/folders/web/__tests__/tree.test.ts +8 -0
- package/src/folders/web/folder-manager.tsx +2 -3
- package/src/folders/web/i18n.ts +0 -2
- package/src/folders/web/tree.ts +13 -2
- package/src/folders-user-data/__tests__/hooks.integration.test.ts +1 -0
- package/src/folders-user-data/index.ts +4 -4
- package/src/inbound-mail-foundation/__tests__/account-state.test.ts +30 -0
- package/src/inbound-mail-foundation/__tests__/connect-routes.integration.test.ts +307 -0
- package/src/inbound-mail-foundation/__tests__/feature.test.ts +169 -0
- package/src/inbound-mail-foundation/__tests__/inbound-mail-foundation.integration.test.ts +644 -0
- package/src/inbound-mail-foundation/__tests__/inbound-mail-provider-contract.ts +111 -0
- package/src/inbound-mail-foundation/__tests__/retention.integration.test.ts +237 -0
- package/src/inbound-mail-foundation/__tests__/watch-supervisor.integration.test.ts +433 -0
- package/src/inbound-mail-foundation/aggregate-id.ts +40 -0
- package/src/inbound-mail-foundation/connect-routes.ts +230 -0
- package/src/inbound-mail-foundation/constants.ts +80 -0
- package/src/inbound-mail-foundation/db/queries/inbound-projections.ts +38 -0
- package/src/inbound-mail-foundation/entities.ts +172 -0
- package/src/inbound-mail-foundation/events.ts +147 -0
- package/src/inbound-mail-foundation/feature.ts +243 -0
- package/src/inbound-mail-foundation/handlers/account-state.ts +23 -0
- package/src/inbound-mail-foundation/handlers/connect-account.write.ts +112 -0
- package/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +69 -0
- package/src/inbound-mail-foundation/handlers/ingest-message.write.ts +333 -0
- package/src/inbound-mail-foundation/handlers/list-accounts.query.ts +44 -0
- package/src/inbound-mail-foundation/handlers/list-messages.query.ts +58 -0
- package/src/inbound-mail-foundation/handlers/scope-visibility.ts +14 -0
- package/src/inbound-mail-foundation/handlers/update-account.write.ts +78 -0
- package/src/inbound-mail-foundation/index.ts +97 -0
- package/src/inbound-mail-foundation/oauth-state.ts +103 -0
- package/src/inbound-mail-foundation/projection.ts +152 -0
- package/src/inbound-mail-foundation/provider-factory.ts +53 -0
- package/src/inbound-mail-foundation/retention-sweep.ts +168 -0
- package/src/inbound-mail-foundation/tenant-destroy-hook.ts +93 -0
- package/src/inbound-mail-foundation/types.ts +250 -0
- package/src/inbound-mail-foundation/watch-supervisor.ts +482 -0
- package/src/inbound-provider-imap/__tests__/feature.test.ts +115 -0
- package/src/inbound-provider-imap/__tests__/imap-foundation.integration.test.ts +302 -0
- package/src/inbound-provider-imap/__tests__/imap-live.integration.test.ts +206 -0
- package/src/inbound-provider-imap/__tests__/plugin-mocked.test.ts +358 -0
- package/src/inbound-provider-imap/credential-document.ts +51 -0
- package/src/inbound-provider-imap/feature.ts +283 -0
- package/src/inbound-provider-imap/imap-client.ts +158 -0
- package/src/inbound-provider-imap/index.ts +17 -0
- package/src/inbound-provider-inmemory/__tests__/feature.test.ts +52 -0
- package/src/inbound-provider-inmemory/feature.ts +154 -0
- package/src/inbound-provider-inmemory/index.ts +12 -0
- package/src/jobs/__tests__/entity-ddl.test.ts +15 -0
- package/src/jobs/__tests__/job-instant.test.ts +15 -0
- package/src/jobs/__tests__/job-system-user.integration.test.ts +4 -8
- package/src/jobs/__tests__/jobs-events.integration.test.ts +37 -1
- package/src/jobs/__tests__/jobs-pii-kms.integration.test.ts +120 -0
- package/src/jobs/__tests__/jobs-screens.boot.test.ts +36 -0
- package/src/jobs/__tests__/jobs-security.integration.test.ts +108 -0
- package/src/jobs/__tests__/reindex-entity-job.integration.test.ts +169 -0
- package/src/jobs/constants.ts +8 -1
- package/src/jobs/feature.ts +52 -8
- package/src/jobs/handlers/detail.query.ts +6 -0
- package/src/jobs/handlers/list.query.ts +8 -1
- package/src/jobs/handlers/projection-rebuild.job.ts +14 -4
- package/src/jobs/handlers/reindex-entity.job.ts +49 -0
- package/src/jobs/i18n.ts +37 -0
- package/src/jobs/job-instant.ts +9 -0
- package/src/jobs/job-run-table.ts +4 -4
- package/src/jobs/web/client-plugin.tsx +22 -0
- package/src/jobs/web/i18n.ts +12 -0
- package/src/jobs/web/index.ts +4 -0
- package/src/jobs/web/job-run-detail-screen.tsx +180 -0
- package/src/jobs/web/job-runs-screen.tsx +146 -0
- package/src/ledger/__tests__/ledger.integration.test.ts +8 -3
- package/src/ledger/handlers/confirm-schedule-period.write.ts +17 -4
- package/src/ledger/recurring.ts +17 -8
- package/src/ledger/web/index.ts +3 -2
- package/src/legal-pages/__tests__/legal-pages.integration.test.ts +55 -0
- package/src/legal-pages/feature.ts +24 -31
- package/src/legal-pages/index.ts +1 -0
- package/src/mail-foundation/__tests__/feature.test.ts +5 -6
- package/src/mail-foundation/__tests__/mail-foundation.integration.test.ts +4 -3
- package/src/mail-foundation/__tests__/mail-transport-contract.ts +58 -0
- package/src/mail-foundation/feature.ts +26 -24
- package/src/mail-transport-inmemory/__tests__/feature.test.ts +23 -0
- package/src/mail-transport-smtp/__tests__/feature.test.ts +65 -0
- package/src/managed-pages/__tests__/branding-coerce.test.ts +2 -6
- package/src/managed-pages/__tests__/i18n-surface.boot.test.ts +30 -0
- package/src/managed-pages/__tests__/managed-pages.integration.test.ts +137 -0
- package/src/managed-pages/feature.ts +45 -47
- package/src/managed-pages/handlers/branding.query.ts +5 -1
- package/src/managed-pages/handlers/by-tenant-published.query.ts +52 -0
- package/src/managed-pages/i18n.ts +66 -0
- package/src/managed-pages/screens/page-screens.ts +5 -1
- package/src/managed-pages/web/client-plugin.tsx +22 -0
- package/src/managed-pages/web/i18n.ts +24 -0
- package/src/managed-pages/web/index.ts +3 -0
- package/src/page-render/__tests__/layout.test.ts +63 -0
- package/src/page-render/__tests__/markdown.test.ts +29 -0
- package/src/page-render/branding.ts +4 -3
- package/src/page-render/index.ts +1 -1
- package/src/page-render/layout.ts +18 -3
- package/src/page-render/markdown.ts +1 -9
- package/src/page-render/web.ts +13 -0
- package/src/personal-access-tokens/__tests__/feature.test.ts +42 -0
- package/src/personal-access-tokens/__tests__/pat.integration.test.ts +239 -0
- package/src/personal-access-tokens/__tests__/scopes.test.ts +42 -0
- package/src/personal-access-tokens/constants.ts +33 -0
- package/src/personal-access-tokens/feature.ts +105 -0
- package/src/personal-access-tokens/handlers/available-scopes.query.ts +21 -0
- package/src/personal-access-tokens/handlers/create.write.ts +47 -0
- package/src/personal-access-tokens/handlers/list.query.ts +50 -0
- package/src/personal-access-tokens/handlers/revoke.write.ts +37 -0
- package/src/personal-access-tokens/hash.ts +17 -0
- package/src/personal-access-tokens/i18n.ts +5 -0
- package/src/personal-access-tokens/index.ts +31 -0
- package/src/personal-access-tokens/resolver.ts +58 -0
- package/src/personal-access-tokens/roles.ts +29 -0
- package/src/personal-access-tokens/schema/api-token.ts +63 -0
- package/src/personal-access-tokens/scopes.ts +43 -0
- package/src/personal-access-tokens/web/client-plugin.tsx +29 -0
- package/src/personal-access-tokens/web/i18n.ts +69 -0
- package/src/personal-access-tokens/web/index.ts +7 -0
- package/src/personal-access-tokens/web/pat-tokens-screen.tsx +265 -0
- package/src/readiness/__tests__/readiness.integration.test.ts +4 -3
- package/src/renderer-simple/__tests__/resolve-variables.test.ts +37 -0
- package/src/renderer-simple/resolve-variables.ts +3 -1
- package/src/secrets/__tests__/rotate.integration.test.ts +115 -18
- package/src/secrets/handlers/rotate.job.ts +77 -98
- package/src/secrets/secrets-context.ts +7 -38
- package/src/secrets/table.ts +7 -8
- package/src/seo/__tests__/llms-txt.test.ts +38 -0
- package/src/seo/__tests__/robots-txt.test.ts +18 -0
- package/src/seo/__tests__/schema-builders.test.ts +57 -0
- package/src/seo/__tests__/seo.integration.test.ts +350 -0
- package/src/seo/__tests__/sitemap.test.ts +38 -0
- package/src/seo/constants.ts +60 -0
- package/src/seo/feature.ts +316 -0
- package/src/seo/handlers/seo-config.query.ts +35 -0
- package/src/seo/index.ts +19 -0
- package/src/seo/llms-txt.ts +33 -0
- package/src/seo/robots-txt.ts +13 -0
- package/src/seo/schema-builders.ts +57 -0
- package/src/seo/sitemap.ts +33 -0
- package/src/sessions/__tests__/auto-revoke-binding.test.ts +71 -0
- package/src/sessions/__tests__/cleanup.integration.test.ts +2 -1
- package/src/sessions/__tests__/password-auto-revoke.integration.test.ts +11 -7
- package/src/sessions/__tests__/rebuild-survival.integration.test.ts +18 -7
- package/src/sessions/__tests__/session-store-registration.test.ts +21 -0
- package/src/sessions/__tests__/sessions-screens.boot.test.ts +55 -0
- package/src/sessions/__tests__/sessions.integration.test.ts +272 -20
- package/src/sessions/__tests__/test-helpers.ts +16 -3
- package/src/sessions/constants.ts +5 -0
- package/src/sessions/db/queries/cleanup.ts +2 -2
- package/src/sessions/feature.ts +175 -26
- package/src/sessions/handlers/detail.query.ts +36 -0
- package/src/sessions/handlers/list.query.ts +14 -9
- package/src/sessions/handlers/mine.query.ts +13 -8
- package/src/sessions/handlers/revoke-all-for-user.write.ts +51 -12
- package/src/sessions/handlers/revoke-all-others.write.ts +19 -1
- package/src/sessions/handlers/revoke.write.ts +13 -0
- package/src/sessions/i18n.ts +23 -0
- package/src/sessions/index.ts +9 -2
- package/src/sessions/schema/user-session.ts +8 -2
- package/src/sessions/session-callbacks.ts +99 -14
- package/src/sessions/session-checker-fail-open.test.ts +77 -0
- package/src/sessions/session-revoked-event.ts +29 -0
- package/src/sessions/testing.ts +14 -1
- package/src/shared/__tests__/chunked-entity-migration.test.ts +145 -0
- package/src/shared/chunked-entity-migration.ts +89 -0
- package/src/shared/decrypt-stored-pii.ts +26 -0
- package/src/shared/encrypt-for-direct-write.ts +23 -0
- package/src/shared/entities-of.ts +17 -0
- package/src/shared/grace-period.test.ts +26 -0
- package/src/shared/grace-period.ts +20 -0
- package/src/{auth-email-password/__tests__ → shared}/identity-v3-hash.test.ts +2 -2
- package/src/shared/index.ts +16 -0
- package/src/shared/map-with-concurrency.test.ts +104 -0
- package/src/shared/map-with-concurrency.ts +38 -0
- package/src/shared/password-hashing.test.ts +35 -0
- package/src/{auth-email-password → shared}/password-hashing.ts +11 -0
- package/src/shared/system-query.ts +5 -0
- package/src/{auth-email-password → shared}/token-burn-store.ts +1 -1
- package/src/subscription-mollie/__tests__/feature.test.ts +97 -0
- package/src/subscription-mollie/__tests__/mollie-foundation.integration.test.ts +12 -1
- package/src/subscription-mollie/__tests__/verify-webhook.test.ts +21 -0
- package/src/subscription-mollie/verify-webhook.ts +1 -0
- package/src/subscription-stripe/__tests__/feature.test.ts +127 -1
- package/src/subscription-stripe/__tests__/runtime.test.ts +14 -24
- package/src/subscription-stripe/__tests__/stripe-foundation.integration.test.ts +15 -5
- package/src/subscription-stripe/__tests__/verify-webhook.test.ts +20 -0
- package/src/subscription-stripe/feature.ts +2 -0
- package/src/subscription-stripe/verify-webhook.ts +3 -2
- package/src/tags/__tests__/schemas.test.ts +34 -0
- package/src/tags/__tests__/tags.integration.test.ts +12 -1
- package/src/tags/entity.ts +6 -1
- package/src/tags/feature.ts +2 -0
- package/src/tags/i18n.ts +5 -0
- package/src/tags/schemas.ts +9 -2
- package/src/tags/web/__tests__/tag-manager.test.tsx +333 -0
- package/src/tags/web/__tests__/tag-section.test.tsx +47 -1
- package/src/tags/web/tag-manager.tsx +1 -1
- package/src/tags/web/tag-section.tsx +6 -3
- package/src/template-resolver/testing.ts +2 -1
- package/src/tenant/__tests__/members-query-kms.integration.test.ts +98 -0
- package/src/tenant/__tests__/members-screens.boot.test.ts +51 -0
- package/src/tenant/__tests__/multi-tenant.integration.test.ts +1 -1
- package/src/tenant/__tests__/tenant-security.integration.test.ts +310 -0
- package/src/tenant/__tests__/tenant.integration.test.ts +8 -4
- package/src/tenant/constants.ts +5 -0
- package/src/tenant/feature.ts +19 -0
- package/src/tenant/handlers/active-tenant-ids.query.ts +8 -2
- package/src/tenant/handlers/cancel-invitation.write.ts +2 -2
- package/src/tenant/handlers/invitations.query.ts +21 -4
- package/src/tenant/handlers/members.query.ts +41 -6
- package/src/tenant/i18n.ts +28 -0
- package/src/tenant/index.ts +7 -2
- package/src/tenant/invitation-table.ts +3 -2
- package/src/tenant/schema/tenant.ts +27 -2
- package/src/tenant/screens.ts +3 -2
- package/src/tenant/seeding.ts +136 -3
- package/src/tenant/web/client-plugin.tsx +20 -0
- package/src/tenant/web/i18n.ts +43 -0
- package/src/tenant/web/index.ts +4 -0
- package/src/tenant/web/members-screen.tsx +270 -0
- package/src/tenant-lifecycle/__tests__/stages.test.ts +22 -0
- package/src/tenant-lifecycle/__tests__/tenant-lifecycle.integration.test.ts +471 -0
- package/src/tenant-lifecycle/boot-checks.ts +45 -0
- package/src/tenant-lifecycle/constants.ts +56 -0
- package/src/tenant-lifecycle/events.ts +46 -0
- package/src/tenant-lifecycle/feature.ts +112 -0
- package/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +79 -0
- package/src/tenant-lifecycle/handlers/request-destruction.write.ts +83 -0
- package/src/tenant-lifecycle/index.ts +7 -0
- package/src/tenant-lifecycle/lib/revoke-tenant-sessions.ts +16 -0
- package/src/tenant-lifecycle/lifecycle-gate.ts +65 -0
- package/src/tenant-lifecycle/run-tenant-destroy.ts +368 -0
- package/src/tenant-lifecycle/stages.ts +206 -0
- package/src/tenant-settings/__tests__/tenant-settings.integration.test.ts +115 -0
- package/src/tenant-settings/config.ts +44 -0
- package/src/tenant-settings/constants.ts +16 -0
- package/src/tenant-settings/feature.ts +49 -0
- package/src/tenant-settings/index.ts +8 -0
- package/src/tenant-settings/tenant-defaults.ts +67 -0
- package/src/text-content/web/__tests__/editor-read-only.test.tsx +33 -6
- package/src/text-content/web/__tests__/group-blocks.test.ts +28 -3
- package/src/text-content/web/client-plugin.tsx +126 -104
- package/src/tier-engine/__tests__/auto-default-tier.integration.test.ts +1 -1
- package/src/tier-engine/__tests__/resolver.integration.test.ts +1 -1
- package/src/tier-engine/__tests__/tier-engine.integration.test.ts +7 -4
- package/src/tier-engine/__tests__/trial-config-validation.test.ts +30 -0
- package/src/tier-engine/feature.ts +13 -6
- package/src/tier-engine/i18n.ts +8 -0
- package/src/tier-engine/web/tier-admin-screen.tsx +26 -19
- package/src/user/__tests__/stream-tenant-backfill.integration.test.ts +186 -0
- package/src/user/__tests__/user.integration.test.ts +26 -0
- package/src/user/constants.ts +1 -0
- package/src/user/db/queries/stream-tenant-backfill.ts +112 -0
- package/src/user/feature.ts +3 -0
- package/src/user/handlers/find-for-auth.query.ts +8 -6
- package/src/user/i18n.ts +20 -0
- package/src/user/index.ts +4 -0
- package/src/user/schema/user.ts +6 -2
- package/src/user/screens.ts +3 -15
- package/src/user/seeding.ts +8 -0
- package/src/user-data-rights/COMPLIANCE.md +21 -1
- package/src/user-data-rights/__tests__/anonymous-deletion-kms.integration.test.ts +145 -0
- package/src/user-data-rights/__tests__/anonymous-deletion.integration.test.ts +3 -0
- package/src/user-data-rights/__tests__/audit-log.integration.test.ts +67 -0
- package/src/user-data-rights/__tests__/boot-checks.test.ts +170 -0
- package/src/user-data-rights/__tests__/cross-data-matrix.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/download-by-token-resolver-trust.integration.test.ts +169 -0
- package/src/user-data-rights/__tests__/download.integration.test.ts +101 -33
- package/src/user-data-rights/__tests__/email-templates.test.ts +15 -0
- package/src/user-data-rights/__tests__/export-encrypted-fields.integration.test.ts +154 -0
- package/src/user-data-rights/__tests__/export-job-idempotency.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/export-job-list-cross-tenant.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/file-binary-forget-cleanup.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/file-binary-forget-failure.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/file-retention.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/file-storage-unification.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/forget-cleanup-hook-ordering.integration.test.ts +41 -62
- package/src/user-data-rights/__tests__/forget-hook-registry.integration.test.ts +110 -0
- package/src/user-data-rights/__tests__/inspector-screens.boot.test.ts +14 -0
- package/src/user-data-rights/__tests__/mail-default-bridge.integration.test.ts +19 -2
- package/src/user-data-rights/__tests__/read-users-rebuild-survives-lifecycle.integration.test.ts +25 -14
- package/src/user-data-rights/__tests__/request-cancel-deletion.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/request-deletion-callback.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/request-export.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/restriction-flow.integration.test.ts +172 -19
- package/src/user-data-rights/__tests__/run-export-jobs-cron-context.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/run-export-jobs.integration.test.ts +257 -26
- package/src/user-data-rights/__tests__/run-forget-cleanup.integration.test.ts +339 -5
- package/src/user-data-rights/__tests__/run-user-export.integration.test.ts +77 -1
- package/src/user-data-rights/__tests__/tenant-model-erasure.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/user-data-rights.integration.test.ts +9 -1
- package/src/user-data-rights/boot-checks.ts +70 -0
- package/src/user-data-rights/db/queries/__tests__/forget-cleanup.integration.test.ts +69 -0
- package/src/user-data-rights/db/queries/forget-cleanup.ts +9 -2
- package/src/user-data-rights/email-templates.ts +6 -6
- package/src/user-data-rights/feature.ts +162 -43
- package/src/user-data-rights/handlers/cancel-deletion.write.ts +8 -11
- package/src/user-data-rights/handlers/deletion-grace-period.ts +4 -1
- package/src/user-data-rights/handlers/download-by-job.query.ts +26 -18
- package/src/user-data-rights/handlers/download-by-token.query.ts +23 -15
- package/src/user-data-rights/handlers/lift-restriction.write.ts +44 -19
- package/src/user-data-rights/handlers/list-download-attempts.query.ts +1 -0
- package/src/user-data-rights/handlers/my-audit-log.query.ts +1 -0
- package/src/user-data-rights/handlers/request-deletion-by-email.write.ts +3 -1
- package/src/user-data-rights/handlers/restrict-account.write.ts +58 -7
- package/src/user-data-rights/handlers/run-forget-cleanup.write.ts +2 -0
- package/src/user-data-rights/i18n.ts +35 -32
- package/src/user-data-rights/lib/is-admin-actor.ts +19 -0
- package/src/user-data-rights/lib/storage-provider-resolver.ts +6 -5
- package/src/user-data-rights/lib/update-user-lifecycle.ts +8 -24
- package/src/user-data-rights/run-export-jobs.ts +55 -19
- package/src/user-data-rights/run-forget-cleanup.ts +112 -7
- package/src/user-data-rights/run-user-export.ts +70 -4
- package/src/user-data-rights/schema/download-token.ts +5 -3
- package/src/user-data-rights/schema/export-job.ts +2 -0
- package/src/user-data-rights/screens.ts +2 -1
- package/src/user-data-rights/web/__tests__/privacy-center-screen.test.tsx +1 -0
- package/src/user-data-rights/web/__tests__/public-deletion-gate.test.tsx +7 -1
- package/src/user-data-rights/web/client-plugin.tsx +6 -1
- package/src/user-data-rights/web/i18n.ts +30 -10
- package/src/user-data-rights/web/privacy-center-screen.tsx +5 -3
- package/src/user-data-rights-defaults/__tests__/bundled-entities.userdata-hooks.integration.test.ts +455 -0
- package/src/user-data-rights-defaults/__tests__/feature-mounted.test.ts +24 -0
- package/src/user-data-rights-defaults/__tests__/user-data-rights-defaults.integration.test.ts +33 -9
- package/src/user-data-rights-defaults/feature.ts +78 -1
- package/src/user-data-rights-defaults/hooks/api-token.userdata-hook.ts +37 -0
- package/src/user-data-rights-defaults/hooks/config-value.userdata-hook.ts +51 -0
- package/src/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.ts +37 -0
- package/src/user-data-rights-defaults/hooks/feature-mounted.ts +10 -0
- package/src/user-data-rights-defaults/hooks/in-app-message.userdata-hook.ts +38 -0
- package/src/user-data-rights-defaults/hooks/job-run.userdata-hook.ts +35 -0
- package/src/user-data-rights-defaults/hooks/notification-preference.userdata-hook.ts +54 -0
- package/src/user-data-rights-defaults/hooks/tenant-invitation.userdata-hook.ts +112 -0
- package/src/user-data-rights-defaults/hooks/user-session.userdata-hook.ts +42 -0
- package/src/user-data-rights-defaults/index.ts +26 -0
- package/src/user-profile/__tests__/change-email.integration.test.ts +7 -1
- package/src/user-profile/__tests__/profile-screen.test.tsx +5 -0
- package/src/user-profile/feature.ts +4 -1
- package/src/user-profile/handlers/change-email.write.ts +15 -10
- package/src/user-profile/web/profile-screen.tsx +15 -3
- package/src/auth-email-password/stream-tenant.ts +0 -31
- package/src/feature-toggles/db/queries/toggle-state.ts +0 -25
- package/src/legal-pages/security-headers.ts +0 -1
- package/src/sessions/schema/index.ts +0 -5
- package/src/tenant/schema/index.ts +0 -5
- package/src/user/schema/index.ts +0 -5
- /package/src/{auth-email-password → shared}/identity-v3-hash.ts +0 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { defineFeature, type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { MIN_HMAC_SECRET_LENGTH } from "./constants";
|
|
3
4
|
import type { AuthMailLocale } from "./email-templates";
|
|
4
5
|
import { changePasswordWrite } from "./handlers/change-password.write";
|
|
6
|
+
import { createConfirmAccountUnlockHandler } from "./handlers/confirm-account-unlock.write";
|
|
5
7
|
import { createInviteAcceptHandler } from "./handlers/invite-accept.write";
|
|
6
8
|
import { createInviteAcceptWithLoginHandler } from "./handlers/invite-accept-with-login.write";
|
|
7
9
|
import {
|
|
@@ -9,11 +11,16 @@ import {
|
|
|
9
11
|
type InviteCreateOptions,
|
|
10
12
|
} from "./handlers/invite-create.write";
|
|
11
13
|
import { createInviteSignupCompleteHandler } from "./handlers/invite-signup-complete.write";
|
|
12
|
-
import { createLoginHandler } from "./handlers/login.write";
|
|
14
|
+
import { createLoginHandler, type LoginHandlerOptions } from "./handlers/login.write";
|
|
13
15
|
import { logoutWrite } from "./handlers/logout.write";
|
|
16
|
+
import {
|
|
17
|
+
createRequestAccountUnlockHandler,
|
|
18
|
+
type RequestAccountUnlockOptions,
|
|
19
|
+
} from "./handlers/request-account-unlock.write";
|
|
14
20
|
import { createRequestEmailVerificationHandler } from "./handlers/request-email-verification.write";
|
|
15
21
|
import { createRequestPasswordResetHandler } from "./handlers/request-password-reset.write";
|
|
16
22
|
import { createResetPasswordHandler } from "./handlers/reset-password.write";
|
|
23
|
+
import { selfRegistrationStatusQuery } from "./handlers/self-registration-status.query";
|
|
17
24
|
import { createSignupConfirmHandler } from "./handlers/signup-confirm.write";
|
|
18
25
|
import {
|
|
19
26
|
createSignupRequestHandler,
|
|
@@ -108,12 +115,26 @@ export type SignupOptions = SignupRequestOptions;
|
|
|
108
115
|
// 7 Tage (industry standard).
|
|
109
116
|
export type InviteOptions = InviteCreateOptions;
|
|
110
117
|
|
|
118
|
+
// Account-Unlock Magic-Link (#1266). When set, registers the
|
|
119
|
+
// request-account-unlock + confirm-account-unlock handlers — the
|
|
120
|
+
// self-service escape hatch for the monotonic lockout counter (see
|
|
121
|
+
// lockout-store.ts). Only meaningful paired with `accountLockout`, but
|
|
122
|
+
// wired independently like passwordReset/emailVerification — no implicit
|
|
123
|
+
// auto-enable, so apps without lockout don't get an unasked-for new
|
|
124
|
+
// public endpoint.
|
|
125
|
+
export type AccountUnlockOptions = RequestAccountUnlockOptions;
|
|
126
|
+
|
|
111
127
|
export type AuthEmailPasswordOptions = {
|
|
128
|
+
// Second-factor gate. auth-mfa (if mounted) wires this in via
|
|
129
|
+
// mfaStatusCheckerFromFeature() at app-composition time — see
|
|
130
|
+
// LoginHandlerOptions.mfaStatusChecker for the contract it must satisfy.
|
|
131
|
+
readonly mfaStatusChecker?: LoginHandlerOptions["mfaStatusChecker"];
|
|
112
132
|
readonly passwordReset?: PasswordResetOptions;
|
|
113
133
|
readonly emailVerification?: EmailVerificationOptions;
|
|
114
134
|
readonly accountLockout?: AccountLockoutOptions;
|
|
115
135
|
readonly signup?: SignupOptions;
|
|
116
136
|
readonly invite?: InviteOptions;
|
|
137
|
+
readonly accountUnlock?: AccountUnlockOptions;
|
|
117
138
|
};
|
|
118
139
|
|
|
119
140
|
// Auth feature — email+password login. Depends on the user feature for
|
|
@@ -122,14 +143,22 @@ export type AuthEmailPasswordOptions = {
|
|
|
122
143
|
export function createAuthEmailPasswordFeature(
|
|
123
144
|
opts: AuthEmailPasswordOptions = {},
|
|
124
145
|
): FeatureDefinition {
|
|
125
|
-
|
|
146
|
+
// ≥32 chars, symmetric to the JWT_SECRET env check: a short HMAC secret
|
|
147
|
+
// makes reset/verify tokens forgeable — that's account takeover, so the
|
|
148
|
+
// factory fails fast instead of trusting the caller.
|
|
149
|
+
if (opts.passwordReset && opts.passwordReset.hmacSecret.length < MIN_HMAC_SECRET_LENGTH) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
`[auth-email-password] passwordReset.hmacSecret must be ≥${MIN_HMAC_SECRET_LENGTH} chars (HMAC-SHA256 token signing)`,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
if (opts.emailVerification && opts.emailVerification.hmacSecret.length < MIN_HMAC_SECRET_LENGTH) {
|
|
126
155
|
throw new Error(
|
|
127
|
-
|
|
156
|
+
`[auth-email-password] emailVerification.hmacSecret must be ≥${MIN_HMAC_SECRET_LENGTH} chars (HMAC-SHA256 token signing)`,
|
|
128
157
|
);
|
|
129
158
|
}
|
|
130
|
-
if (opts.
|
|
159
|
+
if (opts.accountUnlock && opts.accountUnlock.hmacSecret.length < MIN_HMAC_SECRET_LENGTH) {
|
|
131
160
|
throw new Error(
|
|
132
|
-
|
|
161
|
+
`[auth-email-password] accountUnlock.hmacSecret must be ≥${MIN_HMAC_SECRET_LENGTH} chars (HMAC-SHA256 token signing)`,
|
|
133
162
|
);
|
|
134
163
|
}
|
|
135
164
|
|
|
@@ -138,7 +167,7 @@ export function createAuthEmailPasswordFeature(
|
|
|
138
167
|
|
|
139
168
|
return defineFeature("auth-email-password", (r) => {
|
|
140
169
|
r.describe(
|
|
141
|
-
"Provides email+password authentication: the always-on handlers are `login`, `changePassword`, and `logout`; optional flows
|
|
170
|
+
"Provides email+password authentication: the always-on handlers are `login`, `changePassword`, and `logout`; optional flows — password reset, email verification, magic-link self-signup, tenant invite, and account-unlock — are registered only when you pass their respective option objects (`passwordReset`, `emailVerification`, `signup`, `invite`, `accountUnlock`) to `createAuthEmailPasswordFeature(opts)`. All five magic-link flows dispatch their mail through the `delivery` feature via `ctx.notify`, so mounting any of them additionally requires `delivery`. Tokens are HMAC-signed (reset/verify/unlock) or opaque-random in Redis (signup/invite). `accountUnlock` is the self-service escape hatch for `accountLockout`'s monotonic failure-counter (#1266): confirming the mailed token clears the Redis lockout state without touching the user entity. Requires the `user` and `tenant` features, and declares `JWT_SECRET` (≥ 32 chars) in `authEmailPasswordEnvSchema` so a missing secret surfaces at boot validation rather than on the first login attempt.",
|
|
142
171
|
);
|
|
143
172
|
r.uiHints({
|
|
144
173
|
displayLabel: "Auth \u00b7 Email + Password",
|
|
@@ -154,14 +183,21 @@ export function createAuthEmailPasswordFeature(
|
|
|
154
183
|
},
|
|
155
184
|
{ key: "signup", label: "Self-Signup-Flow", type: "boolean", default: false },
|
|
156
185
|
{ key: "invite", label: "Tenant-Invite-Flow", type: "boolean", default: false },
|
|
186
|
+
{ key: "accountUnlock", label: "Account-Unlock-Flow", type: "boolean", default: false },
|
|
157
187
|
],
|
|
158
188
|
});
|
|
159
189
|
r.requires("user");
|
|
160
190
|
r.requires("tenant");
|
|
161
|
-
// All
|
|
162
|
-
// ctx.notify → delivery must be mounted. Fail closed at boot instead
|
|
163
|
-
// silently dropping the mail.
|
|
164
|
-
if (
|
|
191
|
+
// All five magic-link flows (reset/verify/signup/invite/unlock) dispatch
|
|
192
|
+
// via ctx.notify → delivery must be mounted. Fail closed at boot instead
|
|
193
|
+
// of silently dropping the mail.
|
|
194
|
+
if (
|
|
195
|
+
opts.passwordReset ||
|
|
196
|
+
opts.emailVerification ||
|
|
197
|
+
opts.signup ||
|
|
198
|
+
opts.invite ||
|
|
199
|
+
opts.accountUnlock
|
|
200
|
+
) {
|
|
165
201
|
r.requires("delivery");
|
|
166
202
|
}
|
|
167
203
|
r.envSchema(authEmailPasswordEnvSchema);
|
|
@@ -171,6 +207,7 @@ export function createAuthEmailPasswordFeature(
|
|
|
171
207
|
createLoginHandler({
|
|
172
208
|
strictEmailVerification: strictVerification,
|
|
173
209
|
accountLockout: opts.accountLockout,
|
|
210
|
+
mfaStatusChecker: opts.mfaStatusChecker,
|
|
174
211
|
}),
|
|
175
212
|
),
|
|
176
213
|
changePassword: r.writeHandler(changePasswordWrite),
|
|
@@ -187,6 +224,12 @@ export function createAuthEmailPasswordFeature(
|
|
|
187
224
|
r.writeHandler(createVerifyEmailHandler(opts.emailVerification));
|
|
188
225
|
}
|
|
189
226
|
|
|
227
|
+
const queries = {
|
|
228
|
+
...(opts.signup && {
|
|
229
|
+
signupRegistrationStatus: r.queryHandler(selfRegistrationStatusQuery),
|
|
230
|
+
}),
|
|
231
|
+
};
|
|
232
|
+
|
|
190
233
|
if (opts.signup) {
|
|
191
234
|
r.writeHandler(createSignupRequestHandler(opts.signup));
|
|
192
235
|
r.writeHandler(createSignupConfirmHandler());
|
|
@@ -199,6 +242,11 @@ export function createAuthEmailPasswordFeature(
|
|
|
199
242
|
r.writeHandler(createInviteSignupCompleteHandler());
|
|
200
243
|
}
|
|
201
244
|
|
|
202
|
-
|
|
245
|
+
if (opts.accountUnlock) {
|
|
246
|
+
r.writeHandler(createRequestAccountUnlockHandler(opts.accountUnlock));
|
|
247
|
+
r.writeHandler(createConfirmAccountUnlockHandler(opts.accountUnlock));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return { handlers, queries };
|
|
203
251
|
});
|
|
204
252
|
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
access,
|
|
3
|
+
createSystemUser,
|
|
4
|
+
defineWriteHandler,
|
|
5
|
+
SYSTEM_TENANT_ID,
|
|
6
|
+
} from "@cosmicdrift/kumiko-framework/engine";
|
|
3
7
|
import { z } from "zod";
|
|
4
|
-
import {
|
|
8
|
+
import { hashPassword, verifyPassword } from "../../shared";
|
|
9
|
+
import { UserHandlers, UserQueries } from "../../user";
|
|
5
10
|
import { invalidCredentials } from "../errors";
|
|
6
|
-
import {
|
|
11
|
+
import { passwordSchema } from "../password-policy";
|
|
7
12
|
|
|
8
13
|
// Change-password — authenticated. The user supplies their current password
|
|
9
14
|
// (re-auth) and the new one. The new hash is written via ctx.writeAs(system)
|
|
@@ -13,7 +18,7 @@ export const changePasswordWrite = defineWriteHandler({
|
|
|
13
18
|
name: "change-password",
|
|
14
19
|
schema: z.object({
|
|
15
20
|
oldPassword: z.string().min(1),
|
|
16
|
-
newPassword:
|
|
21
|
+
newPassword: passwordSchema,
|
|
17
22
|
}),
|
|
18
23
|
access: { roles: access.authenticated },
|
|
19
24
|
handler: async (event, ctx) => {
|
|
@@ -35,14 +40,10 @@ export const changePasswordWrite = defineWriteHandler({
|
|
|
35
40
|
|
|
36
41
|
const newHash = await hashPassword(event.payload.newPassword);
|
|
37
42
|
|
|
38
|
-
// The user aggregate is
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
// of version_conflict-ing against an empty one. Falls back to the session
|
|
43
|
-
// tenant when the lookup finds nothing (fresh/unknown stream).
|
|
44
|
-
const streamTenant = await getAggregateStreamTenant(ctx.db.raw, event.user.id, USER_FEATURE);
|
|
45
|
-
const writer = createSystemUser(streamTenant ?? event.user.tenantId);
|
|
43
|
+
// The user aggregate is systemStream (#497): its event stream lives on
|
|
44
|
+
// SYSTEM_TENANT_ID deterministically. Pre-#497 scattered streams need the
|
|
45
|
+
// one-time backfillUserStreamTenants migration (#762).
|
|
46
|
+
const writer = createSystemUser(SYSTEM_TENANT_ID);
|
|
46
47
|
|
|
47
48
|
// Apply via user feature's update handler — writeAs(system) satisfies
|
|
48
49
|
// the privileged-only write rule on passwordHash. Pass the current version
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import {
|
|
3
|
+
InternalError,
|
|
4
|
+
UnprocessableError,
|
|
5
|
+
writeFailure,
|
|
6
|
+
} from "@cosmicdrift/kumiko-framework/errors";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { AuthErrors } from "../constants";
|
|
9
|
+
import { invalidUnlockToken } from "../errors";
|
|
10
|
+
import { clearLockoutState } from "../lockout-store";
|
|
11
|
+
import { verifyUnlockToken } from "../unlock-token";
|
|
12
|
+
|
|
13
|
+
export type ConfirmAccountUnlockOptions = {
|
|
14
|
+
readonly hmacSecret: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Confirm step of the unlock flow. Unlike reset/verify, there's no entity
|
|
18
|
+
// write: the token's HMAC signature already proves it was minted for an
|
|
19
|
+
// existing, non-deleted user (see request-account-unlock.write.ts's
|
|
20
|
+
// findForAuth + extraSilentSkip), so there's no user to (re-)load or
|
|
21
|
+
// validate here. Clearing the Redis lockout state is naturally idempotent
|
|
22
|
+
// (DEL on an already-cleared/missing key is a no-op), so a replayed unlock
|
|
23
|
+
// link within its TTL just re-clears — harmless, unlike reset which sets a
|
|
24
|
+
// password. That's why this skips the single-use burn-store confirm-token-
|
|
25
|
+
// flow.ts needs to protect a state-changing write.
|
|
26
|
+
export function createConfirmAccountUnlockHandler(opts: ConfirmAccountUnlockOptions) {
|
|
27
|
+
return defineWriteHandler({
|
|
28
|
+
name: "confirm-account-unlock",
|
|
29
|
+
schema: z.object({
|
|
30
|
+
token: z.string().min(1),
|
|
31
|
+
}),
|
|
32
|
+
access: { roles: ["all"] },
|
|
33
|
+
handler: async (event, ctx) => {
|
|
34
|
+
if (!opts.hmacSecret) {
|
|
35
|
+
return writeFailure(
|
|
36
|
+
new UnprocessableError(AuthErrors.unlockNotConfigured, {
|
|
37
|
+
i18nKey: "auth.errors.unlockNotConfigured",
|
|
38
|
+
}),
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
if (!ctx.redis) {
|
|
42
|
+
return writeFailure(
|
|
43
|
+
new InternalError({
|
|
44
|
+
message: "account-unlock requires ctx.redis to clear lockout state",
|
|
45
|
+
}),
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// All verify failures (malformed / bad_signature / expired) fold into
|
|
50
|
+
// the same invalid_unlock_token error — a probing caller can't
|
|
51
|
+
// distinguish tampered from stale from random-string.
|
|
52
|
+
const verify = verifyUnlockToken(event.payload.token, opts.hmacSecret);
|
|
53
|
+
if (!verify.ok) return invalidUnlockToken();
|
|
54
|
+
|
|
55
|
+
await clearLockoutState(ctx.redis, verify.userId);
|
|
56
|
+
return { isSuccess: true, data: { kind: "account-unlocked" as const } };
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -5,9 +5,8 @@
|
|
|
5
5
|
// 1. Redis check + burn (single-use enforcement)
|
|
6
6
|
// 2. Load user + deleted/missing/no-version guard
|
|
7
7
|
// 3. Optional idempotent short-circuit (verify-email when already done)
|
|
8
|
-
// 4.
|
|
9
|
-
// 5.
|
|
10
|
-
// 6. Release the burn on ANY non-success path so a legit retry isn't
|
|
8
|
+
// 4. Apply the handler-specific `changes` on the user's SYSTEM stream
|
|
9
|
+
// 5. Release the burn on ANY non-success path so a legit retry isn't
|
|
11
10
|
// locked out by a stale marker
|
|
12
11
|
//
|
|
13
12
|
// The top-level `runConfirmTokenFlow` orchestrates and owns the
|
|
@@ -15,27 +14,27 @@
|
|
|
15
14
|
// burn (success, already-done) flips `committed = true`; everything
|
|
16
15
|
// else — including future branches a maintainer adds — releases
|
|
17
16
|
// automatically.
|
|
17
|
+
//
|
|
18
|
+
// The user aggregate is systemStream (#497): its event stream lives on
|
|
19
|
+
// SYSTEM_TENANT_ID deterministically, so the write targets exactly one
|
|
20
|
+
// stream. The former membership-probing (`tryWriteAcrossTenants`) existed
|
|
21
|
+
// for pre-#497 scattered streams; those need the one-time
|
|
22
|
+
// backfillUserStreamTenants migration (#762) — probing them stopped working
|
|
23
|
+
// the moment the executor's stream choke-point landed anyway.
|
|
18
24
|
|
|
19
25
|
import {
|
|
20
26
|
createSystemUser,
|
|
21
27
|
type HandlerContext,
|
|
22
28
|
type SessionUser,
|
|
23
29
|
SYSTEM_TENANT_ID,
|
|
24
|
-
type TenantId,
|
|
25
30
|
type WriteResult,
|
|
26
31
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
27
|
-
import {
|
|
28
|
-
InternalError,
|
|
29
|
-
type WriteFailure,
|
|
30
|
-
writeFailure,
|
|
31
|
-
} from "@cosmicdrift/kumiko-framework/errors";
|
|
32
|
-
import { getAggregateStreamTenant } from "@cosmicdrift/kumiko-framework/event-store";
|
|
32
|
+
import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
33
33
|
import type Redis from "ioredis";
|
|
34
|
-
import {
|
|
34
|
+
import { burnToken, unburnToken } from "../../shared";
|
|
35
|
+
import { UserHandlers, UserQueries } from "../../user";
|
|
35
36
|
import type { AuthUserRow } from "../auth-user-row";
|
|
36
37
|
import { parseAuthUserRow } from "../auth-user-row";
|
|
37
|
-
import { orderTenantsByPreference } from "../stream-tenant";
|
|
38
|
-
import { burnToken, unburnToken } from "../token-burn-store";
|
|
39
38
|
|
|
40
39
|
export type ConfirmTokenFlowSpec<TSuccessData> = {
|
|
41
40
|
// Short purpose-tag used in the burn-store key. Must NOT overlap with
|
|
@@ -46,13 +45,11 @@ export type ConfirmTokenFlowSpec<TSuccessData> = {
|
|
|
46
45
|
// is misconfigured, not the caller's fault.
|
|
47
46
|
readonly redisRequiredMessage: string;
|
|
48
47
|
// Standard failure returned for every "token can't be consumed" path
|
|
49
|
-
// (bad state,
|
|
50
|
-
//
|
|
51
|
-
// branch fired.
|
|
48
|
+
// (bad state, version conflict on the stream). The route layer returns
|
|
49
|
+
// 422 with a uniform code so the caller can't tell which branch fired.
|
|
52
50
|
readonly invalidToken: () => ReturnType<typeof writeFailure>;
|
|
53
|
-
// Handler-specific payload for user:update. Runs once per token
|
|
54
|
-
//
|
|
55
|
-
// (password-reset hashes here).
|
|
51
|
+
// Handler-specific payload for user:update. Runs once per token. Can be
|
|
52
|
+
// async (password-reset hashes here).
|
|
56
53
|
readonly buildChanges: (me: AuthUserRow) => Promise<Record<string, unknown>>;
|
|
57
54
|
// Returned verbatim on a successful write.
|
|
58
55
|
readonly successData: TSuccessData;
|
|
@@ -96,20 +93,21 @@ export async function runConfirmTokenFlow<TSuccessData>(
|
|
|
96
93
|
return { isSuccess: true, data: spec.alreadyDone.data };
|
|
97
94
|
}
|
|
98
95
|
|
|
99
|
-
const tenantOrder = await resolveStreamTenants(ctx, systemUser, me);
|
|
100
|
-
if (tenantOrder.length === 0) return spec.invalidToken();
|
|
101
|
-
|
|
102
96
|
const changes = await spec.buildChanges(me);
|
|
103
|
-
const
|
|
104
|
-
|
|
97
|
+
const writeRes = await ctx.writeAs(systemUser, UserHandlers.update, {
|
|
98
|
+
id: me.id,
|
|
99
|
+
version: me.version,
|
|
100
|
+
changes,
|
|
101
|
+
});
|
|
102
|
+
if (writeRes.isSuccess) {
|
|
105
103
|
committed = true;
|
|
106
104
|
return { isSuccess: true, data: spec.successData };
|
|
107
105
|
}
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
// denied)
|
|
111
|
-
if (
|
|
112
|
-
return
|
|
106
|
+
// version_conflict = concurrent modification (or an un-migrated pre-#497
|
|
107
|
+
// stream, see header) → token-level failure. Anything else (DB down,
|
|
108
|
+
// access denied) bubbles unchanged so ops sees the real failure class.
|
|
109
|
+
if (writeRes.error.code === "version_conflict") return spec.invalidToken();
|
|
110
|
+
return writeRes;
|
|
113
111
|
} finally {
|
|
114
112
|
// committed===false covers EVERY failure path — including branches a
|
|
115
113
|
// future maintainer adds without reading this file. The original
|
|
@@ -121,14 +119,13 @@ export async function runConfirmTokenFlow<TSuccessData>(
|
|
|
121
119
|
}
|
|
122
120
|
}
|
|
123
121
|
|
|
124
|
-
// --- Private helpers ------------------------------------------------------
|
|
125
|
-
|
|
126
122
|
// Fetches the user row via the privileged findForAuth query and validates
|
|
127
|
-
// it's usable for a write: not deleted, has a row.version (
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
// Return type narrows `version` to `number` so the
|
|
131
|
-
// need a `?? 0` fallback — the guard lives here,
|
|
123
|
+
// it's usable for a write: not deleted, has a row.version >= 1 (a row
|
|
124
|
+
// without any event stream — e.g. inserted straight into read_users — must
|
|
125
|
+
// not be confirmable: the write would otherwise seed a fresh stream with a
|
|
126
|
+
// bare user.updated). Return type narrows `version` to `number` so the
|
|
127
|
+
// write-callsite doesn't need a `?? 0` fallback — the guard lives here,
|
|
128
|
+
// not at every callsite.
|
|
132
129
|
async function loadValidatedUser(
|
|
133
130
|
ctx: HandlerContext,
|
|
134
131
|
systemUser: SessionUser,
|
|
@@ -137,74 +134,6 @@ async function loadValidatedUser(
|
|
|
137
134
|
const me = parseAuthUserRow(
|
|
138
135
|
await ctx.queryAs(systemUser, UserQueries.findForAuth, { id: userId }),
|
|
139
136
|
);
|
|
140
|
-
if (!me || me.isDeleted || me.version === undefined) return null;
|
|
137
|
+
if (!me || me.isDeleted || me.version === undefined || me.version < 1) return null;
|
|
141
138
|
return { ...me, version: me.version };
|
|
142
139
|
}
|
|
143
|
-
|
|
144
|
-
// Loads the user's memberships and returns a prioritised tenant list, with the
|
|
145
|
-
// aggregate's real stream tenant recovered from the event log prepended.
|
|
146
|
-
//
|
|
147
|
-
// Empty only when the user has NO memberships AND no recoverable stream tenant
|
|
148
|
-
// — the caller treats that as invalid_token. A zero-membership systemScope user
|
|
149
|
-
// whose stream lives outside any membership (a platform operator seeded under a
|
|
150
|
-
// fixture/SYSTEM tenant) still resolves, because the stream-tenant lookup runs
|
|
151
|
-
// before the empty check rather than being short-circuited by it.
|
|
152
|
-
async function resolveStreamTenants(
|
|
153
|
-
ctx: HandlerContext,
|
|
154
|
-
systemUser: SessionUser,
|
|
155
|
-
me: AuthUserRow,
|
|
156
|
-
): Promise<readonly TenantId[]> {
|
|
157
|
-
const memberships = (await ctx.queryAs(systemUser, "tenant:query:memberships", {
|
|
158
|
-
userId: me.id,
|
|
159
|
-
})) as Array<{ tenantId: TenantId }>; // @cast-boundary db-runner
|
|
160
|
-
const ordered = orderTenantsByPreference(memberships, me.lastActiveTenantId);
|
|
161
|
-
|
|
162
|
-
// The user aggregate is r.systemScope(): its event stream lives in whichever
|
|
163
|
-
// tenant the creating executor used, which need NOT be a membership tenant.
|
|
164
|
-
// A platform operator seeded under a fixture/platform tenant is the live case
|
|
165
|
-
// — its stream tenant is absent from `ordered`, so a membership-only search
|
|
166
|
-
// rejects every write and collapses to invalid_token. Recover the real stream
|
|
167
|
-
// tenant from the event log and try it first; memberships stay as fallback.
|
|
168
|
-
// Pulled BEFORE the empty-membership check so a zero-membership operator whose
|
|
169
|
-
// stream lives in SYSTEM_TENANT is recoverable instead of collapsing to
|
|
170
|
-
// invalid_token — mirrors change-password.write.ts's unconditional recovery.
|
|
171
|
-
const streamTenant = await getAggregateStreamTenant(ctx.db.raw, me.id, USER_FEATURE);
|
|
172
|
-
if (streamTenant && !ordered.includes(streamTenant)) {
|
|
173
|
-
return [streamTenant, ...ordered];
|
|
174
|
-
}
|
|
175
|
-
return ordered;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Discriminated result for the write-across-tenants loop.
|
|
179
|
-
// all_conflicts → every candidate rejected with version_conflict →
|
|
180
|
-
// token-level failure; caller returns invalidToken.
|
|
181
|
-
// hard_failure → a non-conflict error that should bubble unchanged
|
|
182
|
-
// (DB down, access denied, …); caller returns it as-is.
|
|
183
|
-
type TenantWriteResult =
|
|
184
|
-
| { isSuccess: true }
|
|
185
|
-
| { isSuccess: false; reason: "all_conflicts" }
|
|
186
|
-
| { isSuccess: false; reason: "hard_failure"; failure: WriteFailure };
|
|
187
|
-
|
|
188
|
-
// Attempts the update against each candidate stream. memberships-query
|
|
189
|
-
// has no deterministic ORDER BY, so the matching stream is discovered by
|
|
190
|
-
// attempt: version_conflict → try the next candidate, anything else →
|
|
191
|
-
// bubble immediately so ops sees the real failure class.
|
|
192
|
-
async function tryWriteAcrossTenants(
|
|
193
|
-
ctx: HandlerContext,
|
|
194
|
-
me: AuthUserRow & { version: number },
|
|
195
|
-
tenantOrder: readonly TenantId[],
|
|
196
|
-
changes: Record<string, unknown>,
|
|
197
|
-
): Promise<TenantWriteResult> {
|
|
198
|
-
for (const tenantId of tenantOrder) {
|
|
199
|
-
const writeRes = await ctx.writeAs(createSystemUser(tenantId), UserHandlers.update, {
|
|
200
|
-
id: me.id,
|
|
201
|
-
version: me.version,
|
|
202
|
-
changes,
|
|
203
|
-
});
|
|
204
|
-
if (writeRes.isSuccess) return { isSuccess: true };
|
|
205
|
-
if (writeRes.error.code !== "version_conflict") {
|
|
206
|
-
return { isSuccess: false, reason: "hard_failure", failure: writeRes };
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
return { isSuccess: false, reason: "all_conflicts" };
|
|
210
|
-
}
|
|
@@ -20,14 +20,15 @@
|
|
|
20
20
|
import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
21
21
|
import { createEventStoreExecutor, createTenantDb } from "@cosmicdrift/kumiko-framework/db";
|
|
22
22
|
import {
|
|
23
|
+
buildSessionRoles,
|
|
23
24
|
createSystemUser,
|
|
24
25
|
defineWriteHandler,
|
|
25
26
|
type SessionUser,
|
|
26
|
-
stripForbiddenMembershipRoles,
|
|
27
27
|
type TenantId,
|
|
28
28
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
29
29
|
import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
30
30
|
import { z } from "zod";
|
|
31
|
+
import { decryptStoredPii, verifyPassword } from "../../shared";
|
|
31
32
|
// kumiko-lint-ignore cross-feature-import invite-flow
|
|
32
33
|
import {
|
|
33
34
|
INVITATION_STATUS,
|
|
@@ -45,12 +46,12 @@ import {
|
|
|
45
46
|
getInvitationIdForToken,
|
|
46
47
|
unburnInviteToken,
|
|
47
48
|
} from "../invite-token-store";
|
|
48
|
-
import {
|
|
49
|
+
import { passwordSchema } from "../password-policy";
|
|
49
50
|
|
|
50
51
|
const InviteAcceptWithLoginSchema = z.object({
|
|
51
52
|
token: z.string().min(1),
|
|
52
53
|
email: z.email(),
|
|
53
|
-
password:
|
|
54
|
+
password: passwordSchema,
|
|
54
55
|
});
|
|
55
56
|
|
|
56
57
|
export type InviteAcceptWithLoginData = {
|
|
@@ -106,7 +107,11 @@ export function createInviteAcceptWithLoginHandler() {
|
|
|
106
107
|
return invalidInviteToken();
|
|
107
108
|
|
|
108
109
|
const invitationTenantId = invitation.tenantId;
|
|
109
|
-
const invitationEmail =
|
|
110
|
+
const invitationEmail = await decryptStoredPii(
|
|
111
|
+
invitation.email,
|
|
112
|
+
"email",
|
|
113
|
+
"auth:invite-accept-with-login",
|
|
114
|
+
);
|
|
110
115
|
const invitationRole = invitation.role;
|
|
111
116
|
const invitationVersion = invitation.version;
|
|
112
117
|
|
|
@@ -165,7 +170,9 @@ export function createInviteAcceptWithLoginHandler() {
|
|
|
165
170
|
const session: SessionUser = {
|
|
166
171
|
id: userId,
|
|
167
172
|
tenantId: invitationTenantId,
|
|
168
|
-
|
|
173
|
+
// buildSessionRoles calls stripForbiddenMembershipRoles internally —
|
|
174
|
+
// a reserved role on the invitation itself must never reach the session.
|
|
175
|
+
roles: buildSessionRoles([], [invitationRole]),
|
|
169
176
|
};
|
|
170
177
|
|
|
171
178
|
committed = true;
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
25
25
|
import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
26
26
|
import { z } from "zod";
|
|
27
|
+
import { decryptStoredPii } from "../../shared";
|
|
27
28
|
// kumiko-lint-ignore cross-feature-import invite-flow lebt in auth-email-password (Magic-Link), DB-row-owner ist tenant-feature
|
|
28
29
|
import {
|
|
29
30
|
INVITATION_STATUS,
|
|
@@ -100,7 +101,11 @@ export function createInviteAcceptHandler() {
|
|
|
100
101
|
return invalidInviteToken();
|
|
101
102
|
|
|
102
103
|
const invitationTenantId = invitation.tenantId;
|
|
103
|
-
const invitationEmail =
|
|
104
|
+
const invitationEmail = await decryptStoredPii(
|
|
105
|
+
invitation.email,
|
|
106
|
+
"email",
|
|
107
|
+
"auth:invite-accept",
|
|
108
|
+
);
|
|
104
109
|
const invitationRole = invitation.role;
|
|
105
110
|
const invitationVersion = invitation.version;
|
|
106
111
|
|
|
@@ -110,7 +115,9 @@ export function createInviteAcceptHandler() {
|
|
|
110
115
|
const userRow = await fetchOne<UserEmailRow>(ctx.db.raw, userTable, {
|
|
111
116
|
id: event.user.id,
|
|
112
117
|
});
|
|
113
|
-
const userEmail = userRow?.email
|
|
118
|
+
const userEmail = userRow?.email
|
|
119
|
+
? await decryptStoredPii(userRow.email, "email", "auth:invite-accept")
|
|
120
|
+
: undefined;
|
|
114
121
|
if (!userRow || !userEmail || userEmail.toLowerCase() !== invitationEmail) {
|
|
115
122
|
return inviteEmailMismatch();
|
|
116
123
|
}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
import { generateToken } from "@cosmicdrift/kumiko-framework/api";
|
|
20
20
|
import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
21
21
|
import { createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
|
|
22
|
-
import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
22
|
+
import { access, defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
23
23
|
import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
24
24
|
import { Temporal } from "temporal-polyfill";
|
|
25
25
|
import { z } from "zod";
|
|
@@ -77,7 +77,7 @@ export function createInviteCreateHandler(opts: InviteCreateOptions) {
|
|
|
77
77
|
return defineWriteHandler<"invite-create", typeof InviteCreateSchema, InviteCreateData>({
|
|
78
78
|
name: "invite-create",
|
|
79
79
|
schema: InviteCreateSchema,
|
|
80
|
-
access: { roles:
|
|
80
|
+
access: { roles: access.admin },
|
|
81
81
|
handler: async (event, ctx) => {
|
|
82
82
|
if (!ctx.redis) {
|
|
83
83
|
return writeFailure(
|
|
@@ -24,14 +24,15 @@ import {
|
|
|
24
24
|
type DbConnection,
|
|
25
25
|
} from "@cosmicdrift/kumiko-framework/db";
|
|
26
26
|
import {
|
|
27
|
+
buildSessionRoles,
|
|
27
28
|
createSystemUser,
|
|
28
29
|
defineWriteHandler,
|
|
29
30
|
type SessionUser,
|
|
30
|
-
stripForbiddenMembershipRoles,
|
|
31
31
|
type TenantId,
|
|
32
32
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
33
33
|
import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
34
34
|
import { z } from "zod";
|
|
35
|
+
import { decryptStoredPii } from "../../shared";
|
|
35
36
|
// kumiko-lint-ignore cross-feature-import invite-flow
|
|
36
37
|
import {
|
|
37
38
|
INVITATION_STATUS,
|
|
@@ -49,12 +50,13 @@ import {
|
|
|
49
50
|
getInvitationIdForToken,
|
|
50
51
|
unburnInviteToken,
|
|
51
52
|
} from "../invite-token-store";
|
|
53
|
+
import { passwordSchema } from "../password-policy";
|
|
52
54
|
// kumiko-lint-ignore cross-feature-import provisioning needs cross-feature seeding helpers
|
|
53
55
|
import { seedUserWithPassword } from "../seeding";
|
|
54
56
|
|
|
55
57
|
const InviteSignupCompleteSchema = z.object({
|
|
56
58
|
token: z.string().min(1),
|
|
57
|
-
password:
|
|
59
|
+
password: passwordSchema,
|
|
58
60
|
});
|
|
59
61
|
|
|
60
62
|
export type InviteSignupCompleteData = {
|
|
@@ -109,7 +111,11 @@ export function createInviteSignupCompleteHandler() {
|
|
|
109
111
|
return invalidInviteToken();
|
|
110
112
|
|
|
111
113
|
const invitationTenantId = invitation.tenantId;
|
|
112
|
-
const invitationEmail =
|
|
114
|
+
const invitationEmail = await decryptStoredPii(
|
|
115
|
+
invitation.email,
|
|
116
|
+
"email",
|
|
117
|
+
"auth:invite-signup-complete",
|
|
118
|
+
);
|
|
113
119
|
const invitationRole = invitation.role;
|
|
114
120
|
const invitationVersion = invitation.version;
|
|
115
121
|
|
|
@@ -161,7 +167,9 @@ export function createInviteSignupCompleteHandler() {
|
|
|
161
167
|
const session: SessionUser = {
|
|
162
168
|
id: userId,
|
|
163
169
|
tenantId: invitationTenantId,
|
|
164
|
-
|
|
170
|
+
// buildSessionRoles calls stripForbiddenMembershipRoles internally —
|
|
171
|
+
// a reserved role on the invitation itself must never reach the session.
|
|
172
|
+
roles: buildSessionRoles([], [invitationRole]),
|
|
165
173
|
};
|
|
166
174
|
|
|
167
175
|
committed = true;
|