@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
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
|
|
6
6
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
7
7
|
import { randomBytes } from "node:crypto";
|
|
8
|
-
import { deleteMany,
|
|
8
|
+
import { deleteMany, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
9
9
|
import type { AppContext } from "@cosmicdrift/kumiko-framework/engine";
|
|
10
|
+
import { rebuildProjection } from "@cosmicdrift/kumiko-framework/pipeline";
|
|
10
11
|
import {
|
|
11
12
|
createEnvMasterKeyProvider,
|
|
12
|
-
|
|
13
|
+
decodeStoredEnvelope,
|
|
14
|
+
decryptValue,
|
|
13
15
|
type MasterKeyProvider,
|
|
14
16
|
} from "@cosmicdrift/kumiko-framework/secrets";
|
|
15
17
|
import {
|
|
@@ -21,7 +23,7 @@ import {
|
|
|
21
23
|
import { createSecretsFeature } from "../feature";
|
|
22
24
|
import { rotateJob } from "../handlers/rotate.job";
|
|
23
25
|
import { createSecretsContext } from "../secrets-context";
|
|
24
|
-
import { tenantSecretsTable } from "../table";
|
|
26
|
+
import { type StoredEnvelope, tenantSecretsTable } from "../table";
|
|
25
27
|
|
|
26
28
|
const admin = createTestUser({
|
|
27
29
|
id: "00000000-0000-4000-8000-000000000010",
|
|
@@ -29,6 +31,15 @@ const admin = createTestUser({
|
|
|
29
31
|
roles: ["TenantAdmin"],
|
|
30
32
|
});
|
|
31
33
|
|
|
34
|
+
// Captured so the happy-path rotation test can build a provider whose V1
|
|
35
|
+
// key matches the seeded rows exactly — a fresh random V1 would fail the
|
|
36
|
+
// GCM auth tag on every unwrap.
|
|
37
|
+
const SEED_V1_KEY = randomBytes(32).toString("base64");
|
|
38
|
+
// Shared with the happy-path test's rotator AND its verifier — the verifier
|
|
39
|
+
// must decrypt with ONLY this key so a rewrap that bumps kek_version but
|
|
40
|
+
// leaves the envelope V1-wrapped fails loud instead of silently passing.
|
|
41
|
+
const ROTATED_V2_KEY = randomBytes(32).toString("base64");
|
|
42
|
+
|
|
32
43
|
// A provider that encrypts happily on wrapDek but always rejects
|
|
33
44
|
// unwrapDek. Simulates "KEK is unreachable / corrupt" — the failure mode
|
|
34
45
|
// the circuit-breaker exists to contain. The returned object carries a
|
|
@@ -64,7 +75,7 @@ beforeAll(async () => {
|
|
|
64
75
|
// Seeding the table uses a sane V1-only provider so writes land on V1.
|
|
65
76
|
const seedProvider = createEnvMasterKeyProvider({
|
|
66
77
|
env: {
|
|
67
|
-
KUMIKO_SECRETS_MASTER_KEY_V1:
|
|
78
|
+
KUMIKO_SECRETS_MASTER_KEY_V1: SEED_V1_KEY,
|
|
68
79
|
KUMIKO_SECRETS_MASTER_KEY_CURRENT_VERSION: "1",
|
|
69
80
|
},
|
|
70
81
|
});
|
|
@@ -80,21 +91,12 @@ beforeAll(async () => {
|
|
|
80
91
|
tenant_secrets: tenantSecretsTable,
|
|
81
92
|
});
|
|
82
93
|
|
|
83
|
-
// Seed 20 V1 rows
|
|
94
|
+
// Seed 20 V1 rows through the real write path (executor.create) instead
|
|
95
|
+
// of a raw insertOne — the rotate job's executor.update needs an actual
|
|
96
|
+
// event stream to update against, which a headless projection row lacks.
|
|
97
|
+
const seedSecrets = createSecretsContext({ db: stack.db, masterKeyProvider: seedProvider });
|
|
84
98
|
for (let i = 0; i < 20; i++) {
|
|
85
|
-
|
|
86
|
-
await insertOne(stack.db, tenantSecretsTable, {
|
|
87
|
-
tenantId: admin.tenantId,
|
|
88
|
-
key: `test:secret:k-${i}`,
|
|
89
|
-
envelope: {
|
|
90
|
-
ciphertext: envelope.ciphertext.toString("base64"),
|
|
91
|
-
iv: envelope.iv.toString("base64"),
|
|
92
|
-
authTag: envelope.authTag.toString("base64"),
|
|
93
|
-
encryptedDek: envelope.encryptedDek.toString("base64"),
|
|
94
|
-
kekVersion: envelope.kekVersion,
|
|
95
|
-
},
|
|
96
|
-
kekVersion: envelope.kekVersion,
|
|
97
|
-
});
|
|
99
|
+
await seedSecrets.set(admin.tenantId, `test:secret:k-${i}`, `secret-${i}`);
|
|
98
100
|
}
|
|
99
101
|
});
|
|
100
102
|
|
|
@@ -170,4 +172,99 @@ describe("rotate-job circuit-breaker", () => {
|
|
|
170
172
|
expect(await countV1Rows()).toBe(20);
|
|
171
173
|
expect(broken.calls()).toBe(25);
|
|
172
174
|
});
|
|
175
|
+
|
|
176
|
+
// Happy-path counterpart to the failure tests above: a working provider
|
|
177
|
+
// that actually rewraps. Run last — it's the only test that mutates the
|
|
178
|
+
// shared 20-row fixture, so the earlier "stays at 20" assertions must
|
|
179
|
+
// see it before this runs. Enforced below, not just documented: a
|
|
180
|
+
// precondition check fails loud (not silently-wrong) if a test inserted
|
|
181
|
+
// between this and the circuit-breaker tests already touched the
|
|
182
|
+
// fixture — rotateJob's batch scan has no per-row exclusion/ordering
|
|
183
|
+
// to fall back on, so isolating this test onto a second tenant doesn't
|
|
184
|
+
// help (rotator would just exhaust maxFailures on the other tenant's
|
|
185
|
+
// still-V1 rows before ever reaching its own).
|
|
186
|
+
test("successful rotation rewraps the DEK, bumps kekVersion, and preserves plaintext", async () => {
|
|
187
|
+
expect(await countV1Rows()).toBe(20);
|
|
188
|
+
|
|
189
|
+
const rotator = createEnvMasterKeyProvider({
|
|
190
|
+
env: {
|
|
191
|
+
KUMIKO_SECRETS_MASTER_KEY_V1: SEED_V1_KEY,
|
|
192
|
+
KUMIKO_SECRETS_MASTER_KEY_V2: ROTATED_V2_KEY,
|
|
193
|
+
KUMIKO_SECRETS_MASTER_KEY_CURRENT_VERSION: "2",
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Snapshot pre-rotation ciphertext — rotate.job.ts's header comment
|
|
198
|
+
// claims "the ciphertext itself never changes, only the DEK wrapper".
|
|
199
|
+
// Assert that invariant below instead of only checking kekVersion/
|
|
200
|
+
// plaintext, which a rewrap-that-re-encrypts would also satisfy.
|
|
201
|
+
const preRotation = await selectMany<{ key: string; envelope: StoredEnvelope }>(
|
|
202
|
+
stack.db,
|
|
203
|
+
tenantSecretsTable,
|
|
204
|
+
{ tenantId: admin.tenantId },
|
|
205
|
+
);
|
|
206
|
+
const ciphertextByKey = new Map(preRotation.map((row) => [row.key, row.envelope.ciphertext]));
|
|
207
|
+
|
|
208
|
+
await rotateJob({ batchSize: 10, maxFailures: 5 }, jobCtx(rotator));
|
|
209
|
+
|
|
210
|
+
expect(await countV1Rows()).toBe(0);
|
|
211
|
+
|
|
212
|
+
// Decrypt with a V2-ONLY provider — no V1 key available. A rewrap
|
|
213
|
+
// that only bumped the kek_version column but left the envelope
|
|
214
|
+
// wrapped under V1 would throw "no KEK for version 1" here instead
|
|
215
|
+
// of silently passing, which a decrypt via `rotator` (V1+V2) can't
|
|
216
|
+
// catch since it still holds V1.
|
|
217
|
+
const verifier = createEnvMasterKeyProvider({
|
|
218
|
+
env: {
|
|
219
|
+
KUMIKO_SECRETS_MASTER_KEY_V2: ROTATED_V2_KEY,
|
|
220
|
+
KUMIKO_SECRETS_MASTER_KEY_CURRENT_VERSION: "2",
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const rows = await selectMany<{ key: string; envelope: StoredEnvelope; kekVersion: number }>(
|
|
225
|
+
stack.db,
|
|
226
|
+
tenantSecretsTable,
|
|
227
|
+
{ tenantId: admin.tenantId },
|
|
228
|
+
);
|
|
229
|
+
expect(rows).toHaveLength(20);
|
|
230
|
+
for (const row of rows) {
|
|
231
|
+
expect(row.kekVersion).toBe(2);
|
|
232
|
+
const expectedCiphertext = ciphertextByKey.get(row.key);
|
|
233
|
+
expect(expectedCiphertext).toBeDefined();
|
|
234
|
+
expect(row.envelope.ciphertext).toBe(expectedCiphertext as string);
|
|
235
|
+
const expectedIndex = row.key.split("-").at(-1);
|
|
236
|
+
const plaintext = await decryptValue(decodeStoredEnvelope(row.envelope), verifier);
|
|
237
|
+
expect(plaintext).toBe(`secret-${expectedIndex}`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Regression guard (kumiko-framework#1189): rotate.job.ts's
|
|
241
|
+
// executor.update() call carries no options, so it deliberately
|
|
242
|
+
// never sets skipUnchanged — every rotation writes a real .updated
|
|
243
|
+
// event, not a raw UPDATE. A full projection rebuild replays those
|
|
244
|
+
// events; if a future refactor added skipUnchanged (or swapped the
|
|
245
|
+
// event write for a raw column update) the rebuild would resurrect
|
|
246
|
+
// the pre-rotation V1 envelopes instead of landing on V2, exactly
|
|
247
|
+
// the #464 failure class config/auth-mfa already guard against.
|
|
248
|
+
//
|
|
249
|
+
// Vacuous-rebuild guard: confirm the projection name actually exists
|
|
250
|
+
// and the rebuild really replayed events, so a wrong projection name
|
|
251
|
+
// can't silently no-op the assertion below into a false pass.
|
|
252
|
+
const projectionName = "secrets:projection:tenant-secret-entity";
|
|
253
|
+
expect(stack.registry.getAllProjections().has(projectionName)).toBe(true);
|
|
254
|
+
const rebuildResult = await rebuildProjection(projectionName, {
|
|
255
|
+
db: stack.db,
|
|
256
|
+
registry: stack.registry,
|
|
257
|
+
});
|
|
258
|
+
expect(rebuildResult.eventsProcessed).toBeGreaterThan(0);
|
|
259
|
+
|
|
260
|
+
const rowsAfterRebuild = await selectMany<{ key: string; kekVersion: number }>(
|
|
261
|
+
stack.db,
|
|
262
|
+
tenantSecretsTable,
|
|
263
|
+
{ tenantId: admin.tenantId },
|
|
264
|
+
);
|
|
265
|
+
expect(rowsAfterRebuild).toHaveLength(20);
|
|
266
|
+
for (const row of rowsAfterRebuild) {
|
|
267
|
+
expect(row.kekVersion).toBe(2);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
173
270
|
});
|
|
@@ -26,7 +26,12 @@ import {
|
|
|
26
26
|
} from "@cosmicdrift/kumiko-framework/db";
|
|
27
27
|
import type { JobHandlerFn, SessionUser, TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
28
28
|
import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
|
|
29
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
decodeStoredEnvelope,
|
|
31
|
+
encodeStoredEnvelope,
|
|
32
|
+
rewrapDek,
|
|
33
|
+
} from "@cosmicdrift/kumiko-framework/secrets";
|
|
34
|
+
import { type ChunkedMigrationStopReason, runChunkedMigration } from "../../shared";
|
|
30
35
|
import { type StoredEnvelope, tenantSecretEntity, tenantSecretsTable } from "../table";
|
|
31
36
|
|
|
32
37
|
const DEFAULT_BATCH_SIZE = 100;
|
|
@@ -47,7 +52,7 @@ export type RotateJobResult = {
|
|
|
47
52
|
readonly migrated: number;
|
|
48
53
|
readonly failed: number;
|
|
49
54
|
readonly batchesProcessed: number;
|
|
50
|
-
readonly stoppedReason:
|
|
55
|
+
readonly stoppedReason: ChunkedMigrationStopReason;
|
|
51
56
|
};
|
|
52
57
|
|
|
53
58
|
export const rotateJob: JobHandlerFn = async (rawPayload, ctx): Promise<void> => {
|
|
@@ -71,11 +76,6 @@ export const rotateJob: JobHandlerFn = async (rawPayload, ctx): Promise<void> =>
|
|
|
71
76
|
? Date.now() + payload.maxDurationMs
|
|
72
77
|
: Number.POSITIVE_INFINITY;
|
|
73
78
|
|
|
74
|
-
let migrated = 0;
|
|
75
|
-
let failed = 0;
|
|
76
|
-
let batchesProcessed = 0;
|
|
77
|
-
let stoppedReason: RotateJobResult["stoppedReason"] = "empty";
|
|
78
|
-
|
|
79
79
|
// Reuse a TenantDb-per-tenant map so we don't rebuild the wrapper for
|
|
80
80
|
// each row in the same tenant. Rotation typically hits one tenant in a
|
|
81
81
|
// batch; the map trims an allocation without adding complexity.
|
|
@@ -89,101 +89,80 @@ export const rotateJob: JobHandlerFn = async (rawPayload, ctx): Promise<void> =>
|
|
|
89
89
|
return existing;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
kekVersion:
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (batch.length
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (failed >= maxFailures) {
|
|
116
|
-
stoppedReason = "too_many_failures";
|
|
117
|
-
break;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
for (const row of batch) {
|
|
121
|
-
if (failed >= maxFailures) {
|
|
122
|
-
stoppedReason = "too_many_failures";
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
125
|
-
try {
|
|
126
|
-
const oldEnvelope = {
|
|
127
|
-
ciphertext: Buffer.from(row.envelope.ciphertext, "base64"),
|
|
128
|
-
iv: Buffer.from(row.envelope.iv, "base64"),
|
|
129
|
-
authTag: Buffer.from(row.envelope.authTag, "base64"),
|
|
130
|
-
encryptedDek: Buffer.from(row.envelope.encryptedDek, "base64"),
|
|
131
|
-
kekVersion: row.envelope.kekVersion,
|
|
132
|
-
};
|
|
133
|
-
const rotated = await rewrapDek(oldEnvelope, provider);
|
|
134
|
-
|
|
135
|
-
if (rotated.kekVersion === row.kekVersion) continue;
|
|
92
|
+
type SecretRow = {
|
|
93
|
+
id: string;
|
|
94
|
+
tenantId: string;
|
|
95
|
+
version: number;
|
|
96
|
+
envelope: StoredEnvelope;
|
|
97
|
+
kekVersion: number;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// A partial batch means the ne-filter is exhausted — the next re-query
|
|
101
|
+
// would only re-serve rows that failed above; end the run instead.
|
|
102
|
+
let sawPartialBatch = false;
|
|
103
|
+
async function nextBatch(): Promise<readonly SecretRow[]> {
|
|
104
|
+
if (sawPartialBatch) return [];
|
|
105
|
+
const batch = await selectMany<SecretRow>(
|
|
106
|
+
db,
|
|
107
|
+
tenantSecretsTable,
|
|
108
|
+
{ kekVersion: { ne: provider.currentVersion() } },
|
|
109
|
+
{ limit: batchSize },
|
|
110
|
+
);
|
|
111
|
+
if (batch.length < batchSize) sawPartialBatch = true;
|
|
112
|
+
return batch;
|
|
113
|
+
}
|
|
136
114
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
115
|
+
async function migrateRow(row: SecretRow): Promise<"migrated" | "skipped" | "failed"> {
|
|
116
|
+
const rotated = await rewrapDek(decodeStoredEnvelope(row.envelope), provider);
|
|
117
|
+
if (rotated.kekVersion === row.kekVersion) return "skipped";
|
|
118
|
+
|
|
119
|
+
const actor: SessionUser = {
|
|
120
|
+
id: "system",
|
|
121
|
+
tenantId: row.tenantId as TenantId,
|
|
122
|
+
roles: SYSTEM_ROLES,
|
|
123
|
+
};
|
|
124
|
+
const result = await executor.update(
|
|
125
|
+
{
|
|
126
|
+
id: row.id,
|
|
127
|
+
version: row.version,
|
|
128
|
+
changes: {
|
|
129
|
+
envelope: encodeStoredEnvelope(rotated),
|
|
142
130
|
kekVersion: rotated.kekVersion,
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
},
|
|
159
|
-
},
|
|
160
|
-
actor,
|
|
161
|
-
tdbFor(row.tenantId as TenantId),
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
// version_conflict == another writer (secrets.set or a parallel
|
|
165
|
-
// rotation worker) beat us. Count as "skipped" and move on — the
|
|
166
|
-
// row is already in a valid state, potentially even past target.
|
|
167
|
-
if (!result.isSuccess) {
|
|
168
|
-
if (result.error.code === "version_conflict") continue;
|
|
169
|
-
failed++;
|
|
170
|
-
ctx.log?.warn?.(`[secrets:rotate] executor rejected row ${row.id}`, {
|
|
171
|
-
code: result.error.code,
|
|
172
|
-
});
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
} catch (err) {
|
|
176
|
-
failed++;
|
|
177
|
-
ctx.log?.warn?.(`[secrets:rotate] failed to rotate row ${row.id}`, { err });
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
migrated++;
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
actor,
|
|
134
|
+
tdbFor(row.tenantId as TenantId),
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// version_conflict == another writer (secrets.set or a parallel
|
|
138
|
+
// rotation worker) beat us — the row is already in a valid state,
|
|
139
|
+
// potentially even past target.
|
|
140
|
+
if (!result.isSuccess) {
|
|
141
|
+
if (result.error.code === "version_conflict") return "skipped";
|
|
142
|
+
ctx.log?.warn?.(`[secrets:rotate] executor rejected row ${row.id}`, {
|
|
143
|
+
code: result.error.code,
|
|
144
|
+
});
|
|
145
|
+
return "failed";
|
|
181
146
|
}
|
|
182
|
-
|
|
183
|
-
if (stoppedReason === "too_many_failures") break;
|
|
184
|
-
if (batch.length < batchSize) break;
|
|
147
|
+
return "migrated";
|
|
185
148
|
}
|
|
186
149
|
|
|
187
|
-
const
|
|
150
|
+
const outcome = await runChunkedMigration<SecretRow>({
|
|
151
|
+
nextBatch,
|
|
152
|
+
migrateRow,
|
|
153
|
+
maxFailures,
|
|
154
|
+
deadlineAt: deadline,
|
|
155
|
+
signal: ctx.signal,
|
|
156
|
+
onRowError: (row, err) => {
|
|
157
|
+
ctx.log?.warn?.(`[secrets:rotate] failed to rotate row ${row.id}`, { err });
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const result: RotateJobResult = {
|
|
162
|
+
migrated: outcome.migrated,
|
|
163
|
+
failed: outcome.failed,
|
|
164
|
+
batchesProcessed: outcome.batchesProcessed,
|
|
165
|
+
stoppedReason: outcome.stoppedReason,
|
|
166
|
+
};
|
|
188
167
|
ctx.log?.info?.(`[secrets:rotate] complete: ${JSON.stringify(result)}`);
|
|
189
168
|
};
|
|
@@ -25,10 +25,13 @@ import {
|
|
|
25
25
|
createDekCache,
|
|
26
26
|
createSecret,
|
|
27
27
|
type DekCache,
|
|
28
|
+
decodeStoredEnvelope,
|
|
28
29
|
decryptValue,
|
|
30
|
+
encodeStoredEnvelope,
|
|
29
31
|
encryptValue,
|
|
30
32
|
type MasterKeyProvider,
|
|
31
33
|
type SecretsContext,
|
|
34
|
+
withDekCache,
|
|
32
35
|
} from "@cosmicdrift/kumiko-framework/secrets";
|
|
33
36
|
import { generateId } from "@cosmicdrift/kumiko-framework/utils";
|
|
34
37
|
import { z } from "zod";
|
|
@@ -79,37 +82,9 @@ function resolveKey(keyOrHandle: string | { readonly name: string }): string {
|
|
|
79
82
|
return typeof keyOrHandle === "string" ? keyOrHandle : keyOrHandle.name;
|
|
80
83
|
}
|
|
81
84
|
|
|
82
|
-
// Wrap a provider so its unwrapDek goes through the cache. Lets decryptValue
|
|
83
|
-
// use the full provider contract without knowing about caching — separation
|
|
84
|
-
// of concerns: decryptValue handles crypto, cache handles cost.
|
|
85
|
-
function cachedProvider(provider: MasterKeyProvider, cache: DekCache): MasterKeyProvider {
|
|
86
|
-
return {
|
|
87
|
-
wrapDek: provider.wrapDek.bind(provider),
|
|
88
|
-
unwrapDek: (encryptedDek, version) => cache.unwrapDek(encryptedDek, version, provider),
|
|
89
|
-
currentVersion: provider.currentVersion.bind(provider),
|
|
90
|
-
isAvailable: provider.isAvailable.bind(provider),
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function decodeEnvelope(stored: StoredEnvelope): {
|
|
95
|
-
ciphertext: Buffer;
|
|
96
|
-
iv: Buffer;
|
|
97
|
-
authTag: Buffer;
|
|
98
|
-
encryptedDek: Buffer;
|
|
99
|
-
kekVersion: number;
|
|
100
|
-
} {
|
|
101
|
-
return {
|
|
102
|
-
ciphertext: Buffer.from(stored.ciphertext, "base64"),
|
|
103
|
-
iv: Buffer.from(stored.iv, "base64"),
|
|
104
|
-
authTag: Buffer.from(stored.authTag, "base64"),
|
|
105
|
-
encryptedDek: Buffer.from(stored.encryptedDek, "base64"),
|
|
106
|
-
kekVersion: stored.kekVersion,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
|
|
110
85
|
export function createSecretsContext(opts: SecretsContextOptions): SecretsContext {
|
|
111
86
|
const { db, masterKeyProvider } = opts;
|
|
112
|
-
const provider =
|
|
87
|
+
const provider = withDekCache(masterKeyProvider, opts.dekCache ?? createDekCache());
|
|
113
88
|
|
|
114
89
|
type SecretLookupRow = {
|
|
115
90
|
readonly id: string;
|
|
@@ -133,7 +108,7 @@ export function createSecretsContext(opts: SecretsContextOptions): SecretsContex
|
|
|
133
108
|
if (!auditCtx) {
|
|
134
109
|
const existing = await lookup(tenantId, key);
|
|
135
110
|
if (!existing) return undefined;
|
|
136
|
-
const plaintext = await decryptValue(
|
|
111
|
+
const plaintext = await decryptValue(decodeStoredEnvelope(existing.envelope), provider);
|
|
137
112
|
return createSecret(plaintext);
|
|
138
113
|
}
|
|
139
114
|
|
|
@@ -142,7 +117,7 @@ export function createSecretsContext(opts: SecretsContextOptions): SecretsContex
|
|
|
142
117
|
// type doesn't widen to the transaction object cleanly.
|
|
143
118
|
const envelope = await selectTenantSecretEnvelope(tx, tenantId, key);
|
|
144
119
|
if (!envelope) return undefined;
|
|
145
|
-
const pt = await decryptValue(
|
|
120
|
+
const pt = await decryptValue(decodeStoredEnvelope(envelope), provider);
|
|
146
121
|
|
|
147
122
|
// One event per read on its own aggregate-stream (fresh UUID as
|
|
148
123
|
// aggregateId). Avoids version-conflicts between parallel reads —
|
|
@@ -190,13 +165,7 @@ export function createSecretsContext(opts: SecretsContextOptions): SecretsContex
|
|
|
190
165
|
async set(tenantId, keyOrHandle, value, setOpts = {}) {
|
|
191
166
|
const key = resolveKey(keyOrHandle);
|
|
192
167
|
const envelope = await encryptValue(value, masterKeyProvider);
|
|
193
|
-
const stored: StoredEnvelope =
|
|
194
|
-
ciphertext: envelope.ciphertext.toString("base64"),
|
|
195
|
-
iv: envelope.iv.toString("base64"),
|
|
196
|
-
authTag: envelope.authTag.toString("base64"),
|
|
197
|
-
encryptedDek: envelope.encryptedDek.toString("base64"),
|
|
198
|
-
kekVersion: envelope.kekVersion,
|
|
199
|
-
};
|
|
168
|
+
const stored: StoredEnvelope = encodeStoredEnvelope(envelope);
|
|
200
169
|
const metadata: StoredMetadata = {
|
|
201
170
|
...(setOpts.redact ? { redactedPreview: setOpts.redact(value) } : {}),
|
|
202
171
|
...(setOpts.hint ? { hint: setOpts.hint } : {}),
|
package/src/secrets/table.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
createNumberField,
|
|
14
14
|
createTextField,
|
|
15
15
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
16
|
+
import type { StoredEnvelope } from "@cosmicdrift/kumiko-framework/secrets";
|
|
16
17
|
|
|
17
18
|
// Envelope stored as a single jsonb blob. All ops are upsert-by-(tenantId, key)
|
|
18
19
|
// so there's no value in decomposing the envelope into separate columns —
|
|
@@ -22,13 +23,11 @@ import {
|
|
|
22
23
|
// `WHERE kek_version != currentVersion()` with an index on just that column
|
|
23
24
|
// without deserializing the jsonb. Duplicated inside envelope too — the two
|
|
24
25
|
// always stay in sync via the write path.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
readonly kekVersion: number;
|
|
31
|
-
};
|
|
26
|
+
//
|
|
27
|
+
// StoredEnvelope itself is canonical in @cosmicdrift/kumiko-framework/secrets
|
|
28
|
+
// (shared with EnvelopeCipher for TEXT-column stores) — re-exported here so
|
|
29
|
+
// existing consumers keep their import path.
|
|
30
|
+
export type { StoredEnvelope } from "@cosmicdrift/kumiko-framework/secrets";
|
|
32
31
|
|
|
33
32
|
export type StoredMetadata = {
|
|
34
33
|
readonly redactedPreview?: string;
|
|
@@ -50,7 +49,7 @@ export const tenantSecretEntity = createEntity({
|
|
|
50
49
|
table: "read_tenant_secrets",
|
|
51
50
|
fields: {
|
|
52
51
|
key: createTextField({ required: true }),
|
|
53
|
-
kekVersion: createNumberField({ required: true }),
|
|
52
|
+
kekVersion: createNumberField({ required: true, integer: true }),
|
|
54
53
|
},
|
|
55
54
|
});
|
|
56
55
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { buildLlmsTxt } from "../llms-txt";
|
|
3
|
+
|
|
4
|
+
describe("buildLlmsTxt", () => {
|
|
5
|
+
test("emits H1 title + blockquote summary", () => {
|
|
6
|
+
const text = buildLlmsTxt({ title: "Acme", summary: "Acme does things." });
|
|
7
|
+
expect(text).toStartWith("# Acme\n\n> Acme does things.");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("emits ## sections with markdown links", () => {
|
|
11
|
+
const text = buildLlmsTxt({
|
|
12
|
+
title: "Acme",
|
|
13
|
+
summary: "s",
|
|
14
|
+
sections: [
|
|
15
|
+
{
|
|
16
|
+
heading: "Pages",
|
|
17
|
+
links: [{ title: "About", url: "https://acme.test/about", desc: "About us" }],
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
});
|
|
21
|
+
expect(text).toContain("## Pages");
|
|
22
|
+
expect(text).toContain("- [About](https://acme.test/about): About us");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("empty sections are omitted, no trailing section markers", () => {
|
|
26
|
+
const text = buildLlmsTxt({
|
|
27
|
+
title: "Acme",
|
|
28
|
+
summary: "s",
|
|
29
|
+
sections: [{ heading: "Empty", links: [] }],
|
|
30
|
+
});
|
|
31
|
+
expect(text).not.toContain("## Empty");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("no summary → no blockquote line", () => {
|
|
35
|
+
const text = buildLlmsTxt({ title: "Acme", summary: "" });
|
|
36
|
+
expect(text).toBe("# Acme\n");
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { buildRobotsTxt } from "../robots-txt";
|
|
3
|
+
|
|
4
|
+
describe("buildRobotsTxt", () => {
|
|
5
|
+
test("allow: true → no Disallow rule", () => {
|
|
6
|
+
expect(buildRobotsTxt({ allow: true })).toBe("User-agent: *\nDisallow:\n");
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test("allow: false → Disallow: /", () => {
|
|
10
|
+
expect(buildRobotsTxt({ allow: false })).toBe("User-agent: *\nDisallow: /\n");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("sitemapUrl appends a Sitemap line", () => {
|
|
14
|
+
expect(buildRobotsTxt({ allow: true, sitemapUrl: "https://acme.test/sitemap.xml" })).toBe(
|
|
15
|
+
"User-agent: *\nDisallow:\nSitemap: https://acme.test/sitemap.xml\n",
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { faqPageSchema, organizationSchema, webPageSchema } from "../schema-builders";
|
|
3
|
+
|
|
4
|
+
describe("organizationSchema", () => {
|
|
5
|
+
test("minimal input", () => {
|
|
6
|
+
expect(organizationSchema({ name: "Acme" })).toEqual({
|
|
7
|
+
"@context": "https://schema.org",
|
|
8
|
+
"@type": "Organization",
|
|
9
|
+
name: "Acme",
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("full input", () => {
|
|
14
|
+
expect(
|
|
15
|
+
organizationSchema({
|
|
16
|
+
name: "Acme",
|
|
17
|
+
url: "https://acme.test",
|
|
18
|
+
logoUrl: "https://acme.test/logo.png",
|
|
19
|
+
sameAs: ["https://x.com/acme"],
|
|
20
|
+
}),
|
|
21
|
+
).toEqual({
|
|
22
|
+
"@context": "https://schema.org",
|
|
23
|
+
"@type": "Organization",
|
|
24
|
+
name: "Acme",
|
|
25
|
+
url: "https://acme.test",
|
|
26
|
+
logo: "https://acme.test/logo.png",
|
|
27
|
+
sameAs: ["https://x.com/acme"],
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("webPageSchema", () => {
|
|
33
|
+
test("emits WebPage type", () => {
|
|
34
|
+
expect(webPageSchema({ name: "About", url: "https://acme.test/about" })).toMatchObject({
|
|
35
|
+
"@type": "WebPage",
|
|
36
|
+
name: "About",
|
|
37
|
+
url: "https://acme.test/about",
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("faqPageSchema", () => {
|
|
43
|
+
test("maps question/answer pairs to mainEntity", () => {
|
|
44
|
+
const schema = faqPageSchema([{ question: "Q1?", answer: "A1." }]);
|
|
45
|
+
expect(schema).toEqual({
|
|
46
|
+
"@context": "https://schema.org",
|
|
47
|
+
"@type": "FAQPage",
|
|
48
|
+
mainEntity: [
|
|
49
|
+
{
|
|
50
|
+
"@type": "Question",
|
|
51
|
+
name: "Q1?",
|
|
52
|
+
acceptedAnswer: { "@type": "Answer", text: "A1." },
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
});
|