@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,116 @@
|
|
|
1
|
+
import { isIP } from "node:net";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validate a node registration host to prevent SSRF.
|
|
5
|
+
* Blocks loopback, link-local, multicast, broadcast, and unspecified addresses always.
|
|
6
|
+
* Blocks private ranges (10.x, 172.16-31.x, 192.168.x, fc00::/7) unless
|
|
7
|
+
* ALLOW_PRIVATE_NODE_HOSTS=true (for VPC deployments).
|
|
8
|
+
*
|
|
9
|
+
* NOTE: This validates the host string only. It does NOT perform DNS resolution,
|
|
10
|
+
* so hostnames that resolve to private IPs (e.g., via DNS rebinding or nip.io tricks)
|
|
11
|
+
* are not caught here. Out-of-scope for this issue.
|
|
12
|
+
*/
|
|
13
|
+
export function validateNodeHost(host: string): void {
|
|
14
|
+
const trimmed = host.trim();
|
|
15
|
+
if (trimmed.length === 0) {
|
|
16
|
+
throw new Error("Invalid node host: empty");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Check for "localhost" hostname
|
|
20
|
+
if (trimmed.toLowerCase() === "localhost") {
|
|
21
|
+
throw new Error("Invalid node host: loopback hostname");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const ipVersion = isIP(trimmed);
|
|
25
|
+
|
|
26
|
+
if (ipVersion === 4) {
|
|
27
|
+
validateIPv4(trimmed);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (ipVersion === 6) {
|
|
32
|
+
validateIPv6(trimmed);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Reject malformed IPv4-looking strings (e.g. 999.999.999.999) that isIP()
|
|
37
|
+
// returns 0 for but validateHostname() would accept as digit-only labels.
|
|
38
|
+
if (/^\d+\.\d+\.\d+\.\d+$/.test(trimmed)) {
|
|
39
|
+
throw new Error("Invalid node host: malformed IPv4 address");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Not an IP — treat as hostname
|
|
43
|
+
validateHostname(trimmed);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function validateIPv4(ip: string): void {
|
|
47
|
+
const parts = ip.split(".").map(Number);
|
|
48
|
+
const [a, b] = parts;
|
|
49
|
+
|
|
50
|
+
// Unspecified
|
|
51
|
+
if (a === 0) throw new Error("Invalid node host: unspecified address");
|
|
52
|
+
|
|
53
|
+
// Loopback — ALWAYS blocked
|
|
54
|
+
if (a === 127) throw new Error("Invalid node host: loopback address");
|
|
55
|
+
|
|
56
|
+
// Link-local — ALWAYS blocked
|
|
57
|
+
if (a === 169 && b === 254) throw new Error("Invalid node host: link-local address");
|
|
58
|
+
|
|
59
|
+
// Multicast
|
|
60
|
+
if (a >= 224 && a <= 239) throw new Error("Invalid node host: multicast address");
|
|
61
|
+
|
|
62
|
+
// Broadcast
|
|
63
|
+
if (a === 255 && b === 255) throw new Error("Invalid node host: broadcast address");
|
|
64
|
+
|
|
65
|
+
// Private ranges — blocked unless ALLOW_PRIVATE_NODE_HOSTS
|
|
66
|
+
if (!allowPrivateHosts()) {
|
|
67
|
+
if (a === 10) throw new Error("Invalid node host: private address");
|
|
68
|
+
if (a === 172 && b >= 16 && b <= 31) throw new Error("Invalid node host: private address");
|
|
69
|
+
if (a === 192 && b === 168) throw new Error("Invalid node host: private address");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function validateIPv6(ip: string): void {
|
|
74
|
+
const normalized = ip.toLowerCase();
|
|
75
|
+
|
|
76
|
+
// Loopback ::1 — ALWAYS blocked
|
|
77
|
+
if (normalized === "::1") throw new Error("Invalid node host: loopback address");
|
|
78
|
+
|
|
79
|
+
// Unspecified :: — ALWAYS blocked
|
|
80
|
+
if (normalized === "::") throw new Error("Invalid node host: unspecified address");
|
|
81
|
+
|
|
82
|
+
// IPv6-mapped IPv4 — check the embedded IPv4
|
|
83
|
+
const v4MappedMatch = normalized.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
|
|
84
|
+
if (v4MappedMatch) {
|
|
85
|
+
validateIPv4(v4MappedMatch[1]);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Link-local fe80::/10 — ALWAYS blocked
|
|
90
|
+
if (normalized.startsWith("fe80:") || normalized.startsWith("fe80")) {
|
|
91
|
+
throw new Error("Invalid node host: link-local address");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Unique local fc00::/7 (fc00:: and fd00::) — private
|
|
95
|
+
if (!allowPrivateHosts()) {
|
|
96
|
+
if (normalized.startsWith("fc") || normalized.startsWith("fd")) {
|
|
97
|
+
throw new Error("Invalid node host: private address");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function validateHostname(host: string): void {
|
|
103
|
+
if (host.length > 253) throw new Error("Invalid node host: hostname too long");
|
|
104
|
+
if (host.startsWith(".") || host.endsWith(".")) throw new Error("Invalid node host: invalid hostname");
|
|
105
|
+
// RFC 1123: labels separated by dots, alphanumeric + hyphens
|
|
106
|
+
const labelPattern = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/;
|
|
107
|
+
const labels = host.split(".");
|
|
108
|
+
for (const label of labels) {
|
|
109
|
+
if (label.length === 0 || label.length > 63) throw new Error("Invalid node host: invalid hostname");
|
|
110
|
+
if (!labelPattern.test(label)) throw new Error("Invalid node host: invalid hostname");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function allowPrivateHosts(): boolean {
|
|
115
|
+
return process.env.ALLOW_PRIVATE_NODE_HOSTS === "true";
|
|
116
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export type { EncryptedPayload, Provider, ValidateKeyRequest, ValidateKeyResponse, WriteSecretsRequest, ProviderEndpoint } from "./types.js";
|
|
2
|
+
export { providerSchema, validateKeyRequestSchema, writeSecretsRequestSchema } from "./types.js";
|
|
3
|
+
export { deriveInstanceKey, generateInstanceKey, encrypt, decrypt } from "./encryption.js";
|
|
4
|
+
export { validateNodeHost } from "./host-validation.js";
|
|
5
|
+
export { assertSafeRedirectUrl } from "./redirect-allowlist.js";
|
|
6
|
+
export type { KeyLeakMatch } from "./key-audit.js";
|
|
7
|
+
export { scanForKeyLeaks } from "./key-audit.js";
|
|
8
|
+
export { writeEncryptedSeed, forwardSecretsToInstance } from "./key-injection.js";
|
|
9
|
+
export { PROVIDER_ENDPOINTS, validateProviderKey } from "./key-validation.js";
|
|
10
|
+
|
|
11
|
+
// Credential vault
|
|
12
|
+
export {
|
|
13
|
+
type SecretAuditEvent,
|
|
14
|
+
type ISecretAuditRepository,
|
|
15
|
+
DrizzleSecretAuditRepository,
|
|
16
|
+
type CredentialRow,
|
|
17
|
+
type CredentialSummaryRow,
|
|
18
|
+
type InsertCredentialRow,
|
|
19
|
+
type ICredentialMigrationAccess,
|
|
20
|
+
type ICredentialRepository,
|
|
21
|
+
type IMigrationTenantKeyAccess,
|
|
22
|
+
DrizzleCredentialRepository,
|
|
23
|
+
DrizzleMigrationTenantKeyAccess,
|
|
24
|
+
type RotationResult,
|
|
25
|
+
reEncryptAllCredentials,
|
|
26
|
+
type MigrationResult,
|
|
27
|
+
migratePlaintextCredentials,
|
|
28
|
+
type PlaintextFinding,
|
|
29
|
+
auditCredentialEncryption,
|
|
30
|
+
type AuthType,
|
|
31
|
+
type CreateCredentialInput,
|
|
32
|
+
type CredentialSummary,
|
|
33
|
+
type DecryptedCredential,
|
|
34
|
+
type ICredentialVaultStore,
|
|
35
|
+
type RotateCredentialInput,
|
|
36
|
+
CredentialVaultStore,
|
|
37
|
+
getVaultEncryptionKey,
|
|
38
|
+
} from "./credential-vault/index.js";
|
|
39
|
+
|
|
40
|
+
// Tenant keys
|
|
41
|
+
export {
|
|
42
|
+
type IKeyResolutionRepository,
|
|
43
|
+
DrizzleKeyResolutionRepository,
|
|
44
|
+
type ResolvedKey,
|
|
45
|
+
resolveApiKey,
|
|
46
|
+
buildPooledKeysMap,
|
|
47
|
+
type TenantApiKey,
|
|
48
|
+
type ITenantKeyRepository,
|
|
49
|
+
TenantKeyRepository,
|
|
50
|
+
type CapabilityName,
|
|
51
|
+
type TenantCapabilitySetting,
|
|
52
|
+
type ICapabilitySettingsRepository,
|
|
53
|
+
ALL_CAPABILITIES,
|
|
54
|
+
CapabilitySettingsStore,
|
|
55
|
+
type IOrgMembershipRepository,
|
|
56
|
+
type OrgResolvedKey,
|
|
57
|
+
DrizzleOrgMembershipRepository,
|
|
58
|
+
resolveApiKeyWithOrgFallback,
|
|
59
|
+
} from "./tenant-keys/index.js";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { scanForKeyLeaks } from "./key-audit.js";
|
|
3
|
+
|
|
4
|
+
describe("key-audit", () => {
|
|
5
|
+
describe("scanForKeyLeaks", () => {
|
|
6
|
+
it("detects Anthropic key patterns", () => {
|
|
7
|
+
const content = "info: processing request with key sk-ant-abcdefghijklmnopqrstuvwxyz123456";
|
|
8
|
+
const leaks = scanForKeyLeaks(content);
|
|
9
|
+
expect(leaks.length).toBe(1);
|
|
10
|
+
expect(leaks[0].provider).toBe("anthropic");
|
|
11
|
+
expect(leaks[0].line).toBe(1);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("detects OpenAI key patterns", () => {
|
|
15
|
+
const content = "debug: using API key sk-abcdefghijklmnopqrstuvwxyz";
|
|
16
|
+
const leaks = scanForKeyLeaks(content);
|
|
17
|
+
expect(leaks.some((l) => l.provider === "openai")).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("detects Google key patterns", () => {
|
|
21
|
+
const content = "config: api_key=AIzaSyA1234567890abcdefghijklmnopqrstuvwx";
|
|
22
|
+
const leaks = scanForKeyLeaks(content);
|
|
23
|
+
expect(leaks.some((l) => l.provider === "google")).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("returns empty array for clean logs", () => {
|
|
27
|
+
const content = [
|
|
28
|
+
"info: server started on port 3000",
|
|
29
|
+
"info: health check passed",
|
|
30
|
+
"debug: processing request id=abc-123",
|
|
31
|
+
].join("\n");
|
|
32
|
+
const leaks = scanForKeyLeaks(content);
|
|
33
|
+
expect(leaks).toEqual([]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("detects multiple leaks across lines", () => {
|
|
37
|
+
const content = [
|
|
38
|
+
"line 1: safe content",
|
|
39
|
+
"line 2: key=sk-ant-abcdefghijklmnopqrstuvwxyz",
|
|
40
|
+
"line 3: also sk-abcdefghijklmnopqrstuvwxyz",
|
|
41
|
+
].join("\n");
|
|
42
|
+
const leaks = scanForKeyLeaks(content);
|
|
43
|
+
expect(leaks.length).toBeGreaterThanOrEqual(2);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("truncates matched key in output for safety", () => {
|
|
47
|
+
const content = "key: sk-ant-abcdefghijklmnopqrstuvwxyz123456";
|
|
48
|
+
const leaks = scanForKeyLeaks(content);
|
|
49
|
+
expect(leaks[0].match).toContain("...");
|
|
50
|
+
expect(leaks[0].match.length).toBeLessThan(40);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("handles empty input", () => {
|
|
54
|
+
expect(scanForKeyLeaks("")).toEqual([]);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key material audit — grep-based detection of leaked key patterns in log output.
|
|
3
|
+
*
|
|
4
|
+
* This utility scans arbitrary text (log lines, config dumps, etc.) for patterns
|
|
5
|
+
* that look like provider API keys. Used in tests to verify zero key leakage.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Patterns that match common provider key formats. */
|
|
9
|
+
const KEY_PATTERNS: { provider: string; pattern: RegExp }[] = [
|
|
10
|
+
{ provider: "anthropic", pattern: /sk-ant-[A-Za-z0-9_-]{20,}/ },
|
|
11
|
+
{ provider: "openai", pattern: /sk-[A-Za-z0-9]{20,}/ },
|
|
12
|
+
{ provider: "google", pattern: /AIza[A-Za-z0-9_-]{30,}/ },
|
|
13
|
+
{ provider: "discord", pattern: /[A-Za-z0-9_-]{24}\.[A-Za-z0-9_-]{6}\.[A-Za-z0-9_-]{27,}/ },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
export interface KeyLeakMatch {
|
|
17
|
+
provider: string;
|
|
18
|
+
line: number;
|
|
19
|
+
match: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Scan text content for key-like patterns.
|
|
24
|
+
* Returns an array of matches found — empty array means no leaks detected.
|
|
25
|
+
*/
|
|
26
|
+
export function scanForKeyLeaks(content: string): KeyLeakMatch[] {
|
|
27
|
+
const leaks: KeyLeakMatch[] = [];
|
|
28
|
+
const lines = content.split("\n");
|
|
29
|
+
|
|
30
|
+
for (let i = 0; i < lines.length; i++) {
|
|
31
|
+
const line = lines[i];
|
|
32
|
+
for (const { provider, pattern } of KEY_PATTERNS) {
|
|
33
|
+
const match = line.match(pattern);
|
|
34
|
+
if (match) {
|
|
35
|
+
leaks.push({
|
|
36
|
+
provider,
|
|
37
|
+
line: i + 1,
|
|
38
|
+
match: `${match[0].slice(0, 8)}...${match[0].slice(-4)}`,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return leaks;
|
|
45
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { decrypt, generateInstanceKey } from "./encryption.js";
|
|
6
|
+
import { forwardSecretsToInstance, writeEncryptedSeed } from "./key-injection.js";
|
|
7
|
+
|
|
8
|
+
describe("key-injection", () => {
|
|
9
|
+
describe("writeEncryptedSeed", () => {
|
|
10
|
+
let tmpDir: string;
|
|
11
|
+
const key = generateInstanceKey();
|
|
12
|
+
|
|
13
|
+
beforeEach(async () => {
|
|
14
|
+
const { mkdtemp } = await import("node:fs/promises");
|
|
15
|
+
tmpDir = await mkdtemp(path.join(os.tmpdir(), "wopr-test-"));
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(async () => {
|
|
19
|
+
const { rm } = await import("node:fs/promises");
|
|
20
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("writes secrets.enc to the woprHome directory", async () => {
|
|
24
|
+
const woprHome = path.join(tmpDir, "instance-1");
|
|
25
|
+
const secrets = { ANTHROPIC_API_KEY: "sk-ant-test123" };
|
|
26
|
+
|
|
27
|
+
await writeEncryptedSeed(woprHome, secrets, key);
|
|
28
|
+
|
|
29
|
+
const seedPath = path.join(woprHome, "secrets.enc");
|
|
30
|
+
const content = await readFile(seedPath, "utf-8");
|
|
31
|
+
const payload = JSON.parse(content);
|
|
32
|
+
|
|
33
|
+
expect(payload).toHaveProperty("iv");
|
|
34
|
+
expect(payload).toHaveProperty("authTag");
|
|
35
|
+
expect(payload).toHaveProperty("ciphertext");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("encrypted seed can be decrypted back to original secrets", async () => {
|
|
39
|
+
const woprHome = path.join(tmpDir, "instance-2");
|
|
40
|
+
const secrets = { DISCORD_TOKEN: "token123", OPENAI_KEY: "sk-openai" };
|
|
41
|
+
|
|
42
|
+
await writeEncryptedSeed(woprHome, secrets, key);
|
|
43
|
+
|
|
44
|
+
const seedPath = path.join(woprHome, "secrets.enc");
|
|
45
|
+
const content = await readFile(seedPath, "utf-8");
|
|
46
|
+
const payload = JSON.parse(content);
|
|
47
|
+
const decrypted = JSON.parse(decrypt(payload, key));
|
|
48
|
+
|
|
49
|
+
expect(decrypted).toEqual(secrets);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("creates directories recursively", async () => {
|
|
53
|
+
const woprHome = path.join(tmpDir, "deep", "nested", "dir");
|
|
54
|
+
await writeEncryptedSeed(woprHome, { KEY: "val" }, key);
|
|
55
|
+
|
|
56
|
+
const content = await readFile(path.join(woprHome, "secrets.enc"), "utf-8");
|
|
57
|
+
expect(JSON.parse(content)).toHaveProperty("ciphertext");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("seed file does NOT contain plaintext key values", async () => {
|
|
61
|
+
const woprHome = path.join(tmpDir, "instance-3");
|
|
62
|
+
const secrets = { ANTHROPIC_API_KEY: "sk-ant-api0xxxxxxxxxxxxxxxxxxxx" };
|
|
63
|
+
|
|
64
|
+
await writeEncryptedSeed(woprHome, secrets, key);
|
|
65
|
+
|
|
66
|
+
const content = await readFile(path.join(woprHome, "secrets.enc"), "utf-8");
|
|
67
|
+
expect(content).not.toContain("sk-ant-api0xxxxxxxxxxxxxxxxxxxx");
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("forwardSecretsToInstance", () => {
|
|
72
|
+
const originalFetch = globalThis.fetch;
|
|
73
|
+
|
|
74
|
+
beforeEach(() => {
|
|
75
|
+
globalThis.fetch = vi.fn();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
afterEach(() => {
|
|
79
|
+
globalThis.fetch = originalFetch;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("forwards body opaquely and returns ok on success", async () => {
|
|
83
|
+
vi.mocked(globalThis.fetch).mockResolvedValue(new Response(null, { status: 200 }));
|
|
84
|
+
|
|
85
|
+
const result = await forwardSecretsToInstance("http://container:3000", "session-token", '{"KEY":"val"}');
|
|
86
|
+
|
|
87
|
+
expect(result.ok).toBe(true);
|
|
88
|
+
expect(result.status).toBe(200);
|
|
89
|
+
expect(globalThis.fetch).toHaveBeenCalledWith(
|
|
90
|
+
"http://container:3000/config/secrets",
|
|
91
|
+
expect.objectContaining({
|
|
92
|
+
method: "PUT",
|
|
93
|
+
body: '{"KEY":"val"}',
|
|
94
|
+
headers: expect.objectContaining({
|
|
95
|
+
Authorization: "Bearer session-token",
|
|
96
|
+
}),
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("returns error on non-ok response", async () => {
|
|
102
|
+
vi.mocked(globalThis.fetch).mockResolvedValue(new Response("Forbidden", { status: 403 }));
|
|
103
|
+
|
|
104
|
+
const result = await forwardSecretsToInstance("http://container:3000", "token", "{}");
|
|
105
|
+
|
|
106
|
+
expect(result.ok).toBe(false);
|
|
107
|
+
expect(result.status).toBe(403);
|
|
108
|
+
expect(result.error).toBe("Forbidden");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("returns 502 on network error", async () => {
|
|
112
|
+
vi.mocked(globalThis.fetch).mockRejectedValue(new Error("Connection refused"));
|
|
113
|
+
|
|
114
|
+
const result = await forwardSecretsToInstance("http://container:3000", "token", "{}");
|
|
115
|
+
|
|
116
|
+
expect(result.ok).toBe(false);
|
|
117
|
+
expect(result.status).toBe(502);
|
|
118
|
+
expect(result.error).toBe("Connection refused");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("returns 502 with fallback message when thrown value is not an Error", async () => {
|
|
122
|
+
vi.mocked(globalThis.fetch).mockRejectedValue("string error");
|
|
123
|
+
|
|
124
|
+
const result = await forwardSecretsToInstance("http://container:3000", "token", "{}");
|
|
125
|
+
|
|
126
|
+
expect(result.ok).toBe(false);
|
|
127
|
+
expect(result.status).toBe(502);
|
|
128
|
+
expect(result.error).toBe("Failed to forward secrets");
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { logger } from "../config/logger.js";
|
|
4
|
+
import { encrypt } from "./encryption.js";
|
|
5
|
+
import type { EncryptedPayload } from "./types.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Write an encrypted seed file to an instance's WOPR_HOME volume.
|
|
9
|
+
* Used during initial provisioning when the instance container isn't running yet.
|
|
10
|
+
*
|
|
11
|
+
* The platform writes ciphertext only — it never sees or persists the plaintext keys.
|
|
12
|
+
* The instance decrypts on first boot using its own derived key (injected as a Docker secret).
|
|
13
|
+
*
|
|
14
|
+
* @param woprHome - Absolute path to the instance's WOPR_HOME volume mount.
|
|
15
|
+
* @param secrets - Key-value map of secrets to encrypt.
|
|
16
|
+
* @param instanceKey - The 32-byte instance-derived encryption key.
|
|
17
|
+
* @returns The encrypted payload that was written.
|
|
18
|
+
*/
|
|
19
|
+
export async function writeEncryptedSeed(
|
|
20
|
+
woprHome: string,
|
|
21
|
+
secrets: Record<string, string>,
|
|
22
|
+
instanceKey: Buffer,
|
|
23
|
+
): Promise<EncryptedPayload> {
|
|
24
|
+
const serialized = JSON.stringify(secrets);
|
|
25
|
+
const encrypted = encrypt(serialized, instanceKey);
|
|
26
|
+
|
|
27
|
+
await mkdir(woprHome, { recursive: true });
|
|
28
|
+
const seedPath = path.join(woprHome, "secrets.enc");
|
|
29
|
+
await writeFile(seedPath, JSON.stringify(encrypted), { mode: 0o600 });
|
|
30
|
+
|
|
31
|
+
logger.info("Wrote encrypted seed file", { path: seedPath });
|
|
32
|
+
return encrypted;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Forward secrets opaquely to a running instance container.
|
|
37
|
+
* The platform acts as a pass-through — it never parses or logs the request body.
|
|
38
|
+
*
|
|
39
|
+
* @param instanceUrl - The internal URL of the running instance (e.g., http://container:3000).
|
|
40
|
+
* @param sessionToken - The user's session token for authentication.
|
|
41
|
+
* @param opaqueBody - The raw request body string to forward without parsing.
|
|
42
|
+
* @returns Whether the write succeeded.
|
|
43
|
+
*/
|
|
44
|
+
export async function forwardSecretsToInstance(
|
|
45
|
+
instanceUrl: string,
|
|
46
|
+
sessionToken: string,
|
|
47
|
+
opaqueBody: string,
|
|
48
|
+
): Promise<{ ok: boolean; status: number; error?: string }> {
|
|
49
|
+
try {
|
|
50
|
+
const response = await fetch(`${instanceUrl}/config/secrets`, {
|
|
51
|
+
method: "PUT",
|
|
52
|
+
headers: {
|
|
53
|
+
"Content-Type": "application/json",
|
|
54
|
+
Authorization: `Bearer ${sessionToken}`,
|
|
55
|
+
},
|
|
56
|
+
body: opaqueBody,
|
|
57
|
+
signal: AbortSignal.timeout(10_000),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (response.ok) {
|
|
61
|
+
return { ok: true, status: response.status };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
65
|
+
return { ok: false, status: response.status, error: errorText };
|
|
66
|
+
} catch (err) {
|
|
67
|
+
const message = err instanceof Error ? err.message : "Failed to forward secrets";
|
|
68
|
+
logger.error("Failed to forward secrets to instance", { error: message });
|
|
69
|
+
return { ok: false, status: 502, error: message };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { PROVIDER_API_URLS } from "../config/provider-endpoints.js";
|
|
3
|
+
import { PROVIDER_ENDPOINTS, validateProviderKey } from "./key-validation.js";
|
|
4
|
+
|
|
5
|
+
describe("key-validation", () => {
|
|
6
|
+
describe("PROVIDER_API_URLS", () => {
|
|
7
|
+
it("exports a URL for every supported provider", () => {
|
|
8
|
+
expect(PROVIDER_API_URLS.anthropic).toBe("https://api.anthropic.com/v1/models");
|
|
9
|
+
expect(PROVIDER_API_URLS.openai).toBe("https://api.openai.com/v1/models");
|
|
10
|
+
expect(PROVIDER_API_URLS.google).toBe("https://generativelanguage.googleapis.com/v1/models");
|
|
11
|
+
expect(PROVIDER_API_URLS.discord).toBe("https://discord.com/api/v10/users/@me");
|
|
12
|
+
expect(PROVIDER_API_URLS.elevenlabs).toBe("https://api.elevenlabs.io/v1/user");
|
|
13
|
+
expect(PROVIDER_API_URLS.deepgram).toBe("https://api.deepgram.com/v1/projects");
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe("PROVIDER_ENDPOINTS", () => {
|
|
18
|
+
it("has entries for all supported providers", () => {
|
|
19
|
+
expect(PROVIDER_ENDPOINTS.anthropic).toEqual(expect.any(Object));
|
|
20
|
+
expect(PROVIDER_ENDPOINTS.openai).toEqual(expect.any(Object));
|
|
21
|
+
expect(PROVIDER_ENDPOINTS.google).toEqual(expect.any(Object));
|
|
22
|
+
expect(PROVIDER_ENDPOINTS.discord).toEqual(expect.any(Object));
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("anthropic headers include x-api-key", () => {
|
|
26
|
+
const headers = PROVIDER_ENDPOINTS.anthropic.headers("test-key");
|
|
27
|
+
expect(headers["x-api-key"]).toBe("test-key");
|
|
28
|
+
expect(headers["anthropic-version"]).toBe("2023-06-01");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("openai headers include Bearer token", () => {
|
|
32
|
+
const headers = PROVIDER_ENDPOINTS.openai.headers("test-key");
|
|
33
|
+
expect(headers.Authorization).toBe("Bearer test-key");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("google headers include x-goog-api-key", () => {
|
|
37
|
+
const headers = PROVIDER_ENDPOINTS.google.headers("test-key");
|
|
38
|
+
expect(headers["x-goog-api-key"]).toBe("test-key");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("discord headers include Bot prefix", () => {
|
|
42
|
+
const headers = PROVIDER_ENDPOINTS.discord.headers("test-key");
|
|
43
|
+
expect(headers.Authorization).toBe("Bot test-key");
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("validateProviderKey", () => {
|
|
48
|
+
const originalFetch = globalThis.fetch;
|
|
49
|
+
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
globalThis.fetch = vi.fn();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
afterEach(() => {
|
|
55
|
+
globalThis.fetch = originalFetch;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("returns valid=true when provider returns 200", async () => {
|
|
59
|
+
vi.mocked(globalThis.fetch).mockResolvedValue(new Response(null, { status: 200 }));
|
|
60
|
+
|
|
61
|
+
const result = await validateProviderKey("openai", "sk-valid-key");
|
|
62
|
+
expect(result.valid).toBe(true);
|
|
63
|
+
expect(result.error).toBeUndefined();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("returns valid=false with error for 401", async () => {
|
|
67
|
+
vi.mocked(globalThis.fetch).mockResolvedValue(new Response(null, { status: 401 }));
|
|
68
|
+
|
|
69
|
+
const result = await validateProviderKey("anthropic", "sk-ant-invalid");
|
|
70
|
+
expect(result.valid).toBe(false);
|
|
71
|
+
expect(result.error).toBe("Invalid API key");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("returns valid=false with error for 403", async () => {
|
|
75
|
+
vi.mocked(globalThis.fetch).mockResolvedValue(new Response(null, { status: 403 }));
|
|
76
|
+
|
|
77
|
+
const result = await validateProviderKey("discord", "bad-token");
|
|
78
|
+
expect(result.valid).toBe(false);
|
|
79
|
+
expect(result.error).toBe("Invalid API key");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("returns valid=false with status for non-auth errors", async () => {
|
|
83
|
+
vi.mocked(globalThis.fetch).mockResolvedValue(new Response(null, { status: 500 }));
|
|
84
|
+
|
|
85
|
+
const result = await validateProviderKey("openai", "sk-key");
|
|
86
|
+
expect(result.valid).toBe(false);
|
|
87
|
+
expect(result.error).toBe("Provider returned status 500");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("returns valid=false on network error", async () => {
|
|
91
|
+
vi.mocked(globalThis.fetch).mockRejectedValue(new Error("Network timeout"));
|
|
92
|
+
|
|
93
|
+
const result = await validateProviderKey("google", "AIza-key");
|
|
94
|
+
expect(result.valid).toBe(false);
|
|
95
|
+
expect(result.error).toBe("Network timeout");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("calls the correct provider URL", async () => {
|
|
99
|
+
vi.mocked(globalThis.fetch).mockResolvedValue(new Response(null, { status: 200 }));
|
|
100
|
+
|
|
101
|
+
await validateProviderKey("anthropic", "sk-ant-test");
|
|
102
|
+
expect(globalThis.fetch).toHaveBeenCalledWith(
|
|
103
|
+
"https://api.anthropic.com/v1/models",
|
|
104
|
+
expect.objectContaining({
|
|
105
|
+
method: "GET",
|
|
106
|
+
headers: expect.objectContaining({ "x-api-key": "sk-ant-test" }),
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { PROVIDER_API_URLS } from "../config/provider-endpoints.js";
|
|
2
|
+
import type { Provider, ProviderEndpoint, ValidateKeyResponse } from "./types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Provider API endpoints used for key validation.
|
|
6
|
+
* For CORS-friendly providers, the browser can call these directly.
|
|
7
|
+
* For CORS-blocked providers (e.g., Anthropic), the platform proxy decrypts
|
|
8
|
+
* and validates in memory without ever logging the key.
|
|
9
|
+
* URLs are sourced from PROVIDER_API_URLS; headers are provider-specific.
|
|
10
|
+
*/
|
|
11
|
+
export const PROVIDER_ENDPOINTS: Record<Provider, ProviderEndpoint> = {
|
|
12
|
+
anthropic: {
|
|
13
|
+
url: PROVIDER_API_URLS.anthropic,
|
|
14
|
+
headers: (key) => ({
|
|
15
|
+
"x-api-key": key,
|
|
16
|
+
"anthropic-version": "2023-06-01",
|
|
17
|
+
}),
|
|
18
|
+
},
|
|
19
|
+
openai: {
|
|
20
|
+
url: PROVIDER_API_URLS.openai,
|
|
21
|
+
headers: (key) => ({
|
|
22
|
+
Authorization: `Bearer ${key}`,
|
|
23
|
+
}),
|
|
24
|
+
},
|
|
25
|
+
google: {
|
|
26
|
+
url: PROVIDER_API_URLS.google,
|
|
27
|
+
headers: (key) => ({
|
|
28
|
+
"x-goog-api-key": key,
|
|
29
|
+
}),
|
|
30
|
+
},
|
|
31
|
+
discord: {
|
|
32
|
+
url: PROVIDER_API_URLS.discord,
|
|
33
|
+
headers: (key) => ({
|
|
34
|
+
Authorization: `Bot ${key}`,
|
|
35
|
+
}),
|
|
36
|
+
},
|
|
37
|
+
elevenlabs: {
|
|
38
|
+
url: PROVIDER_API_URLS.elevenlabs,
|
|
39
|
+
headers: (key) => ({
|
|
40
|
+
"xi-api-key": key,
|
|
41
|
+
}),
|
|
42
|
+
},
|
|
43
|
+
deepgram: {
|
|
44
|
+
url: PROVIDER_API_URLS.deepgram,
|
|
45
|
+
headers: (key) => ({
|
|
46
|
+
Authorization: `Token ${key}`,
|
|
47
|
+
}),
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Validate a provider API key by making a lightweight read-only request.
|
|
53
|
+
* The key is held in memory only for the duration of the fetch and then discarded.
|
|
54
|
+
*
|
|
55
|
+
* SECURITY: This function must NEVER log, persist, or return the key itself.
|
|
56
|
+
*/
|
|
57
|
+
export async function validateProviderKey(provider: Provider, key: string): Promise<ValidateKeyResponse> {
|
|
58
|
+
const endpoint = PROVIDER_ENDPOINTS[provider];
|
|
59
|
+
if (!endpoint) {
|
|
60
|
+
return { valid: false, error: `Unknown provider: ${provider}` };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const response = await fetch(endpoint.url, {
|
|
65
|
+
method: "GET",
|
|
66
|
+
headers: endpoint.headers(key),
|
|
67
|
+
signal: AbortSignal.timeout(10_000),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (response.ok) {
|
|
71
|
+
return { valid: true };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 401/403 = invalid key; other errors are transient
|
|
75
|
+
if (response.status === 401 || response.status === 403) {
|
|
76
|
+
return { valid: false, error: "Invalid API key" };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { valid: false, error: `Provider returned status ${response.status}` };
|
|
80
|
+
} catch (err) {
|
|
81
|
+
const message = err instanceof Error ? err.message : "Validation request failed";
|
|
82
|
+
return { valid: false, error: message };
|
|
83
|
+
}
|
|
84
|
+
}
|