@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,4 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
assertSafeStorageKey,
|
|
3
|
+
type FileStorageProvider,
|
|
4
|
+
type SignedUrlOptions,
|
|
5
|
+
} from "@cosmicdrift/kumiko-framework/files";
|
|
2
6
|
|
|
3
7
|
// =============================================================================
|
|
4
8
|
// Operator-Pflicht-Setup (Multipart-Upload-Cleanup)
|
|
@@ -72,10 +76,12 @@ export function createS3Provider(config: S3ProviderConfig): FileStorageProvider
|
|
|
72
76
|
|
|
73
77
|
return {
|
|
74
78
|
async write(key, data, mimeType): Promise<void> {
|
|
79
|
+
assertSafeStorageKey(key);
|
|
75
80
|
await client.write(key, data, mimeType !== undefined ? { type: mimeType } : undefined);
|
|
76
81
|
},
|
|
77
82
|
|
|
78
83
|
async writeStream(key, source, options): Promise<void> {
|
|
84
|
+
assertSafeStorageKey(key);
|
|
79
85
|
// Echtes multipart-streaming via Bun's S3-Writer — partSize steuert die
|
|
80
86
|
// Part-Boundary intern (AWS/R2 verlangen non-final Parts >= 5 MiB,
|
|
81
87
|
// sonst EntityTooSmall beim CompleteMultipartUpload). Manuelles flush()
|
|
@@ -96,10 +102,12 @@ export function createS3Provider(config: S3ProviderConfig): FileStorageProvider
|
|
|
96
102
|
},
|
|
97
103
|
|
|
98
104
|
async read(key): Promise<Uint8Array> {
|
|
105
|
+
assertSafeStorageKey(key);
|
|
99
106
|
return new Uint8Array(await client.file(key).arrayBuffer());
|
|
100
107
|
},
|
|
101
108
|
|
|
102
109
|
readStream(key): AsyncIterable<Uint8Array> {
|
|
110
|
+
assertSafeStorageKey(key);
|
|
103
111
|
// Lazy: erst beim ersten chunk-pull wird der GET-Request abgesetzt.
|
|
104
112
|
// Existiert der Key nicht, faellt der Error genau dort (nicht beim
|
|
105
113
|
// readStream-Aufruf) — gleiches Lazy-Verhalten wie inmemory + local.
|
|
@@ -113,10 +121,12 @@ export function createS3Provider(config: S3ProviderConfig): FileStorageProvider
|
|
|
113
121
|
},
|
|
114
122
|
|
|
115
123
|
async delete(key): Promise<void> {
|
|
124
|
+
assertSafeStorageKey(key);
|
|
116
125
|
await client.delete(key);
|
|
117
126
|
},
|
|
118
127
|
|
|
119
128
|
async exists(key): Promise<boolean> {
|
|
129
|
+
assertSafeStorageKey(key);
|
|
120
130
|
return client.exists(key);
|
|
121
131
|
},
|
|
122
132
|
|
|
@@ -125,6 +135,7 @@ export function createS3Provider(config: S3ProviderConfig): FileStorageProvider
|
|
|
125
135
|
expiresInSeconds: number,
|
|
126
136
|
options?: SignedUrlOptions,
|
|
127
137
|
): Promise<string> {
|
|
138
|
+
assertSafeStorageKey(key);
|
|
128
139
|
// contentDisposition wird von Bun als response-content-disposition
|
|
129
140
|
// Query-Param signiert (Response-Override fuer den GET-Download) —
|
|
130
141
|
// der Browser sieht den Original-Dateinamen statt des UUID-Keys.
|
|
@@ -331,6 +331,7 @@ describe("folders-user-data — tenantScopedDelete hooks", () => {
|
|
|
331
331
|
await seedOneFolderWithAssignment();
|
|
332
332
|
const ctx = {
|
|
333
333
|
db: stack.db,
|
|
334
|
+
registry: stack.registry,
|
|
334
335
|
tenantId: admin.tenantId,
|
|
335
336
|
userId: admin.id,
|
|
336
337
|
tenantModel: "multi-user" as const,
|
|
@@ -345,6 +346,7 @@ describe("folders-user-data — tenantScopedDelete hooks", () => {
|
|
|
345
346
|
await seedOneFolderWithAssignment();
|
|
346
347
|
const ctx = {
|
|
347
348
|
db: stack.db,
|
|
349
|
+
registry: stack.registry,
|
|
348
350
|
tenantId: admin.tenantId,
|
|
349
351
|
userId: admin.id,
|
|
350
352
|
tenantModel: "single-user" as const,
|
|
@@ -359,6 +361,7 @@ describe("folders-user-data — tenantScopedDelete hooks", () => {
|
|
|
359
361
|
await seedOneFolderWithAssignment();
|
|
360
362
|
const ctx = {
|
|
361
363
|
db: stack.db,
|
|
364
|
+
registry: stack.registry,
|
|
362
365
|
tenantId: admin.tenantId,
|
|
363
366
|
userId: admin.id,
|
|
364
367
|
tenantModel: "single-user" as const,
|
|
@@ -149,11 +149,28 @@ describe("FolderManager filing mode", () => {
|
|
|
149
149
|
);
|
|
150
150
|
});
|
|
151
151
|
|
|
152
|
-
test("
|
|
152
|
+
test("deleting a folder in filing mode refetches AND tells the host to reassign", async () => {
|
|
153
|
+
folderRows = [{ id: "f1", name: "A", parentId: null, version: 1 }];
|
|
154
|
+
const onReassigned = mock(() => {});
|
|
155
|
+
render(
|
|
156
|
+
<Wrapper>
|
|
157
|
+
<FolderManager filing={filingWith(onReassigned)} />
|
|
158
|
+
</Wrapper>,
|
|
159
|
+
);
|
|
160
|
+
fireEvent.click(screen.getByTestId("folder-delete-f1"));
|
|
161
|
+
fireEvent.click(await screen.findByTestId("folder-manager-delete-dialog-confirm"));
|
|
162
|
+
await waitFor(() =>
|
|
163
|
+
expect(dispatchSpy).toHaveBeenCalledWith(FoldersHandlers.deleteFolder, { id: "f1" }),
|
|
164
|
+
);
|
|
165
|
+
await waitFor(() => expect(onReassigned).toHaveBeenCalled());
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("the in-tree new-folder row opens a draft; submitting (Enter) creates a root folder and reassigns", async () => {
|
|
153
169
|
folderRows = [];
|
|
170
|
+
const onReassigned = mock(() => {});
|
|
154
171
|
render(
|
|
155
172
|
<Wrapper>
|
|
156
|
-
<FolderManager />
|
|
173
|
+
<FolderManager filing={filingWith(onReassigned)} />
|
|
157
174
|
</Wrapper>,
|
|
158
175
|
);
|
|
159
176
|
fireEvent.click(screen.getByTestId("folder-manager-new-root"));
|
|
@@ -164,6 +181,30 @@ describe("FolderManager filing mode", () => {
|
|
|
164
181
|
await waitFor(() =>
|
|
165
182
|
expect(dispatchSpy).toHaveBeenCalledWith(FoldersHandlers.createFolder, { name: "Inbox" }),
|
|
166
183
|
);
|
|
184
|
+
await waitFor(() => expect(onReassigned).toHaveBeenCalled());
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("renaming a folder dispatches update-folder and reassigns", async () => {
|
|
188
|
+
folderRows = [{ id: "f1", name: "A", parentId: null, version: 1 }];
|
|
189
|
+
const onReassigned = mock(() => {});
|
|
190
|
+
render(
|
|
191
|
+
<Wrapper>
|
|
192
|
+
<FolderManager filing={filingWith(onReassigned)} />
|
|
193
|
+
</Wrapper>,
|
|
194
|
+
);
|
|
195
|
+
fireEvent.click(screen.getByTestId("folder-rename-f1"));
|
|
196
|
+
fireEvent.change(document.getElementById("folder-manager-draft") as HTMLInputElement, {
|
|
197
|
+
target: { value: "Renamed" },
|
|
198
|
+
});
|
|
199
|
+
fireEvent.submit(screen.getByTestId("folder-manager-draft"));
|
|
200
|
+
await waitFor(() =>
|
|
201
|
+
expect(dispatchSpy).toHaveBeenCalledWith(FoldersHandlers.updateFolder, {
|
|
202
|
+
id: "f1",
|
|
203
|
+
version: 1,
|
|
204
|
+
changes: { name: "Renamed" },
|
|
205
|
+
}),
|
|
206
|
+
);
|
|
207
|
+
await waitFor(() => expect(onReassigned).toHaveBeenCalled());
|
|
167
208
|
});
|
|
168
209
|
|
|
169
210
|
test("a mixed-type tree files each leaf under its own entityType (per-leaf override + fallback)", async () => {
|
|
@@ -55,4 +55,12 @@ describe("folderPath", () => {
|
|
|
55
55
|
test("unknown id → empty string", () => {
|
|
56
56
|
expect(folderPath(rows, "nope")).toBe("");
|
|
57
57
|
});
|
|
58
|
+
|
|
59
|
+
// 658/4: a parentId cycle must terminate at the row-count cap, not loop
|
|
60
|
+
// forever or run one iteration past it.
|
|
61
|
+
test("a parentId cycle terminates instead of looping forever", () => {
|
|
62
|
+
const cyclic = [f("x", "X", "y"), f("y", "Y", "x")];
|
|
63
|
+
expect(() => folderPath(cyclic, "x")).not.toThrow();
|
|
64
|
+
expect(folderPath(cyclic, "x").split(" / ")).toHaveLength(cyclic.length);
|
|
65
|
+
});
|
|
58
66
|
});
|
|
@@ -47,9 +47,7 @@ export type FolderLeaf = {
|
|
|
47
47
|
readonly label: string;
|
|
48
48
|
readonly trailing?: ReactNode;
|
|
49
49
|
readonly onOpen?: () => void;
|
|
50
|
-
// Overrides filing.entityType
|
|
51
|
-
// of mixed entity types (e.g. credits + Bausparverträge), each filed/cleared
|
|
52
|
-
// under its own type. Omit for single-type trees.
|
|
50
|
+
// Overrides filing.entityType (mixed-entity-type trees); omit for single-type trees.
|
|
53
51
|
readonly entityType?: string;
|
|
54
52
|
};
|
|
55
53
|
|
|
@@ -183,6 +181,7 @@ export function FolderManager({
|
|
|
183
181
|
if (await write()) {
|
|
184
182
|
setPending(null);
|
|
185
183
|
await catalog.refetch();
|
|
184
|
+
await filing?.onReassigned();
|
|
186
185
|
}
|
|
187
186
|
} finally {
|
|
188
187
|
setBusy(false);
|
package/src/folders/web/i18n.ts
CHANGED
|
@@ -18,7 +18,6 @@ export const defaultTranslations: TranslationsByLocale = {
|
|
|
18
18
|
"folders.section.working": "Speichert…",
|
|
19
19
|
|
|
20
20
|
"folders.manager.loading": "Lädt…",
|
|
21
|
-
"folders.manager.empty": "Noch keine Ordner.",
|
|
22
21
|
"folders.manager.newRoot": "Neuer Ordner",
|
|
23
22
|
"folders.manager.add": "Anlegen",
|
|
24
23
|
"folders.manager.addChild": "Unterordner",
|
|
@@ -44,7 +43,6 @@ export const defaultTranslations: TranslationsByLocale = {
|
|
|
44
43
|
"folders.section.working": "Saving…",
|
|
45
44
|
|
|
46
45
|
"folders.manager.loading": "Loading…",
|
|
47
|
-
"folders.manager.empty": "No folders yet.",
|
|
48
46
|
"folders.manager.newRoot": "New folder",
|
|
49
47
|
"folders.manager.add": "Create",
|
|
50
48
|
"folders.manager.addChild": "Subfolder",
|
package/src/folders/web/tree.ts
CHANGED
|
@@ -31,6 +31,15 @@ export function buildFolderTree(rows: readonly FolderRow[]): readonly FolderNode
|
|
|
31
31
|
if (siblings) siblings.push(row);
|
|
32
32
|
else byParent.set(key, [row]);
|
|
33
33
|
}
|
|
34
|
+
// visited guards against a parent cycle recursing forever (658/4) —
|
|
35
|
+
// currently impossible (no reparent feature yet), but cheap insurance for
|
|
36
|
+
// when one lands; a cyclic node's subtree just stops re-descending.
|
|
37
|
+
// No cycle guard needed (658/4, verified not just asserted): build() only
|
|
38
|
+
// descends via byParent[parentId] starting from roots (parentId === null
|
|
39
|
+
// or dangling) — a row in a cycle has a non-null, existing parentId by
|
|
40
|
+
// definition, so it can never be a root, and each row contributes exactly
|
|
41
|
+
// one parentId edge, so nothing outside a cycle points into it either. A
|
|
42
|
+
// pure a<->b cycle is structurally unreachable from build(null).
|
|
34
43
|
const build = (parentId: string | null, depth: number): readonly FolderNode[] =>
|
|
35
44
|
(byParent.get(parentId) ?? [])
|
|
36
45
|
.slice()
|
|
@@ -41,12 +50,14 @@ export function buildFolderTree(rows: readonly FolderRow[]): readonly FolderNode
|
|
|
41
50
|
|
|
42
51
|
// "Immobilie Berlin / Person Müller" for a folder id, walking parentId up.
|
|
43
52
|
// Empty string if the id is unknown. The row-count cap stops a (currently
|
|
44
|
-
// impossible — no reparent yet) parent cycle from spinning forever
|
|
53
|
+
// impossible — no reparent yet) parent cycle from spinning forever — `< `,
|
|
54
|
+
// not `<=` (658/4): a cycle of exactly rows.length nodes must stop AT the
|
|
55
|
+
// cap, not run one iteration past it.
|
|
45
56
|
export function folderPath(rows: readonly FolderRow[], id: string, separator = " / "): string {
|
|
46
57
|
const byId = new Map(rows.map((r) => [r.id, r]));
|
|
47
58
|
const names: string[] = [];
|
|
48
59
|
let current = byId.get(id);
|
|
49
|
-
for (let i = 0; current !== undefined && i
|
|
60
|
+
for (let i = 0; current !== undefined && i < rows.length; i += 1) {
|
|
50
61
|
names.unshift(current.name);
|
|
51
62
|
current = current.parentId !== null ? byId.get(current.parentId) : undefined;
|
|
52
63
|
}
|
|
@@ -16,10 +16,10 @@ export const foldersUserDataFeature = defineFeature("folders-user-data", (r) =>
|
|
|
16
16
|
r.describe(
|
|
17
17
|
"GDPR (Art. 20 export / Art. 17 erasure) coverage for the `folders` feature's `folder` + `folder-assignment` entities. Mounts the EXT_USER_DATA export + delete hooks so a tenant's folder tree and its entity-to-folder assignments are included in the user-data export bundle and erased on a tenant-scoped forget (single-user tenants only; multi-user + anonymize are no-ops since folder rows carry no per-user PII). Kept separate from `folders` so folder consumers without the user-data-rights pipeline don't pull a hard dependency — requires `user-data-rights`, optionalRequires `folders`.",
|
|
18
18
|
);
|
|
19
|
-
// user-data-rights
|
|
20
|
-
// OPTIONAL:
|
|
21
|
-
//
|
|
22
|
-
//
|
|
19
|
+
// user-data-rights is the hard dependency (EXT_USER_DATA host). `folders` is
|
|
20
|
+
// OPTIONAL: if it's mounted toggleable(default=false) (e.g. per-tenant via
|
|
21
|
+
// tier), a hard r.requires would throw an "effectively disabled" boot
|
|
22
|
+
// warning even though the folder entities exist and the hooks work fine.
|
|
23
23
|
r.requires("user-data-rights");
|
|
24
24
|
r.optionalRequires("folders");
|
|
25
25
|
r.useExtension(EXT_USER_DATA, "folder", {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { HandlerContext } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import type { MailAccountEventPayload } from "../events";
|
|
4
|
+
import { loadCurrentMailAccountPayload } from "../handlers/account-state";
|
|
5
|
+
|
|
6
|
+
function ctxWithEvents(
|
|
7
|
+
payloads: readonly MailAccountEventPayload[],
|
|
8
|
+
): Pick<HandlerContext, "loadAggregate"> {
|
|
9
|
+
return {
|
|
10
|
+
loadAggregate: async () =>
|
|
11
|
+
payloads.map((payload) => ({
|
|
12
|
+
// @cast-boundary test fixture — only `payload` is read by the function under test
|
|
13
|
+
payload,
|
|
14
|
+
})) as unknown as Awaited<ReturnType<HandlerContext["loadAggregate"]>>,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe("loadCurrentMailAccountPayload", () => {
|
|
19
|
+
test("returns undefined when the aggregate has no events", async () => {
|
|
20
|
+
const ctx = ctxWithEvents([]);
|
|
21
|
+
expect(await loadCurrentMailAccountPayload(ctx, "acc-1")).toBeUndefined();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("returns the payload of the LAST event — full-snapshot semantics", async () => {
|
|
25
|
+
const first = { status: "active" } as unknown as MailAccountEventPayload;
|
|
26
|
+
const last = { status: "degraded" } as unknown as MailAccountEventPayload;
|
|
27
|
+
const ctx = ctxWithEvents([first, last]);
|
|
28
|
+
expect(await loadCurrentMailAccountPayload(ctx, "acc-1")).toBe(last);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
// Connect-routes integration — mounts createInboundMailConnectRoutes on a
|
|
2
|
+
// Hono app wired to a real setupTestStack dispatcher + secrets context.
|
|
3
|
+
// Exercises OAuth connect redirect, HMAC state, callback account create,
|
|
4
|
+
// and error paths (401/400/404/502) — not mock-only existence checks.
|
|
5
|
+
|
|
6
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
7
|
+
import { randomBytes } from "node:crypto";
|
|
8
|
+
import { ROLES } from "@cosmicdrift/kumiko-framework/auth";
|
|
9
|
+
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
10
|
+
import {
|
|
11
|
+
configurePiiSubjectKms,
|
|
12
|
+
InMemoryKmsAdapter,
|
|
13
|
+
resetPiiSubjectKmsForTests,
|
|
14
|
+
} from "@cosmicdrift/kumiko-framework/crypto";
|
|
15
|
+
import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
16
|
+
import {
|
|
17
|
+
createSystemUser,
|
|
18
|
+
defineFeature,
|
|
19
|
+
type TenantId,
|
|
20
|
+
} from "@cosmicdrift/kumiko-framework/engine";
|
|
21
|
+
import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
22
|
+
import { createEnvMasterKeyProvider } from "@cosmicdrift/kumiko-framework/secrets";
|
|
23
|
+
import {
|
|
24
|
+
createTestUser,
|
|
25
|
+
setupTestStack,
|
|
26
|
+
type TestStack,
|
|
27
|
+
testTenantId,
|
|
28
|
+
unsafeCreateEntityTable,
|
|
29
|
+
unsafePushTables,
|
|
30
|
+
} from "@cosmicdrift/kumiko-framework/stack";
|
|
31
|
+
import {
|
|
32
|
+
createMutableMasterKeyProvider,
|
|
33
|
+
type MutableMasterKeyProvider,
|
|
34
|
+
} from "@cosmicdrift/kumiko-framework/testing";
|
|
35
|
+
import { Hono } from "hono";
|
|
36
|
+
import {
|
|
37
|
+
createComplianceProfilesFeature,
|
|
38
|
+
tenantComplianceProfileEntity,
|
|
39
|
+
} from "../../compliance-profiles";
|
|
40
|
+
import { createConfigFeature } from "../../config";
|
|
41
|
+
import { inboundProviderInMemoryFeature } from "../../inbound-provider-inmemory";
|
|
42
|
+
import { createSecretsContext, createSecretsFeature, tenantSecretsTable } from "../../secrets";
|
|
43
|
+
import { createTenantFeature } from "../../tenant/feature";
|
|
44
|
+
import { createTenantLifecycleFeature } from "../../tenant-lifecycle";
|
|
45
|
+
import {
|
|
46
|
+
createInboundMailConnectRoutes,
|
|
47
|
+
INBOUND_MAIL_PROVIDER_EXTENSION,
|
|
48
|
+
type InboundMailProviderPlugin,
|
|
49
|
+
inboundCredentialSecretKey,
|
|
50
|
+
inboundMailFoundationFeature,
|
|
51
|
+
mailAccountsProjectionTable,
|
|
52
|
+
type OAuthTokenSet,
|
|
53
|
+
seenMessageEntity,
|
|
54
|
+
syncCursorEntity,
|
|
55
|
+
} from "../index";
|
|
56
|
+
|
|
57
|
+
const OAUTH_PROVIDER_KEY = "oauth-test";
|
|
58
|
+
const STATE_SECRET = "inbound-mail-connect-test-state-secret-32b";
|
|
59
|
+
const CALLBACK_URL = "http://localhost/inbound-mail/oauth/callback";
|
|
60
|
+
|
|
61
|
+
const oauthTestPlugin: InboundMailProviderPlugin = {
|
|
62
|
+
verify: async () => {},
|
|
63
|
+
fetch: async () => ({ messages: [], nextCursor: { offset: 0 }, hasMore: false }),
|
|
64
|
+
oauth: {
|
|
65
|
+
scopes: { receive: ["mail.read"] },
|
|
66
|
+
buildAuthorizeUrl: async (_ctx, p) =>
|
|
67
|
+
`https://oauth.test/authorize?state=${encodeURIComponent(p.state)}&redirect_uri=${encodeURIComponent(p.redirectUri)}`,
|
|
68
|
+
exchangeCode: async (_ctx, p): Promise<OAuthTokenSet> => {
|
|
69
|
+
if (p.code === "fail-exchange") {
|
|
70
|
+
throw new Error("token endpoint unavailable");
|
|
71
|
+
}
|
|
72
|
+
if (p.code === "no-refresh") {
|
|
73
|
+
return {
|
|
74
|
+
accessToken: "access-only",
|
|
75
|
+
expiresAt: "2099-01-01T00:00:00Z",
|
|
76
|
+
scopesGranted: ["mail.read"],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
accessToken: "access-token",
|
|
81
|
+
refreshToken: "refresh-token-abc",
|
|
82
|
+
expiresAt: "2099-01-01T00:00:00Z",
|
|
83
|
+
scopesGranted: ["mail.read"],
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
refreshAccessToken: async () => {
|
|
87
|
+
throw new Error("not used in connect-routes tests");
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const oauthTestProviderFeature = defineFeature("inbound-provider-oauth-test", (r) => {
|
|
93
|
+
r.requires("inbound-mail-foundation");
|
|
94
|
+
r.useExtension(INBOUND_MAIL_PROVIDER_EXTENSION, OAUTH_PROVIDER_KEY, oauthTestPlugin);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
let stack: TestStack;
|
|
98
|
+
let db: DbConnection;
|
|
99
|
+
let secrets: ReturnType<typeof createSecretsContext>;
|
|
100
|
+
let providerRef: MutableMasterKeyProvider;
|
|
101
|
+
let routes: ReturnType<typeof createInboundMailConnectRoutes>;
|
|
102
|
+
|
|
103
|
+
beforeAll(async () => {
|
|
104
|
+
const initialKp = createEnvMasterKeyProvider({
|
|
105
|
+
env: {
|
|
106
|
+
KUMIKO_SECRETS_MASTER_KEY_V1: randomBytes(32).toString("base64"),
|
|
107
|
+
KUMIKO_SECRETS_MASTER_KEY_CURRENT_VERSION: "1",
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
providerRef = createMutableMasterKeyProvider(initialKp);
|
|
111
|
+
|
|
112
|
+
stack = await setupTestStack({
|
|
113
|
+
features: [
|
|
114
|
+
createConfigFeature(),
|
|
115
|
+
createTenantFeature(),
|
|
116
|
+
createSecretsFeature(),
|
|
117
|
+
createComplianceProfilesFeature(),
|
|
118
|
+
createTenantLifecycleFeature(),
|
|
119
|
+
inboundMailFoundationFeature,
|
|
120
|
+
inboundProviderInMemoryFeature,
|
|
121
|
+
oauthTestProviderFeature,
|
|
122
|
+
],
|
|
123
|
+
masterKeyProvider: providerRef,
|
|
124
|
+
extraContext: ({ db: stackDb }) => ({
|
|
125
|
+
secrets: createSecretsContext({ db: stackDb, masterKeyProvider: providerRef }),
|
|
126
|
+
}),
|
|
127
|
+
});
|
|
128
|
+
db = stack.db;
|
|
129
|
+
secrets = createSecretsContext({ db, masterKeyProvider: providerRef });
|
|
130
|
+
|
|
131
|
+
await createEventsTable(db);
|
|
132
|
+
await unsafeCreateEntityTable(db, tenantComplianceProfileEntity);
|
|
133
|
+
await unsafeCreateEntityTable(db, syncCursorEntity);
|
|
134
|
+
await unsafeCreateEntityTable(db, seenMessageEntity);
|
|
135
|
+
await unsafePushTables(db, { tenant_secrets: tenantSecretsTable });
|
|
136
|
+
configurePiiSubjectKms(new InMemoryKmsAdapter());
|
|
137
|
+
|
|
138
|
+
routes = createInboundMailConnectRoutes({
|
|
139
|
+
providerCtx: { registry: stack.registry, secrets },
|
|
140
|
+
dispatchWrite: ({ handlerQn, payload, tenantId }) =>
|
|
141
|
+
stack.dispatcher.write(
|
|
142
|
+
handlerQn,
|
|
143
|
+
payload,
|
|
144
|
+
createSystemUser(tenantId as TenantId, [ROLES.SystemAdmin]),
|
|
145
|
+
),
|
|
146
|
+
secrets,
|
|
147
|
+
stateSecret: STATE_SECRET,
|
|
148
|
+
callbackUrl: CALLBACK_URL,
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
afterAll(async () => {
|
|
153
|
+
await stack.cleanup();
|
|
154
|
+
resetPiiSubjectKmsForTests();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
function adminFor(tenantNumber: number) {
|
|
158
|
+
return createTestUser({
|
|
159
|
+
id: tenantNumber,
|
|
160
|
+
tenantId: testTenantId(tenantNumber),
|
|
161
|
+
roles: ["TenantAdmin", "SystemAdmin"],
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function buildApp(opts: { user?: { id: string; tenantId: string } | null } = {}) {
|
|
166
|
+
const app = new Hono<{ Variables: { user?: { id: string; tenantId: string } } }>();
|
|
167
|
+
if (opts.user !== null) {
|
|
168
|
+
const user = opts.user ?? adminFor(4201);
|
|
169
|
+
app.use("*", async (c, next) => {
|
|
170
|
+
c.set("user", { id: user.id, tenantId: user.tenantId });
|
|
171
|
+
await next();
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
app.get("/api/inbound-mail/connect", routes.connect);
|
|
175
|
+
app.get("/inbound-mail/oauth/callback", routes.callback);
|
|
176
|
+
return app;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
describe("connect-routes — connect", () => {
|
|
180
|
+
test("401 without session user", async () => {
|
|
181
|
+
const app = buildApp({ user: null });
|
|
182
|
+
const res = await app.request(
|
|
183
|
+
`/api/inbound-mail/connect?provider=${OAUTH_PROVIDER_KEY}&scope=shared&mailbox=inbox@acme.test`,
|
|
184
|
+
);
|
|
185
|
+
expect(res.status).toBe(401);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("400 when query params are incomplete", async () => {
|
|
189
|
+
const app = buildApp();
|
|
190
|
+
const res = await app.request(`/api/inbound-mail/connect?provider=${OAUTH_PROVIDER_KEY}`);
|
|
191
|
+
expect(res.status).toBe(400);
|
|
192
|
+
const body = (await res.json()) as { error: { code: string } };
|
|
193
|
+
expect(body.error.code).toBe("invalid_connect_request");
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("404 for unknown provider key", async () => {
|
|
197
|
+
const app = buildApp();
|
|
198
|
+
const res = await app.request(
|
|
199
|
+
"/api/inbound-mail/connect?provider=does-not-exist&scope=shared&mailbox=inbox@acme.test",
|
|
200
|
+
);
|
|
201
|
+
expect(res.status).toBe(404);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("400 when provider has no oauth flow (inmemory)", async () => {
|
|
205
|
+
const app = buildApp();
|
|
206
|
+
const res = await app.request(
|
|
207
|
+
"/api/inbound-mail/connect?provider=inmemory&scope=shared&mailbox=inbox@acme.test",
|
|
208
|
+
);
|
|
209
|
+
expect(res.status).toBe(400);
|
|
210
|
+
const body = (await res.json()) as { error: { code: string } };
|
|
211
|
+
expect(body.error.code).toBe("provider_has_no_oauth_flow");
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("302 redirect to provider authorize URL with signed state", async () => {
|
|
215
|
+
const app = buildApp({ user: adminFor(4202) });
|
|
216
|
+
const res = await app.request(
|
|
217
|
+
`/api/inbound-mail/connect?provider=${OAUTH_PROVIDER_KEY}&scope=shared&mailbox=team@acme.test`,
|
|
218
|
+
{ redirect: "manual" },
|
|
219
|
+
);
|
|
220
|
+
expect(res.status).toBe(302);
|
|
221
|
+
const location = res.headers.get("location");
|
|
222
|
+
expect(location).toBeTruthy();
|
|
223
|
+
const url = new URL(location!);
|
|
224
|
+
expect(url.origin + url.pathname).toBe("https://oauth.test/authorize");
|
|
225
|
+
expect(url.searchParams.get("redirect_uri")).toBe(CALLBACK_URL);
|
|
226
|
+
expect(url.searchParams.get("state")).toBeTruthy();
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
describe("connect-routes — oauth callback", () => {
|
|
231
|
+
test("400 when code/state missing", async () => {
|
|
232
|
+
const app = buildApp({ user: null });
|
|
233
|
+
const res = await app.request("/inbound-mail/oauth/callback");
|
|
234
|
+
expect(res.status).toBe(400);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test("400 on tampered state", async () => {
|
|
238
|
+
const app = buildApp({ user: null });
|
|
239
|
+
const res = await app.request("/inbound-mail/oauth/callback?code=ok&state=not.a.valid.state");
|
|
240
|
+
expect(res.status).toBe(400);
|
|
241
|
+
const body = (await res.json()) as { error: { code: string } };
|
|
242
|
+
expect(body.error.code).toBe("invalid_state");
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("happy path: creates mail account + stores refresh token secret", async () => {
|
|
246
|
+
const user = adminFor(4203);
|
|
247
|
+
const connectApp = buildApp({ user });
|
|
248
|
+
const connectRes = await connectApp.request(
|
|
249
|
+
`/api/inbound-mail/connect?provider=${OAUTH_PROVIDER_KEY}&scope=user&mailbox=owner@acme.test`,
|
|
250
|
+
{ redirect: "manual" },
|
|
251
|
+
);
|
|
252
|
+
const state = new URL(connectRes.headers.get("location")!).searchParams.get("state");
|
|
253
|
+
expect(state).toBeTruthy();
|
|
254
|
+
|
|
255
|
+
const callbackApp = buildApp({ user: null });
|
|
256
|
+
const callbackRes = await callbackApp.request(
|
|
257
|
+
`/inbound-mail/oauth/callback?code=ok&state=${encodeURIComponent(state!)}`,
|
|
258
|
+
);
|
|
259
|
+
expect(callbackRes.status).toBe(200);
|
|
260
|
+
const body = (await callbackRes.json()) as { connected: boolean; accountId: string };
|
|
261
|
+
expect(body.connected).toBe(true);
|
|
262
|
+
expect(body.accountId).toBeTruthy();
|
|
263
|
+
|
|
264
|
+
const accounts = await selectMany(db, mailAccountsProjectionTable, { id: body.accountId });
|
|
265
|
+
expect(accounts).toHaveLength(1);
|
|
266
|
+
expect(accounts[0]?.["provider"]).toBe(OAUTH_PROVIDER_KEY);
|
|
267
|
+
expect(accounts[0]?.["ownerUserId"]).toBe(user.id);
|
|
268
|
+
|
|
269
|
+
const stored = await secrets.get(user.tenantId, inboundCredentialSecretKey(body.accountId));
|
|
270
|
+
expect(stored).toBeDefined();
|
|
271
|
+
expect(stored!.reveal()).toBe("refresh-token-abc");
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test("502 when token exchange fails", async () => {
|
|
275
|
+
const user = adminFor(4204);
|
|
276
|
+
const connectApp = buildApp({ user });
|
|
277
|
+
const connectRes = await connectApp.request(
|
|
278
|
+
`/api/inbound-mail/connect?provider=${OAUTH_PROVIDER_KEY}&scope=shared&mailbox=fail@acme.test`,
|
|
279
|
+
{ redirect: "manual" },
|
|
280
|
+
);
|
|
281
|
+
const state = new URL(connectRes.headers.get("location")!).searchParams.get("state");
|
|
282
|
+
|
|
283
|
+
const res = await buildApp({ user: null }).request(
|
|
284
|
+
`/inbound-mail/oauth/callback?code=fail-exchange&state=${encodeURIComponent(state!)}`,
|
|
285
|
+
);
|
|
286
|
+
expect(res.status).toBe(502);
|
|
287
|
+
const body = (await res.json()) as { error: { code: string } };
|
|
288
|
+
expect(body.error.code).toBe("token_exchange_failed");
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test("502 when provider omits refresh token", async () => {
|
|
292
|
+
const user = adminFor(4205);
|
|
293
|
+
const connectApp = buildApp({ user });
|
|
294
|
+
const connectRes = await connectApp.request(
|
|
295
|
+
`/api/inbound-mail/connect?provider=${OAUTH_PROVIDER_KEY}&scope=shared&mailbox=norefresh@acme.test`,
|
|
296
|
+
{ redirect: "manual" },
|
|
297
|
+
);
|
|
298
|
+
const state = new URL(connectRes.headers.get("location")!).searchParams.get("state");
|
|
299
|
+
|
|
300
|
+
const res = await buildApp({ user: null }).request(
|
|
301
|
+
`/inbound-mail/oauth/callback?code=no-refresh&state=${encodeURIComponent(state!)}`,
|
|
302
|
+
);
|
|
303
|
+
expect(res.status).toBe(502);
|
|
304
|
+
const body = (await res.json()) as { error: { code: string } };
|
|
305
|
+
expect(body.error.code).toBe("no_refresh_token");
|
|
306
|
+
});
|
|
307
|
+
});
|