@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
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
12
12
|
import { randomBytes } from "node:crypto";
|
|
13
|
-
import {
|
|
13
|
+
import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
14
14
|
import { defineFeature, defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
15
15
|
import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
16
16
|
import { createEnvMasterKeyProvider } from "@cosmicdrift/kumiko-framework/secrets";
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
25
25
|
import {
|
|
26
26
|
createMutableMasterKeyProvider,
|
|
27
|
+
createTestEnvelopeCipher,
|
|
27
28
|
type MutableMasterKeyProvider,
|
|
28
29
|
} from "@cosmicdrift/kumiko-framework/testing";
|
|
29
30
|
import { z } from "zod";
|
|
@@ -87,8 +88,8 @@ let providerRef: MutableMasterKeyProvider;
|
|
|
87
88
|
const testEncryptionKey = randomBytes(32).toString("base64");
|
|
88
89
|
|
|
89
90
|
beforeAll(async () => {
|
|
90
|
-
const encryption =
|
|
91
|
-
resolver = createConfigResolver({ encryption });
|
|
91
|
+
const encryption = createTestEnvelopeCipher(testEncryptionKey);
|
|
92
|
+
resolver = createConfigResolver({ cipher: encryption });
|
|
92
93
|
|
|
93
94
|
// Master-key for the secrets-feature. Production env shape:
|
|
94
95
|
// KUMIKO_SECRETS_MASTER_KEY_V1=<base64 32 bytes>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import type { EmailMessage } from "@cosmicdrift/kumiko-bundled-features/channel-email";
|
|
3
|
+
import type { MailTransportContext, MailTransportPlugin } from "../feature";
|
|
4
|
+
|
|
5
|
+
export type MailTransportContractFixture = {
|
|
6
|
+
readonly plugin: MailTransportPlugin;
|
|
7
|
+
readonly ctx: MailTransportContext;
|
|
8
|
+
readonly tenantId: string;
|
|
9
|
+
// Local providers (inmemory) expose readBack to assert real delivery;
|
|
10
|
+
// remote providers (smtp) omit it — send() needs a live server.
|
|
11
|
+
readonly readBack?: (tenantId: string) => readonly EmailMessage[];
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type MailTransportContractOptions = {
|
|
15
|
+
// Local providers (inmemory) can assert real delivery via readBack —
|
|
16
|
+
// remote providers (smtp) can't (send() needs a live server), so their
|
|
17
|
+
// delivery test must show as an explicit `skip` in test output, not a
|
|
18
|
+
// silently-passing green with zero assertions (#1334).
|
|
19
|
+
readonly verifiesDelivery: boolean;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function describeMailTransportContract(
|
|
23
|
+
name: string,
|
|
24
|
+
factory: () => MailTransportContractFixture | Promise<MailTransportContractFixture>,
|
|
25
|
+
options: MailTransportContractOptions,
|
|
26
|
+
): void {
|
|
27
|
+
describe(`${name} — MailTransportPlugin contract`, () => {
|
|
28
|
+
let fixture: MailTransportContractFixture;
|
|
29
|
+
|
|
30
|
+
beforeEach(async () => {
|
|
31
|
+
fixture = await factory();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("build resolves to an EmailTransport with a send function", async () => {
|
|
35
|
+
const transport = await fixture.plugin.build(fixture.ctx, fixture.tenantId);
|
|
36
|
+
expect(typeof transport.send).toBe("function");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
(options.verifiesDelivery ? test : test.skip)(
|
|
40
|
+
"send delivers the message — verified via readBack",
|
|
41
|
+
async () => {
|
|
42
|
+
if (!fixture.readBack) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
"describeMailTransportContract: verifiesDelivery:true but the fixture has no readBack",
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
const message: EmailMessage = {
|
|
48
|
+
to: "contract-recipient@example.test",
|
|
49
|
+
subject: "contract-test-subject",
|
|
50
|
+
html: "<p>contract-test-body</p>",
|
|
51
|
+
};
|
|
52
|
+
const transport = await fixture.plugin.build(fixture.ctx, fixture.tenantId);
|
|
53
|
+
await transport.send(message);
|
|
54
|
+
expect(fixture.readBack(fixture.tenantId)).toContainEqual(message);
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
@@ -31,7 +31,10 @@
|
|
|
31
31
|
// `channel-email` (App-wide-Mail-Sender via delivery) bleibt unangetastet
|
|
32
32
|
// — additive Feature.
|
|
33
33
|
|
|
34
|
-
import
|
|
34
|
+
import {
|
|
35
|
+
type EmailTransport,
|
|
36
|
+
withPiiCiphertextGuard,
|
|
37
|
+
} from "@cosmicdrift/kumiko-bundled-features/channel-email";
|
|
35
38
|
import { requireDefined } from "@cosmicdrift/kumiko-bundled-features/foundation-shared";
|
|
36
39
|
import {
|
|
37
40
|
access,
|
|
@@ -127,32 +130,31 @@ export const mailFoundationFeature = defineFeature(FEATURE_NAME, (r) => {
|
|
|
127
130
|
},
|
|
128
131
|
});
|
|
129
132
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
});
|
|
133
|
+
// Provider-selector. Default empty so the boot-validator throws if a
|
|
134
|
+
// tenant tries to send mail without first picking + setting up a
|
|
135
|
+
// provider — better than a silent fallback. The actual list of valid
|
|
136
|
+
// values lives in the registered plugins, not here — Designer-UI can
|
|
137
|
+
// render `getExtensionUsages("mailTransport").map(u => u.entityName)`
|
|
138
|
+
// as the option-list.
|
|
139
|
+
const providerConfigKey = r.config(
|
|
140
|
+
"provider",
|
|
141
|
+
createTenantConfig("text", {
|
|
142
|
+
default: "",
|
|
143
|
+
// required: ohne gewählten Provider wirft createTransportForTenant —
|
|
144
|
+
// readiness meldete vorher ready:true und der erste Mail-Send
|
|
145
|
+
// lieferte den UnconfiguredError (280/1).
|
|
146
|
+
required: true,
|
|
147
|
+
write: access.roles("TenantAdmin", "SystemAdmin"),
|
|
148
|
+
read: access.roles("TenantAdmin", "SystemAdmin", "User"),
|
|
149
|
+
}),
|
|
150
|
+
);
|
|
149
151
|
// Readiness gating: transport-plugins' required keys/secrets count only
|
|
150
152
|
// while their plugin is the one this key selects.
|
|
151
|
-
r.extensionSelector("mailTransport",
|
|
153
|
+
r.extensionSelector("mailTransport", providerConfigKey);
|
|
152
154
|
|
|
153
155
|
return {
|
|
154
156
|
/** Config-key-handle for the provider-selector. */
|
|
155
|
-
|
|
157
|
+
providerConfigKey,
|
|
156
158
|
};
|
|
157
159
|
});
|
|
158
160
|
|
|
@@ -187,7 +189,7 @@ export async function createTransportForTenant(
|
|
|
187
189
|
}
|
|
188
190
|
|
|
189
191
|
const provider = requireDefined(
|
|
190
|
-
await ctxConfig(mailFoundationFeature.exports.
|
|
192
|
+
await ctxConfig(mailFoundationFeature.exports.providerConfigKey),
|
|
191
193
|
FEATURE_NAME,
|
|
192
194
|
"provider",
|
|
193
195
|
) as string; // @cast-boundary engine-payload
|
|
@@ -216,5 +218,5 @@ export async function createTransportForTenant(
|
|
|
216
218
|
`extension options must be a MailTransportPlugin.`,
|
|
217
219
|
);
|
|
218
220
|
}
|
|
219
|
-
return usage.options.build(ctx, tenantId);
|
|
221
|
+
return withPiiCiphertextGuard(await usage.options.build(ctx, tenantId));
|
|
220
222
|
}
|
|
@@ -1,8 +1,31 @@
|
|
|
1
1
|
// feature.ts contract tests for mail-transport-inmemory.
|
|
2
2
|
|
|
3
3
|
import { describe, expect, test } from "bun:test";
|
|
4
|
+
import { isMailTransportPlugin, type MailTransportPlugin } from "../../mail-foundation";
|
|
5
|
+
import { describeMailTransportContract } from "../../mail-foundation/__tests__/mail-transport-contract";
|
|
4
6
|
import { clearInbox, getInbox, mailTransportInMemoryFeature } from "../feature";
|
|
5
7
|
|
|
8
|
+
function registeredPlugin(): MailTransportPlugin {
|
|
9
|
+
const usage = mailTransportInMemoryFeature.extensionUsages.find(
|
|
10
|
+
(u) => u.extensionName === "mailTransport" && u.entityName === "inmemory",
|
|
11
|
+
);
|
|
12
|
+
if (!usage || !isMailTransportPlugin(usage.options)) {
|
|
13
|
+
throw new Error("mail-transport-inmemory: plugin not registered under 'inmemory'");
|
|
14
|
+
}
|
|
15
|
+
return usage.options;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describeMailTransportContract(
|
|
19
|
+
"mail-transport-inmemory",
|
|
20
|
+
() => ({
|
|
21
|
+
plugin: registeredPlugin(),
|
|
22
|
+
ctx: {},
|
|
23
|
+
tenantId: "contract-test-tenant",
|
|
24
|
+
readBack: getInbox,
|
|
25
|
+
}),
|
|
26
|
+
{ verifiesDelivery: true },
|
|
27
|
+
);
|
|
28
|
+
|
|
6
29
|
describe("mailTransportInMemoryFeature — shape", () => {
|
|
7
30
|
test("has the expected name", () => {
|
|
8
31
|
expect(mailTransportInMemoryFeature.name).toBe("mail-transport-inmemory");
|
|
@@ -4,8 +4,73 @@
|
|
|
4
4
|
// build-fn presence).
|
|
5
5
|
|
|
6
6
|
import { describe, expect, test } from "bun:test";
|
|
7
|
+
import type { ConfigAccessor } from "@cosmicdrift/kumiko-framework/engine";
|
|
8
|
+
import type { SecretsContext } from "@cosmicdrift/kumiko-framework/secrets";
|
|
9
|
+
import { createSecret } from "@cosmicdrift/kumiko-framework/secrets";
|
|
10
|
+
import { isMailTransportPlugin, type MailTransportPlugin } from "../../mail-foundation";
|
|
11
|
+
import { describeMailTransportContract } from "../../mail-foundation/__tests__/mail-transport-contract";
|
|
7
12
|
import { mailTransportSmtpFeature, SMTP_PASSWORD } from "../feature";
|
|
8
13
|
|
|
14
|
+
function registeredPlugin(): MailTransportPlugin {
|
|
15
|
+
const usage = mailTransportSmtpFeature.extensionUsages.find(
|
|
16
|
+
(u) => u.extensionName === "mailTransport" && u.entityName === "smtp",
|
|
17
|
+
);
|
|
18
|
+
if (!usage || !isMailTransportPlugin(usage.options)) {
|
|
19
|
+
throw new Error("mail-transport-smtp: plugin not registered under 'smtp'");
|
|
20
|
+
}
|
|
21
|
+
return usage.options;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function fakeConfig(
|
|
25
|
+
overrides: Partial<
|
|
26
|
+
Record<"host" | "port" | "secure" | "from" | "authUser", string | number | boolean>
|
|
27
|
+
> = {},
|
|
28
|
+
): ConfigAccessor {
|
|
29
|
+
const keys = mailTransportSmtpFeature.exports.configKeys;
|
|
30
|
+
const values = new Map<string, string | number | boolean>([
|
|
31
|
+
[keys.host.name, overrides.host ?? "smtp.contract-test.invalid"],
|
|
32
|
+
[keys.port.name, overrides.port ?? 587],
|
|
33
|
+
[keys.secure.name, overrides.secure ?? false],
|
|
34
|
+
[keys.from.name, overrides.from ?? "noreply@contract-test.invalid"],
|
|
35
|
+
[keys.authUser.name, overrides.authUser ?? "contract-test-user"],
|
|
36
|
+
]);
|
|
37
|
+
return (async (handle: string | { readonly name: string }) => {
|
|
38
|
+
const key = typeof handle === "string" ? handle : handle.name;
|
|
39
|
+
return values.get(key);
|
|
40
|
+
}) as ConfigAccessor;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function fakeSecrets(): SecretsContext {
|
|
44
|
+
return {
|
|
45
|
+
get: async () => createSecret("contract-test-password"),
|
|
46
|
+
has: async () => true,
|
|
47
|
+
set: async () => {},
|
|
48
|
+
delete: async () => true,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describeMailTransportContract(
|
|
53
|
+
"mail-transport-smtp",
|
|
54
|
+
() => ({
|
|
55
|
+
plugin: registeredPlugin(),
|
|
56
|
+
ctx: { config: fakeConfig(), secrets: fakeSecrets(), _userId: "contract-test-user" },
|
|
57
|
+
tenantId: "contract-test-tenant",
|
|
58
|
+
}),
|
|
59
|
+
// send() needs a live SMTP server — can't assert real delivery here.
|
|
60
|
+
{ verifiesDelivery: false },
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
describe("mailTransportSmtpFeature — build error path", () => {
|
|
64
|
+
test("build rejects when a required config-key (host) is unset", async () => {
|
|
65
|
+
const ctx = {
|
|
66
|
+
config: fakeConfig({ host: "" }),
|
|
67
|
+
secrets: fakeSecrets(),
|
|
68
|
+
_userId: "contract-test-user",
|
|
69
|
+
};
|
|
70
|
+
await expect(registeredPlugin().build(ctx, "contract-test-tenant")).rejects.toThrow(/host/);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
9
74
|
describe("mailTransportSmtpFeature — shape", () => {
|
|
10
75
|
test("has the expected name", () => {
|
|
11
76
|
expect(mailTransportSmtpFeature.name).toBe("mail-transport-smtp");
|
|
@@ -2,12 +2,8 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
import { type BrandingTokens, EMPTY_BRANDING } from "../../page-render";
|
|
3
3
|
import { coerceBranding } from "../branding";
|
|
4
4
|
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// non-string field must collapse to "" so a malformed/empty response renders
|
|
8
|
-
// the unbranded default rather than throwing — and an attacker-controlled
|
|
9
|
-
// non-string (e.g. a logoUrl object) can never leak through as a live render
|
|
10
|
-
// token. Exercised only indirectly by the integration path before this.
|
|
5
|
+
// IO-boundary coercion: untrusted `unknown` → BrandingTokens, no `as` cast —
|
|
6
|
+
// every missing/non-string field collapses to "" instead of throwing.
|
|
11
7
|
|
|
12
8
|
describe("coerceBranding", () => {
|
|
13
9
|
const FULL: BrandingTokens = {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { validateBoot } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import { createConfigFeature } from "../../config/feature";
|
|
4
|
+
import { createManagedPagesFeature } from "../feature";
|
|
5
|
+
|
|
6
|
+
// Completeness-Gate: managed-pages ruft jetzt r.translations → der Boot-Validator
|
|
7
|
+
// erzwingt den KOMPLETTEN Required-Surface-Key-Satz (screen:*.title,
|
|
8
|
+
// entity:*:field:*, Section-Header, rowAction-Labels/Confirms) in de+en. Fehlt
|
|
9
|
+
// EIN Key, wirft validateBoot. Beide allowCustomCss-Varianten müssen booten —
|
|
10
|
+
// bei true kommen die zwei customCss-Keys als required dazu.
|
|
11
|
+
|
|
12
|
+
const resolveApexTenant = () => null;
|
|
13
|
+
|
|
14
|
+
describe("managed-pages i18n surface completeness", () => {
|
|
15
|
+
test("boot-validates with allowCustomCss: false", () => {
|
|
16
|
+
const features = [
|
|
17
|
+
createConfigFeature(),
|
|
18
|
+
createManagedPagesFeature({ resolveApexTenant, allowCustomCss: false }),
|
|
19
|
+
];
|
|
20
|
+
expect(() => validateBoot(features)).not.toThrow();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("boot-validates with allowCustomCss: true (customCss keys required)", () => {
|
|
24
|
+
const features = [
|
|
25
|
+
createConfigFeature(),
|
|
26
|
+
createManagedPagesFeature({ resolveApexTenant, allowCustomCss: true }),
|
|
27
|
+
];
|
|
28
|
+
expect(() => validateBoot(features)).not.toThrow();
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -398,6 +398,32 @@ describe("managed-pages :: set with tenantIdOverride (cross-tenant, SystemAdmin)
|
|
|
398
398
|
});
|
|
399
399
|
});
|
|
400
400
|
|
|
401
|
+
describe("managed-pages :: by-tenant-published (Discovery-Query)", () => {
|
|
402
|
+
// tenantAdmin/otherAdmin operate under the default test tenant + a third
|
|
403
|
+
// tenant (see file header) — neither is TENANT_A/TENANT_B, so this needs
|
|
404
|
+
// its own explicit-tenantId users (same pattern as adminA/adminB below).
|
|
405
|
+
const pageAdminA = createTestUser({ id: 14, roles: ["TenantAdmin"], tenantId: TENANT_A });
|
|
406
|
+
const pageAdminB = createTestUser({ id: 15, roles: ["TenantAdmin"], tenantId: TENANT_B });
|
|
407
|
+
|
|
408
|
+
test("listet nur published Pages des Tenants, SQL-gefiltert (Drafts fehlen)", async () => {
|
|
409
|
+
const res = await stack.http.queryOk<{
|
|
410
|
+
pages: Array<{ slug: string; lang: string; title: string }>;
|
|
411
|
+
}>("managed-pages:query:by-tenant-published", {}, pageAdminA);
|
|
412
|
+
const slugs = res.pages.map((p) => p.slug);
|
|
413
|
+
expect(slugs).toContain("about");
|
|
414
|
+
expect(slugs).not.toContain("secret");
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test("Cross-Tenant-Isolation: TENANT_B sieht nur eigene Slugs, keine von TENANT_A", async () => {
|
|
418
|
+
const res = await stack.http.queryOk<{ pages: Array<{ slug: string; title: string }> }>(
|
|
419
|
+
"managed-pages:query:by-tenant-published",
|
|
420
|
+
{},
|
|
421
|
+
pageAdminB,
|
|
422
|
+
);
|
|
423
|
+
expect(res.pages).toEqual([expect.objectContaining({ slug: "about", title: "About B" })]);
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
|
|
401
427
|
describe("managed-pages :: Branding (Config + Render)", () => {
|
|
402
428
|
// config:write:set leitet tenantId aus user.tenantId ab → tenant-spezifische
|
|
403
429
|
// Admins, damit das Branding auf TENANT_A bzw. TENANT_B landet (Host a.*/b.*).
|
|
@@ -671,3 +697,114 @@ describe("managed-pages :: Custom CSS (gated, sanitized render)", () => {
|
|
|
671
697
|
expect(JSON.stringify(screen)).toContain("branding-custom-css");
|
|
672
698
|
});
|
|
673
699
|
});
|
|
700
|
+
|
|
701
|
+
// resolverTrust: "authoritative" (kumiko-platform#278/1, show-pony#51):
|
|
702
|
+
// diese Suite verdrahtet einen ECHTEN host-basierten anonymousAccess.
|
|
703
|
+
// tenantResolver (nicht nur tenantExists wie die Suite oben) — genau die
|
|
704
|
+
// Konfiguration, unter der publicstatus/show-pony laufen.
|
|
705
|
+
//
|
|
706
|
+
// Mechanismus: ein interner Self-Fetch (der alte app.fetch-Pfad) trägt
|
|
707
|
+
// KEINEN Host-Header — Host steckt nur implizit in der Request-URL, nicht
|
|
708
|
+
// in Headers. Ein Resolver, der (wie publicstatus' echter Resolver) nur
|
|
709
|
+
// c.req.header("host") liest, löst für den inneren Request also null auf;
|
|
710
|
+
// unter resolverTrust: "authoritative" gibt es dafür keinen Client-Tenant-
|
|
711
|
+
// Fallback → tenant_required → "page unavailable" 503, obwohl der äußere
|
|
712
|
+
// Request (Host da) den Tenant längst korrekt aufgelöst hätte. systemQuery
|
|
713
|
+
// (siehe feature.ts) entfernt den internen HTTP-Roundtrip komplett und
|
|
714
|
+
// erzwingt den bereits aufgelösten tenantId in-process — kein Header mehr,
|
|
715
|
+
// der fehlen könnte. Rot/Grün verifiziert: mit dem alten app.fetch-Pfad UND
|
|
716
|
+
// einem fallback-freien Resolver (wie hier) schlagen 4 der Tests in dieser
|
|
717
|
+
// Datei/seo.integration.test.ts fehl (503/leere sitemap-Einträge); mit
|
|
718
|
+
// systemQuery sind alle grün.
|
|
719
|
+
describe("managed-pages :: resolverTrust authoritative (host-based, wie publicstatus/show-pony)", () => {
|
|
720
|
+
let authStack: TestStack;
|
|
721
|
+
const authManaged = createManagedPagesFeature({
|
|
722
|
+
resolveApexTenant: (host) => {
|
|
723
|
+
if (host.startsWith("a.")) return TENANT_A;
|
|
724
|
+
if (host.startsWith("b.")) return TENANT_B;
|
|
725
|
+
return null;
|
|
726
|
+
},
|
|
727
|
+
});
|
|
728
|
+
const authConfigFeature = createConfigFeature();
|
|
729
|
+
const hostTenant = (host: string): string | null => {
|
|
730
|
+
if (host.startsWith("a.")) return TENANT_A;
|
|
731
|
+
if (host.startsWith("b.")) return TENANT_B;
|
|
732
|
+
return null;
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
beforeAll(async () => {
|
|
736
|
+
const resolver = createConfigResolver();
|
|
737
|
+
authStack = await setupTestStack({
|
|
738
|
+
features: [authConfigFeature, authManaged],
|
|
739
|
+
anonymousAccess: {
|
|
740
|
+
// KEIN URL-Fallback — spiegelt publicstatus' echten Resolver
|
|
741
|
+
// (resolveSubdomain(c.req.header("Host") ?? "")). Ein interner
|
|
742
|
+
// Self-Fetch trägt keinen Host-Header (Host steckt nur in der URL,
|
|
743
|
+
// nicht in Headers) — ein Resolver, der nur den Header liest, löst
|
|
744
|
+
// für den inneren Request also null auf. Mit URL-Fallback würde
|
|
745
|
+
// dieser Test den Bug maskieren.
|
|
746
|
+
tenantResolver: (c) => hostTenant(c.req.header("host") ?? ""),
|
|
747
|
+
resolverTrust: "authoritative",
|
|
748
|
+
tenantExists: async (id) => id === TENANT_A || id === TENANT_B,
|
|
749
|
+
},
|
|
750
|
+
extraContext: ({ registry }) => ({
|
|
751
|
+
configResolver: resolver,
|
|
752
|
+
_configAccessorFactory: createConfigAccessorFactory(registry, resolver),
|
|
753
|
+
}),
|
|
754
|
+
});
|
|
755
|
+
await unsafeCreateEntityTable(authStack.db, pageEntity);
|
|
756
|
+
await unsafePushTables(authStack.db, { configValuesTable });
|
|
757
|
+
await createEventsTable(authStack.db);
|
|
758
|
+
await seedPage(authStack.db, {
|
|
759
|
+
tenantId: TENANT_A,
|
|
760
|
+
slug: "about",
|
|
761
|
+
lang: "en",
|
|
762
|
+
title: "About A",
|
|
763
|
+
body: "# Hello from **A**",
|
|
764
|
+
published: true,
|
|
765
|
+
});
|
|
766
|
+
await seedPage(authStack.db, {
|
|
767
|
+
tenantId: TENANT_B,
|
|
768
|
+
slug: "about",
|
|
769
|
+
lang: "en",
|
|
770
|
+
title: "About B",
|
|
771
|
+
body: "# Hello from **B**",
|
|
772
|
+
published: true,
|
|
773
|
+
});
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
afterAll(async () => {
|
|
777
|
+
await authStack.cleanup();
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
test("published Page → 200 (systemQuery-Render-Pfad übersteht authoritative resolver)", async () => {
|
|
781
|
+
const res = await authStack.app.request("http://a.example.com/p/about");
|
|
782
|
+
expect(res.status).toBe(200);
|
|
783
|
+
const html = await res.text();
|
|
784
|
+
expect(html).toContain("About A");
|
|
785
|
+
expect(html).toContain("<strong>A</strong>");
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
test("Cross-Tenant-Isolation: Host löst den jeweils eigenen Tenant auf", async () => {
|
|
789
|
+
const a = await (await authStack.app.request("http://a.example.com/p/about")).text();
|
|
790
|
+
const b = await (await authStack.app.request("http://b.example.com/p/about")).text();
|
|
791
|
+
expect(a).toContain("About A");
|
|
792
|
+
expect(a).not.toContain("About B");
|
|
793
|
+
expect(b).toContain("About B");
|
|
794
|
+
expect(b).not.toContain("About A");
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
test("X-Tenant-Header auf der Render-Route ist wirkungslos — Host bleibt allein maßgeblich", async () => {
|
|
798
|
+
// Die Render-Route liest den Tenant NUR über resolveApexTenant(host),
|
|
799
|
+
// nie über den Client-Header — anders als bei Query/Write-Dispatch gibt
|
|
800
|
+
// es hier also gar keinen X-Tenant-Override-Vektor. Ein gesetzter,
|
|
801
|
+
// abweichender Header wird schlicht ignoriert: 200, weiterhin A's Page.
|
|
802
|
+
const res = await authStack.app.request("http://a.example.com/p/about", {
|
|
803
|
+
headers: { "X-Tenant": TENANT_B },
|
|
804
|
+
});
|
|
805
|
+
expect(res.status).toBe(200);
|
|
806
|
+
const body = await res.text();
|
|
807
|
+
expect(body).toContain("About A");
|
|
808
|
+
expect(body).not.toContain("About B");
|
|
809
|
+
});
|
|
810
|
+
});
|
|
@@ -15,10 +15,13 @@ import {
|
|
|
15
15
|
renderSafeMarkdown,
|
|
16
16
|
wrapInLayout,
|
|
17
17
|
} from "../page-render";
|
|
18
|
+
import type { SystemQueryFn } from "../shared";
|
|
18
19
|
import { BRANDING_KEYS, BRANDING_QUERY_QN, CUSTOM_CSS_KEY, coerceBranding } from "./branding";
|
|
19
20
|
import { createBrandingQuery } from "./handlers/branding.query";
|
|
20
21
|
import { bySlugQuery } from "./handlers/by-slug.query";
|
|
22
|
+
import { byTenantPublishedQuery } from "./handlers/by-tenant-published.query";
|
|
21
23
|
import { setWrite } from "./handlers/set.write";
|
|
24
|
+
import { MANAGED_PAGES_I18N } from "./i18n";
|
|
22
25
|
import { createBrandingSettingsScreen } from "./screens/branding-screen";
|
|
23
26
|
import { pageEditScreen, pageListScreen } from "./screens/page-screens";
|
|
24
27
|
import { pageEntity } from "./table";
|
|
@@ -28,28 +31,25 @@ import { pageEntity } from "./table";
|
|
|
28
31
|
// Alias (publicstatus = "Admin") müssen TenantAdmin granten/mappen.
|
|
29
32
|
const ADMIN_ACCESS = { roles: ["TenantAdmin", "SystemAdmin"] } as const;
|
|
30
33
|
|
|
31
|
-
//
|
|
32
|
-
// spart den Origin-Revalidate-Roundtrip (jeder 304 re-runt sonst Page- +
|
|
33
|
-
// Branding-Query), Edits sind nach spätestens 60s live.
|
|
34
|
+
// 60s-shared-cache saves the origin-revalidate roundtrip; CMS edits are live within 60s.
|
|
34
35
|
const PUBLIC_PAGE_CACHE = { kind: "revalidate", maxAgeSeconds: 60 } as const;
|
|
35
36
|
|
|
36
37
|
// QN-Konstante als dokumentierter Public-Contract — der Render-Pfad ruft
|
|
37
|
-
// die by-slug-Query via
|
|
38
|
-
// symmetrisch zum legal-pages-Muster).
|
|
38
|
+
// die by-slug-Query via systemQuery in-process (kein Code-Import des
|
|
39
|
+
// Handlers, symmetrisch zum legal-pages-Muster).
|
|
39
40
|
const BY_SLUG_QN = "managed-pages:query:by-slug";
|
|
40
41
|
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
};
|
|
42
|
+
// Raw Handler-Return von bySlugQuery (published-only) — systemQuery dispatcht
|
|
43
|
+
// direkt gegen den Handler, keine `{data}`-Envelope wie am /api/query-Wire.
|
|
44
|
+
type BySlugQueryResult = {
|
|
45
|
+
title: string;
|
|
46
|
+
body: string;
|
|
47
|
+
lang: string;
|
|
48
|
+
description: string | null;
|
|
49
|
+
ogImage: string | null;
|
|
50
|
+
version: number;
|
|
51
|
+
updatedAt: string;
|
|
52
|
+
} | null;
|
|
53
53
|
|
|
54
54
|
function brandingRevisionSeed(branding: BrandingTokens): string {
|
|
55
55
|
return JSON.stringify([
|
|
@@ -63,14 +63,15 @@ function brandingRevisionSeed(branding: BrandingTokens): string {
|
|
|
63
63
|
]);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
async function
|
|
70
|
-
|
|
66
|
+
// Never throws: a query failure or malformed result degrades to the
|
|
67
|
+
// unbranded default (branding is decoration, not a hard dependency of the
|
|
68
|
+
// page render).
|
|
69
|
+
async function readBrandingViaSystemQuery(
|
|
70
|
+
systemQuery: SystemQueryFn,
|
|
71
|
+
tenantId: string,
|
|
72
|
+
): Promise<BrandingTokens> {
|
|
71
73
|
try {
|
|
72
|
-
|
|
73
|
-
return coerceBranding(body.data);
|
|
74
|
+
return coerceBranding(await systemQuery(BRANDING_QUERY_QN, {}, tenantId));
|
|
74
75
|
} catch {
|
|
75
76
|
return EMPTY_BRANDING;
|
|
76
77
|
}
|
|
@@ -162,7 +163,7 @@ export function createManagedPagesFeature(opts: ManagedPagesOptions): FeatureDef
|
|
|
162
163
|
|
|
163
164
|
return defineFeature("managed-pages", (r) => {
|
|
164
165
|
r.describe(
|
|
165
|
-
"Tenant-editable, server-rendered public pages with per-tenant branding. Stores one Markdown `page` per `(tenantId, slug, lang)` in the `read_pages` entity table with a `published` gate plus `description`/`ogImage` SEO meta. Registers an anonymous `GET {basePath}/:slug` route that resolves the tenant from the request Host via the app-supplied `resolveApexTenant`, serves only published pages (drafts → 404), renders Markdown through the hardened `page-render` core, and isolates per-tenant content with `Vary: Host`. Ships TenantAdmin/SystemAdmin admin screens (`entityList` + `entityEdit`) backed by convention CRUD handlers (`managed-pages:write:page:{create,update,delete}`, `managed-pages:query:page:{list,detail}`); the app wires nav/workspace onto `managed-pages:screen:page-list`. Branding (via `config`, scope tenant): `branding-{title,description,site-url,accent-color,logo-url,layout-preset}` keys with write-time validation (hex color, https URLs), a `configEdit` self-service screen (`managed-pages:screen:branding-settings`), and a `managed-pages:query:branding` read that the render path applies as scoped `:root` CSS vars + a logo/title header. Also exposes `managed-pages:write:set` (idempotent slug-keyed upsert, SystemAdmin cross-tenant via `tenantIdOverride`) as a provisioning API. Requires `config` + `anonymousAccess` wired at app bootstrap.",
|
|
166
|
+
"Tenant-editable, server-rendered public pages with per-tenant branding. Stores one Markdown `page` per `(tenantId, slug, lang)` in the `read_pages` entity table with a `published` gate plus `description`/`ogImage` SEO meta. Registers an anonymous `GET {basePath}/:slug` route that resolves the tenant from the request Host via the app-supplied `resolveApexTenant`, serves only published pages (drafts → 404), renders Markdown through the hardened `page-render` core, and isolates per-tenant content with `Vary: Host`. Ships TenantAdmin/SystemAdmin admin screens (`entityList` + `entityEdit`) backed by convention CRUD handlers (`managed-pages:write:page:{create,update,delete}`, `managed-pages:query:page:{list,detail}`); the app wires nav/workspace onto `managed-pages:screen:page-list`. Also exposes `managed-pages:query:by-tenant-published` (anonymous, SQL-filtered on `published`) for sitemap/discovery consumers such as the `seo` feature. Branding (via `config`, scope tenant): `branding-{title,description,site-url,accent-color,logo-url,layout-preset,custom-css}` keys with write-time validation (hex color, https URLs), a `configEdit` self-service screen (`managed-pages:screen:branding-settings`), and a `managed-pages:query:branding` read that the render path applies as scoped `:root` CSS vars + a logo/title header. Also exposes `managed-pages:write:set` (idempotent slug-keyed upsert, SystemAdmin cross-tenant via `tenantIdOverride`) as a provisioning API. Requires `config` + `anonymousAccess` wired at app bootstrap.",
|
|
166
167
|
);
|
|
167
168
|
r.uiHints({
|
|
168
169
|
displayLabel: "Managed Pages · Public CMS",
|
|
@@ -181,6 +182,7 @@ export function createManagedPagesFeature(opts: ManagedPagesOptions): FeatureDef
|
|
|
181
182
|
const handlers = { set: r.writeHandler(setWrite) };
|
|
182
183
|
const queries = {
|
|
183
184
|
bySlug: r.queryHandler(bySlugQuery),
|
|
185
|
+
byTenantPublished: r.queryHandler(byTenantPublishedQuery),
|
|
184
186
|
branding: r.queryHandler(createBrandingQuery({ allowCustomCss })),
|
|
185
187
|
};
|
|
186
188
|
|
|
@@ -198,11 +200,13 @@ export function createManagedPagesFeature(opts: ManagedPagesOptions): FeatureDef
|
|
|
198
200
|
r.screen(pageEditScreen);
|
|
199
201
|
r.screen(createBrandingSettingsScreen({ allowCustomCss }));
|
|
200
202
|
|
|
203
|
+
r.translations({ keys: MANAGED_PAGES_I18N });
|
|
204
|
+
|
|
201
205
|
r.httpRoute({
|
|
202
206
|
method: "GET",
|
|
203
207
|
path: `${basePath}/:slug`,
|
|
204
208
|
anonymous: true,
|
|
205
|
-
handler: async (c, {
|
|
209
|
+
handler: async (c, { systemQuery }) => {
|
|
206
210
|
// `param("slug")` ist string|undefined, weil `path` ein computed
|
|
207
211
|
// Template ist (Hono inferiert `:slug` nur aus String-Literalen).
|
|
208
212
|
const slug = c.req.param("slug");
|
|
@@ -215,31 +219,25 @@ export function createManagedPagesFeature(opts: ManagedPagesOptions): FeatureDef
|
|
|
215
219
|
const tenantId = await opts.resolveApexTenant(host);
|
|
216
220
|
if (!tenantId) return c.text("not found", 404);
|
|
217
221
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
// X-Tenant). Branding is decoration → a failed/empty branding read
|
|
231
|
-
// degrades to the unbranded default, it never blocks the page.
|
|
232
|
-
const [pageRes, brandingRes] = await Promise.all([
|
|
233
|
-
queryReq(BY_SLUG_QN, { slug, lang }),
|
|
234
|
-
queryReq(BRANDING_QUERY_QN, {}),
|
|
222
|
+
// Page + branding read in parallel, beide über systemQuery in-
|
|
223
|
+
// process gegen den host-resolved tenantId erzwungen (kein X-Tenant-
|
|
224
|
+
// Header-Trick über app.fetch — der sähe für einen host-basierten
|
|
225
|
+
// anonymousAccess-Resolver wie ein Client-Override aus und würde
|
|
226
|
+
// von resolverTrust: "authoritative" zurecht abgelehnt; siehe
|
|
227
|
+
// systemQuery's Doc in http-route.ts). Branding ist Deko → ein
|
|
228
|
+
// Fehlschlag degradiert zum unbranded Default, blockt nie die Page.
|
|
229
|
+
const [pageSettled, brandingResult] = await Promise.all([
|
|
230
|
+
systemQuery(BY_SLUG_QN, { slug, lang }, tenantId)
|
|
231
|
+
.then((data) => ({ ok: true as const, data: data as BySlugQueryResult }))
|
|
232
|
+
.catch(() => ({ ok: false as const, data: null as BySlugQueryResult })),
|
|
233
|
+
readBrandingViaSystemQuery(systemQuery, tenantId),
|
|
235
234
|
]);
|
|
236
|
-
if (!
|
|
235
|
+
if (!pageSettled.ok) return c.text("page unavailable", 503);
|
|
237
236
|
|
|
238
|
-
const
|
|
239
|
-
const data = body.data;
|
|
237
|
+
const data = pageSettled.data;
|
|
240
238
|
if (!data) return c.text("not found", 404);
|
|
241
239
|
|
|
242
|
-
const branding =
|
|
240
|
+
const branding = brandingResult;
|
|
243
241
|
|
|
244
242
|
const etag = computeRevisionEtag([
|
|
245
243
|
tenantId,
|
|
@@ -21,7 +21,11 @@ export function createBrandingQuery(opts: { readonly allowCustomCss: boolean })
|
|
|
21
21
|
access: { roles: ["anonymous", "User", "TenantAdmin", "SystemAdmin"] },
|
|
22
22
|
handler: async (_query, ctx) => {
|
|
23
23
|
const base = await readBranding(ctx.config);
|
|
24
|
-
if (
|
|
24
|
+
if (
|
|
25
|
+
!opts.allowCustomCss ||
|
|
26
|
+
!ctx.config ||
|
|
27
|
+
!(await ctx.hasFeature(MANAGED_PAGES_CSS_FEATURE))
|
|
28
|
+
) {
|
|
25
29
|
return base;
|
|
26
30
|
}
|
|
27
31
|
return { ...base, customCss: await readCustomCss(ctx.config) };
|