@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,469 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email Templates — HTML and plain text templates for all transactional emails.
|
|
3
|
+
*
|
|
4
|
+
* Each template has an HTML and a plain text version. HTML uses inline styles
|
|
5
|
+
* for maximum email client compatibility. All user-supplied values are escaped
|
|
6
|
+
* to prevent XSS.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { escapeHtml } from "./resend-adapter.js";
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Shared layout helpers
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
function wrapHtml(title: string, bodyContent: string): string {
|
|
16
|
+
return `<!DOCTYPE html>
|
|
17
|
+
<html>
|
|
18
|
+
<head>
|
|
19
|
+
<meta charset="utf-8">
|
|
20
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
21
|
+
<title>${escapeHtml(title)}</title>
|
|
22
|
+
</head>
|
|
23
|
+
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f4f4f4;">
|
|
24
|
+
<table role="presentation" style="width: 100%; border-collapse: collapse;">
|
|
25
|
+
<tr>
|
|
26
|
+
<td style="padding: 40px 0; text-align: center;">
|
|
27
|
+
<table role="presentation" style="width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
|
28
|
+
${bodyContent}
|
|
29
|
+
</table>
|
|
30
|
+
<p style="margin-top: 20px; color: #a0aec0; font-size: 12px;">© ${new Date().getFullYear()} WOPR Network. All rights reserved.</p>
|
|
31
|
+
</td>
|
|
32
|
+
</tr>
|
|
33
|
+
</table>
|
|
34
|
+
</body>
|
|
35
|
+
</html>`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function heading(text: string): string {
|
|
39
|
+
return `<tr>
|
|
40
|
+
<td style="padding: 40px 40px 20px 40px; text-align: center;">
|
|
41
|
+
<h1 style="margin: 0; font-size: 24px; font-weight: 600; color: #1a1a1a;">${escapeHtml(text)}</h1>
|
|
42
|
+
</td>
|
|
43
|
+
</tr>`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function paragraph(html: string): string {
|
|
47
|
+
return `<tr>
|
|
48
|
+
<td style="padding: 0 40px 20px 40px; color: #4a5568; font-size: 16px; line-height: 24px;">
|
|
49
|
+
${html}
|
|
50
|
+
</td>
|
|
51
|
+
</tr>`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function button(url: string, label: string, color = "#2563eb"): string {
|
|
55
|
+
return `<tr>
|
|
56
|
+
<td style="padding: 0 40px 30px 40px; text-align: center;">
|
|
57
|
+
<a href="${escapeHtml(url)}" style="display: inline-block; padding: 12px 32px; background-color: ${color}; color: #ffffff; text-decoration: none; font-weight: 600; border-radius: 6px; font-size: 16px;">${escapeHtml(label)}</a>
|
|
58
|
+
</td>
|
|
59
|
+
</tr>`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function footer(text: string): string {
|
|
63
|
+
return `<tr>
|
|
64
|
+
<td style="padding: 0 40px 40px 40px; color: #718096; font-size: 14px; line-height: 20px; border-top: 1px solid #e2e8f0;">
|
|
65
|
+
<p style="margin-top: 20px;">${text}</p>
|
|
66
|
+
</td>
|
|
67
|
+
</tr>`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function unsubscribeFooter(unsubscribeUrl?: string): string {
|
|
71
|
+
if (!unsubscribeUrl) return "";
|
|
72
|
+
return `<tr>
|
|
73
|
+
<td style="padding: 0 40px 20px 40px; text-align: center; color: #a0aec0; font-size: 12px;">
|
|
74
|
+
<a href="${escapeHtml(unsubscribeUrl)}" style="color: #a0aec0; text-decoration: underline;">Unsubscribe from billing notifications</a>
|
|
75
|
+
</td>
|
|
76
|
+
</tr>`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function unsubscribeText(unsubscribeUrl?: string): string {
|
|
80
|
+
if (!unsubscribeUrl) return "";
|
|
81
|
+
return `\n\nTo unsubscribe from billing notifications: ${unsubscribeUrl}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Build the unsubscribe URL from a creditsUrl by deriving the origin. */
|
|
85
|
+
function buildUnsubscribeUrl(creditsUrl: string): string {
|
|
86
|
+
try {
|
|
87
|
+
const base = new URL(creditsUrl);
|
|
88
|
+
return `${base.origin}/settings/notifications`;
|
|
89
|
+
} catch {
|
|
90
|
+
// If creditsUrl is not a valid absolute URL, fall back to simple concatenation.
|
|
91
|
+
return `${creditsUrl.replace(/\/+$/, "").split("/").slice(0, 3).join("/")}/settings/notifications`;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// Template types
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
export type TemplateName =
|
|
100
|
+
| "verify-email"
|
|
101
|
+
| "welcome"
|
|
102
|
+
| "password-reset"
|
|
103
|
+
| "credit-purchase"
|
|
104
|
+
| "low-balance"
|
|
105
|
+
| "bot-suspended"
|
|
106
|
+
| "bot-destruction"
|
|
107
|
+
| "data-deleted";
|
|
108
|
+
|
|
109
|
+
export interface TemplateResult {
|
|
110
|
+
subject: string;
|
|
111
|
+
html: string;
|
|
112
|
+
text: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// 1. Verify Email
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
export function verifyEmailTemplate(verifyUrl: string, email: string): TemplateResult {
|
|
120
|
+
const escapedEmail = escapeHtml(email);
|
|
121
|
+
const escapedUrl = escapeHtml(verifyUrl);
|
|
122
|
+
|
|
123
|
+
const html = wrapHtml(
|
|
124
|
+
"Verify Your Email",
|
|
125
|
+
[
|
|
126
|
+
heading("Verify Your Email"),
|
|
127
|
+
paragraph(`<p>Thanks for signing up for WOPR! Please verify your email address (<strong>${escapedEmail}</strong>) to activate your account.</p>
|
|
128
|
+
<p>Click the button below to verify. This link will expire in 24 hours.</p>`),
|
|
129
|
+
button(verifyUrl, "Verify Email"),
|
|
130
|
+
paragraph(`<p style="color: #718096; font-size: 14px;">Or copy and paste this URL into your browser:</p>
|
|
131
|
+
<p style="word-break: break-all; color: #2563eb; font-size: 14px;">${escapedUrl}</p>`),
|
|
132
|
+
footer("If you didn't create a WOPR account, you can safely ignore this email."),
|
|
133
|
+
].join("\n"),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const text = `Verify Your Email
|
|
137
|
+
|
|
138
|
+
Thanks for signing up for WOPR! Please verify your email address (${email}) to activate your account.
|
|
139
|
+
|
|
140
|
+
Click the link below to verify. This link will expire in 24 hours.
|
|
141
|
+
|
|
142
|
+
${verifyUrl}
|
|
143
|
+
|
|
144
|
+
If you didn't create a WOPR account, you can safely ignore this email.
|
|
145
|
+
|
|
146
|
+
(c) ${new Date().getFullYear()} WOPR Network. All rights reserved.`;
|
|
147
|
+
|
|
148
|
+
return { subject: "Verify your WOPR account", html, text };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
// 2. Welcome
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
export function welcomeTemplate(email: string): TemplateResult {
|
|
156
|
+
const escapedEmail = escapeHtml(email);
|
|
157
|
+
|
|
158
|
+
const html = wrapHtml(
|
|
159
|
+
"Welcome to WOPR",
|
|
160
|
+
[
|
|
161
|
+
heading("Welcome to WOPR!"),
|
|
162
|
+
paragraph(`<p>Hi <strong>${escapedEmail}</strong>,</p>
|
|
163
|
+
<p>Your email has been verified and your account is now active. You've been granted <strong>$5.00 in free credits</strong> to get started.</p>
|
|
164
|
+
<p>You can now create bots, connect them to Discord, Slack, and more.</p>`),
|
|
165
|
+
footer("Happy building!"),
|
|
166
|
+
].join("\n"),
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const text = `Welcome to WOPR!
|
|
170
|
+
|
|
171
|
+
Hi ${email},
|
|
172
|
+
|
|
173
|
+
Your email has been verified and your account is now active. You've been granted $5.00 in free credits to get started.
|
|
174
|
+
|
|
175
|
+
You can now create bots, connect them to Discord, Slack, and more.
|
|
176
|
+
|
|
177
|
+
Happy building!
|
|
178
|
+
|
|
179
|
+
(c) ${new Date().getFullYear()} WOPR Network. All rights reserved.`;
|
|
180
|
+
|
|
181
|
+
return { subject: "Welcome to WOPR", html, text };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
// 3. Password Reset (supersedes WOP-346 inline template)
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
|
|
188
|
+
export function passwordResetEmailTemplate(resetUrl: string, email: string): TemplateResult {
|
|
189
|
+
const escapedEmail = escapeHtml(email);
|
|
190
|
+
const escapedUrl = escapeHtml(resetUrl);
|
|
191
|
+
|
|
192
|
+
const html = wrapHtml(
|
|
193
|
+
"Reset Your Password",
|
|
194
|
+
[
|
|
195
|
+
heading("Reset Your Password"),
|
|
196
|
+
paragraph(`<p>Hi there,</p>
|
|
197
|
+
<p>You requested a password reset for your WOPR account (<strong>${escapedEmail}</strong>).</p>
|
|
198
|
+
<p>Click the button below to create a new password. This link will expire in 1 hour.</p>`),
|
|
199
|
+
button(resetUrl, "Reset Password"),
|
|
200
|
+
paragraph(`<p style="color: #718096; font-size: 14px;">Or copy and paste this URL into your browser:</p>
|
|
201
|
+
<p style="word-break: break-all; color: #2563eb; font-size: 14px;">${escapedUrl}</p>`),
|
|
202
|
+
footer("If you didn't request this password reset, you can safely ignore this email."),
|
|
203
|
+
].join("\n"),
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
const text = `Reset Your Password
|
|
207
|
+
|
|
208
|
+
Hi there,
|
|
209
|
+
|
|
210
|
+
You requested a password reset for your WOPR account (${email}).
|
|
211
|
+
|
|
212
|
+
Click the link below to create a new password. This link will expire in 1 hour.
|
|
213
|
+
|
|
214
|
+
${resetUrl}
|
|
215
|
+
|
|
216
|
+
If you didn't request this password reset, you can safely ignore this email.
|
|
217
|
+
|
|
218
|
+
(c) ${new Date().getFullYear()} WOPR Network. All rights reserved.`;
|
|
219
|
+
|
|
220
|
+
return { subject: "Reset your WOPR password", html, text };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
// 4. Credit Purchase
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
|
|
227
|
+
export function creditPurchaseTemplate(
|
|
228
|
+
email: string,
|
|
229
|
+
amountDollars: string,
|
|
230
|
+
newBalanceDollars?: string,
|
|
231
|
+
creditsUrl?: string,
|
|
232
|
+
): TemplateResult {
|
|
233
|
+
const escapedEmail = escapeHtml(email);
|
|
234
|
+
const escapedAmount = escapeHtml(amountDollars);
|
|
235
|
+
const balanceLine = newBalanceDollars
|
|
236
|
+
? `<p>Your new balance is <strong>${escapeHtml(newBalanceDollars)}</strong>.</p>`
|
|
237
|
+
: "<p>Your updated balance is now available in your dashboard.</p>";
|
|
238
|
+
const balanceTextLine = newBalanceDollars
|
|
239
|
+
? `Your new balance is ${newBalanceDollars}.`
|
|
240
|
+
: "Your updated balance is now available in your dashboard.";
|
|
241
|
+
|
|
242
|
+
const parts = [
|
|
243
|
+
heading("Credits Added to Your Account"),
|
|
244
|
+
paragraph(`<p>Hi <strong>${escapedEmail}</strong>,</p>
|
|
245
|
+
<p><strong>${escapedAmount}</strong> in credits has been added to your WOPR account.</p>
|
|
246
|
+
${balanceLine}`),
|
|
247
|
+
];
|
|
248
|
+
if (creditsUrl) parts.push(button(creditsUrl, "View Credits"));
|
|
249
|
+
parts.push(footer("Thank you for supporting WOPR!"));
|
|
250
|
+
parts.push(unsubscribeFooter(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined));
|
|
251
|
+
|
|
252
|
+
const html = wrapHtml("Credits Added", parts.join("\n"));
|
|
253
|
+
|
|
254
|
+
const text = `Credits Added to Your Account
|
|
255
|
+
|
|
256
|
+
Hi ${email},
|
|
257
|
+
|
|
258
|
+
${amountDollars} in credits has been added to your WOPR account.
|
|
259
|
+
${balanceTextLine}
|
|
260
|
+
${creditsUrl ? `\nView your credits: ${creditsUrl}` : ""}
|
|
261
|
+
Thank you for supporting WOPR!${unsubscribeText(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined)}
|
|
262
|
+
|
|
263
|
+
(c) ${new Date().getFullYear()} WOPR Network. All rights reserved.`;
|
|
264
|
+
|
|
265
|
+
return { subject: "Credits added to your account", html, text };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ---------------------------------------------------------------------------
|
|
269
|
+
// 5. Low Balance
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
|
|
272
|
+
export function lowBalanceTemplate(
|
|
273
|
+
email: string,
|
|
274
|
+
balanceDollars: string,
|
|
275
|
+
estimatedDaysRemaining?: number,
|
|
276
|
+
creditsUrl?: string,
|
|
277
|
+
): TemplateResult {
|
|
278
|
+
const escapedEmail = escapeHtml(email);
|
|
279
|
+
const escapedBalance = escapeHtml(balanceDollars);
|
|
280
|
+
const daysLine =
|
|
281
|
+
estimatedDaysRemaining != null
|
|
282
|
+
? `<p>At your current usage, your credits will run out in approximately <strong>${estimatedDaysRemaining} day${estimatedDaysRemaining === 1 ? "" : "s"}</strong>.</p>`
|
|
283
|
+
: "";
|
|
284
|
+
const daysTextLine =
|
|
285
|
+
estimatedDaysRemaining != null
|
|
286
|
+
? `At your current usage, your credits will run out in approximately ${estimatedDaysRemaining} day${estimatedDaysRemaining === 1 ? "" : "s"}.`
|
|
287
|
+
: "";
|
|
288
|
+
|
|
289
|
+
const parts = [
|
|
290
|
+
heading("Your WOPR Credits Are Running Low"),
|
|
291
|
+
paragraph(`<p>Hi <strong>${escapedEmail}</strong>,</p>
|
|
292
|
+
<p>Your WOPR credit balance is now <strong>${escapedBalance}</strong>. When your balance reaches $0, your bots will be paused.</p>
|
|
293
|
+
${daysLine}
|
|
294
|
+
<p>Top up your credits to keep your bots running.</p>`),
|
|
295
|
+
];
|
|
296
|
+
if (creditsUrl) parts.push(button(creditsUrl, "Buy Credits"));
|
|
297
|
+
parts.push(footer("This is an automated notification based on your account balance."));
|
|
298
|
+
parts.push(unsubscribeFooter(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined));
|
|
299
|
+
|
|
300
|
+
const html = wrapHtml("Low Balance", parts.join("\n"));
|
|
301
|
+
|
|
302
|
+
const text = `Your WOPR Credits Are Running Low
|
|
303
|
+
|
|
304
|
+
Hi ${email},
|
|
305
|
+
|
|
306
|
+
Your WOPR credit balance is now ${balanceDollars}. When your balance reaches $0, your bots will be paused.
|
|
307
|
+
${daysTextLine ? `${daysTextLine}\n` : ""}
|
|
308
|
+
Top up your credits to keep your bots running.
|
|
309
|
+
${creditsUrl ? `\nBuy credits: ${creditsUrl}` : ""}${unsubscribeText(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined)}
|
|
310
|
+
|
|
311
|
+
(c) ${new Date().getFullYear()} WOPR Network. All rights reserved.`;
|
|
312
|
+
|
|
313
|
+
return { subject: "Your WOPR credits are running low", html, text };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// ---------------------------------------------------------------------------
|
|
317
|
+
// 6. Bot Suspended
|
|
318
|
+
// ---------------------------------------------------------------------------
|
|
319
|
+
|
|
320
|
+
export function botSuspendedTemplate(
|
|
321
|
+
email: string,
|
|
322
|
+
botName: string,
|
|
323
|
+
reason: string,
|
|
324
|
+
creditsUrl?: string,
|
|
325
|
+
): TemplateResult {
|
|
326
|
+
const escapedEmail = escapeHtml(email);
|
|
327
|
+
const escapedBotName = escapeHtml(botName);
|
|
328
|
+
const escapedReason = escapeHtml(reason);
|
|
329
|
+
|
|
330
|
+
const parts = [
|
|
331
|
+
heading("Your Bot Has Been Suspended"),
|
|
332
|
+
paragraph(`<p>Hi <strong>${escapedEmail}</strong>,</p>
|
|
333
|
+
<p>Your bot <strong>${escapedBotName}</strong> has been suspended.</p>
|
|
334
|
+
<p><strong>Reason:</strong> ${escapedReason}</p>
|
|
335
|
+
<p>Buy credits to reactivate instantly. Your data is preserved for 30 days.</p>`),
|
|
336
|
+
];
|
|
337
|
+
if (creditsUrl) parts.push(button(creditsUrl, "Buy Credits to Reactivate"));
|
|
338
|
+
parts.push(footer("If you need help, reply to this email or contact support@wopr.bot."));
|
|
339
|
+
parts.push(unsubscribeFooter(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined));
|
|
340
|
+
|
|
341
|
+
const html = wrapHtml("Bot Suspended", parts.join("\n"));
|
|
342
|
+
|
|
343
|
+
const text = `Your Bot Has Been Suspended
|
|
344
|
+
|
|
345
|
+
Hi ${email},
|
|
346
|
+
|
|
347
|
+
Your bot "${botName}" has been suspended.
|
|
348
|
+
|
|
349
|
+
Reason: ${reason}
|
|
350
|
+
|
|
351
|
+
Buy credits to reactivate instantly. Your data is preserved for 30 days.
|
|
352
|
+
${creditsUrl ? `\nBuy credits: ${creditsUrl}` : ""}
|
|
353
|
+
If you need help, reply to this email or contact support@wopr.bot.${unsubscribeText(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined)}
|
|
354
|
+
|
|
355
|
+
(c) ${new Date().getFullYear()} WOPR Network. All rights reserved.`;
|
|
356
|
+
|
|
357
|
+
return { subject: "Your bot has been suspended", html, text };
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ---------------------------------------------------------------------------
|
|
361
|
+
// 7. Bot Destruction Warning
|
|
362
|
+
// ---------------------------------------------------------------------------
|
|
363
|
+
|
|
364
|
+
export function botDestructionTemplate(
|
|
365
|
+
email: string,
|
|
366
|
+
botName: string,
|
|
367
|
+
daysRemaining: number,
|
|
368
|
+
creditsUrl?: string,
|
|
369
|
+
): TemplateResult {
|
|
370
|
+
const escapedEmail = escapeHtml(email);
|
|
371
|
+
const escapedBotName = escapeHtml(botName);
|
|
372
|
+
const days = String(daysRemaining);
|
|
373
|
+
const deadline = new Date(Date.now() + daysRemaining * 24 * 60 * 60 * 1000).toLocaleDateString("en-US", {
|
|
374
|
+
month: "long",
|
|
375
|
+
day: "numeric",
|
|
376
|
+
year: "numeric",
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const parts = [
|
|
380
|
+
heading("URGENT: Bot Data Will Be Deleted"),
|
|
381
|
+
paragraph(`<p>Hi <strong>${escapedEmail}</strong>,</p>
|
|
382
|
+
<p>Your bot <strong>${escapedBotName}</strong> has been suspended and its data will be permanently deleted in <strong>${escapeHtml(days)} days</strong> (by ${escapeHtml(deadline)}).</p>
|
|
383
|
+
<p>Buy credits before the deadline to save your data.</p>`),
|
|
384
|
+
];
|
|
385
|
+
if (creditsUrl) parts.push(button(creditsUrl, "Buy Credits Now", "#dc2626"));
|
|
386
|
+
parts.push(
|
|
387
|
+
footer("This action is irreversible. All bot configuration, history, and connected services will be removed."),
|
|
388
|
+
);
|
|
389
|
+
parts.push(unsubscribeFooter(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined));
|
|
390
|
+
|
|
391
|
+
const html = wrapHtml("Bot Data Deletion", parts.join("\n"));
|
|
392
|
+
|
|
393
|
+
const text = `URGENT: Bot Data Will Be Deleted
|
|
394
|
+
|
|
395
|
+
Hi ${email},
|
|
396
|
+
|
|
397
|
+
Your bot "${botName}" has been suspended and its data will be permanently deleted in ${daysRemaining} days (by ${deadline}).
|
|
398
|
+
|
|
399
|
+
Buy credits before the deadline to save your data.
|
|
400
|
+
${creditsUrl ? `\nBuy credits now: ${creditsUrl}` : ""}
|
|
401
|
+
This action is irreversible. All bot configuration, history, and connected services will be removed.${unsubscribeText(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined)}
|
|
402
|
+
|
|
403
|
+
(c) ${new Date().getFullYear()} WOPR Network. All rights reserved.`;
|
|
404
|
+
|
|
405
|
+
return { subject: `URGENT: Your bot data will be deleted in ${daysRemaining} days`, html, text };
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// ---------------------------------------------------------------------------
|
|
409
|
+
// 8. Data Deleted Confirmation
|
|
410
|
+
// ---------------------------------------------------------------------------
|
|
411
|
+
|
|
412
|
+
export function dataDeletedTemplate(email: string, creditsUrl?: string): TemplateResult {
|
|
413
|
+
const escapedEmail = escapeHtml(email);
|
|
414
|
+
|
|
415
|
+
const parts = [
|
|
416
|
+
heading("Your Bot Data Has Been Deleted"),
|
|
417
|
+
paragraph(`<p>Hi <strong>${escapedEmail}</strong>,</p>
|
|
418
|
+
<p>Your suspended bot data has been permanently deleted after 30 days of inactivity.</p>
|
|
419
|
+
<p>You can create a new bot anytime by adding credits to your account.</p>`),
|
|
420
|
+
];
|
|
421
|
+
if (creditsUrl) parts.push(button(creditsUrl, "Add Credits"));
|
|
422
|
+
parts.push(footer("If you have questions, contact support@wopr.bot."));
|
|
423
|
+
parts.push(unsubscribeFooter(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined));
|
|
424
|
+
|
|
425
|
+
const html = wrapHtml("Data Deleted", parts.join("\n"));
|
|
426
|
+
|
|
427
|
+
const text = `Your Bot Data Has Been Deleted
|
|
428
|
+
|
|
429
|
+
Hi ${email},
|
|
430
|
+
|
|
431
|
+
Your suspended bot data has been permanently deleted after 30 days of inactivity.
|
|
432
|
+
|
|
433
|
+
You can create a new bot anytime by adding credits to your account.
|
|
434
|
+
${creditsUrl ? `\nAdd credits: ${creditsUrl}` : ""}
|
|
435
|
+
If you have questions, contact support@wopr.bot.${unsubscribeText(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined)}
|
|
436
|
+
|
|
437
|
+
(c) ${new Date().getFullYear()} WOPR Network. All rights reserved.`;
|
|
438
|
+
|
|
439
|
+
return { subject: "Your bot data has been deleted", html, text };
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// ---------------------------------------------------------------------------
|
|
443
|
+
// Org Invite
|
|
444
|
+
// ---------------------------------------------------------------------------
|
|
445
|
+
|
|
446
|
+
export function orgInviteEmailTemplate(inviteUrl: string, orgName: string): TemplateResult {
|
|
447
|
+
const safeOrg = escapeHtml(orgName);
|
|
448
|
+
const safeUrl = escapeHtml(inviteUrl);
|
|
449
|
+
|
|
450
|
+
const html = wrapHtml(
|
|
451
|
+
`You're invited to join ${safeOrg}`,
|
|
452
|
+
[
|
|
453
|
+
heading(`Join ${safeOrg}`),
|
|
454
|
+
paragraph(
|
|
455
|
+
`You've been invited to join <strong>${safeOrg}</strong> on WOPR Network. Click the button below to accept the invitation.`,
|
|
456
|
+
),
|
|
457
|
+
button(safeUrl, "Accept Invitation"),
|
|
458
|
+
footer("This invitation expires in 7 days. If you didn't expect this email, you can safely ignore it."),
|
|
459
|
+
].join("\n"),
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
const text = `You're invited to join ${orgName} on WOPR Network.
|
|
463
|
+
|
|
464
|
+
Accept the invitation: ${inviteUrl}
|
|
465
|
+
|
|
466
|
+
This invitation expires in 7 days.`;
|
|
467
|
+
|
|
468
|
+
return { subject: `You're invited to join ${orgName}`, html, text };
|
|
469
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { PGlite } from "@electric-sql/pglite";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
3
|
+
import {
|
|
4
|
+
generateVerificationToken,
|
|
5
|
+
getUserEmail,
|
|
6
|
+
initVerificationSchema,
|
|
7
|
+
isEmailVerified,
|
|
8
|
+
verifyToken,
|
|
9
|
+
} from "./verification.js";
|
|
10
|
+
|
|
11
|
+
/** Minimal Pool-like wrapper around PGlite for testing. */
|
|
12
|
+
// biome-ignore lint/suspicious/noExplicitAny: test helper wrapping PGlite as Pool
|
|
13
|
+
function pgliteAsPool(pg: PGlite): any {
|
|
14
|
+
return { query: (text: string, params?: unknown[]) => pg.query(text, params) };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("email verification", () => {
|
|
18
|
+
let pg: PGlite;
|
|
19
|
+
// biome-ignore lint/suspicious/noExplicitAny: test pool wrapper
|
|
20
|
+
let pool: any;
|
|
21
|
+
|
|
22
|
+
beforeEach(async () => {
|
|
23
|
+
pg = new PGlite();
|
|
24
|
+
pool = pgliteAsPool(pg);
|
|
25
|
+
|
|
26
|
+
// Create a minimal user table mimicking better-auth's schema
|
|
27
|
+
await pg.query(`
|
|
28
|
+
CREATE TABLE "user" (
|
|
29
|
+
id TEXT PRIMARY KEY,
|
|
30
|
+
email TEXT NOT NULL,
|
|
31
|
+
name TEXT,
|
|
32
|
+
"createdAt" TEXT,
|
|
33
|
+
"emailVerified" boolean DEFAULT false
|
|
34
|
+
)
|
|
35
|
+
`);
|
|
36
|
+
await initVerificationSchema(pool);
|
|
37
|
+
|
|
38
|
+
// Insert test users
|
|
39
|
+
await pg.query(`INSERT INTO "user" (id, email, name) VALUES ($1, $2, $3)`, ["user-1", "alice@test.com", "Alice"]);
|
|
40
|
+
await pg.query(`INSERT INTO "user" (id, email, name) VALUES ($1, $2, $3)`, ["user-2", "bob@test.com", "Bob"]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
afterEach(async () => {
|
|
44
|
+
await pg.close();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("initVerificationSchema", () => {
|
|
48
|
+
it("should add verification columns idempotently", async () => {
|
|
49
|
+
// Call again — should not throw
|
|
50
|
+
await initVerificationSchema(pool);
|
|
51
|
+
|
|
52
|
+
const { rows } = await pg.query(
|
|
53
|
+
`SELECT column_name FROM information_schema.columns WHERE table_name = 'user' ORDER BY ordinal_position`,
|
|
54
|
+
);
|
|
55
|
+
const names = (rows as Array<{ column_name: string }>).map((r) => r.column_name);
|
|
56
|
+
expect(names).toContain("email_verified");
|
|
57
|
+
expect(names).toContain("verification_token");
|
|
58
|
+
expect(names).toContain("verification_expires");
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("generateVerificationToken", () => {
|
|
63
|
+
it("should generate a 64-char hex token", async () => {
|
|
64
|
+
const result = await generateVerificationToken(pool, "user-1");
|
|
65
|
+
expect(result.token).toHaveLength(64);
|
|
66
|
+
expect(result.token).toMatch(/^[a-f0-9]+$/);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should store token and expiry in the database", async () => {
|
|
70
|
+
const result = await generateVerificationToken(pool, "user-1");
|
|
71
|
+
|
|
72
|
+
const { rows } = await pg.query(`SELECT verification_token, verification_expires FROM "user" WHERE id = $1`, [
|
|
73
|
+
"user-1",
|
|
74
|
+
]);
|
|
75
|
+
const row = rows[0] as { verification_token: string; verification_expires: string };
|
|
76
|
+
|
|
77
|
+
expect(row.verification_token).toBe(result.token);
|
|
78
|
+
expect(row.verification_expires).toBe(result.expiresAt);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("should set expiry 24 hours in the future", async () => {
|
|
82
|
+
const before = Date.now();
|
|
83
|
+
const result = await generateVerificationToken(pool, "user-1");
|
|
84
|
+
const after = Date.now();
|
|
85
|
+
|
|
86
|
+
const expiresMs = new Date(result.expiresAt).getTime();
|
|
87
|
+
const twentyFourHours = 24 * 60 * 60 * 1000;
|
|
88
|
+
|
|
89
|
+
expect(expiresMs).toBeGreaterThanOrEqual(before + twentyFourHours);
|
|
90
|
+
expect(expiresMs).toBeLessThanOrEqual(after + twentyFourHours);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("should overwrite previous token on re-generation", async () => {
|
|
94
|
+
const first = await generateVerificationToken(pool, "user-1");
|
|
95
|
+
const second = await generateVerificationToken(pool, "user-1");
|
|
96
|
+
|
|
97
|
+
expect(first.token).not.toBe(second.token);
|
|
98
|
+
|
|
99
|
+
const { rows } = await pg.query(`SELECT verification_token FROM "user" WHERE id = $1`, ["user-1"]);
|
|
100
|
+
const row = rows[0] as { verification_token: string };
|
|
101
|
+
expect(row.verification_token).toBe(second.token);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe("verifyToken", () => {
|
|
106
|
+
it("should verify a valid token and mark user as verified", async () => {
|
|
107
|
+
const { token } = await generateVerificationToken(pool, "user-1");
|
|
108
|
+
const result = await verifyToken(pool, token);
|
|
109
|
+
|
|
110
|
+
expect(result).toEqual({ userId: "user-1", email: "alice@test.com" });
|
|
111
|
+
|
|
112
|
+
const { rows } = await pg.query(`SELECT email_verified, verification_token FROM "user" WHERE id = $1`, [
|
|
113
|
+
"user-1",
|
|
114
|
+
]);
|
|
115
|
+
const row = rows[0] as { email_verified: boolean; verification_token: string | null };
|
|
116
|
+
expect(row.email_verified).toBe(true);
|
|
117
|
+
expect(row.verification_token).toBeNull();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("should return null for non-existent token", async () => {
|
|
121
|
+
expect(await verifyToken(pool, "a".repeat(64))).toBeNull();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("should return null for empty token", async () => {
|
|
125
|
+
expect(await verifyToken(pool, "")).toBeNull();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("should return null for wrong-length token", async () => {
|
|
129
|
+
expect(await verifyToken(pool, "abc")).toBeNull();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("should return null for expired token", async () => {
|
|
133
|
+
const { token } = await generateVerificationToken(pool, "user-1");
|
|
134
|
+
|
|
135
|
+
await pg.query(`UPDATE "user" SET verification_expires = $1 WHERE id = $2`, [
|
|
136
|
+
new Date(Date.now() - 1000).toISOString(),
|
|
137
|
+
"user-1",
|
|
138
|
+
]);
|
|
139
|
+
|
|
140
|
+
expect(await verifyToken(pool, token)).toBeNull();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("should return null for already-verified user", async () => {
|
|
144
|
+
const { token } = await generateVerificationToken(pool, "user-1");
|
|
145
|
+
await pg.query(`UPDATE "user" SET email_verified = true WHERE id = $1`, ["user-1"]);
|
|
146
|
+
expect(await verifyToken(pool, token)).toBeNull();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("should only allow single verification per token", async () => {
|
|
150
|
+
const { token } = await generateVerificationToken(pool, "user-1");
|
|
151
|
+
|
|
152
|
+
const first = await verifyToken(pool, token);
|
|
153
|
+
const second = await verifyToken(pool, token);
|
|
154
|
+
|
|
155
|
+
expect(first).toEqual({ userId: "user-1", email: "alice@test.com" });
|
|
156
|
+
expect(second).toBeNull();
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe("isEmailVerified", () => {
|
|
161
|
+
it("should return false for unverified user", async () => {
|
|
162
|
+
expect(await isEmailVerified(pool, "user-1")).toBe(false);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("should return true after verification", async () => {
|
|
166
|
+
const { token } = await generateVerificationToken(pool, "user-1");
|
|
167
|
+
await verifyToken(pool, token);
|
|
168
|
+
expect(await isEmailVerified(pool, "user-1")).toBe(true);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("should return false for non-existent user", async () => {
|
|
172
|
+
expect(await isEmailVerified(pool, "no-such-user")).toBe(false);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe("getUserEmail", () => {
|
|
177
|
+
it("should return email for existing user", async () => {
|
|
178
|
+
expect(await getUserEmail(pool, "user-1")).toBe("alice@test.com");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("should return null for non-existent user", async () => {
|
|
182
|
+
expect(await getUserEmail(pool, "no-such-user")).toBeNull();
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
});
|