@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,15 +1,22 @@
|
|
|
1
1
|
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
2
|
import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
3
|
+
// Value-only import, aliased to avoid shadowing the ambient global
|
|
4
|
+
// `Temporal` TYPE that gracePeriodEndCutoff's parameter type resolves
|
|
5
|
+
// against (same #1438 dual-package-hazard pattern as event-store.ts).
|
|
6
|
+
import { Temporal as TemporalPolyfill } from "temporal-polyfill";
|
|
3
7
|
import { userTable } from "../../../user";
|
|
4
8
|
|
|
5
9
|
export async function selectUsersDueForForgetCleanup(
|
|
6
10
|
db: DbConnection,
|
|
7
11
|
status: string,
|
|
8
|
-
gracePeriodEndCutoff:
|
|
12
|
+
gracePeriodEndCutoff: InstanceType<typeof TemporalPolyfill.Instant> | string,
|
|
9
13
|
): Promise<readonly { id: string }[]> {
|
|
14
|
+
// @cast-boundary temporal-polyfill-vs-ambient: same TC39 Temporal.Instant
|
|
15
|
+
// at runtime, distinct nominal types across the two .d.ts sources (see
|
|
16
|
+
// event-store.ts). Only ever consumed via the `lte` comparison below.
|
|
10
17
|
const cutoff =
|
|
11
18
|
typeof gracePeriodEndCutoff === "string"
|
|
12
|
-
?
|
|
19
|
+
? TemporalPolyfill.Instant.from(gracePeriodEndCutoff)
|
|
13
20
|
: gracePeriodEndCutoff;
|
|
14
21
|
return selectMany<{ id: string }>(db, userTable, {
|
|
15
22
|
status,
|
|
@@ -117,7 +117,7 @@ export function renderExportReadyEmail(args: RenderExportReadyEmailArgs): Render
|
|
|
117
117
|
<p style="margin: 0 0 24px;">${renderButton({ url: args.downloadUrl, label: t.exportReadyButton })}</p>
|
|
118
118
|
<p style="margin: 0 0 8px; font-size: 13px; color: #555;">${escapeHtml(t.exportReadyExpiry(formatTimestamp(args.expiresAt)))}</p>
|
|
119
119
|
${renderFallbackUrl({ url: args.downloadUrl, label: t.fallbackUrl })}`;
|
|
120
|
-
return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body) }) };
|
|
120
|
+
return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }) };
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
export function renderExportFailedEmail(args: RenderExportFailedEmailArgs): RenderedEmail {
|
|
@@ -128,7 +128,7 @@ export function renderExportFailedEmail(args: RenderExportFailedEmailArgs): Rend
|
|
|
128
128
|
const body = `
|
|
129
129
|
<p style="margin: 0 0 16px; font-size: 16px;">${escapeHtml(t.greeting)}</p>
|
|
130
130
|
<p style="margin: 0; font-size: 14px; line-height: 1.5;">${escapeHtml(t.exportFailedIntro(app))}</p>`;
|
|
131
|
-
return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body) }) };
|
|
131
|
+
return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }) };
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
export function renderDeletionRequestedEmail(
|
|
@@ -142,7 +142,7 @@ export function renderDeletionRequestedEmail(
|
|
|
142
142
|
<p style="margin: 0 0 16px; font-size: 16px;">${escapeHtml(t.greeting)}</p>
|
|
143
143
|
<p style="margin: 0 0 16px; font-size: 14px; line-height: 1.5;">${escapeHtml(t.deletionRequestedIntro(app, formatTimestamp(args.gracePeriodEnd)))}</p>
|
|
144
144
|
<p style="margin: 0; font-size: 13px; color: #555;">${escapeHtml(t.deletionRequestedCancel)}</p>`;
|
|
145
|
-
return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body) }) };
|
|
145
|
+
return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }) };
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
export function renderDeletionExecutedEmail(args: RenderDeletionExecutedEmailArgs): RenderedEmail {
|
|
@@ -153,7 +153,7 @@ export function renderDeletionExecutedEmail(args: RenderDeletionExecutedEmailArg
|
|
|
153
153
|
const body = `
|
|
154
154
|
<p style="margin: 0 0 16px; font-size: 16px;">${escapeHtml(t.greeting)}</p>
|
|
155
155
|
<p style="margin: 0; font-size: 14px; line-height: 1.5;">${escapeHtml(t.deletionExecutedIntro(app, formatTimestamp(args.executedAt)))}</p>`;
|
|
156
|
-
return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body) }) };
|
|
156
|
+
return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }) };
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
function wrapCell(bodyHtml: string): string {
|
|
@@ -162,9 +162,9 @@ function wrapCell(bodyHtml: string): string {
|
|
|
162
162
|
|
|
163
163
|
// Plain inline-styled HTML — gespiegelt von auth-email-password.
|
|
164
164
|
// guard:dup-ok — Email-HTML (table-layout, inline CSS) ≠ Web-HTML (legal-pages/markdown.ts)
|
|
165
|
-
function renderShell(args: { title: string; bodyHtml: string }): string {
|
|
165
|
+
function renderShell(args: { title: string; bodyHtml: string; locale: GdprMailLocale }): string {
|
|
166
166
|
return `<!DOCTYPE html>
|
|
167
|
-
<html lang="
|
|
167
|
+
<html lang="${args.locale}">
|
|
168
168
|
<head>
|
|
169
169
|
<meta charset="utf-8" />
|
|
170
170
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type FeatureDefinition,
|
|
6
6
|
SYSTEM_USER_ID,
|
|
7
7
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
8
|
+
import { validateGdprHookCompleteness, validateGdprPiiHookCoverage } from "./boot-checks";
|
|
8
9
|
import { PRIVACY_CENTER_SCREEN_ID } from "./constants";
|
|
9
10
|
import { cancelDeletionWrite } from "./handlers/cancel-deletion.write";
|
|
10
11
|
import { createConfirmDeletionByTokenHandler } from "./handlers/confirm-deletion-by-token.write";
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
import { requestExportWrite } from "./handlers/request-export.write";
|
|
29
30
|
import { restrictAccountWrite } from "./handlers/restrict-account.write";
|
|
30
31
|
import { createRunForgetCleanupHandler } from "./handlers/run-forget-cleanup.write";
|
|
32
|
+
import { USER_DATA_RIGHTS_I18N } from "./i18n";
|
|
31
33
|
import {
|
|
32
34
|
type GdprMailDefaults,
|
|
33
35
|
isMailTransportAvailable,
|
|
@@ -43,7 +45,11 @@ import {
|
|
|
43
45
|
type SendExportFailedEmailFn,
|
|
44
46
|
type SendExportReadyEmailFn,
|
|
45
47
|
} from "./run-export-jobs";
|
|
46
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
runForgetCleanup,
|
|
50
|
+
type SendDeletionExecutedEmailFn,
|
|
51
|
+
sanitizeReasonForLog,
|
|
52
|
+
} from "./run-forget-cleanup";
|
|
47
53
|
import { downloadAttemptEntity } from "./schema/download-attempt";
|
|
48
54
|
import { exportDownloadTokenEntity } from "./schema/download-token";
|
|
49
55
|
import { exportJobEntity } from "./schema/export-job";
|
|
@@ -86,7 +92,7 @@ export type UserDataRightsOptions = {
|
|
|
86
92
|
readonly sendExportFailedEmail?: SendExportFailedEmailFn;
|
|
87
93
|
/** Base-URL fuer den Magic-Link, z.B.
|
|
88
94
|
* "https://app.example.com/user-export/by-token". Worker bauen
|
|
89
|
-
*
|
|
95
|
+
* a URL fragment (`#token=<plain>`, issue #1271). Required wenn
|
|
90
96
|
* sendExportReadyEmail gesetzt. Per-Tenant via reverse-proxy host
|
|
91
97
|
* routing — nicht via per-Tenant-config-key (App-Author-Decision). */
|
|
92
98
|
readonly appExportDownloadUrl?: string;
|
|
@@ -154,20 +160,26 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
154
160
|
|
|
155
161
|
r.extendsRegistrar(EXT_USER_DATA, {});
|
|
156
162
|
|
|
163
|
+
// GDPR-storage guards V2+V3 (#1314) — moved off the framework-internal
|
|
164
|
+
// boot-validator onto this feature's own r.bootCheck(), since it's the
|
|
165
|
+
// EXT_USER_DATA channel owner: any r.useExtension(EXT_USER_DATA, ...)
|
|
166
|
+
// usage already requires user-data-rights to be mounted.
|
|
167
|
+
r.bootCheck(({ features }) => validateGdprHookCompleteness(features));
|
|
168
|
+
r.bootCheck(({ features }) => validateGdprPiiHookCoverage(features));
|
|
169
|
+
|
|
157
170
|
// App-level tenant-occupancy model. Default "multi-user" → tenant-scoped
|
|
158
171
|
// contributors (e.g. credit) NEVER erase tenant data on a per-user forget
|
|
159
172
|
// (would harm co-members). An app with one user per tenant sets this to
|
|
160
173
|
// "single-user" via appOverrides (TENANT_MODEL_CONFIG_KEY) so the forget
|
|
161
174
|
// pipeline may erase the tenant's data as that user's personal data — still
|
|
162
175
|
// gated by a runtime sole-member check in run-forget-cleanup.
|
|
163
|
-
r.config(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
});
|
|
176
|
+
r.config(
|
|
177
|
+
"tenantModel",
|
|
178
|
+
createSystemConfig("select", {
|
|
179
|
+
default: "multi-user",
|
|
180
|
+
options: ["single-user", "multi-user"],
|
|
181
|
+
}),
|
|
182
|
+
);
|
|
171
183
|
|
|
172
184
|
// S2.U3 Atom 1b — ExportJob-Lifecycle-Entity.
|
|
173
185
|
r.entity("export-job", exportJobEntity);
|
|
@@ -249,9 +261,7 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
249
261
|
r.queryHandler(myAuditLogQuery);
|
|
250
262
|
r.queryHandler(listDownloadAttemptsQuery);
|
|
251
263
|
|
|
252
|
-
// Read-only operator inspector
|
|
253
|
-
// Convention list/detail handlers so entityList/entityEdit resolve by QN;
|
|
254
|
-
// the screens stay inert until an app navs them (opt-in at wire time).
|
|
264
|
+
// Read-only GDPR operator inspector (SystemAdmin) — screens stay inert until an app navs them.
|
|
255
265
|
r.queryHandler(exportJobListQuery);
|
|
256
266
|
r.queryHandler(exportJobDetailQuery);
|
|
257
267
|
r.queryHandler(downloadAttemptListQuery);
|
|
@@ -259,6 +269,17 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
259
269
|
r.screen(exportJobDetailScreen);
|
|
260
270
|
r.screen(downloadAttemptListScreen);
|
|
261
271
|
|
|
272
|
+
r.nav({
|
|
273
|
+
id: "export-job-list",
|
|
274
|
+
label: "user-data-rights:nav.exportJobs",
|
|
275
|
+
icon: "download",
|
|
276
|
+
screen: "user-data-rights:screen:export-job-list",
|
|
277
|
+
order: 25,
|
|
278
|
+
access: { roles: ["SystemAdmin"] },
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
r.translations({ keys: USER_DATA_RIGHTS_I18N });
|
|
282
|
+
|
|
262
283
|
// Dormant Self-Service-Screen (Art. 15/17/18/20): Export, Aktivitäts-
|
|
263
284
|
// protokoll, Einschränkung, Löschung in einem Screen. Kein r.nav — die
|
|
264
285
|
// App platziert ihn im eingeloggten Bereich. Die React-Component kommt
|
|
@@ -273,10 +294,20 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
273
294
|
access: { openToAll: true },
|
|
274
295
|
});
|
|
275
296
|
|
|
276
|
-
// Magic-
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
//
|
|
297
|
+
// Magic-link path (anonymous): the email link carries the token as a
|
|
298
|
+
// URL fragment (`#token=<plain>`) instead of a query param — fragments
|
|
299
|
+
// never leave the browser, so they never hit proxy/access logs
|
|
300
|
+
// (issue #1271). This route only serves the interstitial page; the
|
|
301
|
+
// external script reads the fragment client-side and exchanges the
|
|
302
|
+
// token via POST for the signed URL.
|
|
303
|
+
//
|
|
304
|
+
// No `?token=` query fallback: framework is zero-legacy (#1512) — no
|
|
305
|
+
// live customer data / no pre-#1271 emails in flight that would need
|
|
306
|
+
// bridging, so there's nothing a fallback would need to cover. A query
|
|
307
|
+
// param reaches the server as part of the request URL and gets written
|
|
308
|
+
// to proxy/access logs regardless of what the handler does with it
|
|
309
|
+
// afterward (a redirect doesn't undo an already-logged request) — the
|
|
310
|
+
// only leak-proof fix is to never accept the token that way.
|
|
280
311
|
//
|
|
281
312
|
// Path liegt AUSSERHALB /api/* weil r.httpRoute den /api-namespace nicht
|
|
282
313
|
// claimen darf (reserved fuer write/query/batch/auth/sse-dispatcher).
|
|
@@ -289,23 +320,76 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
289
320
|
method: "GET",
|
|
290
321
|
path: "/user-export/by-token",
|
|
291
322
|
anonymous: true,
|
|
323
|
+
handler: async (c) => {
|
|
324
|
+
return c.body(TOKEN_EXCHANGE_PAGE_HTML, 200, {
|
|
325
|
+
"content-type": "text/html; charset=utf-8",
|
|
326
|
+
});
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// External script for the interstitial page above — same-origin file
|
|
331
|
+
// instead of inline <script>, so the page also works under a strict
|
|
332
|
+
// `script-src 'self'` CSP (apps set CSP via the security-headers
|
|
333
|
+
// option, without assuming `unsafe-inline`).
|
|
334
|
+
r.httpRoute({
|
|
335
|
+
method: "GET",
|
|
336
|
+
path: "/user-export/by-token.js",
|
|
337
|
+
anonymous: true,
|
|
338
|
+
handler: async (c) => {
|
|
339
|
+
return c.body(TOKEN_EXCHANGE_PAGE_JS, 200, {
|
|
340
|
+
"content-type": "application/javascript; charset=utf-8",
|
|
341
|
+
});
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// Exchange endpoint for the interstitial page: the token arrives here
|
|
346
|
+
// as a POST body instead of a query string (issue #1271). A dedicated
|
|
347
|
+
// route rather than letting the browser JS call /api/query directly,
|
|
348
|
+
// for two reasons:
|
|
349
|
+
// 1. IP/UA audit — extractAuditMeta needs the raw request headers,
|
|
350
|
+
// which only the server has, not the browser (see the GET path
|
|
351
|
+
// above). Without this route every fragment-path download would
|
|
352
|
+
// land in download-attempt with lastUsedFromIp/UA = null.
|
|
353
|
+
// 2. /api/query is the reserved dispatcher namespace for
|
|
354
|
+
// authenticated writes/queries — an anonymous cross-origin POST
|
|
355
|
+
// there isn't guaranteed to be reachable (origin guard).
|
|
356
|
+
r.httpRoute({
|
|
357
|
+
method: "POST",
|
|
358
|
+
path: "/user-export/by-token",
|
|
359
|
+
anonymous: true,
|
|
292
360
|
handler: async (c, { app }) => {
|
|
293
361
|
const url = new URL(c.req.url);
|
|
294
|
-
const
|
|
295
|
-
|
|
362
|
+
const parsedBody = await c.req.json().catch(() => null);
|
|
363
|
+
const token =
|
|
364
|
+
parsedBody && typeof parsedBody === "object" && "token" in parsedBody
|
|
365
|
+
? (parsedBody as { token?: unknown }).token
|
|
366
|
+
: undefined;
|
|
367
|
+
if (typeof token !== "string" || token.length === 0) {
|
|
296
368
|
return c.json({ error: "missing_token" }, 400);
|
|
297
369
|
}
|
|
298
|
-
const
|
|
370
|
+
const auditMeta = extractAuditMeta(c.req.raw.headers);
|
|
371
|
+
return app.fetch(
|
|
299
372
|
new Request(`${url.origin}/api/query`, {
|
|
300
373
|
method: "POST",
|
|
301
|
-
headers: {
|
|
374
|
+
headers: {
|
|
375
|
+
"content-type": "application/json",
|
|
376
|
+
// download-by-token.query.ts's `rateLimit: { per: "ip", limit: 30 }`
|
|
377
|
+
// keys off request-id-middleware's own x-forwarded-for read on
|
|
378
|
+
// THIS internal request — without forwarding it, every fragment-
|
|
379
|
+
// POST download shares the request's own (empty/localhost) IP,
|
|
380
|
+
// turning the per-user 30/min backstop into one global bucket
|
|
381
|
+
// (#1307). Forwarding the same value extractAuditMeta already
|
|
382
|
+
// computed from the original request's headers doesn't widen the
|
|
383
|
+
// trust boundary: it's the identical XFF-trusts-first-hop model
|
|
384
|
+
// request-id-middleware.ts already applies framework-wide.
|
|
385
|
+
...(auditMeta.ip ? { "x-forwarded-for": auditMeta.ip } : {}),
|
|
386
|
+
},
|
|
302
387
|
body: JSON.stringify({
|
|
303
388
|
type: "user-data-rights:query:download-by-token",
|
|
304
|
-
payload: { token, auditMeta
|
|
389
|
+
payload: { token, auditMeta },
|
|
305
390
|
}),
|
|
306
391
|
}),
|
|
307
392
|
);
|
|
308
|
-
return mapQueryResponseToRedirect(c, queryRes);
|
|
309
393
|
},
|
|
310
394
|
});
|
|
311
395
|
|
|
@@ -435,7 +519,7 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
435
519
|
? makeDefaultDeletionExecutedEmail(forgetMailResolver, opts.mailDefaults)
|
|
436
520
|
: undefined);
|
|
437
521
|
|
|
438
|
-
await runForgetCleanup({
|
|
522
|
+
const forgetResult = await runForgetCleanup({
|
|
439
523
|
db: forgetDb,
|
|
440
524
|
registry: forgetRegistry,
|
|
441
525
|
now: T.Now.instant(),
|
|
@@ -452,30 +536,24 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
452
536
|
}),
|
|
453
537
|
...(sendDeletionExecutedEmail && { sendDeletionExecutedEmail }),
|
|
454
538
|
});
|
|
539
|
+
|
|
540
|
+
// Persist each incomplete report on the job-run log (#1572). The write
|
|
541
|
+
// handler returns incomplete[] in its response; the cron path used to
|
|
542
|
+
// discard the array (only a console.warn for rolledBack cases).
|
|
543
|
+
// ctx.log feeds createJobRunLogger → store_job_run_logs.
|
|
544
|
+
for (const entry of forgetResult.incomplete) {
|
|
545
|
+
ctx.log?.warn?.(
|
|
546
|
+
`[user-data-rights:run-forget-cleanup] incomplete hook ` +
|
|
547
|
+
`userId=${entry.userId} tenantId=${entry.tenantId} ` +
|
|
548
|
+
`entityName=${entry.entityName} rolledBack=${entry.rolledBack} ` +
|
|
549
|
+
`reason=${sanitizeReasonForLog(entry.reason)}`,
|
|
550
|
+
);
|
|
551
|
+
}
|
|
455
552
|
},
|
|
456
553
|
);
|
|
457
554
|
});
|
|
458
555
|
}
|
|
459
556
|
|
|
460
|
-
// Map /api/query-Response auf 302-Redirect oder Error-Passthrough.
|
|
461
|
-
async function mapQueryResponseToRedirect(
|
|
462
|
-
c: import("hono").Context,
|
|
463
|
-
queryRes: Response,
|
|
464
|
-
): Promise<Response> {
|
|
465
|
-
if (!queryRes.ok) {
|
|
466
|
-
const errorBody = await queryRes.text();
|
|
467
|
-
const statusCode = queryRes.status as 400 | 401 | 404 | 410 | 500; // @cast-boundary engine-payload
|
|
468
|
-
return c.body(errorBody, statusCode, {
|
|
469
|
-
"content-type": queryRes.headers.get("content-type") ?? "application/json",
|
|
470
|
-
});
|
|
471
|
-
}
|
|
472
|
-
const body = (await queryRes.json()) as { data?: { url?: string } }; // @cast-boundary engine-payload
|
|
473
|
-
if (!body.data?.url) {
|
|
474
|
-
return c.json({ error: "download_resolution_failed" }, 500);
|
|
475
|
-
}
|
|
476
|
-
return c.redirect(body.data.url, 302);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
557
|
// Extract Audit-Meta (IP + UA) aus den HTTP-Headers + steck es in die
|
|
480
558
|
// query-payload. Der httpRoute-Wrapper ist trusted-source — er hat den
|
|
481
559
|
// raw-request gesehen, nicht der direkter /api/query-Caller. User der
|
|
@@ -495,3 +573,44 @@ function extractAuditMeta(headers: Headers): { ip: string | null; userAgent: str
|
|
|
495
573
|
}
|
|
496
574
|
return { ip, userAgent: headers.get("user-agent") };
|
|
497
575
|
}
|
|
576
|
+
|
|
577
|
+
// Interstitial page for the magic-link fragment exchange (see the
|
|
578
|
+
// httpRoute above). Plain static markup — no build step, no i18n lookup
|
|
579
|
+
// (the server doesn't know a user locale here; anonymous path).
|
|
580
|
+
const TOKEN_EXCHANGE_PAGE_HTML = `<!doctype html>
|
|
581
|
+
<html lang="en">
|
|
582
|
+
<head><meta charset="utf-8"><title>Export Download</title></head>
|
|
583
|
+
<body>
|
|
584
|
+
<p id="status">Preparing your download…</p>
|
|
585
|
+
<script src="/user-export/by-token.js"></script>
|
|
586
|
+
</body>
|
|
587
|
+
</html>`;
|
|
588
|
+
|
|
589
|
+
// Reads the token client-side from the URL fragment (never sent to the
|
|
590
|
+
// server) and exchanges it via POST for the signed URL. Same-origin
|
|
591
|
+
// external file instead of inline <script>, so the page also works under
|
|
592
|
+
// a strict `script-src 'self'` CSP.
|
|
593
|
+
const TOKEN_EXCHANGE_PAGE_JS = `(async () => {
|
|
594
|
+
const status = document.getElementById("status");
|
|
595
|
+
const params = new URLSearchParams(location.hash.slice(1));
|
|
596
|
+
const token = params.get("token");
|
|
597
|
+
if (!token) {
|
|
598
|
+
status.textContent = "Invalid download link.";
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
try {
|
|
602
|
+
const res = await fetch("/user-export/by-token", {
|
|
603
|
+
method: "POST",
|
|
604
|
+
headers: { "content-type": "application/json" },
|
|
605
|
+
body: JSON.stringify({ token }),
|
|
606
|
+
});
|
|
607
|
+
const body = await res.json();
|
|
608
|
+
if (!res.ok || !body || !body.data || !body.data.url) {
|
|
609
|
+
status.textContent = "This download link is invalid or has expired.";
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
location.href = body.data.url;
|
|
613
|
+
} catch {
|
|
614
|
+
status.textContent = "Could not start the download.";
|
|
615
|
+
}
|
|
616
|
+
})();`;
|
|
@@ -2,9 +2,12 @@ import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
|
2
2
|
import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
3
|
import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { isWithinGracePeriod } from "../../shared";
|
|
5
6
|
import { USER_STATUS, userTable } from "../../user";
|
|
6
7
|
import { updateUserLifecycle } from "../lib/update-user-lifecycle";
|
|
7
8
|
|
|
9
|
+
export { isWithinGracePeriod };
|
|
10
|
+
|
|
8
11
|
// POST /api/user/cancel-deletion (S2.U5).
|
|
9
12
|
//
|
|
10
13
|
// Innerhalb der Grace-Period kann User seinen Forget-Antrag zurueck-
|
|
@@ -24,11 +27,10 @@ export const cancelDeletionWrite = defineWriteHandler({
|
|
|
24
27
|
// ctx.db.raw (kein TenantDb-Wrapper) weil User-Entity tenant-agnostisch
|
|
25
28
|
// ist — siehe request-deletion.write.ts fuer die Begruendung. Cancel
|
|
26
29
|
// muss aus jedem Tenant-Mode den User finden + zuruecksetzen koennen.
|
|
27
|
-
const row = await fetchOne<{
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
);
|
|
30
|
+
const row = await fetchOne<{
|
|
31
|
+
status: string;
|
|
32
|
+
gracePeriodEnd: Temporal.Instant | null;
|
|
33
|
+
}>(ctx.db.raw, userTable, { id: event.user.id });
|
|
32
34
|
|
|
33
35
|
if (!row) {
|
|
34
36
|
return writeFailure(
|
|
@@ -49,12 +51,7 @@ export const cancelDeletionWrite = defineWriteHandler({
|
|
|
49
51
|
);
|
|
50
52
|
}
|
|
51
53
|
|
|
52
|
-
|
|
53
|
-
const inGrace =
|
|
54
|
-
gracePeriodEnd != null &&
|
|
55
|
-
Temporal.Instant.compare(gracePeriodEnd, Temporal.Now.instant()) > 0;
|
|
56
|
-
|
|
57
|
-
if (!inGrace) {
|
|
54
|
+
if (!isWithinGracePeriod(row.gracePeriodEnd)) {
|
|
58
55
|
return writeFailure(
|
|
59
56
|
new UnprocessableError("grace_period_expired", {
|
|
60
57
|
details: { reason: "grace_period_expired" },
|
|
@@ -3,6 +3,7 @@ import { addDurationSpec, type DurationSpec } from "@cosmicdrift/kumiko-framewor
|
|
|
3
3
|
import { createSystemUser, type HandlerContext } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
4
|
import { UnprocessableError } from "@cosmicdrift/kumiko-framework/errors";
|
|
5
5
|
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
6
|
+
import { decryptStoredPii } from "../../shared";
|
|
6
7
|
import { USER_STATUS, userTable } from "../../user";
|
|
7
8
|
import { updateUserLifecycle } from "../lib/update-user-lifecycle";
|
|
8
9
|
|
|
@@ -73,7 +74,9 @@ export async function startDeletionGracePeriod(
|
|
|
73
74
|
return {
|
|
74
75
|
ok: true,
|
|
75
76
|
gracePeriodEnd,
|
|
76
|
-
userEmail: userRow["email"]
|
|
77
|
+
userEmail: userRow["email"]
|
|
78
|
+
? await decryptStoredPii(userRow["email"], "email", "user-data-rights:grace-period")
|
|
79
|
+
: "",
|
|
77
80
|
userLocale: userRow["locale"] ?? null,
|
|
78
81
|
};
|
|
79
82
|
}
|
|
@@ -25,12 +25,16 @@
|
|
|
25
25
|
|
|
26
26
|
import { requestContext } from "@cosmicdrift/kumiko-framework/api";
|
|
27
27
|
import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
defineQueryHandler,
|
|
30
|
+
SYSTEM_USER_ID,
|
|
31
|
+
type TenantId,
|
|
32
|
+
} from "@cosmicdrift/kumiko-framework/engine";
|
|
29
33
|
import { NotFoundError, UnprocessableError } from "@cosmicdrift/kumiko-framework/errors";
|
|
30
34
|
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
31
35
|
import { z } from "zod";
|
|
32
|
-
import { createFileProviderForTenant } from "../../file-foundation";
|
|
33
36
|
import { recordDownloadUse, recordInvalidAttempt } from "../audit-download";
|
|
37
|
+
import { makeTenantStorageProviderResolver } from "../lib/storage-provider-resolver";
|
|
34
38
|
import { exportDownloadTokensTable } from "../schema/download-token";
|
|
35
39
|
import { EXPORT_JOB_STATUS, exportJobsTable } from "../schema/export-job";
|
|
36
40
|
|
|
@@ -63,13 +67,11 @@ export const downloadByJobQuery = defineQueryHandler({
|
|
|
63
67
|
const userId = query.user.id;
|
|
64
68
|
const jobId = query.payload.jobId;
|
|
65
69
|
const tenantId = query.user.tenantId;
|
|
66
|
-
// IP aus dem request-scoped Kontext (von requestIdMiddleware
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
// laesst sie null (best-effort, via requestId in den Server-Logs
|
|
70
|
-
// cross-referenzierbar).
|
|
70
|
+
// IP + UA aus dem request-scoped Kontext (von requestIdMiddleware
|
|
71
|
+
// befuellt) — server-trusted, anders als ein vom Client mitgeschickter
|
|
72
|
+
// Wert (603/2).
|
|
71
73
|
const auditIp = requestContext.get()?.ip ?? null;
|
|
72
|
-
const auditUa
|
|
74
|
+
const auditUa = requestContext.get()?.userAgent ?? null;
|
|
73
75
|
|
|
74
76
|
// Step 1-2: job-lookup + cross-user-isolation
|
|
75
77
|
// ctx.db.raw weil tenant-agnostisch — Alice in Tenant B sucht den
|
|
@@ -78,7 +80,7 @@ export const downloadByJobQuery = defineQueryHandler({
|
|
|
78
80
|
|
|
79
81
|
if (!jobRow || jobRow.userId !== userId) {
|
|
80
82
|
await recordInvalidAttempt({
|
|
81
|
-
db: ctx.db.raw,
|
|
83
|
+
db: ctx.db.raw as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,
|
|
82
84
|
tenantId,
|
|
83
85
|
now,
|
|
84
86
|
result: "notFound",
|
|
@@ -96,7 +98,7 @@ export const downloadByJobQuery = defineQueryHandler({
|
|
|
96
98
|
|
|
97
99
|
if (jobRow.status !== EXPORT_JOB_STATUS.Done) {
|
|
98
100
|
await recordInvalidAttempt({
|
|
99
|
-
db: ctx.db.raw,
|
|
101
|
+
db: ctx.db.raw as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,
|
|
100
102
|
tenantId,
|
|
101
103
|
now,
|
|
102
104
|
result: "failed",
|
|
@@ -113,7 +115,7 @@ export const downloadByJobQuery = defineQueryHandler({
|
|
|
113
115
|
}
|
|
114
116
|
if (!jobRow.downloadStorageKey) {
|
|
115
117
|
await recordInvalidAttempt({
|
|
116
|
-
db: ctx.db.raw,
|
|
118
|
+
db: ctx.db.raw as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,
|
|
117
119
|
tenantId,
|
|
118
120
|
now,
|
|
119
121
|
result: "expired",
|
|
@@ -129,14 +131,20 @@ export const downloadByJobQuery = defineQueryHandler({
|
|
|
129
131
|
});
|
|
130
132
|
}
|
|
131
133
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
// Provider bound explicitly to the job's tenant — NOT the ambient
|
|
135
|
+
// session tenant (cross-tenant-same-user: job.requestedFromTenantId
|
|
136
|
+
// can differ from query.user.tenantId).
|
|
137
|
+
const provider = await makeTenantStorageProviderResolver({
|
|
138
|
+
registry: ctx.registry,
|
|
139
|
+
configResolver: ctx.configResolver,
|
|
140
|
+
secrets: ctx.secrets,
|
|
141
|
+
db: ctx.db.raw as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,
|
|
142
|
+
userId: SYSTEM_USER_ID,
|
|
143
|
+
handlerName: "user-data-rights:query:download-by-job",
|
|
144
|
+
})(jobRow.requestedFromTenantId as TenantId); // @cast-boundary engine-payload: TenantId brand
|
|
137
145
|
if (!provider.getSignedUrl) {
|
|
138
146
|
await recordInvalidAttempt({
|
|
139
|
-
db: ctx.db.raw,
|
|
147
|
+
db: ctx.db.raw as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,
|
|
140
148
|
tenantId,
|
|
141
149
|
now,
|
|
142
150
|
result: "signedUrlNotSupported",
|
|
@@ -173,7 +181,7 @@ export const downloadByJobQuery = defineQueryHandler({
|
|
|
173
181
|
|
|
174
182
|
if (tokenRow) {
|
|
175
183
|
await recordDownloadUse({
|
|
176
|
-
db: ctx.db.raw,
|
|
184
|
+
db: ctx.db.raw as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,
|
|
177
185
|
tokenId: tokenRow.id,
|
|
178
186
|
tokenVersion: tokenRow.version,
|
|
179
187
|
tokenUseCount: tokenRow.useCount ?? 0,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// GET /api/query → user-data-rights:query:download-by-token (S2.U3 Atom 4b).
|
|
2
2
|
//
|
|
3
3
|
// **Magic-Link-Pfad** (anonymous): User klickt Email-Link mit
|
|
4
|
-
//
|
|
4
|
+
// `#token=<plain>` (URL fragment, issue #1271). Worker hat beim done-flip Token-Hash in DB
|
|
5
5
|
// persistiert (Atom 4a). Verify-Pipeline:
|
|
6
6
|
//
|
|
7
7
|
// 1. hashDownloadToken(plain) → fetchOne in download-tokens
|
|
@@ -27,12 +27,16 @@
|
|
|
27
27
|
// startet. Dieser query-handler liefert nur das JSON.
|
|
28
28
|
|
|
29
29
|
import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
30
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
defineQueryHandler,
|
|
32
|
+
SYSTEM_USER_ID,
|
|
33
|
+
type TenantId,
|
|
34
|
+
} from "@cosmicdrift/kumiko-framework/engine";
|
|
31
35
|
import { NotFoundError, UnprocessableError } from "@cosmicdrift/kumiko-framework/errors";
|
|
32
36
|
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
33
37
|
import { z } from "zod";
|
|
34
|
-
import { createFileProviderForTenant } from "../../file-foundation";
|
|
35
38
|
import { recordDownloadUse, recordInvalidAttempt } from "../audit-download";
|
|
39
|
+
import { makeTenantStorageProviderResolver } from "../lib/storage-provider-resolver";
|
|
36
40
|
import { exportDownloadTokensTable } from "../schema/download-token";
|
|
37
41
|
import { EXPORT_JOB_STATUS, exportJobsTable } from "../schema/export-job";
|
|
38
42
|
import { hashDownloadToken } from "../token-helpers";
|
|
@@ -119,7 +123,7 @@ export const downloadByTokenQuery = defineQueryHandler({
|
|
|
119
123
|
);
|
|
120
124
|
if (jobForAudit) {
|
|
121
125
|
await recordInvalidAttempt({
|
|
122
|
-
db: ctx.db.raw,
|
|
126
|
+
db: ctx.db.raw as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,
|
|
123
127
|
tenantId: jobForAudit.requestedFromTenantId,
|
|
124
128
|
now,
|
|
125
129
|
result: "expired",
|
|
@@ -148,7 +152,7 @@ export const downloadByTokenQuery = defineQueryHandler({
|
|
|
148
152
|
}
|
|
149
153
|
if (jobRow.status !== EXPORT_JOB_STATUS.Done) {
|
|
150
154
|
await recordInvalidAttempt({
|
|
151
|
-
db: ctx.db.raw,
|
|
155
|
+
db: ctx.db.raw as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,
|
|
152
156
|
tenantId: jobRow.requestedFromTenantId,
|
|
153
157
|
now,
|
|
154
158
|
result: "failed",
|
|
@@ -165,7 +169,7 @@ export const downloadByTokenQuery = defineQueryHandler({
|
|
|
165
169
|
}
|
|
166
170
|
if (!jobRow.downloadStorageKey) {
|
|
167
171
|
await recordInvalidAttempt({
|
|
168
|
-
db: ctx.db.raw,
|
|
172
|
+
db: ctx.db.raw as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,
|
|
169
173
|
tenantId: jobRow.requestedFromTenantId,
|
|
170
174
|
now,
|
|
171
175
|
result: "expired",
|
|
@@ -181,16 +185,20 @@ export const downloadByTokenQuery = defineQueryHandler({
|
|
|
181
185
|
});
|
|
182
186
|
}
|
|
183
187
|
|
|
184
|
-
// Step 5: signed-URL via provider
|
|
185
|
-
//
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
188
|
+
// Step 5: signed-URL via provider, explicitly bound to the job's
|
|
189
|
+
// tenant — NOT the ambient request tenant (anonymous magic-link path
|
|
190
|
+
// has none under resolverTrust: "authoritative").
|
|
191
|
+
const provider = await makeTenantStorageProviderResolver({
|
|
192
|
+
registry: ctx.registry,
|
|
193
|
+
configResolver: ctx.configResolver,
|
|
194
|
+
secrets: ctx.secrets,
|
|
195
|
+
db: ctx.db.raw as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,
|
|
196
|
+
userId: SYSTEM_USER_ID,
|
|
197
|
+
handlerName: "user-data-rights:query:download-by-token",
|
|
198
|
+
})(jobRow.requestedFromTenantId as TenantId); // @cast-boundary engine-payload: TenantId brand
|
|
191
199
|
if (!provider.getSignedUrl) {
|
|
192
200
|
await recordInvalidAttempt({
|
|
193
|
-
db: ctx.db.raw,
|
|
201
|
+
db: ctx.db.raw as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,
|
|
194
202
|
tenantId: jobRow.requestedFromTenantId,
|
|
195
203
|
now,
|
|
196
204
|
result: "signedUrlNotSupported",
|
|
@@ -221,7 +229,7 @@ export const downloadByTokenQuery = defineQueryHandler({
|
|
|
221
229
|
// Wrapper (trusted-source). Direct-API-caller koennen luegen, aber
|
|
222
230
|
// Audit ist nicht security-relevant.
|
|
223
231
|
await recordDownloadUse({
|
|
224
|
-
db: ctx.db.raw,
|
|
232
|
+
db: ctx.db.raw as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,
|
|
225
233
|
tokenId: tokenRow.id,
|
|
226
234
|
tokenVersion: tokenRow.version,
|
|
227
235
|
tokenUseCount: tokenRow.useCount ?? 0,
|