@wopr-network/platform-core 0.1.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/biome.json +61 -0
- package/dist/admin/admin-audit-log-repository.d.ts +33 -0
- package/dist/admin/admin-audit-log-repository.js +102 -0
- package/dist/admin/audit-log.d.ts +49 -0
- package/dist/admin/audit-log.js +63 -0
- package/dist/admin/index.d.ts +6 -0
- package/dist/admin/index.js +3 -0
- package/dist/admin/role-store.d.ts +37 -0
- package/dist/admin/role-store.js +106 -0
- package/dist/auth/api-key-repository.d.ts +11 -0
- package/dist/auth/api-key-repository.js +33 -0
- package/dist/auth/api-key-repository.test.d.ts +1 -0
- package/dist/auth/api-key-repository.test.js +46 -0
- package/dist/auth/auth.test.d.ts +1 -0
- package/dist/auth/auth.test.js +140 -0
- package/dist/auth/better-auth.d.ts +42 -0
- package/dist/auth/better-auth.js +196 -0
- package/dist/auth/index.d.ts +186 -0
- package/dist/auth/index.js +422 -0
- package/dist/auth/login-history-repository.d.ts +14 -0
- package/dist/auth/login-history-repository.js +15 -0
- package/dist/auth/login-history-repository.test.d.ts +1 -0
- package/dist/auth/login-history-repository.test.js +47 -0
- package/dist/auth/middleware.d.ts +55 -0
- package/dist/auth/middleware.js +101 -0
- package/dist/auth/middleware.test.d.ts +1 -0
- package/dist/auth/middleware.test.js +213 -0
- package/dist/auth/scoped-tokens.test.d.ts +1 -0
- package/dist/auth/scoped-tokens.test.js +306 -0
- package/dist/auth/tenant-access.test.d.ts +1 -0
- package/dist/auth/tenant-access.test.js +62 -0
- package/dist/auth/user-creator.d.ts +9 -0
- package/dist/auth/user-creator.js +47 -0
- package/dist/auth/user-creator.test.d.ts +1 -0
- package/dist/auth/user-creator.test.js +78 -0
- package/dist/auth/user-role-repository.d.ts +31 -0
- package/dist/auth/user-role-repository.js +53 -0
- package/dist/auth/user-role-repository.test.d.ts +1 -0
- package/dist/auth/user-role-repository.test.js +122 -0
- package/dist/billing/drizzle-webhook-seen-repository.d.ts +10 -0
- package/dist/billing/drizzle-webhook-seen-repository.js +28 -0
- package/dist/billing/index.d.ts +7 -0
- package/dist/billing/index.js +7 -0
- package/dist/billing/payment-processor.d.ts +127 -0
- package/dist/billing/payment-processor.js +8 -0
- package/dist/billing/payment-processor.test.d.ts +1 -0
- package/dist/billing/payment-processor.test.js +71 -0
- package/dist/billing/payram/cents-credits-boundary.test.d.ts +1 -0
- package/dist/billing/payram/cents-credits-boundary.test.js +75 -0
- package/dist/billing/payram/charge-store.d.ts +41 -0
- package/dist/billing/payram/charge-store.js +72 -0
- package/dist/billing/payram/charge-store.test.d.ts +1 -0
- package/dist/billing/payram/charge-store.test.js +64 -0
- package/dist/billing/payram/checkout.d.ts +15 -0
- package/dist/billing/payram/checkout.js +24 -0
- package/dist/billing/payram/checkout.test.d.ts +1 -0
- package/dist/billing/payram/checkout.test.js +74 -0
- package/dist/billing/payram/client.d.ts +7 -0
- package/dist/billing/payram/client.js +15 -0
- package/dist/billing/payram/client.test.d.ts +1 -0
- package/dist/billing/payram/client.test.js +52 -0
- package/dist/billing/payram/index.d.ts +8 -0
- package/dist/billing/payram/index.js +4 -0
- package/dist/billing/payram/types.d.ts +40 -0
- package/dist/billing/payram/types.js +1 -0
- package/dist/billing/payram/webhook.d.ts +19 -0
- package/dist/billing/payram/webhook.js +67 -0
- package/dist/billing/payram/webhook.test.d.ts +7 -0
- package/dist/billing/payram/webhook.test.js +248 -0
- package/dist/billing/stripe/cents-credits-boundary.test.d.ts +1 -0
- package/dist/billing/stripe/cents-credits-boundary.test.js +62 -0
- package/dist/billing/stripe/checkout.d.ts +20 -0
- package/dist/billing/stripe/checkout.js +63 -0
- package/dist/billing/stripe/checkout.test.d.ts +1 -0
- package/dist/billing/stripe/checkout.test.js +148 -0
- package/dist/billing/stripe/client.d.ts +14 -0
- package/dist/billing/stripe/client.js +33 -0
- package/dist/billing/stripe/client.test.d.ts +1 -0
- package/dist/billing/stripe/client.test.js +58 -0
- package/dist/billing/stripe/credit-prices.d.ts +63 -0
- package/dist/billing/stripe/credit-prices.js +81 -0
- package/dist/billing/stripe/credit-prices.test.d.ts +1 -0
- package/dist/billing/stripe/credit-prices.test.js +87 -0
- package/dist/billing/stripe/index.d.ts +14 -0
- package/dist/billing/stripe/index.js +8 -0
- package/dist/billing/stripe/payment-methods-detach-all.test.d.ts +1 -0
- package/dist/billing/stripe/payment-methods-detach-all.test.js +40 -0
- package/dist/billing/stripe/payment-methods.d.ts +25 -0
- package/dist/billing/stripe/payment-methods.js +53 -0
- package/dist/billing/stripe/payment-methods.test.d.ts +1 -0
- package/dist/billing/stripe/payment-methods.test.js +122 -0
- package/dist/billing/stripe/portal.d.ts +10 -0
- package/dist/billing/stripe/portal.js +16 -0
- package/dist/billing/stripe/portal.test.d.ts +1 -0
- package/dist/billing/stripe/portal.test.js +48 -0
- package/dist/billing/stripe/setup-intent.d.ts +16 -0
- package/dist/billing/stripe/setup-intent.js +22 -0
- package/dist/billing/stripe/setup-intent.test.d.ts +1 -0
- package/dist/billing/stripe/setup-intent.test.js +58 -0
- package/dist/billing/stripe/stripe-payment-processor.d.ts +49 -0
- package/dist/billing/stripe/stripe-payment-processor.js +166 -0
- package/dist/billing/stripe/stripe-payment-processor.test.d.ts +1 -0
- package/dist/billing/stripe/stripe-payment-processor.test.js +413 -0
- package/dist/billing/stripe/tenant-store.d.ts +56 -0
- package/dist/billing/stripe/tenant-store.js +119 -0
- package/dist/billing/stripe/tenant-store.test.d.ts +1 -0
- package/dist/billing/stripe/tenant-store.test.js +97 -0
- package/dist/billing/stripe/types.d.ts +49 -0
- package/dist/billing/stripe/types.js +1 -0
- package/dist/billing/webhook-seen-repository.d.ts +14 -0
- package/dist/billing/webhook-seen-repository.js +13 -0
- package/dist/config/billing-env.test.d.ts +1 -0
- package/dist/config/billing-env.test.js +48 -0
- package/dist/config/index.d.ts +46 -0
- package/dist/config/index.js +38 -0
- package/dist/config/logger.d.ts +2 -0
- package/dist/config/logger.js +11 -0
- package/dist/config/provider-endpoints.d.ts +6 -0
- package/dist/config/provider-endpoints.js +12 -0
- package/dist/credits/auto-topup-charge.d.ts +27 -0
- package/dist/credits/auto-topup-charge.js +139 -0
- package/dist/credits/auto-topup-charge.test.d.ts +1 -0
- package/dist/credits/auto-topup-charge.test.js +242 -0
- package/dist/credits/auto-topup-event-log-repository.d.ts +16 -0
- package/dist/credits/auto-topup-event-log-repository.js +18 -0
- package/dist/credits/auto-topup-event-log-repository.test.d.ts +1 -0
- package/dist/credits/auto-topup-event-log-repository.test.js +83 -0
- package/dist/credits/auto-topup-schedule.d.ts +27 -0
- package/dist/credits/auto-topup-schedule.js +66 -0
- package/dist/credits/auto-topup-schedule.test.d.ts +1 -0
- package/dist/credits/auto-topup-schedule.test.js +145 -0
- package/dist/credits/auto-topup-settings-repository.d.ts +54 -0
- package/dist/credits/auto-topup-settings-repository.js +184 -0
- package/dist/credits/auto-topup-settings-repository.test.d.ts +1 -0
- package/dist/credits/auto-topup-settings-repository.test.js +104 -0
- package/dist/credits/auto-topup-usage.d.ts +22 -0
- package/dist/credits/auto-topup-usage.js +56 -0
- package/dist/credits/auto-topup-usage.test.d.ts +1 -0
- package/dist/credits/auto-topup-usage.test.js +181 -0
- package/dist/credits/credit-expiry-cron.d.ts +19 -0
- package/dist/credits/credit-expiry-cron.js +50 -0
- package/dist/credits/credit-expiry-cron.test.d.ts +1 -0
- package/dist/credits/credit-expiry-cron.test.js +67 -0
- package/dist/credits/credit-ledger-extra.test.d.ts +1 -0
- package/dist/credits/credit-ledger-extra.test.js +40 -0
- package/dist/credits/credit-ledger.bench.d.ts +1 -0
- package/dist/credits/credit-ledger.bench.js +33 -0
- package/dist/credits/credit-ledger.d.ts +130 -0
- package/dist/credits/credit-ledger.js +293 -0
- package/dist/credits/credit-ledger.test.d.ts +4 -0
- package/dist/credits/credit-ledger.test.js +203 -0
- package/dist/credits/credit-transaction-repository.d.ts +17 -0
- package/dist/credits/credit-transaction-repository.js +35 -0
- package/dist/credits/credit-transaction-repository.test.d.ts +1 -0
- package/dist/credits/credit-transaction-repository.test.js +232 -0
- package/dist/credits/credit.d.ts +75 -0
- package/dist/credits/credit.js +139 -0
- package/dist/credits/credit.test.d.ts +1 -0
- package/dist/credits/credit.test.js +196 -0
- package/dist/credits/dividend-cron.d.ts +29 -0
- package/dist/credits/dividend-cron.js +88 -0
- package/dist/credits/dividend-cron.test.d.ts +1 -0
- package/dist/credits/dividend-cron.test.js +128 -0
- package/dist/credits/dividend-repository.d.ts +29 -0
- package/dist/credits/dividend-repository.js +126 -0
- package/dist/credits/dividend-repository.test.d.ts +1 -0
- package/dist/credits/dividend-repository.test.js +176 -0
- package/dist/credits/index.d.ts +9 -0
- package/dist/credits/index.js +5 -0
- package/dist/credits/repository-types.d.ts +29 -0
- package/dist/credits/repository-types.js +1 -0
- package/dist/credits/signup-grant.d.ts +12 -0
- package/dist/credits/signup-grant.js +35 -0
- package/dist/credits/signup-grant.test.d.ts +1 -0
- package/dist/credits/signup-grant.test.js +51 -0
- package/dist/credits/tenant-customer-repository.d.ts +30 -0
- package/dist/credits/tenant-customer-repository.js +5 -0
- package/dist/db/auth-user-repository.d.ts +46 -0
- package/dist/db/auth-user-repository.js +90 -0
- package/dist/db/credit-column.d.ts +27 -0
- package/dist/db/credit-column.js +13 -0
- package/dist/db/index.d.ts +14 -0
- package/dist/db/index.js +8 -0
- package/dist/db/schema/account-deletion-requests.d.ts +203 -0
- package/dist/db/schema/account-deletion-requests.js +36 -0
- package/dist/db/schema/account-export-requests.d.ts +148 -0
- package/dist/db/schema/account-export-requests.js +19 -0
- package/dist/db/schema/admin-audit.d.ts +194 -0
- package/dist/db/schema/admin-audit.js +21 -0
- package/dist/db/schema/admin-users.d.ts +177 -0
- package/dist/db/schema/admin-users.js +23 -0
- package/dist/db/schema/affiliate-fraud.d.ts +160 -0
- package/dist/db/schema/affiliate-fraud.js +18 -0
- package/dist/db/schema/affiliate.d.ts +277 -0
- package/dist/db/schema/affiliate.js +32 -0
- package/dist/db/schema/coupon-codes.d.ts +143 -0
- package/dist/db/schema/coupon-codes.js +17 -0
- package/dist/db/schema/credit-auto-topup-settings.d.ts +232 -0
- package/dist/db/schema/credit-auto-topup-settings.js +27 -0
- package/dist/db/schema/credit-auto-topup.d.ts +130 -0
- package/dist/db/schema/credit-auto-topup.js +21 -0
- package/dist/db/schema/credits.d.ts +283 -0
- package/dist/db/schema/credits.js +38 -0
- package/dist/db/schema/dividend-distributions.d.ts +130 -0
- package/dist/db/schema/dividend-distributions.js +19 -0
- package/dist/db/schema/email-notifications.d.ts +99 -0
- package/dist/db/schema/email-notifications.js +21 -0
- package/dist/db/schema/index.d.ts +33 -0
- package/dist/db/schema/index.js +33 -0
- package/dist/db/schema/meter-events.d.ts +599 -0
- package/dist/db/schema/meter-events.js +55 -0
- package/dist/db/schema/notification-preferences.d.ts +165 -0
- package/dist/db/schema/notification-preferences.js +18 -0
- package/dist/db/schema/notification-queue.d.ts +236 -0
- package/dist/db/schema/notification-queue.js +40 -0
- package/dist/db/schema/org-memberships.d.ts +63 -0
- package/dist/db/schema/org-memberships.js +15 -0
- package/dist/db/schema/organization-members.d.ts +235 -0
- package/dist/db/schema/organization-members.js +27 -0
- package/dist/db/schema/payram.d.ts +164 -0
- package/dist/db/schema/payram.js +21 -0
- package/dist/db/schema/platform-api-keys.d.ts +143 -0
- package/dist/db/schema/platform-api-keys.js +20 -0
- package/dist/db/schema/promotion-redemptions.d.ts +143 -0
- package/dist/db/schema/promotion-redemptions.js +18 -0
- package/dist/db/schema/promotions.d.ts +445 -0
- package/dist/db/schema/promotions.js +48 -0
- package/dist/db/schema/provider-credentials.d.ts +201 -0
- package/dist/db/schema/provider-credentials.js +36 -0
- package/dist/db/schema/rate-limit-entries.d.ts +75 -0
- package/dist/db/schema/rate-limit-entries.js +7 -0
- package/dist/db/schema/secret-audit-log.d.ts +109 -0
- package/dist/db/schema/secret-audit-log.js +15 -0
- package/dist/db/schema/session-usage.d.ts +194 -0
- package/dist/db/schema/session-usage.js +19 -0
- package/dist/db/schema/spending-limits.d.ts +92 -0
- package/dist/db/schema/spending-limits.js +8 -0
- package/dist/db/schema/tenant-addons.d.ts +58 -0
- package/dist/db/schema/tenant-addons.js +9 -0
- package/dist/db/schema/tenant-api-keys.d.ts +131 -0
- package/dist/db/schema/tenant-api-keys.js +21 -0
- package/dist/db/schema/tenant-capability-settings.d.ts +79 -0
- package/dist/db/schema/tenant-capability-settings.js +12 -0
- package/dist/db/schema/tenant-customers.d.ts +303 -0
- package/dist/db/schema/tenant-customers.js +25 -0
- package/dist/db/schema/tenants.d.ts +126 -0
- package/dist/db/schema/tenants.js +18 -0
- package/dist/db/schema/user-roles.d.ts +98 -0
- package/dist/db/schema/user-roles.js +18 -0
- package/dist/db/schema/webhook-seen-events.d.ts +58 -0
- package/dist/db/schema/webhook-seen-events.js +9 -0
- package/dist/email/billing-emails.d.ts +51 -0
- package/dist/email/billing-emails.js +163 -0
- package/dist/email/billing-emails.test.d.ts +1 -0
- package/dist/email/billing-emails.test.js +162 -0
- package/dist/email/client.d.ts +51 -0
- package/dist/email/client.js +102 -0
- package/dist/email/client.test.d.ts +1 -0
- package/dist/email/client.test.js +120 -0
- package/dist/email/drizzle-billing-email-repository.d.ts +21 -0
- package/dist/email/drizzle-billing-email-repository.js +36 -0
- package/dist/email/drizzle-billing-email-repository.test.d.ts +1 -0
- package/dist/email/drizzle-billing-email-repository.test.js +42 -0
- package/dist/email/index.d.ts +33 -0
- package/dist/email/index.js +22 -0
- package/dist/email/notification-preferences-store.d.ts +12 -0
- package/dist/email/notification-preferences-store.js +82 -0
- package/dist/email/notification-preferences-store.test.d.ts +1 -0
- package/dist/email/notification-preferences-store.test.js +86 -0
- package/dist/email/notification-queue-store.d.ts +25 -0
- package/dist/email/notification-queue-store.js +97 -0
- package/dist/email/notification-queue-store.test.d.ts +1 -0
- package/dist/email/notification-queue-store.test.js +177 -0
- package/dist/email/notification-repository-types.d.ts +70 -0
- package/dist/email/notification-repository-types.js +6 -0
- package/dist/email/notification-service.d.ts +41 -0
- package/dist/email/notification-service.js +196 -0
- package/dist/email/notification-service.test.d.ts +1 -0
- package/dist/email/notification-service.test.js +160 -0
- package/dist/email/notification-templates.d.ts +18 -0
- package/dist/email/notification-templates.js +574 -0
- package/dist/email/notification-templates.test.d.ts +1 -0
- package/dist/email/notification-templates.test.js +238 -0
- package/dist/email/notification-worker.d.ts +24 -0
- package/dist/email/notification-worker.js +109 -0
- package/dist/email/notification-worker.test.d.ts +1 -0
- package/dist/email/notification-worker.test.js +153 -0
- package/dist/email/require-verified.d.ts +25 -0
- package/dist/email/require-verified.js +52 -0
- package/dist/email/require-verified.test.d.ts +1 -0
- package/dist/email/require-verified.test.js +62 -0
- package/dist/email/resend-adapter.d.ts +47 -0
- package/dist/email/resend-adapter.js +137 -0
- package/dist/email/resend-adapter.test.d.ts +1 -0
- package/dist/email/resend-adapter.test.js +190 -0
- package/dist/email/templates.d.ts +22 -0
- package/dist/email/templates.js +359 -0
- package/dist/email/templates.test.d.ts +1 -0
- package/dist/email/templates.test.js +170 -0
- package/dist/email/verification.d.ts +42 -0
- package/dist/email/verification.js +83 -0
- package/dist/email/verification.test.d.ts +1 -0
- package/dist/email/verification.test.js +141 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +23 -0
- package/dist/metering/aggregator.d.ts +54 -0
- package/dist/metering/aggregator.js +123 -0
- package/dist/metering/aggregator.test.d.ts +1 -0
- package/dist/metering/aggregator.test.js +179 -0
- package/dist/metering/dlq.d.ts +31 -0
- package/dist/metering/dlq.js +82 -0
- package/dist/metering/dlq.test.d.ts +1 -0
- package/dist/metering/dlq.test.js +117 -0
- package/dist/metering/drizzle-usage-summary-repository.d.ts +67 -0
- package/dist/metering/drizzle-usage-summary-repository.js +98 -0
- package/dist/metering/emitter.d.ts +66 -0
- package/dist/metering/emitter.js +185 -0
- package/dist/metering/emitter.test.d.ts +1 -0
- package/dist/metering/emitter.test.js +171 -0
- package/dist/metering/index.d.ts +11 -0
- package/dist/metering/index.js +5 -0
- package/dist/metering/load-test.bench.d.ts +1 -0
- package/dist/metering/load-test.bench.js +103 -0
- package/dist/metering/meter-event-repository.d.ts +33 -0
- package/dist/metering/meter-event-repository.js +58 -0
- package/dist/metering/meter-repositories.test.d.ts +1 -0
- package/dist/metering/meter-repositories.test.js +419 -0
- package/dist/metering/metering.test.d.ts +1 -0
- package/dist/metering/metering.test.js +1046 -0
- package/dist/metering/reconciliation-cron.d.ts +37 -0
- package/dist/metering/reconciliation-cron.js +85 -0
- package/dist/metering/reconciliation-cron.test.d.ts +1 -0
- package/dist/metering/reconciliation-cron.test.js +162 -0
- package/dist/metering/reconciliation-repository.d.ts +27 -0
- package/dist/metering/reconciliation-repository.js +43 -0
- package/dist/metering/reconciliation-repository.test.d.ts +1 -0
- package/dist/metering/reconciliation-repository.test.js +160 -0
- package/dist/metering/types.d.ts +88 -0
- package/dist/metering/types.js +1 -0
- package/dist/metering/wal.d.ts +49 -0
- package/dist/metering/wal.js +124 -0
- package/dist/metering/wal.test.d.ts +1 -0
- package/dist/metering/wal.test.js +175 -0
- package/dist/middleware/csrf.d.ts +24 -0
- package/dist/middleware/csrf.js +80 -0
- package/dist/middleware/csrf.test.d.ts +1 -0
- package/dist/middleware/csrf.test.js +152 -0
- package/dist/middleware/drizzle-rate-limit-repository.d.ts +9 -0
- package/dist/middleware/drizzle-rate-limit-repository.js +52 -0
- package/dist/middleware/drizzle-rate-limit-repository.test.d.ts +1 -0
- package/dist/middleware/drizzle-rate-limit-repository.test.js +74 -0
- package/dist/middleware/get-client-ip.d.ts +22 -0
- package/dist/middleware/get-client-ip.js +51 -0
- package/dist/middleware/get-client-ip.test.d.ts +1 -0
- package/dist/middleware/get-client-ip.test.js +40 -0
- package/dist/middleware/index.d.ts +5 -0
- package/dist/middleware/index.js +4 -0
- package/dist/middleware/rate-limit-repository.d.ts +19 -0
- package/dist/middleware/rate-limit-repository.js +1 -0
- package/dist/middleware/rate-limit.d.ts +57 -0
- package/dist/middleware/rate-limit.js +109 -0
- package/dist/middleware/rate-limit.test.d.ts +1 -0
- package/dist/middleware/rate-limit.test.js +247 -0
- package/dist/security/credential-vault/audit-repository.d.ts +27 -0
- package/dist/security/credential-vault/audit-repository.js +42 -0
- package/dist/security/credential-vault/audit-repository.test.d.ts +1 -0
- package/dist/security/credential-vault/audit-repository.test.js +78 -0
- package/dist/security/credential-vault/credential-repository.d.ts +94 -0
- package/dist/security/credential-vault/credential-repository.js +145 -0
- package/dist/security/credential-vault/credential-repository.test.d.ts +1 -0
- package/dist/security/credential-vault/credential-repository.test.js +206 -0
- package/dist/security/credential-vault/index.d.ts +12 -0
- package/dist/security/credential-vault/index.js +6 -0
- package/dist/security/credential-vault/key-rotation.d.ts +18 -0
- package/dist/security/credential-vault/key-rotation.js +52 -0
- package/dist/security/credential-vault/key-rotation.test.d.ts +1 -0
- package/dist/security/credential-vault/key-rotation.test.js +95 -0
- package/dist/security/credential-vault/migrate-plaintext.d.ts +15 -0
- package/dist/security/credential-vault/migrate-plaintext.js +80 -0
- package/dist/security/credential-vault/migrate-plaintext.test.d.ts +1 -0
- package/dist/security/credential-vault/migrate-plaintext.test.js +111 -0
- package/dist/security/credential-vault/migration-check.d.ts +15 -0
- package/dist/security/credential-vault/migration-check.js +71 -0
- package/dist/security/credential-vault/migration-check.test.d.ts +1 -0
- package/dist/security/credential-vault/migration-check.test.js +457 -0
- package/dist/security/credential-vault/store.d.ts +106 -0
- package/dist/security/credential-vault/store.js +181 -0
- package/dist/security/credential-vault/store.test.d.ts +1 -0
- package/dist/security/credential-vault/store.test.js +482 -0
- package/dist/security/encryption.d.ts +22 -0
- package/dist/security/encryption.js +53 -0
- package/dist/security/encryption.test.d.ts +1 -0
- package/dist/security/encryption.test.js +95 -0
- package/dist/security/host-validation.d.ts +11 -0
- package/dist/security/host-validation.js +108 -0
- package/dist/security/host-validation.test.d.ts +1 -0
- package/dist/security/host-validation.test.js +106 -0
- package/dist/security/index.d.ts +11 -0
- package/dist/security/index.js +11 -0
- package/dist/security/key-audit.d.ts +16 -0
- package/dist/security/key-audit.js +35 -0
- package/dist/security/key-audit.test.d.ts +1 -0
- package/dist/security/key-audit.test.js +50 -0
- package/dist/security/key-injection.d.ts +28 -0
- package/dist/security/key-injection.js +57 -0
- package/dist/security/key-injection.test.d.ts +1 -0
- package/dist/security/key-injection.test.js +97 -0
- package/dist/security/key-validation.d.ts +16 -0
- package/dist/security/key-validation.js +78 -0
- package/dist/security/key-validation.test.d.ts +1 -0
- package/dist/security/key-validation.test.js +87 -0
- package/dist/security/redirect-allowlist.d.ts +6 -0
- package/dist/security/redirect-allowlist.js +36 -0
- package/dist/security/redirect-allowlist.test.d.ts +1 -0
- package/dist/security/redirect-allowlist.test.js +55 -0
- package/dist/security/tenant-keys/capability-settings-store.d.ts +22 -0
- package/dist/security/tenant-keys/capability-settings-store.js +33 -0
- package/dist/security/tenant-keys/capability-settings-store.test.d.ts +1 -0
- package/dist/security/tenant-keys/capability-settings-store.test.js +77 -0
- package/dist/security/tenant-keys/index.d.ts +10 -0
- package/dist/security/tenant-keys/index.js +5 -0
- package/dist/security/tenant-keys/key-resolution-repository.d.ts +15 -0
- package/dist/security/tenant-keys/key-resolution-repository.js +18 -0
- package/dist/security/tenant-keys/key-resolution-repository.test.d.ts +1 -0
- package/dist/security/tenant-keys/key-resolution-repository.test.js +72 -0
- package/dist/security/tenant-keys/key-resolution.d.ts +39 -0
- package/dist/security/tenant-keys/key-resolution.js +59 -0
- package/dist/security/tenant-keys/key-resolution.test.d.ts +1 -0
- package/dist/security/tenant-keys/key-resolution.test.js +97 -0
- package/dist/security/tenant-keys/org-key-resolution.d.ts +30 -0
- package/dist/security/tenant-keys/org-key-resolution.js +50 -0
- package/dist/security/tenant-keys/org-key-resolution.test.d.ts +1 -0
- package/dist/security/tenant-keys/org-key-resolution.test.js +103 -0
- package/dist/security/tenant-keys/tenant-key-repository.d.ts +36 -0
- package/dist/security/tenant-keys/tenant-key-repository.js +96 -0
- package/dist/security/tenant-keys/tenant-key-repository.test.d.ts +1 -0
- package/dist/security/tenant-keys/tenant-key-repository.test.js +114 -0
- package/dist/security/types.d.ts +35 -0
- package/dist/security/types.js +15 -0
- package/dist/tenancy/drizzle-org-repository.d.ts +40 -0
- package/dist/tenancy/drizzle-org-repository.js +126 -0
- package/dist/tenancy/index.d.ts +6 -0
- package/dist/tenancy/index.js +3 -0
- package/dist/tenancy/org-member-repository.d.ts +57 -0
- package/dist/tenancy/org-member-repository.js +99 -0
- package/dist/tenancy/org-repository.test.d.ts +1 -0
- package/dist/tenancy/org-repository.test.js +143 -0
- package/dist/tenancy/org-service.d.ts +70 -0
- package/dist/tenancy/org-service.js +223 -0
- package/dist/tenancy/org-service.test.d.ts +1 -0
- package/dist/tenancy/org-service.test.js +550 -0
- package/dist/test/db.d.ts +33 -0
- package/dist/test/db.js +65 -0
- package/dist/trpc/index.d.ts +1 -0
- package/dist/trpc/index.js +1 -0
- package/dist/trpc/init.d.ts +49 -0
- package/dist/trpc/init.js +108 -0
- package/dist/trpc/init.test.d.ts +1 -0
- package/dist/trpc/init.test.js +154 -0
- package/drizzle/migrations/0000_slippery_mandrill.sql +559 -0
- package/drizzle/migrations/meta/0000_snapshot.json +4374 -0
- package/drizzle/migrations/meta/_journal.json +13 -0
- package/drizzle.config.ts +41 -0
- package/package.json +64 -0
- package/src/admin/admin-audit-log-repository.ts +135 -0
- package/src/admin/audit-log.ts +111 -0
- package/src/admin/index.ts +6 -0
- package/src/admin/role-store.ts +134 -0
- package/src/auth/api-key-repository.test.ts +63 -0
- package/src/auth/api-key-repository.ts +46 -0
- package/src/auth/auth.test.ts +166 -0
- package/src/auth/better-auth.ts +216 -0
- package/src/auth/index.ts +520 -0
- package/src/auth/login-history-repository.test.ts +54 -0
- package/src/auth/login-history-repository.ts +28 -0
- package/src/auth/middleware.test.ts +264 -0
- package/src/auth/middleware.ts +117 -0
- package/src/auth/scoped-tokens.test.ts +362 -0
- package/src/auth/tenant-access.test.ts +69 -0
- package/src/auth/user-creator.test.ts +98 -0
- package/src/auth/user-creator.ts +54 -0
- package/src/auth/user-role-repository.test.ts +149 -0
- package/src/auth/user-role-repository.ts +67 -0
- package/src/billing/drizzle-webhook-seen-repository.ts +34 -0
- package/src/billing/index.ts +22 -0
- package/src/billing/payment-processor.test.ts +93 -0
- package/src/billing/payment-processor.ts +150 -0
- package/src/billing/payram/cents-credits-boundary.test.ts +84 -0
- package/src/billing/payram/charge-store.test.ts +84 -0
- package/src/billing/payram/charge-store.ts +109 -0
- package/src/billing/payram/checkout.test.ts +99 -0
- package/src/billing/payram/checkout.ts +40 -0
- package/src/billing/payram/client.test.ts +62 -0
- package/src/billing/payram/client.ts +21 -0
- package/src/billing/payram/index.ts +14 -0
- package/src/billing/payram/types.ts +44 -0
- package/src/billing/payram/webhook.test.ts +318 -0
- package/src/billing/payram/webhook.ts +97 -0
- package/src/billing/stripe/cents-credits-boundary.test.ts +70 -0
- package/src/billing/stripe/checkout.test.ts +186 -0
- package/src/billing/stripe/checkout.ts +82 -0
- package/src/billing/stripe/client.test.ts +64 -0
- package/src/billing/stripe/client.ts +39 -0
- package/src/billing/stripe/credit-prices.test.ts +114 -0
- package/src/billing/stripe/credit-prices.ts +113 -0
- package/src/billing/stripe/index.ts +14 -0
- package/src/billing/stripe/payment-methods-detach-all.test.ts +53 -0
- package/src/billing/stripe/payment-methods.test.ts +157 -0
- package/src/billing/stripe/payment-methods.ts +76 -0
- package/src/billing/stripe/portal.test.ts +63 -0
- package/src/billing/stripe/portal.ts +25 -0
- package/src/billing/stripe/setup-intent.test.ts +78 -0
- package/src/billing/stripe/setup-intent.ts +34 -0
- package/src/billing/stripe/stripe-payment-processor.test.ts +517 -0
- package/src/billing/stripe/stripe-payment-processor.ts +255 -0
- package/src/billing/stripe/tenant-store.test.ts +124 -0
- package/src/billing/stripe/tenant-store.ts +151 -0
- package/src/billing/stripe/types.ts +53 -0
- package/src/billing/webhook-seen-repository.ts +24 -0
- package/src/config/billing-env.test.ts +54 -0
- package/src/config/index.ts +44 -0
- package/src/config/logger.ts +12 -0
- package/src/config/provider-endpoints.ts +14 -0
- package/src/credits/auto-topup-charge.test.ts +292 -0
- package/src/credits/auto-topup-charge.ts +171 -0
- package/src/credits/auto-topup-event-log-repository.test.ts +99 -0
- package/src/credits/auto-topup-event-log-repository.ts +30 -0
- package/src/credits/auto-topup-schedule.test.ts +179 -0
- package/src/credits/auto-topup-schedule.ts +93 -0
- package/src/credits/auto-topup-settings-repository.test.ts +123 -0
- package/src/credits/auto-topup-settings-repository.ts +245 -0
- package/src/credits/auto-topup-usage.test.ts +220 -0
- package/src/credits/auto-topup-usage.ts +68 -0
- package/src/credits/credit-expiry-cron.test.ts +125 -0
- package/src/credits/credit-expiry-cron.ts +76 -0
- package/src/credits/credit-ledger-extra.test.ts +57 -0
- package/src/credits/credit-ledger.bench.ts +56 -0
- package/src/credits/credit-ledger.test.ts +276 -0
- package/src/credits/credit-ledger.ts +450 -0
- package/src/credits/credit-transaction-repository.test.ts +274 -0
- package/src/credits/credit-transaction-repository.ts +62 -0
- package/src/credits/credit.test.ts +234 -0
- package/src/credits/credit.ts +160 -0
- package/src/credits/dividend-cron.test.ts +158 -0
- package/src/credits/dividend-cron.ts +127 -0
- package/src/credits/dividend-repository.test.ts +223 -0
- package/src/credits/dividend-repository.ts +182 -0
- package/src/credits/index.ts +25 -0
- package/src/credits/repository-types.ts +33 -0
- package/src/credits/signup-grant.test.ts +63 -0
- package/src/credits/signup-grant.ts +44 -0
- package/src/credits/tenant-customer-repository.ts +28 -0
- package/src/db/auth-user-repository.ts +124 -0
- package/src/db/credit-column.ts +17 -0
- package/src/db/index.ts +21 -0
- package/src/db/schema/account-deletion-requests.ts +41 -0
- package/src/db/schema/account-export-requests.ts +24 -0
- package/src/db/schema/admin-audit.ts +26 -0
- package/src/db/schema/admin-users.ts +31 -0
- package/src/db/schema/affiliate-fraud.ts +23 -0
- package/src/db/schema/affiliate.ts +38 -0
- package/src/db/schema/coupon-codes.ts +22 -0
- package/src/db/schema/credit-auto-topup-settings.ts +32 -0
- package/src/db/schema/credit-auto-topup.ts +26 -0
- package/src/db/schema/credits.ts +44 -0
- package/src/db/schema/dividend-distributions.ts +24 -0
- package/src/db/schema/email-notifications.ts +26 -0
- package/src/db/schema/index.ts +33 -0
- package/src/db/schema/meter-events.ts +70 -0
- package/src/db/schema/notification-preferences.ts +19 -0
- package/src/db/schema/notification-queue.ts +45 -0
- package/src/db/schema/org-memberships.ts +20 -0
- package/src/db/schema/organization-members.ts +37 -0
- package/src/db/schema/payram.ts +26 -0
- package/src/db/schema/platform-api-keys.ts +25 -0
- package/src/db/schema/promotion-redemptions.ts +23 -0
- package/src/db/schema/promotions.ts +57 -0
- package/src/db/schema/provider-credentials.ts +41 -0
- package/src/db/schema/rate-limit-entries.ts +12 -0
- package/src/db/schema/secret-audit-log.ts +20 -0
- package/src/db/schema/session-usage.ts +24 -0
- package/src/db/schema/spending-limits.ts +9 -0
- package/src/db/schema/tenant-addons.ts +14 -0
- package/src/db/schema/tenant-api-keys.ts +26 -0
- package/src/db/schema/tenant-capability-settings.ts +17 -0
- package/src/db/schema/tenant-customers.ts +35 -0
- package/src/db/schema/tenants.ts +23 -0
- package/src/db/schema/user-roles.ts +23 -0
- package/src/db/schema/webhook-seen-events.ts +14 -0
- package/src/email/billing-emails.test.ts +198 -0
- package/src/email/billing-emails.ts +211 -0
- package/src/email/client.test.ts +149 -0
- package/src/email/client.ts +137 -0
- package/src/email/drizzle-billing-email-repository.test.ts +52 -0
- package/src/email/drizzle-billing-email-repository.ts +59 -0
- package/src/email/index.ts +57 -0
- package/src/email/notification-preferences-store.test.ts +102 -0
- package/src/email/notification-preferences-store.ts +90 -0
- package/src/email/notification-queue-store.test.ts +215 -0
- package/src/email/notification-queue-store.ts +127 -0
- package/src/email/notification-repository-types.ts +101 -0
- package/src/email/notification-service.test.ts +178 -0
- package/src/email/notification-service.ts +265 -0
- package/src/email/notification-templates.test.ts +261 -0
- package/src/email/notification-templates.ts +727 -0
- package/src/email/notification-worker.test.ts +189 -0
- package/src/email/notification-worker.ts +133 -0
- package/src/email/require-verified.ts +65 -0
- package/src/email/resend-adapter.test.ts +253 -0
- package/src/email/resend-adapter.ts +157 -0
- package/src/email/templates.test.ts +217 -0
- package/src/email/templates.ts +469 -0
- package/src/email/verification.test.ts +185 -0
- package/src/email/verification.ts +110 -0
- package/src/index.ts +51 -0
- package/src/metering/aggregator.test.ts +239 -0
- package/src/metering/aggregator.ts +160 -0
- package/src/metering/dlq.test.ts +134 -0
- package/src/metering/dlq.ts +102 -0
- package/src/metering/drizzle-usage-summary-repository.ts +167 -0
- package/src/metering/emitter.test.ts +202 -0
- package/src/metering/emitter.ts +227 -0
- package/src/metering/index.ts +21 -0
- package/src/metering/load-test.bench.ts +130 -0
- package/src/metering/meter-event-repository.ts +87 -0
- package/src/metering/meter-repositories.test.ts +491 -0
- package/src/metering/metering.test.ts +1317 -0
- package/src/metering/reconciliation-cron.test.ts +202 -0
- package/src/metering/reconciliation-cron.ts +134 -0
- package/src/metering/reconciliation-repository.test.ts +196 -0
- package/src/metering/reconciliation-repository.ts +83 -0
- package/src/metering/types.ts +93 -0
- package/src/metering/wal.test.ts +222 -0
- package/src/metering/wal.ts +139 -0
- package/src/middleware/csrf.test.ts +178 -0
- package/src/middleware/csrf.ts +101 -0
- package/src/middleware/drizzle-rate-limit-repository.test.ts +97 -0
- package/src/middleware/drizzle-rate-limit-repository.ts +57 -0
- package/src/middleware/get-client-ip.test.ts +49 -0
- package/src/middleware/get-client-ip.ts +62 -0
- package/src/middleware/index.ts +12 -0
- package/src/middleware/rate-limit-repository.ts +22 -0
- package/src/middleware/rate-limit.test.ts +338 -0
- package/src/middleware/rate-limit.ts +169 -0
- package/src/security/credential-vault/audit-repository.test.ts +91 -0
- package/src/security/credential-vault/audit-repository.ts +64 -0
- package/src/security/credential-vault/credential-repository.test.ts +264 -0
- package/src/security/credential-vault/credential-repository.ts +233 -0
- package/src/security/credential-vault/index.ts +26 -0
- package/src/security/credential-vault/key-rotation.test.ts +139 -0
- package/src/security/credential-vault/key-rotation.ts +70 -0
- package/src/security/credential-vault/migrate-plaintext.test.ts +138 -0
- package/src/security/credential-vault/migrate-plaintext.ts +101 -0
- package/src/security/credential-vault/migration-check.test.ts +533 -0
- package/src/security/credential-vault/migration-check.ts +88 -0
- package/src/security/credential-vault/store.test.ts +569 -0
- package/src/security/credential-vault/store.ts +284 -0
- package/src/security/encryption.test.ts +114 -0
- package/src/security/encryption.ts +65 -0
- package/src/security/host-validation.test.ts +136 -0
- package/src/security/host-validation.ts +116 -0
- package/src/security/index.ts +59 -0
- package/src/security/key-audit.test.ts +57 -0
- package/src/security/key-audit.ts +45 -0
- package/src/security/key-injection.test.ts +131 -0
- package/src/security/key-injection.ts +71 -0
- package/src/security/key-validation.test.ts +111 -0
- package/src/security/key-validation.ts +84 -0
- package/src/security/redirect-allowlist.test.ts +70 -0
- package/src/security/redirect-allowlist.ts +35 -0
- package/src/security/tenant-keys/capability-settings-store.test.ts +98 -0
- package/src/security/tenant-keys/capability-settings-store.ts +53 -0
- package/src/security/tenant-keys/index.ts +10 -0
- package/src/security/tenant-keys/key-resolution-repository.test.ts +95 -0
- package/src/security/tenant-keys/key-resolution-repository.ts +31 -0
- package/src/security/tenant-keys/key-resolution.test.ts +173 -0
- package/src/security/tenant-keys/key-resolution.ts +87 -0
- package/src/security/tenant-keys/org-key-resolution.test.ts +217 -0
- package/src/security/tenant-keys/org-key-resolution.ts +76 -0
- package/src/security/tenant-keys/tenant-key-repository.test.ts +143 -0
- package/src/security/tenant-keys/tenant-key-repository.ts +130 -0
- package/src/security/types.ts +43 -0
- package/src/tenancy/drizzle-org-repository.ts +169 -0
- package/src/tenancy/index.ts +6 -0
- package/src/tenancy/org-member-repository.ts +159 -0
- package/src/tenancy/org-repository.test.ts +172 -0
- package/src/tenancy/org-service.test.ts +634 -0
- package/src/tenancy/org-service.ts +290 -0
- package/src/test/db.ts +97 -0
- package/src/trpc/index.ts +11 -0
- package/src/trpc/init.test.ts +196 -0
- package/src/trpc/init.ts +138 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
import type { PGlite } from "@electric-sql/pglite";
|
|
2
|
+
import { eq } from "drizzle-orm";
|
|
3
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
4
|
+
import type { PlatformDb } from "../../db/index.js";
|
|
5
|
+
import { providerCredentials, tenantApiKeys } from "../../db/schema/index.js";
|
|
6
|
+
import { createTestDb, truncateAllTables } from "../../test/db.js";
|
|
7
|
+
import { decrypt, encrypt, generateInstanceKey } from "../encryption.js";
|
|
8
|
+
import type { EncryptedPayload } from "../types.js";
|
|
9
|
+
import { DrizzleCredentialRepository, DrizzleMigrationTenantKeyAccess } from "./credential-repository.js";
|
|
10
|
+
import { reEncryptAllCredentials } from "./key-rotation.js";
|
|
11
|
+
import { migratePlaintextCredentials } from "./migrate-plaintext.js";
|
|
12
|
+
import { auditCredentialEncryption } from "./migration-check.js";
|
|
13
|
+
import { CredentialVaultStore, getVaultEncryptionKey } from "./store.js";
|
|
14
|
+
|
|
15
|
+
// TOP OF FILE - shared across ALL describes
|
|
16
|
+
let pool: PGlite;
|
|
17
|
+
let db: PlatformDb;
|
|
18
|
+
|
|
19
|
+
beforeAll(async () => {
|
|
20
|
+
({ db, pool } = await createTestDb());
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterAll(async () => {
|
|
24
|
+
await pool.close();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("auditCredentialEncryption", () => {
|
|
28
|
+
beforeEach(async () => {
|
|
29
|
+
await truncateAllTables(pool);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("returns empty array when all credentials are properly encrypted", async () => {
|
|
33
|
+
const key = generateInstanceKey();
|
|
34
|
+
const encrypted = JSON.stringify(encrypt("sk-ant-test123", key));
|
|
35
|
+
await db.insert(providerCredentials).values({
|
|
36
|
+
id: "cred-1",
|
|
37
|
+
provider: "anthropic",
|
|
38
|
+
keyName: "Test",
|
|
39
|
+
encryptedValue: encrypted,
|
|
40
|
+
authType: "header",
|
|
41
|
+
createdBy: "admin",
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const findings = await auditCredentialEncryption(db);
|
|
45
|
+
expect(findings).toEqual([]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("detects plaintext API keys", async () => {
|
|
49
|
+
await db.insert(providerCredentials).values({
|
|
50
|
+
id: "cred-1",
|
|
51
|
+
provider: "anthropic",
|
|
52
|
+
keyName: "Test",
|
|
53
|
+
encryptedValue: "sk-ant-api12345678901234567890",
|
|
54
|
+
authType: "header",
|
|
55
|
+
createdBy: "admin",
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const findings = await auditCredentialEncryption(db);
|
|
59
|
+
expect(findings).toHaveLength(1);
|
|
60
|
+
expect(findings[0].table).toBe("provider_credentials");
|
|
61
|
+
expect(findings[0].rowId).toBe("cred-1");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("detects malformed encrypted payloads (missing fields)", async () => {
|
|
65
|
+
await db.insert(providerCredentials).values({
|
|
66
|
+
id: "cred-1",
|
|
67
|
+
provider: "anthropic",
|
|
68
|
+
keyName: "Test",
|
|
69
|
+
encryptedValue: JSON.stringify({ iv: "aa" }), // missing authTag and ciphertext
|
|
70
|
+
authType: "header",
|
|
71
|
+
createdBy: "admin",
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const findings = await auditCredentialEncryption(db);
|
|
75
|
+
expect(findings).toHaveLength(1);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("returns empty array when table has no rows", async () => {
|
|
79
|
+
const findings = await auditCredentialEncryption(db);
|
|
80
|
+
expect(findings).toEqual([]);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("handles missing tenant_api_keys gracefully (no plaintext tenant keys)", async () => {
|
|
84
|
+
// tenant_api_keys table exists but is empty — should not throw
|
|
85
|
+
const findings = await auditCredentialEncryption(db);
|
|
86
|
+
expect(findings).toEqual([]);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("detects plaintext in tenant_api_keys when table exists", async () => {
|
|
90
|
+
await db.insert(tenantApiKeys).values({
|
|
91
|
+
id: "tk-1",
|
|
92
|
+
tenantId: "tenant-a",
|
|
93
|
+
provider: "anthropic",
|
|
94
|
+
label: "Test",
|
|
95
|
+
encryptedKey: "sk-ant-api12345678901234567890",
|
|
96
|
+
createdAt: Date.now(),
|
|
97
|
+
updatedAt: Date.now(),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const findings = await auditCredentialEncryption(db);
|
|
101
|
+
expect(findings).toHaveLength(1);
|
|
102
|
+
expect(findings[0].table).toBe("tenant_api_keys");
|
|
103
|
+
expect(findings[0].rowId).toBe("tk-1");
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("migratePlaintextCredentials", () => {
|
|
108
|
+
let vaultKey: Buffer;
|
|
109
|
+
let credRepo: DrizzleCredentialRepository;
|
|
110
|
+
let tenantAccess: DrizzleMigrationTenantKeyAccess;
|
|
111
|
+
|
|
112
|
+
beforeEach(async () => {
|
|
113
|
+
await truncateAllTables(pool);
|
|
114
|
+
vaultKey = generateInstanceKey();
|
|
115
|
+
credRepo = new DrizzleCredentialRepository(db);
|
|
116
|
+
tenantAccess = new DrizzleMigrationTenantKeyAccess(db);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("skips already-encrypted rows", async () => {
|
|
120
|
+
const encrypted = JSON.stringify(encrypt("sk-ant-test", vaultKey));
|
|
121
|
+
await db.insert(providerCredentials).values({
|
|
122
|
+
id: "cred-1",
|
|
123
|
+
provider: "anthropic",
|
|
124
|
+
keyName: "Test",
|
|
125
|
+
encryptedValue: encrypted,
|
|
126
|
+
authType: "header",
|
|
127
|
+
createdBy: "admin",
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const results = await migratePlaintextCredentials(credRepo, vaultKey, () => vaultKey, tenantAccess);
|
|
131
|
+
expect(results[0].migratedCount).toBe(0);
|
|
132
|
+
expect(results[0].errors).toHaveLength(0);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("encrypts plaintext rows", async () => {
|
|
136
|
+
await db.insert(providerCredentials).values({
|
|
137
|
+
id: "cred-1",
|
|
138
|
+
provider: "anthropic",
|
|
139
|
+
keyName: "Test",
|
|
140
|
+
encryptedValue: "sk-ant-plaintext-key-1234567890",
|
|
141
|
+
authType: "header",
|
|
142
|
+
createdBy: "admin",
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const results = await migratePlaintextCredentials(credRepo, vaultKey, () => vaultKey, tenantAccess);
|
|
146
|
+
expect(results[0].migratedCount).toBe(1);
|
|
147
|
+
|
|
148
|
+
// Verify the value is now encrypted JSON
|
|
149
|
+
const rows = await db
|
|
150
|
+
.select({ encryptedValue: providerCredentials.encryptedValue })
|
|
151
|
+
.from(providerCredentials)
|
|
152
|
+
.where(eq(providerCredentials.id, "cred-1"));
|
|
153
|
+
const parsed = JSON.parse(rows[0].encryptedValue);
|
|
154
|
+
expect(parsed).toHaveProperty("iv");
|
|
155
|
+
expect(parsed).toHaveProperty("authTag");
|
|
156
|
+
expect(parsed).toHaveProperty("ciphertext");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("handles empty provider_credentials gracefully", async () => {
|
|
160
|
+
// Should not throw when table is empty
|
|
161
|
+
const results = await migratePlaintextCredentials(credRepo, vaultKey, () => vaultKey, tenantAccess);
|
|
162
|
+
expect(results[0].table).toBe("provider_credentials");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("migrates tenant_api_keys when table exists", async () => {
|
|
166
|
+
await db.insert(tenantApiKeys).values({
|
|
167
|
+
id: "tk-1",
|
|
168
|
+
tenantId: "tenant-a",
|
|
169
|
+
provider: "anthropic",
|
|
170
|
+
label: "Test",
|
|
171
|
+
encryptedKey: "sk-ant-plaintext-key-1234567890",
|
|
172
|
+
createdAt: Date.now(),
|
|
173
|
+
updatedAt: Date.now(),
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const results = await migratePlaintextCredentials(credRepo, vaultKey, () => vaultKey, tenantAccess);
|
|
177
|
+
const tenantResult = results.find((r) => r.table === "tenant_api_keys");
|
|
178
|
+
expect(tenantResult).not.toBeNull();
|
|
179
|
+
expect(tenantResult?.migratedCount).toBe(1);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("returns table name in results", async () => {
|
|
183
|
+
const results = await migratePlaintextCredentials(credRepo, vaultKey, () => vaultKey, tenantAccess);
|
|
184
|
+
expect(results[0].table).toBe("provider_credentials");
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("migrated row is subsequently detected as already-encrypted", async () => {
|
|
188
|
+
await db.insert(providerCredentials).values({
|
|
189
|
+
id: "cred-1",
|
|
190
|
+
provider: "anthropic",
|
|
191
|
+
keyName: "Test",
|
|
192
|
+
encryptedValue: "sk-ant-plaintext-key-1234567890",
|
|
193
|
+
authType: "header",
|
|
194
|
+
createdBy: "admin",
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// First migration: should encrypt
|
|
198
|
+
const results1 = await migratePlaintextCredentials(credRepo, vaultKey, () => vaultKey, tenantAccess);
|
|
199
|
+
expect(results1[0].migratedCount).toBe(1);
|
|
200
|
+
|
|
201
|
+
// Second migration: row is now encrypted, should be skipped
|
|
202
|
+
const results2 = await migratePlaintextCredentials(credRepo, vaultKey, () => vaultKey, tenantAccess);
|
|
203
|
+
expect(results2[0].migratedCount).toBe(0);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("migrating already-migrated credential is a no-op (value unchanged)", async () => {
|
|
207
|
+
await db.insert(providerCredentials).values({
|
|
208
|
+
id: "cred-1",
|
|
209
|
+
provider: "anthropic",
|
|
210
|
+
keyName: "Test",
|
|
211
|
+
encryptedValue: "sk-ant-plaintext-key-1234567890",
|
|
212
|
+
authType: "header",
|
|
213
|
+
createdBy: "admin",
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Migrate once
|
|
217
|
+
await migratePlaintextCredentials(credRepo, vaultKey, () => vaultKey, tenantAccess);
|
|
218
|
+
|
|
219
|
+
// Capture the encrypted value
|
|
220
|
+
const rowsBefore = await db
|
|
221
|
+
.select({ encryptedValue: providerCredentials.encryptedValue })
|
|
222
|
+
.from(providerCredentials)
|
|
223
|
+
.where(eq(providerCredentials.id, "cred-1"));
|
|
224
|
+
const valueBefore = rowsBefore[0].encryptedValue;
|
|
225
|
+
|
|
226
|
+
// Migrate again — should be no-op
|
|
227
|
+
const results = await migratePlaintextCredentials(credRepo, vaultKey, () => vaultKey, tenantAccess);
|
|
228
|
+
expect(results[0].migratedCount).toBe(0);
|
|
229
|
+
|
|
230
|
+
// Value should be identical (not re-encrypted)
|
|
231
|
+
const rowsAfter = await db
|
|
232
|
+
.select({ encryptedValue: providerCredentials.encryptedValue })
|
|
233
|
+
.from(providerCredentials)
|
|
234
|
+
.where(eq(providerCredentials.id, "cred-1"));
|
|
235
|
+
expect(rowsAfter[0].encryptedValue).toBe(valueBefore);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("batch migrates 100 plaintext credentials with no plaintext remaining", async () => {
|
|
239
|
+
// Insert 100 plaintext credentials
|
|
240
|
+
for (let i = 0; i < 100; i++) {
|
|
241
|
+
await db.insert(providerCredentials).values({
|
|
242
|
+
id: `cred-${i}`,
|
|
243
|
+
provider: "anthropic",
|
|
244
|
+
keyName: `Key-${i}`,
|
|
245
|
+
encryptedValue: `sk-ant-plaintext-batch-key-${String(i).padStart(4, "0")}`,
|
|
246
|
+
authType: "header",
|
|
247
|
+
createdBy: "admin",
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const results = await migratePlaintextCredentials(credRepo, vaultKey, () => vaultKey, tenantAccess);
|
|
252
|
+
expect(results[0].migratedCount).toBe(100);
|
|
253
|
+
expect(results[0].errors).toHaveLength(0);
|
|
254
|
+
|
|
255
|
+
// Verify no plaintext remains
|
|
256
|
+
const allRows = await db
|
|
257
|
+
.select({ id: providerCredentials.id, encryptedValue: providerCredentials.encryptedValue })
|
|
258
|
+
.from(providerCredentials);
|
|
259
|
+
expect(allRows).toHaveLength(100);
|
|
260
|
+
|
|
261
|
+
for (const row of allRows) {
|
|
262
|
+
const parsed = JSON.parse(row.encryptedValue);
|
|
263
|
+
expect(parsed).toHaveProperty("iv");
|
|
264
|
+
expect(parsed).toHaveProperty("authTag");
|
|
265
|
+
expect(parsed).toHaveProperty("ciphertext");
|
|
266
|
+
// Ensure the raw value does not contain any plaintext key pattern
|
|
267
|
+
expect(row.encryptedValue).not.toMatch(/sk-ant-plaintext/);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe("credential vault migration path", () => {
|
|
273
|
+
let vaultKey: Buffer;
|
|
274
|
+
let credRepo: DrizzleCredentialRepository;
|
|
275
|
+
let tenantAccess: DrizzleMigrationTenantKeyAccess;
|
|
276
|
+
|
|
277
|
+
beforeEach(async () => {
|
|
278
|
+
await truncateAllTables(pool);
|
|
279
|
+
vaultKey = generateInstanceKey();
|
|
280
|
+
credRepo = new DrizzleCredentialRepository(db);
|
|
281
|
+
tenantAccess = new DrizzleMigrationTenantKeyAccess(db);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("pre-migration plaintext credential is readable after migratePlaintextCredentials", async () => {
|
|
285
|
+
// Insert a plaintext credential (simulating legacy state)
|
|
286
|
+
await db.insert(providerCredentials).values({
|
|
287
|
+
id: "cred-legacy",
|
|
288
|
+
provider: "anthropic",
|
|
289
|
+
keyName: "Legacy Key",
|
|
290
|
+
encryptedValue: "sk-ant-legacy-plaintext-key-999",
|
|
291
|
+
authType: "header",
|
|
292
|
+
authHeader: "x-api-key",
|
|
293
|
+
createdBy: "admin",
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// Pre-migration: raw value is plaintext
|
|
297
|
+
const rowsBefore = await db
|
|
298
|
+
.select({ encryptedValue: providerCredentials.encryptedValue })
|
|
299
|
+
.from(providerCredentials)
|
|
300
|
+
.where(eq(providerCredentials.id, "cred-legacy"));
|
|
301
|
+
expect(rowsBefore[0].encryptedValue).toBe("sk-ant-legacy-plaintext-key-999");
|
|
302
|
+
|
|
303
|
+
// Migrate
|
|
304
|
+
const results = await migratePlaintextCredentials(credRepo, vaultKey, () => vaultKey, tenantAccess);
|
|
305
|
+
expect(results[0].migratedCount).toBe(1);
|
|
306
|
+
expect(results[0].errors).toHaveLength(0);
|
|
307
|
+
|
|
308
|
+
// Post-migration: decrypt through CredentialVaultStore
|
|
309
|
+
const repo = new DrizzleCredentialRepository(db);
|
|
310
|
+
const store = new CredentialVaultStore(repo, vaultKey);
|
|
311
|
+
const decrypted = await store.decrypt("cred-legacy");
|
|
312
|
+
expect(decrypted).not.toBeNull();
|
|
313
|
+
const cred = decrypted as NonNullable<typeof decrypted>;
|
|
314
|
+
expect(cred.plaintextKey).toBe("sk-ant-legacy-plaintext-key-999");
|
|
315
|
+
expect(cred.provider).toBe("anthropic");
|
|
316
|
+
expect(cred.keyName).toBe("Legacy Key");
|
|
317
|
+
expect(cred.authType).toBe("header");
|
|
318
|
+
expect(cred.authHeader).toBe("x-api-key");
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it("post-migration credential metadata is fully preserved", async () => {
|
|
322
|
+
await db.insert(providerCredentials).values({
|
|
323
|
+
id: "cred-meta",
|
|
324
|
+
provider: "openai",
|
|
325
|
+
keyName: "Metadata Test",
|
|
326
|
+
encryptedValue: "sk-openai-meta-test-key-12345",
|
|
327
|
+
authType: "bearer",
|
|
328
|
+
authHeader: null,
|
|
329
|
+
createdBy: "admin-user",
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
await migratePlaintextCredentials(credRepo, vaultKey, () => vaultKey, tenantAccess);
|
|
333
|
+
|
|
334
|
+
const repo = new DrizzleCredentialRepository(db);
|
|
335
|
+
const store = new CredentialVaultStore(repo, vaultKey);
|
|
336
|
+
|
|
337
|
+
// Verify summary metadata is intact
|
|
338
|
+
const summary = await store.getById("cred-meta");
|
|
339
|
+
expect(summary).not.toBeNull();
|
|
340
|
+
const s = summary as NonNullable<typeof summary>;
|
|
341
|
+
expect(s.provider).toBe("openai");
|
|
342
|
+
expect(s.keyName).toBe("Metadata Test");
|
|
343
|
+
expect(s.authType).toBe("bearer");
|
|
344
|
+
expect(s.authHeader).toBeNull();
|
|
345
|
+
expect(s.createdBy).toBe("admin-user");
|
|
346
|
+
expect(s.isActive).toBe(true);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("migration idempotency: running twice does not corrupt and encrypted value is unchanged", async () => {
|
|
350
|
+
await db.insert(providerCredentials).values({
|
|
351
|
+
id: "cred-idem",
|
|
352
|
+
provider: "anthropic",
|
|
353
|
+
keyName: "Idempotent",
|
|
354
|
+
encryptedValue: "sk-ant-idempotent-test-key-000",
|
|
355
|
+
authType: "header",
|
|
356
|
+
createdBy: "admin",
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// First migration
|
|
360
|
+
const r1 = await migratePlaintextCredentials(credRepo, vaultKey, () => vaultKey, tenantAccess);
|
|
361
|
+
expect(r1[0].migratedCount).toBe(1);
|
|
362
|
+
|
|
363
|
+
// Capture encrypted value
|
|
364
|
+
const rowsAfterFirst = await db
|
|
365
|
+
.select({ encryptedValue: providerCredentials.encryptedValue })
|
|
366
|
+
.from(providerCredentials)
|
|
367
|
+
.where(eq(providerCredentials.id, "cred-idem"));
|
|
368
|
+
const encryptedAfterFirst = rowsAfterFirst[0].encryptedValue;
|
|
369
|
+
|
|
370
|
+
// Second migration (should be no-op)
|
|
371
|
+
const r2 = await migratePlaintextCredentials(credRepo, vaultKey, () => vaultKey, tenantAccess);
|
|
372
|
+
expect(r2[0].migratedCount).toBe(0);
|
|
373
|
+
|
|
374
|
+
// Encrypted value must be byte-identical
|
|
375
|
+
const rowsAfterSecond = await db
|
|
376
|
+
.select({ encryptedValue: providerCredentials.encryptedValue })
|
|
377
|
+
.from(providerCredentials)
|
|
378
|
+
.where(eq(providerCredentials.id, "cred-idem"));
|
|
379
|
+
expect(rowsAfterSecond[0].encryptedValue).toBe(encryptedAfterFirst);
|
|
380
|
+
|
|
381
|
+
// Still decryptable through the store
|
|
382
|
+
const repo = new DrizzleCredentialRepository(db);
|
|
383
|
+
const store = new CredentialVaultStore(repo, vaultKey);
|
|
384
|
+
const decrypted = await store.decrypt("cred-idem");
|
|
385
|
+
expect((decrypted as NonNullable<typeof decrypted>).plaintextKey).toBe("sk-ant-idempotent-test-key-000");
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it("partial migration failure: valid rows are migrated, invalid rows produce errors, valid rows remain readable", async () => {
|
|
389
|
+
// Row 1: valid plaintext
|
|
390
|
+
await db.insert(providerCredentials).values({
|
|
391
|
+
id: "cred-valid",
|
|
392
|
+
provider: "anthropic",
|
|
393
|
+
keyName: "Valid",
|
|
394
|
+
encryptedValue: "sk-ant-valid-plaintext-key-111",
|
|
395
|
+
authType: "header",
|
|
396
|
+
createdBy: "admin",
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// Row 2: already encrypted (should be skipped, not an error)
|
|
400
|
+
const alreadyEncrypted = JSON.stringify(encrypt("sk-ant-already-encrypted", vaultKey));
|
|
401
|
+
await db.insert(providerCredentials).values({
|
|
402
|
+
id: "cred-encrypted",
|
|
403
|
+
provider: "openai",
|
|
404
|
+
keyName: "Already Encrypted",
|
|
405
|
+
encryptedValue: alreadyEncrypted,
|
|
406
|
+
authType: "bearer",
|
|
407
|
+
createdBy: "admin",
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
const results = await migratePlaintextCredentials(credRepo, vaultKey, () => vaultKey, tenantAccess);
|
|
411
|
+
expect(results[0].migratedCount).toBe(1); // only the plaintext row
|
|
412
|
+
expect(results[0].errors).toHaveLength(0);
|
|
413
|
+
|
|
414
|
+
// Both rows are now readable through the store
|
|
415
|
+
const repo = new DrizzleCredentialRepository(db);
|
|
416
|
+
const store = new CredentialVaultStore(repo, vaultKey);
|
|
417
|
+
|
|
418
|
+
const d1 = await store.decrypt("cred-valid");
|
|
419
|
+
expect((d1 as NonNullable<typeof d1>).plaintextKey).toBe("sk-ant-valid-plaintext-key-111");
|
|
420
|
+
|
|
421
|
+
const d2 = await store.decrypt("cred-encrypted");
|
|
422
|
+
expect((d2 as NonNullable<typeof d2>).plaintextKey).toBe("sk-ant-already-encrypted");
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it("key rotation after plaintext migration: full chain works", async () => {
|
|
426
|
+
const oldSecret = "old-platform-secret-rotation-test";
|
|
427
|
+
const newSecret = "new-platform-secret-rotation-test";
|
|
428
|
+
const oldKey = getVaultEncryptionKey(oldSecret);
|
|
429
|
+
|
|
430
|
+
// Start with plaintext
|
|
431
|
+
await db.insert(providerCredentials).values({
|
|
432
|
+
id: "cred-chain",
|
|
433
|
+
provider: "anthropic",
|
|
434
|
+
keyName: "Chain Test",
|
|
435
|
+
encryptedValue: "sk-ant-chain-test-key-xyz",
|
|
436
|
+
authType: "header",
|
|
437
|
+
authHeader: "x-api-key",
|
|
438
|
+
createdBy: "admin",
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
// Step 1: Migrate plaintext to encrypted with old key
|
|
442
|
+
const oldCredRepo = new DrizzleCredentialRepository(db);
|
|
443
|
+
const migrateResults = await migratePlaintextCredentials(oldCredRepo, oldKey, () => oldKey);
|
|
444
|
+
expect(migrateResults[0].migratedCount).toBe(1);
|
|
445
|
+
|
|
446
|
+
// Verify readable with old key
|
|
447
|
+
const repo1 = new DrizzleCredentialRepository(db);
|
|
448
|
+
const store1 = new CredentialVaultStore(repo1, oldKey);
|
|
449
|
+
const d1 = await store1.decrypt("cred-chain");
|
|
450
|
+
expect((d1 as NonNullable<typeof d1>).plaintextKey).toBe("sk-ant-chain-test-key-xyz");
|
|
451
|
+
|
|
452
|
+
// Step 2: Rotate keys from old secret to new secret
|
|
453
|
+
const rotCredAccess = new DrizzleCredentialRepository(db);
|
|
454
|
+
const rotTenantKeyAccess = new DrizzleMigrationTenantKeyAccess(db);
|
|
455
|
+
const rotResult = await reEncryptAllCredentials(rotCredAccess, rotTenantKeyAccess, oldSecret, newSecret);
|
|
456
|
+
expect(rotResult.providerCredentials.migrated).toBe(1);
|
|
457
|
+
expect(rotResult.providerCredentials.errors).toHaveLength(0);
|
|
458
|
+
|
|
459
|
+
// Verify readable with new key
|
|
460
|
+
const newKey = getVaultEncryptionKey(newSecret);
|
|
461
|
+
const repo2 = new DrizzleCredentialRepository(db);
|
|
462
|
+
const store2 = new CredentialVaultStore(repo2, newKey);
|
|
463
|
+
const d2 = await store2.decrypt("cred-chain");
|
|
464
|
+
const d2cred = d2 as NonNullable<typeof d2>;
|
|
465
|
+
expect(d2cred.plaintextKey).toBe("sk-ant-chain-test-key-xyz");
|
|
466
|
+
expect(d2cred.provider).toBe("anthropic");
|
|
467
|
+
expect(d2cred.authHeader).toBe("x-api-key");
|
|
468
|
+
|
|
469
|
+
// Old key can no longer decrypt
|
|
470
|
+
const rowRaw = await db
|
|
471
|
+
.select({ encryptedValue: providerCredentials.encryptedValue })
|
|
472
|
+
.from(providerCredentials)
|
|
473
|
+
.where(eq(providerCredentials.id, "cred-chain"));
|
|
474
|
+
const payload: EncryptedPayload = JSON.parse(rowRaw[0].encryptedValue);
|
|
475
|
+
expect(() => decrypt(payload, oldKey)).toThrow();
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it("tenant_api_keys migration: plaintext tenant key is readable post-migration", async () => {
|
|
479
|
+
await db.insert(tenantApiKeys).values({
|
|
480
|
+
id: "tk-migrate",
|
|
481
|
+
tenantId: "tenant-test",
|
|
482
|
+
provider: "anthropic",
|
|
483
|
+
label: "Tenant Migration Test",
|
|
484
|
+
encryptedKey: "sk-ant-tenant-plaintext-key-888",
|
|
485
|
+
createdAt: Date.now(),
|
|
486
|
+
updatedAt: Date.now(),
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
const tenantKeyDeriver = (_tenantId: string) => vaultKey;
|
|
490
|
+
const localTenantAccess = new DrizzleMigrationTenantKeyAccess(db);
|
|
491
|
+
const results = await migratePlaintextCredentials(credRepo, vaultKey, tenantKeyDeriver, localTenantAccess);
|
|
492
|
+
const tenantResult = results.find((r) => r.table === "tenant_api_keys");
|
|
493
|
+
expect(tenantResult).not.toBeNull();
|
|
494
|
+
const tr = tenantResult as NonNullable<typeof tenantResult>;
|
|
495
|
+
expect(tr.migratedCount).toBe(1);
|
|
496
|
+
expect(tr.errors).toHaveLength(0);
|
|
497
|
+
|
|
498
|
+
// Verify the encrypted value is valid
|
|
499
|
+
const rows = await db
|
|
500
|
+
.select({ encryptedKey: tenantApiKeys.encryptedKey })
|
|
501
|
+
.from(tenantApiKeys)
|
|
502
|
+
.where(eq(tenantApiKeys.id, "tk-migrate"));
|
|
503
|
+
const payload: EncryptedPayload = JSON.parse(rows[0].encryptedKey);
|
|
504
|
+
const decrypted = decrypt(payload, vaultKey);
|
|
505
|
+
expect(decrypted).toBe("sk-ant-tenant-plaintext-key-888");
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it("audit after migration: no plaintext findings remain", async () => {
|
|
509
|
+
// Insert 3 plaintext credentials
|
|
510
|
+
for (let i = 0; i < 3; i++) {
|
|
511
|
+
await db.insert(providerCredentials).values({
|
|
512
|
+
id: `cred-audit-${i}`,
|
|
513
|
+
provider: "anthropic",
|
|
514
|
+
keyName: `Audit Key ${i}`,
|
|
515
|
+
encryptedValue: `sk-ant-audit-plaintext-key-${i}`,
|
|
516
|
+
authType: "header",
|
|
517
|
+
createdBy: "admin",
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Pre-migration audit should find 3 plaintext entries
|
|
522
|
+
const findingsBefore = await auditCredentialEncryption(db);
|
|
523
|
+
expect(findingsBefore).toHaveLength(3);
|
|
524
|
+
|
|
525
|
+
// Migrate
|
|
526
|
+
const results = await migratePlaintextCredentials(credRepo, vaultKey, () => vaultKey, tenantAccess);
|
|
527
|
+
expect(results[0].migratedCount).toBe(3);
|
|
528
|
+
|
|
529
|
+
// Post-migration audit should find 0 plaintext entries
|
|
530
|
+
const findingsAfter = await auditCredentialEncryption(db);
|
|
531
|
+
expect(findingsAfter).toHaveLength(0);
|
|
532
|
+
});
|
|
533
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { PlatformDb } from "../../db/index.js";
|
|
2
|
+
import { providerCredentials, tenantApiKeys } from "../../db/schema/index.js";
|
|
3
|
+
import { scanForKeyLeaks } from "../key-audit.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Scan all credential columns in the database for plaintext API key patterns.
|
|
7
|
+
* Returns an array of findings. Empty array = all clear.
|
|
8
|
+
*
|
|
9
|
+
* This is a safety net, not a migration — the platform was designed encrypted-first.
|
|
10
|
+
* Run as part of deployment validation or as a periodic security check.
|
|
11
|
+
*/
|
|
12
|
+
export interface PlaintextFinding {
|
|
13
|
+
table: string;
|
|
14
|
+
column: string;
|
|
15
|
+
rowId: string;
|
|
16
|
+
provider: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function auditCredentialEncryption(db: PlatformDb): Promise<PlaintextFinding[]> {
|
|
20
|
+
const findings: PlaintextFinding[] = [];
|
|
21
|
+
|
|
22
|
+
// Check provider_credentials.encrypted_value
|
|
23
|
+
const providerRows = await db
|
|
24
|
+
.select({ id: providerCredentials.id, encryptedValue: providerCredentials.encryptedValue })
|
|
25
|
+
.from(providerCredentials);
|
|
26
|
+
|
|
27
|
+
for (const row of providerRows) {
|
|
28
|
+
// A properly encrypted value should be a JSON object with iv/authTag/ciphertext
|
|
29
|
+
try {
|
|
30
|
+
const parsed = JSON.parse(row.encryptedValue);
|
|
31
|
+
if (!parsed.iv || !parsed.authTag || !parsed.ciphertext) {
|
|
32
|
+
findings.push({
|
|
33
|
+
table: "provider_credentials",
|
|
34
|
+
column: "encrypted_value",
|
|
35
|
+
rowId: row.id,
|
|
36
|
+
provider: "unknown",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
// Not valid JSON = likely plaintext
|
|
41
|
+
const leaks = scanForKeyLeaks(row.encryptedValue);
|
|
42
|
+
if (leaks.length > 0 || row.encryptedValue.trim().length > 0) {
|
|
43
|
+
findings.push({
|
|
44
|
+
table: "provider_credentials",
|
|
45
|
+
column: "encrypted_value",
|
|
46
|
+
rowId: row.id,
|
|
47
|
+
provider: leaks[0]?.provider ?? "unknown",
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check tenant_api_keys.encrypted_key (if table exists)
|
|
54
|
+
try {
|
|
55
|
+
const tenantRows = await db
|
|
56
|
+
.select({ id: tenantApiKeys.id, encryptedKey: tenantApiKeys.encryptedKey })
|
|
57
|
+
.from(tenantApiKeys);
|
|
58
|
+
|
|
59
|
+
for (const row of tenantRows) {
|
|
60
|
+
try {
|
|
61
|
+
const parsed = JSON.parse(row.encryptedKey);
|
|
62
|
+
if (!parsed.iv || !parsed.authTag || !parsed.ciphertext) {
|
|
63
|
+
findings.push({
|
|
64
|
+
table: "tenant_api_keys",
|
|
65
|
+
column: "encrypted_key",
|
|
66
|
+
rowId: row.id,
|
|
67
|
+
provider: "unknown",
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
const leaks = scanForKeyLeaks(row.encryptedKey);
|
|
72
|
+
if (leaks.length > 0 || row.encryptedKey.trim().length > 0) {
|
|
73
|
+
findings.push({
|
|
74
|
+
table: "tenant_api_keys",
|
|
75
|
+
column: "encrypted_key",
|
|
76
|
+
rowId: row.id,
|
|
77
|
+
provider: leaks[0]?.provider ?? "unknown",
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} catch (err) {
|
|
83
|
+
if (!(err instanceof Error && err.message.includes("no such table"))) throw err;
|
|
84
|
+
// Table doesn't exist yet — that's fine
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return findings;
|
|
88
|
+
}
|