@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
|
@@ -12,15 +12,38 @@
|
|
|
12
12
|
// einem separaten Test mit Hono-mock geprüft.
|
|
13
13
|
|
|
14
14
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
15
|
+
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
16
|
+
import {
|
|
17
|
+
configuredPiiSubjectKms,
|
|
18
|
+
configurePiiSubjectKms,
|
|
19
|
+
decryptPiiFieldValues,
|
|
20
|
+
InMemoryKmsAdapter,
|
|
21
|
+
isPiiCiphertext,
|
|
22
|
+
PII_ERASED_SENTINEL,
|
|
23
|
+
resetPiiSubjectKmsForTests,
|
|
24
|
+
} from "@cosmicdrift/kumiko-framework/crypto";
|
|
15
25
|
import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
16
26
|
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
17
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
createEventsTable,
|
|
29
|
+
isStreamArchived,
|
|
30
|
+
loadAggregate,
|
|
31
|
+
} from "@cosmicdrift/kumiko-framework/event-store";
|
|
18
32
|
import {
|
|
19
33
|
createTestUser,
|
|
20
34
|
setupTestStack,
|
|
21
35
|
type TestStack,
|
|
22
36
|
testTenantId,
|
|
37
|
+
unsafeCreateEntityTable,
|
|
23
38
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
39
|
+
import {
|
|
40
|
+
ComplianceProfileHandlers,
|
|
41
|
+
createComplianceProfilesFeature,
|
|
42
|
+
tenantComplianceProfileEntity,
|
|
43
|
+
} from "../../compliance-profiles";
|
|
44
|
+
import { createConfigFeature } from "../../config";
|
|
45
|
+
import { createTenantFeature } from "../../tenant/feature";
|
|
46
|
+
import { createTenantLifecycleFeature } from "../../tenant-lifecycle";
|
|
24
47
|
import { subscriptionAggregateId } from "../aggregate-id";
|
|
25
48
|
import {
|
|
26
49
|
SubscriptionEventTypes,
|
|
@@ -28,6 +51,8 @@ import {
|
|
|
28
51
|
SubscriptionStatuses,
|
|
29
52
|
} from "../constants";
|
|
30
53
|
import { billingFoundationFeature } from "../feature";
|
|
54
|
+
import { subscriptionsProjectionTable } from "../projection";
|
|
55
|
+
import { subscriptionTenantDestroyHook } from "../tenant-destroy-hook";
|
|
31
56
|
import type { SubscriptionProviderPlugin } from "../types";
|
|
32
57
|
|
|
33
58
|
// =============================================================================
|
|
@@ -80,16 +105,30 @@ let db: DbConnection;
|
|
|
80
105
|
|
|
81
106
|
beforeAll(async () => {
|
|
82
107
|
stack = await setupTestStack({
|
|
83
|
-
features: [
|
|
108
|
+
features: [
|
|
109
|
+
createConfigFeature(),
|
|
110
|
+
createTenantFeature(),
|
|
111
|
+
createComplianceProfilesFeature(),
|
|
112
|
+
createTenantLifecycleFeature(),
|
|
113
|
+
billingFoundationFeature,
|
|
114
|
+
mockProviderFeature,
|
|
115
|
+
],
|
|
84
116
|
});
|
|
85
117
|
db = stack.db;
|
|
86
118
|
// subscriptionsProjectionTable wird von setupTestStack automatisch
|
|
87
119
|
// gepusht (r.projection mit `table`-Property → auto-push).
|
|
88
120
|
await createEventsTable(db);
|
|
121
|
+
await unsafeCreateEntityTable(db, tenantComplianceProfileEntity);
|
|
122
|
+
// providerCustomerId/providerSubscriptionId are `tenantOwned` PII-subject
|
|
123
|
+
// fields — no executor wires the KMS automatically here (raw r.projection,
|
|
124
|
+
// see feature.ts), so process-event.write.ts calls configuredPiiSubjectKms()
|
|
125
|
+
// directly and needs one configured, same as run{Prod,Dev}App do at boot.
|
|
126
|
+
configurePiiSubjectKms(new InMemoryKmsAdapter());
|
|
89
127
|
});
|
|
90
128
|
|
|
91
129
|
afterAll(async () => {
|
|
92
130
|
await stack.cleanup();
|
|
131
|
+
resetPiiSubjectKmsForTests();
|
|
93
132
|
});
|
|
94
133
|
|
|
95
134
|
function adminFor(tenantNumber: number) {
|
|
@@ -566,3 +605,168 @@ describe("scenario 8: cancel-event setzt status auf canceled, behält subscripti
|
|
|
566
605
|
expect(subs.rows[0]?.["tier"]).toBe("free");
|
|
567
606
|
});
|
|
568
607
|
});
|
|
608
|
+
|
|
609
|
+
describe("scenario 9: subscriptionTenantDestroyHook (#800 tenant-destroy PII erasure)", () => {
|
|
610
|
+
test("default profile → row hard-deleted, stream archived", async () => {
|
|
611
|
+
const admin = adminFor(3009);
|
|
612
|
+
await stack.http.writeOk(
|
|
613
|
+
SubscriptionFoundationHandlers.processEvent,
|
|
614
|
+
buildEvent({
|
|
615
|
+
providerEventId: "evt_3009_create",
|
|
616
|
+
providerCustomerId: "cus_3009",
|
|
617
|
+
providerSubscriptionId: "sub_3009",
|
|
618
|
+
}),
|
|
619
|
+
admin,
|
|
620
|
+
);
|
|
621
|
+
|
|
622
|
+
await subscriptionTenantDestroyHook({ db, tenantId: admin.tenantId });
|
|
623
|
+
|
|
624
|
+
const rows = await selectMany(db, subscriptionsProjectionTable, {
|
|
625
|
+
id: subscriptionAggregateId(admin.tenantId),
|
|
626
|
+
});
|
|
627
|
+
expect(rows).toHaveLength(0);
|
|
628
|
+
expect(
|
|
629
|
+
await isStreamArchived(db, admin.tenantId, subscriptionAggregateId(admin.tenantId)),
|
|
630
|
+
).toBe(true);
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
test("HGB profile → PII redacted, accounting fields + row survive, stream archived", async () => {
|
|
634
|
+
const admin = adminFor(3010);
|
|
635
|
+
await stack.http.writeOk(
|
|
636
|
+
ComplianceProfileHandlers.setProfile,
|
|
637
|
+
{ profileKey: "de-hr-dsgvo-hgb" },
|
|
638
|
+
admin,
|
|
639
|
+
);
|
|
640
|
+
await stack.http.writeOk(
|
|
641
|
+
SubscriptionFoundationHandlers.processEvent,
|
|
642
|
+
buildEvent({
|
|
643
|
+
providerEventId: "evt_3010_create",
|
|
644
|
+
providerCustomerId: "cus_3010",
|
|
645
|
+
providerSubscriptionId: "sub_3010",
|
|
646
|
+
tier: "pro",
|
|
647
|
+
}),
|
|
648
|
+
admin,
|
|
649
|
+
);
|
|
650
|
+
|
|
651
|
+
await subscriptionTenantDestroyHook({ db, tenantId: admin.tenantId });
|
|
652
|
+
|
|
653
|
+
const rows = await selectMany<{
|
|
654
|
+
providerCustomerId: string;
|
|
655
|
+
providerSubscriptionId: string;
|
|
656
|
+
tier: string;
|
|
657
|
+
status: string;
|
|
658
|
+
}>(db, subscriptionsProjectionTable, { id: subscriptionAggregateId(admin.tenantId) });
|
|
659
|
+
expect(rows).toHaveLength(1);
|
|
660
|
+
expect(rows[0]?.providerCustomerId).toBe("[erased]");
|
|
661
|
+
expect(rows[0]?.providerSubscriptionId).toBe("[erased]");
|
|
662
|
+
// accounting facts survive — that's the point of the HGB retention branch
|
|
663
|
+
expect(rows[0]?.tier).toBe("pro");
|
|
664
|
+
expect(
|
|
665
|
+
await isStreamArchived(db, admin.tenantId, subscriptionAggregateId(admin.tenantId)),
|
|
666
|
+
).toBe(true);
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
describe("scenario 10: PII is encrypted at rest, not just erasable on destroy", () => {
|
|
671
|
+
test("raw event-log payload and raw projection row both hold ciphertext, not the plaintext provider id", async () => {
|
|
672
|
+
const admin = adminFor(3011);
|
|
673
|
+
await stack.http.writeOk(
|
|
674
|
+
SubscriptionFoundationHandlers.processEvent,
|
|
675
|
+
buildEvent({
|
|
676
|
+
providerEventId: "evt_3011_create",
|
|
677
|
+
providerCustomerId: "cus_3011",
|
|
678
|
+
providerSubscriptionId: "sub_3011",
|
|
679
|
+
}),
|
|
680
|
+
admin,
|
|
681
|
+
);
|
|
682
|
+
|
|
683
|
+
// Raw event-log row (kumiko_events) — archiveStream only stops REPLAY,
|
|
684
|
+
// it never deletes rows, so if this were plaintext it would sit here
|
|
685
|
+
// forever regardless of what a later tenant-destroy hook does.
|
|
686
|
+
const esEvents = await loadAggregate(
|
|
687
|
+
db,
|
|
688
|
+
subscriptionAggregateId(admin.tenantId),
|
|
689
|
+
admin.tenantId,
|
|
690
|
+
);
|
|
691
|
+
const createdPayload = esEvents[0]?.payload as
|
|
692
|
+
| { providerCustomerId?: string; providerSubscriptionId?: string }
|
|
693
|
+
| undefined;
|
|
694
|
+
expect(createdPayload?.providerCustomerId).not.toBe("cus_3011");
|
|
695
|
+
expect(createdPayload?.providerSubscriptionId).not.toBe("sub_3011");
|
|
696
|
+
expect(isPiiCiphertext(createdPayload?.providerCustomerId)).toBe(true);
|
|
697
|
+
expect(isPiiCiphertext(createdPayload?.providerSubscriptionId)).toBe(true);
|
|
698
|
+
|
|
699
|
+
// Raw projection row — same story, decrypt-on-read (list-query,
|
|
700
|
+
// get-subscription-for-tenant) is the only path that sees plaintext.
|
|
701
|
+
const rawRows = await selectMany(db, subscriptionsProjectionTable, {
|
|
702
|
+
id: subscriptionAggregateId(admin.tenantId),
|
|
703
|
+
});
|
|
704
|
+
expect(isPiiCiphertext(rawRows[0]?.["providerCustomerId"])).toBe(true);
|
|
705
|
+
|
|
706
|
+
// But the decrypt-on-read query path still returns plaintext to callers.
|
|
707
|
+
const subs = (await stack.http.queryOk(
|
|
708
|
+
"billing-foundation:query:subscription:list",
|
|
709
|
+
{},
|
|
710
|
+
admin,
|
|
711
|
+
)) as { rows: Array<Record<string, unknown>> };
|
|
712
|
+
expect(subs.rows[0]?.["providerCustomerId"]).toBe("cus_3011");
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
test("crypto-shredding: erasing the tenant's subject key makes the event-log payload permanently unreadable (#800)", async () => {
|
|
716
|
+
const admin = adminFor(3012);
|
|
717
|
+
await stack.http.writeOk(
|
|
718
|
+
SubscriptionFoundationHandlers.processEvent,
|
|
719
|
+
buildEvent({
|
|
720
|
+
providerEventId: "evt_3012_create",
|
|
721
|
+
providerCustomerId: "cus_3012",
|
|
722
|
+
providerSubscriptionId: "sub_3012",
|
|
723
|
+
}),
|
|
724
|
+
admin,
|
|
725
|
+
);
|
|
726
|
+
const esEventsBefore = await loadAggregate(
|
|
727
|
+
db,
|
|
728
|
+
subscriptionAggregateId(admin.tenantId),
|
|
729
|
+
admin.tenantId,
|
|
730
|
+
);
|
|
731
|
+
const ciphertextBefore = (esEventsBefore[0]?.payload as { providerCustomerId?: string })
|
|
732
|
+
.providerCustomerId as string;
|
|
733
|
+
|
|
734
|
+
// This is exactly what tenant-lifecycle's "subject-keys" pipeline stage
|
|
735
|
+
// (eraseSubjectKeys, stages.ts) does on tenant-destroy — same kms
|
|
736
|
+
// instance, same subject shape, called directly instead of through the
|
|
737
|
+
// full destroy pipeline so this test doesn't need to drive 8 stages.
|
|
738
|
+
const kms = configuredPiiSubjectKms();
|
|
739
|
+
if (!kms) throw new Error("expected a configured PII-subject KMS in this test stack");
|
|
740
|
+
await kms.eraseKey(
|
|
741
|
+
{ kind: "tenant", tenantId: admin.tenantId },
|
|
742
|
+
{ requestId: "test:crypto-shred", eraseReason: "test" },
|
|
743
|
+
);
|
|
744
|
+
|
|
745
|
+
// The DEK is gone — the SAME ciphertext that decrypted cleanly a moment
|
|
746
|
+
// ago now resolves to the erased sentinel, not the original value and
|
|
747
|
+
// not a crash. That's the actual #800 guarantee: not "redacted in the
|
|
748
|
+
// read model" but "cryptographically impossible to recover", including
|
|
749
|
+
// from the raw event-log row nothing in this feature ever deletes.
|
|
750
|
+
const decrypted = await decryptPiiFieldValues(
|
|
751
|
+
{ providerCustomerId: ciphertextBefore },
|
|
752
|
+
["providerCustomerId"],
|
|
753
|
+
kms,
|
|
754
|
+
{ requestId: "test:crypto-shred-verify" },
|
|
755
|
+
);
|
|
756
|
+
expect(decrypted["providerCustomerId"]).toBe(PII_ERASED_SENTINEL);
|
|
757
|
+
expect(decrypted["providerCustomerId"]).not.toBe("cus_3012");
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
// No backfill job here on purpose: billing-foundation has never shipped
|
|
761
|
+
// with a live subject KMS, so there is no pre-existing plaintext
|
|
762
|
+
// subscription data anywhere to migrate — every row that will ever exist
|
|
763
|
+
// is written through the encrypted path above. If that changes (billing-
|
|
764
|
+
// foundation goes live before a KMS is configured somewhere), note for
|
|
765
|
+
// whoever builds the backfill: the existing backfillEventPiiEncryption
|
|
766
|
+
// (#799, db/queries/backfill-pii.ts) does NOT cover this feature yet —
|
|
767
|
+
// its lifecycle-event matcher expects `<aggregateType>.<verb>` event
|
|
768
|
+
// names (billing's are `billing-foundation:event:subscription-created`)
|
|
769
|
+
// and its custom-event catalog only supports user-subject fields
|
|
770
|
+
// (`{kind: "user"}`), not the tenant-subject fields billing uses. Closing
|
|
771
|
+
// that gap is new framework capability, not a wiring fix.
|
|
772
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { HandlerContext } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import type { SubscriptionProviderPlugin } from "../types";
|
|
4
|
+
|
|
5
|
+
export type SubscriptionProviderContractFixture = {
|
|
6
|
+
readonly plugin: SubscriptionProviderPlugin;
|
|
7
|
+
readonly ctx: HandlerContext;
|
|
8
|
+
readonly checkout: Parameters<
|
|
9
|
+
NonNullable<SubscriptionProviderPlugin["createCheckoutSession"]>
|
|
10
|
+
>[1];
|
|
11
|
+
// Providers without a customer-portal (e.g. Mollie) omit this — the
|
|
12
|
+
// portal-test then skips, mirroring `createPortalSession` being optional.
|
|
13
|
+
readonly portal?: Parameters<NonNullable<SubscriptionProviderPlugin["createPortalSession"]>>[1];
|
|
14
|
+
readonly cancelSubscriptionId?: string;
|
|
15
|
+
// A real, provider-signed/verifiable webhook payload for the happy path.
|
|
16
|
+
// Providers without a fixture skip the webhook-parsing test — building one
|
|
17
|
+
// needs provider-specific signing (Stripe HMAC) or a mocked API client
|
|
18
|
+
// (Mollie lazy-fetch), which the provider's own verify-webhook.test.ts
|
|
19
|
+
// already assembles.
|
|
20
|
+
readonly webhook?: {
|
|
21
|
+
readonly rawBody: string;
|
|
22
|
+
readonly headers: Record<string, string>;
|
|
23
|
+
readonly expectedTenantId: string;
|
|
24
|
+
readonly expectedTier: string;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type SubscriptionProviderContractOptions = {
|
|
29
|
+
// Providers without portal/cancel support (e.g. Mollie) must say so
|
|
30
|
+
// explicitly — a bare `if (!plugin.x) return` inside the test body makes
|
|
31
|
+
// the skip invisible in test output (a coverage illusion: Stripe's
|
|
32
|
+
// portal/cancel tests and Mollie's "skip" both show as green) (#1339).
|
|
33
|
+
readonly hasPortal: boolean;
|
|
34
|
+
readonly hasCancel: boolean;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export function describeSubscriptionProviderContract(
|
|
38
|
+
name: string,
|
|
39
|
+
factory: () => SubscriptionProviderContractFixture | Promise<SubscriptionProviderContractFixture>,
|
|
40
|
+
options: SubscriptionProviderContractOptions,
|
|
41
|
+
): void {
|
|
42
|
+
describe(`${name} — SubscriptionProviderPlugin contract`, () => {
|
|
43
|
+
test("implements verifyAndParseWebhook", async () => {
|
|
44
|
+
const { plugin } = await factory();
|
|
45
|
+
expect(typeof plugin.verifyAndParseWebhook).toBe("function");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("verifyAndParseWebhook parses a valid provider event into a SubscriptionEvent", async () => {
|
|
49
|
+
const { plugin, webhook } = await factory();
|
|
50
|
+
if (!webhook) return;
|
|
51
|
+
const event = await plugin.verifyAndParseWebhook(webhook.rawBody, webhook.headers);
|
|
52
|
+
expect(event).not.toBeNull();
|
|
53
|
+
expect(event?.tenantId).toBe(webhook.expectedTenantId);
|
|
54
|
+
expect(event?.tier).toBe(webhook.expectedTier);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("createCheckoutSession returns a hosted-page url", async () => {
|
|
58
|
+
const { plugin, ctx, checkout } = await factory();
|
|
59
|
+
if (!plugin.createCheckoutSession) return;
|
|
60
|
+
const result = await plugin.createCheckoutSession(ctx, checkout);
|
|
61
|
+
expect(typeof result.url).toBe("string");
|
|
62
|
+
expect(result.url.length).toBeGreaterThan(0);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
(options.hasPortal ? test : test.skip)(
|
|
66
|
+
"createPortalSession returns a hosted-page url",
|
|
67
|
+
async () => {
|
|
68
|
+
const { plugin, ctx, portal } = await factory();
|
|
69
|
+
if (!plugin.createPortalSession || !portal) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
"describeSubscriptionProviderContract: hasPortal:true but the fixture has no createPortalSession/portal",
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
const result = await plugin.createPortalSession(ctx, portal);
|
|
75
|
+
expect(typeof result.url).toBe("string");
|
|
76
|
+
expect(result.url.length).toBeGreaterThan(0);
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
(options.hasCancel ? test : test.skip)(
|
|
81
|
+
"cancelSubscription resolves without throwing",
|
|
82
|
+
async () => {
|
|
83
|
+
const { plugin, ctx, cancelSubscriptionId } = await factory();
|
|
84
|
+
if (!plugin.cancelSubscription || !cancelSubscriptionId) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
"describeSubscriptionProviderContract: hasCancel:true but the fixture has no cancelSubscription/cancelSubscriptionId",
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
await expect(plugin.cancelSubscription(ctx, cancelSubscriptionId)).resolves.toBeUndefined();
|
|
90
|
+
},
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
+
import { collectPiiSubjectFields } from "@cosmicdrift/kumiko-framework/crypto";
|
|
1
2
|
import {
|
|
2
3
|
createEntity,
|
|
3
4
|
createTextField,
|
|
4
5
|
createTimestampField,
|
|
5
6
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
6
|
-
|
|
7
|
-
// =============================================================================
|
|
8
7
|
// `subscription` — current state pro Plattform-Tenant (= Read-Model)
|
|
9
8
|
// =============================================================================
|
|
10
9
|
//
|
|
@@ -41,10 +40,35 @@ export const subscriptionEntity = createEntity({
|
|
|
41
40
|
table: "read_subscriptions",
|
|
42
41
|
fields: {
|
|
43
42
|
providerName: createTextField({ required: true, maxLength: 50 }),
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
// tenantOwned (not `encrypted`): the field must crypto-shred when
|
|
44
|
+
// eraseSubjectKeys erases the tenant's subject key on tenant-destroy
|
|
45
|
+
// (#800). `encrypted: true` uses the app-wide master key instead — that
|
|
46
|
+
// key is never erased per-tenant, so it would only add encryption-at-
|
|
47
|
+
// rest, not the erasure guarantee #800 actually asks for.
|
|
48
|
+
// maxLength 1000, not 200: the stored value is PII-ciphertext
|
|
49
|
+
// (`kumiko-pii:v1:<subject>:<blob>`), not the raw provider id — a
|
|
50
|
+
// 200-char plaintext id becomes ~300+ chars of ciphertext. Matches
|
|
51
|
+
// subscriptionEventPayloadSchema in events.ts.
|
|
52
|
+
providerCustomerId: createTextField({
|
|
53
|
+
required: true,
|
|
54
|
+
maxLength: 1000,
|
|
55
|
+
tenantOwned: true,
|
|
56
|
+
}),
|
|
57
|
+
providerSubscriptionId: createTextField({
|
|
58
|
+
required: true,
|
|
59
|
+
maxLength: 1000,
|
|
60
|
+
tenantOwned: true,
|
|
61
|
+
}),
|
|
46
62
|
status: createTextField({ required: true, maxLength: 30 }),
|
|
47
63
|
tier: createTextField({ required: true, maxLength: 50 }),
|
|
48
64
|
currentPeriodEnd: createTimestampField({ required: true }),
|
|
49
65
|
},
|
|
50
66
|
});
|
|
67
|
+
|
|
68
|
+
// No executor manages this table (raw r.projection, see feature.ts) — the
|
|
69
|
+
// process-event write-handler and every read site must encrypt/decrypt
|
|
70
|
+
// these fields manually via the PII-subject-KMS path (same mechanism
|
|
71
|
+
// eraseSubjectKeys erases). Single source of truth so a future third
|
|
72
|
+
// `tenantOwned`/`pii`/`userOwned` field doesn't need a matching manual
|
|
73
|
+
// update at each call site.
|
|
74
|
+
export const SUBSCRIPTION_PII_FIELDS = collectPiiSubjectFields(subscriptionEntity);
|
|
@@ -51,8 +51,12 @@ const statusEnum = z.enum([
|
|
|
51
51
|
// payload — payload ist domain-clean, metadata ist provider-truth).
|
|
52
52
|
export const subscriptionEventPayloadSchema = z.object({
|
|
53
53
|
providerName: z.string().min(1).max(50),
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
// 1000, not 200: these two are `tenantOwned: true` on the entity (see
|
|
55
|
+
// entities.ts — crypto-shreds on tenant-destroy, unlike `encrypted`).
|
|
56
|
+
// process-event.write.ts stores the PII-ciphertext here, not the raw
|
|
57
|
+
// provider id. A 200-char plaintext id becomes ~300+ chars of ciphertext.
|
|
58
|
+
providerCustomerId: z.string().min(1).max(1000),
|
|
59
|
+
providerSubscriptionId: z.string().min(1).max(1000),
|
|
56
60
|
status: statusEnum,
|
|
57
61
|
tier: z.string().min(1).max(50),
|
|
58
62
|
currentPeriodEndIso: z.string().min(1),
|
|
@@ -41,8 +41,9 @@
|
|
|
41
41
|
// (z.B. für analytics: "wie viele Wechsel im Monat?"), kommt ein
|
|
42
42
|
// `subscription-provider-changed`-event-type später.
|
|
43
43
|
|
|
44
|
-
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
44
|
+
import { defineFeature, EXT_TENANT_DATA } from "@cosmicdrift/kumiko-framework/engine";
|
|
45
45
|
import { BILLING_FOUNDATION_FEATURE, SUBSCRIPTION_PROVIDER_EXTENSION } from "./constants";
|
|
46
|
+
import { subscriptionEntity } from "./entities";
|
|
46
47
|
import {
|
|
47
48
|
INVOICE_PAID_EVENT_QN,
|
|
48
49
|
INVOICE_PAID_EVENT_SHORT,
|
|
@@ -69,6 +70,7 @@ import {
|
|
|
69
70
|
applySubscriptionUpdated,
|
|
70
71
|
subscriptionsProjectionTable,
|
|
71
72
|
} from "./projection";
|
|
73
|
+
import { subscriptionTenantDestroyHook } from "./tenant-destroy-hook";
|
|
72
74
|
|
|
73
75
|
export const billingFoundationFeature = defineFeature(BILLING_FOUNDATION_FEATURE, (r) => {
|
|
74
76
|
r.describe(
|
|
@@ -79,6 +81,7 @@ export const billingFoundationFeature = defineFeature(BILLING_FOUNDATION_FEATURE
|
|
|
79
81
|
category: "billing",
|
|
80
82
|
recommended: false,
|
|
81
83
|
});
|
|
84
|
+
r.requires("tenant-lifecycle", "compliance-profiles");
|
|
82
85
|
// 5 fine-grained domain-events. Alle 5 nutzen denselben payload-
|
|
83
86
|
// shape (= subscription-state-snapshot); der event-type taggt was
|
|
84
87
|
// passiert ist. Future-consumer (billing-history, accounting)
|
|
@@ -96,6 +99,7 @@ export const billingFoundationFeature = defineFeature(BILLING_FOUNDATION_FEATURE
|
|
|
96
99
|
name: "subscription",
|
|
97
100
|
source: SUBSCRIPTION_AGGREGATE_TYPE,
|
|
98
101
|
table: subscriptionsProjectionTable,
|
|
102
|
+
entity: subscriptionEntity,
|
|
99
103
|
apply: {
|
|
100
104
|
[SUBSCRIPTION_CREATED_EVENT_QN]: applySubscriptionCreated,
|
|
101
105
|
[SUBSCRIPTION_UPDATED_EVENT_QN]: applySubscriptionUpdated,
|
|
@@ -124,4 +128,8 @@ export const billingFoundationFeature = defineFeature(BILLING_FOUNDATION_FEATURE
|
|
|
124
128
|
// Custom list-query auf der subscription-projection (raw drizzle-
|
|
125
129
|
// table; kein r.entity weil Schreiben via projection-apply läuft).
|
|
126
130
|
r.queryHandler(listSubscriptionsQuery);
|
|
131
|
+
|
|
132
|
+
r.useExtension(EXT_TENANT_DATA, "subscription", {
|
|
133
|
+
destroy: subscriptionTenantDestroyHook,
|
|
134
|
+
});
|
|
127
135
|
});
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
// Resolver-helper: liest die current subscription-row für einen Tenant
|
|
2
2
|
// aus der read_subscriptions-projection.
|
|
3
3
|
|
|
4
|
+
import {
|
|
5
|
+
configuredPiiSubjectKms,
|
|
6
|
+
decryptPiiFieldValues,
|
|
7
|
+
} from "@cosmicdrift/kumiko-framework/crypto";
|
|
4
8
|
import type { HandlerContext } from "@cosmicdrift/kumiko-framework/engine";
|
|
5
9
|
import { subscriptionAggregateId } from "./aggregate-id";
|
|
10
|
+
import { SUBSCRIPTION_PII_FIELDS } from "./entities";
|
|
6
11
|
import { subscriptionsProjectionTable } from "./projection";
|
|
7
12
|
|
|
8
13
|
export type SubscriptionView = {
|
|
@@ -24,12 +29,18 @@ export async function getSubscriptionForTenant(
|
|
|
24
29
|
const rows = await ctx.db.selectMany(subscriptionsProjectionTable, { id: aggId }, { limit: 1 });
|
|
25
30
|
const row = rows[0];
|
|
26
31
|
if (!row) return null;
|
|
32
|
+
const piiKms = configuredPiiSubjectKms();
|
|
33
|
+
const decrypted = piiKms
|
|
34
|
+
? await decryptPiiFieldValues(row as Record<string, unknown>, SUBSCRIPTION_PII_FIELDS, piiKms, {
|
|
35
|
+
requestId: `billing-foundation:get-subscription:${tenantId}`,
|
|
36
|
+
})
|
|
37
|
+
: (row as Record<string, unknown>);
|
|
27
38
|
// @cast-boundary db-row — drizzle-row carries column-as-unknown
|
|
28
39
|
return {
|
|
29
|
-
tier:
|
|
30
|
-
status:
|
|
31
|
-
providerName:
|
|
32
|
-
providerCustomerId:
|
|
33
|
-
providerSubscriptionId:
|
|
40
|
+
tier: decrypted["tier"] as string,
|
|
41
|
+
status: decrypted["status"] as string,
|
|
42
|
+
providerName: decrypted["providerName"] as string,
|
|
43
|
+
providerCustomerId: decrypted["providerCustomerId"] as string,
|
|
44
|
+
providerSubscriptionId: decrypted["providerSubscriptionId"] as string,
|
|
34
45
|
};
|
|
35
46
|
}
|
|
@@ -10,10 +10,15 @@
|
|
|
10
10
|
// kennt.
|
|
11
11
|
|
|
12
12
|
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
13
|
+
import {
|
|
14
|
+
configuredPiiSubjectKms,
|
|
15
|
+
decryptPiiFieldValues,
|
|
16
|
+
} from "@cosmicdrift/kumiko-framework/crypto";
|
|
13
17
|
import type { WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
|
|
14
18
|
import { z } from "zod";
|
|
15
19
|
import { subscriptionAggregateId } from "../aggregate-id";
|
|
16
20
|
import { SUBSCRIPTION_PROVIDER_EXTENSION } from "../constants";
|
|
21
|
+
import { SUBSCRIPTION_PII_FIELDS } from "../entities";
|
|
17
22
|
import { subscriptionsProjectionTable as subTable } from "../projection";
|
|
18
23
|
import type { SubscriptionProviderPlugin } from "../types";
|
|
19
24
|
|
|
@@ -41,8 +46,19 @@ export const createPortalSessionHandler: WriteHandlerDef = {
|
|
|
41
46
|
"subscription-foundation: no active subscription for this tenant. Create one via create-checkout-session first.",
|
|
42
47
|
);
|
|
43
48
|
}
|
|
49
|
+
const piiKms = configuredPiiSubjectKms();
|
|
50
|
+
const decrypted = piiKms
|
|
51
|
+
? await decryptPiiFieldValues(
|
|
52
|
+
row as Record<string, unknown>,
|
|
53
|
+
SUBSCRIPTION_PII_FIELDS,
|
|
54
|
+
piiKms,
|
|
55
|
+
{
|
|
56
|
+
requestId: `billing-foundation:create-portal-session:${tenantId}`,
|
|
57
|
+
},
|
|
58
|
+
)
|
|
59
|
+
: (row as Record<string, unknown>);
|
|
44
60
|
const providerName = row["providerName"] as string; // @cast-boundary db-row
|
|
45
|
-
const providerCustomerId =
|
|
61
|
+
const providerCustomerId = decrypted["providerCustomerId"] as string; // @cast-boundary db-row
|
|
46
62
|
|
|
47
63
|
// 2. Plugin-Lookup
|
|
48
64
|
const usages = ctx.registry.getExtensionUsages(SUBSCRIPTION_PROVIDER_EXTENSION);
|
|
@@ -4,8 +4,13 @@
|
|
|
4
4
|
// tenant-scoped, gibt automatisch nur die row des Callers zurück).
|
|
5
5
|
|
|
6
6
|
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
7
|
+
import {
|
|
8
|
+
configuredPiiSubjectKms,
|
|
9
|
+
decryptPiiFieldValues,
|
|
10
|
+
} from "@cosmicdrift/kumiko-framework/crypto";
|
|
7
11
|
import type { QueryHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
|
|
8
12
|
import { z } from "zod";
|
|
13
|
+
import { SUBSCRIPTION_PII_FIELDS } from "../entities";
|
|
9
14
|
import { subscriptionsProjectionTable } from "../projection";
|
|
10
15
|
|
|
11
16
|
const listSchema = z.object({}).strict();
|
|
@@ -18,6 +23,15 @@ export const listSubscriptionsQuery: QueryHandlerDef = {
|
|
|
18
23
|
const rows = await selectMany(ctx.db.raw, subscriptionsProjectionTable, {
|
|
19
24
|
tenantId: ctx.user.tenantId,
|
|
20
25
|
});
|
|
21
|
-
|
|
26
|
+
const piiKms = configuredPiiSubjectKms();
|
|
27
|
+
if (!piiKms) return { rows };
|
|
28
|
+
const decryptedRows = await Promise.all(
|
|
29
|
+
rows.map((row) =>
|
|
30
|
+
decryptPiiFieldValues(row as Record<string, unknown>, SUBSCRIPTION_PII_FIELDS, piiKms, {
|
|
31
|
+
requestId: `billing-foundation:list-subscriptions:${ctx.user.tenantId}`,
|
|
32
|
+
}),
|
|
33
|
+
),
|
|
34
|
+
);
|
|
35
|
+
return { rows: decryptedRows };
|
|
22
36
|
},
|
|
23
37
|
};
|
|
@@ -12,10 +12,15 @@
|
|
|
12
12
|
// 3. ctx.unsafeAppendEvent — Inline-projection materialisiert die
|
|
13
13
|
// `read_subscriptions`-row in derselben TX.
|
|
14
14
|
|
|
15
|
+
import {
|
|
16
|
+
configuredPiiSubjectKms,
|
|
17
|
+
encryptPiiFieldValues,
|
|
18
|
+
} from "@cosmicdrift/kumiko-framework/crypto";
|
|
15
19
|
import type { WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
|
|
16
20
|
import { z } from "zod";
|
|
17
21
|
import { subscriptionAggregateId } from "../aggregate-id";
|
|
18
22
|
import { SubscriptionEventTypes, SubscriptionStatuses } from "../constants";
|
|
23
|
+
import { SUBSCRIPTION_PII_FIELDS, subscriptionEntity } from "../entities";
|
|
19
24
|
import {
|
|
20
25
|
INVOICE_PAID_EVENT_QN,
|
|
21
26
|
INVOICE_PAYMENT_FAILED_EVENT_QN,
|
|
@@ -125,13 +130,44 @@ export const processEventHandler: WriteHandlerDef = {
|
|
|
125
130
|
}
|
|
126
131
|
|
|
127
132
|
// ---------------------------------------------------------------
|
|
128
|
-
// 3.
|
|
133
|
+
// 3. Encrypt the two provider-subject PII fields before they touch
|
|
134
|
+
// storage — this is the ONLY write path onto the subscription
|
|
135
|
+
// stream, so encrypting here covers both the event-log payload AND
|
|
136
|
+
// (via projection.ts copying the event fields as-is) the
|
|
137
|
+
// read_subscriptions row with a single call. The subject is the
|
|
138
|
+
// TENANT (tenantOwned) — tenant-destroy's subject-keys stage
|
|
139
|
+
// (eraseSubjectKeys) erases exactly this key, so both copies become
|
|
140
|
+
// genuinely unreadable (#800) once that stage runs, not just
|
|
141
|
+
// "encrypted at rest". No adapter configured = engine off (fields
|
|
142
|
+
// stay plaintext, pre-#724-phase-C behavior) — mirrors how the
|
|
143
|
+
// event-store-executor treats an absent piiKms().
|
|
144
|
+
// ---------------------------------------------------------------
|
|
145
|
+
const piiKms = configuredPiiSubjectKms();
|
|
146
|
+
const encryptedFields = piiKms
|
|
147
|
+
? await encryptPiiFieldValues(
|
|
148
|
+
{
|
|
149
|
+
tenantId,
|
|
150
|
+
providerCustomerId: payload.providerCustomerId,
|
|
151
|
+
providerSubscriptionId: payload.providerSubscriptionId,
|
|
152
|
+
},
|
|
153
|
+
subscriptionEntity,
|
|
154
|
+
SUBSCRIPTION_PII_FIELDS,
|
|
155
|
+
piiKms,
|
|
156
|
+
{ requestId: `billing-foundation:process-event:${payload.providerEventId}`, tenantId },
|
|
157
|
+
)
|
|
158
|
+
: {
|
|
159
|
+
providerCustomerId: payload.providerCustomerId,
|
|
160
|
+
providerSubscriptionId: payload.providerSubscriptionId,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// ---------------------------------------------------------------
|
|
164
|
+
// 4. Append event auf den subscription-stream. Inline-projection
|
|
129
165
|
// materialisiert die read_subscriptions-row in derselben TX.
|
|
130
166
|
// ---------------------------------------------------------------
|
|
131
167
|
const eventPayload: SubscriptionEventPayload = {
|
|
132
168
|
providerName: payload.providerName,
|
|
133
|
-
providerCustomerId:
|
|
134
|
-
providerSubscriptionId:
|
|
169
|
+
providerCustomerId: encryptedFields["providerCustomerId"] as string,
|
|
170
|
+
providerSubscriptionId: encryptedFields["providerSubscriptionId"] as string,
|
|
135
171
|
status: payload.status,
|
|
136
172
|
tier: payload.tier,
|
|
137
173
|
currentPeriodEndIso: payload.currentPeriodEndIso,
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { deleteMany, type EntityTableMeta, updateMany } from "@cosmicdrift/kumiko-framework/db";
|
|
2
|
+
import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import { archiveStream } from "@cosmicdrift/kumiko-framework/event-store";
|
|
4
|
+
import { resolveProfileForTenant } from "../compliance-profiles";
|
|
5
|
+
import { subscriptionAggregateId } from "./aggregate-id";
|
|
6
|
+
import { SUBSCRIPTION_AGGREGATE_TYPE } from "./events";
|
|
7
|
+
import { subscriptionsProjectionTable } from "./projection";
|
|
8
|
+
|
|
9
|
+
// providerCustomerId/providerSubscriptionId are `tenantOwned: true` on the
|
|
10
|
+
// entity (envelope-encrypted with the tenant subject key, see entities.ts) —
|
|
11
|
+
// eraseSubjectKeys crypto-shreds them for tenants that hard-delete. HGB
|
|
12
|
+
// can not: it needs the accounting facts (status/tier/period) to survive, so
|
|
13
|
+
// it redacts the projection row directly instead of relying on key erasure.
|
|
14
|
+
const REDACTED_PII_VALUE = "[erased]";
|
|
15
|
+
|
|
16
|
+
/** Tenant-destroy hook for billing PII (#800). HGB retains the accounting
|
|
17
|
+
* row but redacts the two provider-subject PII fields; other profiles
|
|
18
|
+
* hard-delete the row. Either way the subscription stream is archived so a
|
|
19
|
+
* future projection rebuild can't resurrect what was erased. */
|
|
20
|
+
export async function subscriptionTenantDestroyHook(ctx: {
|
|
21
|
+
readonly db: import("@cosmicdrift/kumiko-framework/db").DbRunner;
|
|
22
|
+
readonly tenantId: TenantId;
|
|
23
|
+
}): Promise<void> {
|
|
24
|
+
const { profile } = await resolveProfileForTenant({
|
|
25
|
+
db: ctx.db,
|
|
26
|
+
tenantId: ctx.tenantId,
|
|
27
|
+
});
|
|
28
|
+
const aggregateId = subscriptionAggregateId(ctx.tenantId);
|
|
29
|
+
if (profile.key === "de-hr-dsgvo-hgb") {
|
|
30
|
+
await updateMany(
|
|
31
|
+
ctx.db,
|
|
32
|
+
subscriptionsProjectionTable as EntityTableMeta,
|
|
33
|
+
{ providerCustomerId: REDACTED_PII_VALUE, providerSubscriptionId: REDACTED_PII_VALUE },
|
|
34
|
+
{ id: aggregateId },
|
|
35
|
+
);
|
|
36
|
+
} else {
|
|
37
|
+
await deleteMany(ctx.db, subscriptionsProjectionTable as EntityTableMeta, { id: aggregateId });
|
|
38
|
+
}
|
|
39
|
+
await archiveStream(ctx.db, {
|
|
40
|
+
tenantId: ctx.tenantId,
|
|
41
|
+
aggregateId,
|
|
42
|
+
aggregateType: SUBSCRIPTION_AGGREGATE_TYPE,
|
|
43
|
+
archivedBy: "tenant-lifecycle:destroy",
|
|
44
|
+
reason: "tenant_destroy",
|
|
45
|
+
});
|
|
46
|
+
}
|