@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,422 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth — Session management, token verification, and middleware.
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - `requireRole` middleware for role-based access control
|
|
6
|
+
* - `scopedBearerAuth` middleware for operation-scoped API tokens
|
|
7
|
+
*/
|
|
8
|
+
import { timingSafeEqual } from "node:crypto";
|
|
9
|
+
/**
|
|
10
|
+
* Extract the bearer token from an Authorization header value.
|
|
11
|
+
* Returns `null` if the header is missing, empty, or not a Bearer scheme.
|
|
12
|
+
*/
|
|
13
|
+
export function extractBearerToken(header) {
|
|
14
|
+
if (!header)
|
|
15
|
+
return null;
|
|
16
|
+
const trimmed = header.trim();
|
|
17
|
+
if (!trimmed.toLowerCase().startsWith("bearer "))
|
|
18
|
+
return null;
|
|
19
|
+
const token = trimmed.slice(7).trim();
|
|
20
|
+
return token || null;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Timing-safe lookup of a string key in a Map.
|
|
24
|
+
*
|
|
25
|
+
* Iterates all entries and compares each key using `crypto.timingSafeEqual`
|
|
26
|
+
* to prevent timing side-channel attacks. Returns the value of the first
|
|
27
|
+
* matching key, or `undefined` if no key matches.
|
|
28
|
+
*/
|
|
29
|
+
export function timingSafeMapLookup(map, candidate) {
|
|
30
|
+
const candidateBuf = Buffer.from(candidate);
|
|
31
|
+
let found;
|
|
32
|
+
for (const [key, value] of map) {
|
|
33
|
+
const keyBuf = Buffer.from(key);
|
|
34
|
+
if (candidateBuf.length === keyBuf.length && timingSafeEqual(candidateBuf, keyBuf)) {
|
|
35
|
+
found = value;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return found;
|
|
39
|
+
}
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Middleware
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
/**
|
|
44
|
+
* Create a `requireRole` middleware that rejects users without the specified role.
|
|
45
|
+
* Must be used after `requireAuth`.
|
|
46
|
+
*/
|
|
47
|
+
export function requireRole(role) {
|
|
48
|
+
return async (c, next) => {
|
|
49
|
+
let user;
|
|
50
|
+
try {
|
|
51
|
+
user = c.get("user");
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return c.json({ error: "Authentication required" }, 401);
|
|
55
|
+
}
|
|
56
|
+
if (!user) {
|
|
57
|
+
return c.json({ error: "Authentication required" }, 401);
|
|
58
|
+
}
|
|
59
|
+
if (!user.roles.includes(role)) {
|
|
60
|
+
return c.json({ error: "Insufficient permissions", required: role }, 403);
|
|
61
|
+
}
|
|
62
|
+
return next();
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** Privilege hierarchy: admin > write > read. */
|
|
66
|
+
const SCOPE_LEVEL = {
|
|
67
|
+
read: 0,
|
|
68
|
+
write: 1,
|
|
69
|
+
admin: 2,
|
|
70
|
+
};
|
|
71
|
+
const VALID_SCOPES = new Set(["read", "write", "admin"]);
|
|
72
|
+
/**
|
|
73
|
+
* Parse a token's scope from the `wopr_<scope>_<random>` format.
|
|
74
|
+
*
|
|
75
|
+
* - `wopr_read_abc123` -> `"read"`
|
|
76
|
+
* - `wopr_write_abc123` -> `"write"`
|
|
77
|
+
* - `wopr_admin_abc123` -> `"admin"`
|
|
78
|
+
* - Any other format -> `null` (not a scoped token)
|
|
79
|
+
*/
|
|
80
|
+
export function parseTokenScope(token) {
|
|
81
|
+
if (!token.startsWith("wopr_"))
|
|
82
|
+
return null;
|
|
83
|
+
const parts = token.split("_");
|
|
84
|
+
// Must be at least 3 parts: "wopr", scope, random
|
|
85
|
+
if (parts.length < 3)
|
|
86
|
+
return null;
|
|
87
|
+
const scope = parts[1];
|
|
88
|
+
if (!VALID_SCOPES.has(scope))
|
|
89
|
+
return null;
|
|
90
|
+
// The random portion (everything after wopr_<scope>_) must be non-empty
|
|
91
|
+
const random = parts.slice(2).join("_");
|
|
92
|
+
if (!random)
|
|
93
|
+
return null;
|
|
94
|
+
return scope;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Check whether a token's scope satisfies the required minimum scope.
|
|
98
|
+
* admin >= write >= read.
|
|
99
|
+
*/
|
|
100
|
+
export function scopeSatisfies(tokenScope, requiredScope) {
|
|
101
|
+
return SCOPE_LEVEL[tokenScope] >= SCOPE_LEVEL[requiredScope];
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Build a token-to-scope map from environment variables.
|
|
105
|
+
*
|
|
106
|
+
* Accepts:
|
|
107
|
+
* - `FLEET_API_TOKEN` (legacy single token, treated as admin)
|
|
108
|
+
* - `FLEET_API_TOKEN_READ` (read-scoped token)
|
|
109
|
+
* - `FLEET_API_TOKEN_WRITE` (write-scoped token)
|
|
110
|
+
* - `FLEET_API_TOKEN_ADMIN` (admin-scoped token)
|
|
111
|
+
* - Any `wopr_<scope>_<random>` formatted token has its scope inferred.
|
|
112
|
+
*/
|
|
113
|
+
export function buildTokenMap(env = process.env) {
|
|
114
|
+
const tokens = new Map();
|
|
115
|
+
// Scoped env vars
|
|
116
|
+
const scopedVars = [
|
|
117
|
+
["FLEET_API_TOKEN_READ", "read"],
|
|
118
|
+
["FLEET_API_TOKEN_WRITE", "write"],
|
|
119
|
+
["FLEET_API_TOKEN_ADMIN", "admin"],
|
|
120
|
+
];
|
|
121
|
+
for (const [envVar, scope] of scopedVars) {
|
|
122
|
+
const val = env[envVar]?.trim();
|
|
123
|
+
if (val)
|
|
124
|
+
tokens.set(val, scope);
|
|
125
|
+
}
|
|
126
|
+
// Legacy single token -- admin scope for backwards compatibility
|
|
127
|
+
const legacyToken = env.FLEET_API_TOKEN?.trim();
|
|
128
|
+
if (legacyToken && !tokens.has(legacyToken)) {
|
|
129
|
+
// If the token is in wopr_ format, parse its actual scope
|
|
130
|
+
const parsed = parseTokenScope(legacyToken);
|
|
131
|
+
tokens.set(legacyToken, parsed ?? "admin");
|
|
132
|
+
}
|
|
133
|
+
return tokens;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Build a token-to-metadata map from environment variables.
|
|
137
|
+
*
|
|
138
|
+
* Accepts:
|
|
139
|
+
* - `FLEET_TOKEN_<TENANT>=<scope>:<token>` (tenant-scoped tokens)
|
|
140
|
+
* - Fallback to legacy token map for backwards compatibility (no tenant constraint)
|
|
141
|
+
*
|
|
142
|
+
* Example: `FLEET_TOKEN_ACME=write:wopr_write_abc123`
|
|
143
|
+
*/
|
|
144
|
+
export function buildTokenMetadataMap(env = process.env) {
|
|
145
|
+
const metadataMap = new Map();
|
|
146
|
+
// Parse FLEET_TOKEN_<TENANT>=<scope>:<token> format
|
|
147
|
+
for (const [key, value] of Object.entries(env)) {
|
|
148
|
+
if (key.startsWith("FLEET_TOKEN_") && value) {
|
|
149
|
+
const tenantId = key.slice("FLEET_TOKEN_".length);
|
|
150
|
+
if (!tenantId)
|
|
151
|
+
continue;
|
|
152
|
+
const trimmed = value.trim();
|
|
153
|
+
const colonIdx = trimmed.indexOf(":");
|
|
154
|
+
if (colonIdx === -1)
|
|
155
|
+
continue;
|
|
156
|
+
const scopeStr = trimmed.slice(0, colonIdx);
|
|
157
|
+
const token = trimmed.slice(colonIdx + 1);
|
|
158
|
+
if (!VALID_SCOPES.has(scopeStr) || !token)
|
|
159
|
+
continue;
|
|
160
|
+
metadataMap.set(token, {
|
|
161
|
+
scope: scopeStr,
|
|
162
|
+
tenantId,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Fallback: add legacy tokens without tenant constraint
|
|
167
|
+
const legacyTokenMap = buildTokenMap(env);
|
|
168
|
+
for (const [token, scope] of legacyTokenMap) {
|
|
169
|
+
if (!metadataMap.has(token)) {
|
|
170
|
+
metadataMap.set(token, { scope });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return metadataMap;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Create a scoped bearer auth middleware.
|
|
177
|
+
*
|
|
178
|
+
* Validates the bearer token against the token map and checks that the
|
|
179
|
+
* token's scope satisfies the required minimum scope for the route.
|
|
180
|
+
*
|
|
181
|
+
* @param tokenMap - Map of token -> scope (use `buildTokenMap()` to create)
|
|
182
|
+
* @param requiredScope - Minimum scope required for this route/group
|
|
183
|
+
*/
|
|
184
|
+
export function scopedBearerAuth(tokenMap, requiredScope) {
|
|
185
|
+
return async (c, next) => {
|
|
186
|
+
const authHeader = c.req.header("Authorization");
|
|
187
|
+
const token = extractBearerToken(authHeader);
|
|
188
|
+
if (!token) {
|
|
189
|
+
return c.json({ error: "Authentication required" }, 401);
|
|
190
|
+
}
|
|
191
|
+
// Look up the token in the map
|
|
192
|
+
const scope = timingSafeMapLookup(tokenMap, token);
|
|
193
|
+
// If not in map, try to parse scope from token format for dynamic tokens
|
|
194
|
+
if (scope === undefined) {
|
|
195
|
+
return c.json({ error: "Invalid or expired token" }, 401);
|
|
196
|
+
}
|
|
197
|
+
// Check scope hierarchy
|
|
198
|
+
if (!scopeSatisfies(scope, requiredScope)) {
|
|
199
|
+
return c.json({ error: "Insufficient scope", required: requiredScope, provided: scope }, 403);
|
|
200
|
+
}
|
|
201
|
+
// Set user context for downstream middleware (audit, etc.)
|
|
202
|
+
c.set("user", { id: `token:${scope}`, roles: [scope] });
|
|
203
|
+
c.set("authMethod", "api_key");
|
|
204
|
+
return next();
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Create a tenant-aware scoped bearer auth middleware.
|
|
209
|
+
*
|
|
210
|
+
* Validates the bearer token against the metadata map, checks scope,
|
|
211
|
+
* and stores the token's associated tenantId for downstream ownership checks.
|
|
212
|
+
*
|
|
213
|
+
* @param metadataMap - Map of token -> metadata (use `buildTokenMetadataMap()` to create)
|
|
214
|
+
* @param requiredScope - Minimum scope required for this route/group
|
|
215
|
+
*/
|
|
216
|
+
export function scopedBearerAuthWithTenant(metadataMap, requiredScope) {
|
|
217
|
+
return async (c, next) => {
|
|
218
|
+
const authHeader = c.req.header("Authorization");
|
|
219
|
+
const token = extractBearerToken(authHeader);
|
|
220
|
+
if (!token) {
|
|
221
|
+
return c.json({ error: "Authentication required" }, 401);
|
|
222
|
+
}
|
|
223
|
+
// Look up the token in the metadata map
|
|
224
|
+
const metadata = timingSafeMapLookup(metadataMap, token);
|
|
225
|
+
if (!metadata) {
|
|
226
|
+
return c.json({ error: "Invalid or expired token" }, 401);
|
|
227
|
+
}
|
|
228
|
+
// Check scope hierarchy
|
|
229
|
+
if (!scopeSatisfies(metadata.scope, requiredScope)) {
|
|
230
|
+
return c.json({ error: "Insufficient scope", required: requiredScope, provided: metadata.scope }, 403);
|
|
231
|
+
}
|
|
232
|
+
// Set user context for downstream middleware (audit, etc.)
|
|
233
|
+
c.set("user", { id: `token:${metadata.scope}`, roles: [metadata.scope] });
|
|
234
|
+
c.set("authMethod", "api_key");
|
|
235
|
+
// Store tenant constraint if present; absence is allowed here — admin/legacy
|
|
236
|
+
// tokens have no tenantId and must still reach admin routes.
|
|
237
|
+
// Tenant-scoped routes enforce ownership via requireTenantOwnership /
|
|
238
|
+
// validateTenantOwnership (WOP-1264).
|
|
239
|
+
if (metadata.tenantId) {
|
|
240
|
+
c.set("tokenTenantId", metadata.tenantId);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
// Operator/admin tokens have no tenant scope — mark them so ownership
|
|
244
|
+
// middlewares can pass them through without a tenantId check.
|
|
245
|
+
c.set("isOperatorToken", true);
|
|
246
|
+
}
|
|
247
|
+
return next();
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
// Session Resolution (better-auth)
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
/**
|
|
254
|
+
* Middleware that resolves the current user from a better-auth session cookie.
|
|
255
|
+
*
|
|
256
|
+
* On success, sets:
|
|
257
|
+
* - `c.set("user", { id, roles })`
|
|
258
|
+
* - `c.set("authMethod", "session")`
|
|
259
|
+
*
|
|
260
|
+
* If no session cookie is present (or session is invalid), the request
|
|
261
|
+
* continues without a user — downstream middleware like `scopedBearerAuth`
|
|
262
|
+
* or `requireAuth` will handle enforcement.
|
|
263
|
+
*
|
|
264
|
+
* Uses lazy `getAuth()` to avoid initializing the DB at import time.
|
|
265
|
+
*/
|
|
266
|
+
export function resolveSessionUser() {
|
|
267
|
+
return async (c, next) => {
|
|
268
|
+
// Skip if user is already set (e.g., by scopedBearerAuth)
|
|
269
|
+
try {
|
|
270
|
+
if (c.get("user"))
|
|
271
|
+
return next();
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
// c.get throws if variable not set — that's fine, continue
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
const { getAuth } = await import("./better-auth.js");
|
|
278
|
+
const auth = getAuth();
|
|
279
|
+
const session = await auth.api.getSession({ headers: c.req.raw.headers });
|
|
280
|
+
if (session?.user) {
|
|
281
|
+
const user = session.user;
|
|
282
|
+
const roles = [];
|
|
283
|
+
if (user.role)
|
|
284
|
+
roles.push(user.role);
|
|
285
|
+
c.set("user", { id: user.id, roles });
|
|
286
|
+
c.set("authMethod", "session");
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
// Session resolution failed — continue without user
|
|
291
|
+
}
|
|
292
|
+
return next();
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Middleware that requires either a valid session or a scoped API token.
|
|
297
|
+
*
|
|
298
|
+
* Tries session first, then falls back to scoped bearer auth.
|
|
299
|
+
* Returns 401 if neither is present.
|
|
300
|
+
*/
|
|
301
|
+
export function requireSessionOrToken(tokenMap, requiredScope) {
|
|
302
|
+
return async (c, next) => {
|
|
303
|
+
// Check if user was already resolved by resolveSessionUser
|
|
304
|
+
try {
|
|
305
|
+
if (c.get("user"))
|
|
306
|
+
return next();
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
// Not set — continue to check bearer token
|
|
310
|
+
}
|
|
311
|
+
// Fall back to scoped bearer auth
|
|
312
|
+
const authHeader = c.req.header("Authorization");
|
|
313
|
+
const token = extractBearerToken(authHeader);
|
|
314
|
+
if (!token) {
|
|
315
|
+
return c.json({ error: "Authentication required" }, 401);
|
|
316
|
+
}
|
|
317
|
+
const scope = timingSafeMapLookup(tokenMap, token);
|
|
318
|
+
if (scope === undefined) {
|
|
319
|
+
return c.json({ error: "Invalid or expired token" }, 401);
|
|
320
|
+
}
|
|
321
|
+
if (!scopeSatisfies(scope, requiredScope)) {
|
|
322
|
+
return c.json({ error: "Insufficient scope", required: requiredScope, provided: scope }, 403);
|
|
323
|
+
}
|
|
324
|
+
c.set("user", { id: `token:${scope}`, roles: [scope] });
|
|
325
|
+
c.set("authMethod", "api_key");
|
|
326
|
+
return next();
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
// ---------------------------------------------------------------------------
|
|
330
|
+
// Tenant Ownership Validation
|
|
331
|
+
// ---------------------------------------------------------------------------
|
|
332
|
+
/**
|
|
333
|
+
* Middleware that enforces tenant ownership on tenant-scoped resources.
|
|
334
|
+
*
|
|
335
|
+
* Must be used after `scopedBearerAuthWithTenant` middleware.
|
|
336
|
+
* Compares the resource's tenantId against the token's tenantId.
|
|
337
|
+
* - If token has no tenantId (legacy/admin tokens), returns 403 Forbidden.
|
|
338
|
+
* - If resource tenantId matches token tenantId, passes through.
|
|
339
|
+
* - Otherwise, returns 403 Forbidden.
|
|
340
|
+
*
|
|
341
|
+
* @param _getResourceTenantId - Function that extracts tenantId from the resource (reserved for future use)
|
|
342
|
+
*/
|
|
343
|
+
export function requireTenantOwnership(_getResourceTenantId) {
|
|
344
|
+
return async (c, next) => {
|
|
345
|
+
// Operator/admin tokens (fleet env var tokens) have no tenantId but must
|
|
346
|
+
// still access tenant-scoped routes — they are trusted at the operator level.
|
|
347
|
+
const isOperatorToken = c.get("isOperatorToken");
|
|
348
|
+
if (isOperatorToken) {
|
|
349
|
+
return next();
|
|
350
|
+
}
|
|
351
|
+
const tokenTenantId = c.get("tokenTenantId");
|
|
352
|
+
// If token has no tenant constraint and is not an operator token, reject.
|
|
353
|
+
if (!tokenTenantId) {
|
|
354
|
+
return c.json({ error: "Token lacks tenant scope" }, 403);
|
|
355
|
+
}
|
|
356
|
+
// Resource tenantId will be validated by route handler
|
|
357
|
+
// Store tokenTenantId for route to check
|
|
358
|
+
return next();
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Validate that a resource belongs to the authenticated tenant.
|
|
363
|
+
* Returns a response if validation fails (404 for not found or tenant mismatch).
|
|
364
|
+
*
|
|
365
|
+
* @param c - Hono context
|
|
366
|
+
* @param resource - The resource to check (null/undefined = not found)
|
|
367
|
+
* @param resourceTenantId - The resource's tenantId
|
|
368
|
+
* @returns Response if validation fails, undefined if validation passes
|
|
369
|
+
*/
|
|
370
|
+
export function validateTenantOwnership(c, resource, resourceTenantId) {
|
|
371
|
+
// Resource not found
|
|
372
|
+
if (resource == null) {
|
|
373
|
+
return c.json({ error: "Resource not found" }, 404);
|
|
374
|
+
}
|
|
375
|
+
// Get token's tenant constraint
|
|
376
|
+
let tokenTenantId;
|
|
377
|
+
try {
|
|
378
|
+
tokenTenantId = c.get("tokenTenantId");
|
|
379
|
+
}
|
|
380
|
+
catch {
|
|
381
|
+
// No tokenTenantId set — this is a legacy/admin token
|
|
382
|
+
tokenTenantId = undefined;
|
|
383
|
+
}
|
|
384
|
+
// Operator/admin tokens (fleet env var tokens) have no tenantId but must
|
|
385
|
+
// still access tenant-scoped resources — they are trusted at the operator level.
|
|
386
|
+
let isOperatorToken;
|
|
387
|
+
try {
|
|
388
|
+
isOperatorToken = c.get("isOperatorToken");
|
|
389
|
+
}
|
|
390
|
+
catch {
|
|
391
|
+
isOperatorToken = undefined;
|
|
392
|
+
}
|
|
393
|
+
if (isOperatorToken) {
|
|
394
|
+
return undefined;
|
|
395
|
+
}
|
|
396
|
+
// No tenant constraint and not an operator token — reject (WOP-1264)
|
|
397
|
+
if (!tokenTenantId) {
|
|
398
|
+
return c.json({ error: "Token lacks tenant scope" }, 403);
|
|
399
|
+
}
|
|
400
|
+
// Validate tenant match
|
|
401
|
+
if (resourceTenantId !== tokenTenantId) {
|
|
402
|
+
return c.json({ error: "Resource not found" }, 404);
|
|
403
|
+
}
|
|
404
|
+
return undefined;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Validate that a user has access to the requested tenant.
|
|
408
|
+
*
|
|
409
|
+
* - If requestedTenantId is falsy or matches userId, access is allowed (personal tenant).
|
|
410
|
+
* - Otherwise, checks org membership via IOrgMemberRepository.
|
|
411
|
+
*
|
|
412
|
+
* @returns true if the user has access, false otherwise.
|
|
413
|
+
*/
|
|
414
|
+
export async function validateTenantAccess(userId, requestedTenantId, orgMemberRepo) {
|
|
415
|
+
// No tenant requested or personal tenant — always allowed
|
|
416
|
+
if (!requestedTenantId || requestedTenantId === userId) {
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
// Check org membership
|
|
420
|
+
const member = await orgMemberRepo.findMember(requestedTenantId, userId);
|
|
421
|
+
return member !== null;
|
|
422
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Pool } from "pg";
|
|
2
|
+
export interface LoginHistoryEntry {
|
|
3
|
+
ip: string | null;
|
|
4
|
+
userAgent: string | null;
|
|
5
|
+
createdAt: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ILoginHistoryRepository {
|
|
8
|
+
findByUserId(userId: string, limit?: number): Promise<LoginHistoryEntry[]>;
|
|
9
|
+
}
|
|
10
|
+
export declare class BetterAuthLoginHistoryRepository implements ILoginHistoryRepository {
|
|
11
|
+
private readonly pool;
|
|
12
|
+
constructor(pool: Pool);
|
|
13
|
+
findByUserId(userId: string, limit?: number): Promise<LoginHistoryEntry[]>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export class BetterAuthLoginHistoryRepository {
|
|
2
|
+
pool;
|
|
3
|
+
constructor(pool) {
|
|
4
|
+
this.pool = pool;
|
|
5
|
+
}
|
|
6
|
+
async findByUserId(userId, limit = 20) {
|
|
7
|
+
// raw SQL: better-auth manages its own schema outside Drizzle
|
|
8
|
+
const { rows } = await this.pool.query(`SELECT "ipAddress", "userAgent", "createdAt" FROM "session" WHERE "userId" = $1 ORDER BY "createdAt" DESC LIMIT $2`, [userId, Math.min(Math.max(1, limit), 100)]);
|
|
9
|
+
return rows.map((r) => ({
|
|
10
|
+
ip: r.ipAddress,
|
|
11
|
+
userAgent: r.userAgent,
|
|
12
|
+
createdAt: r.createdAt instanceof Date ? r.createdAt.toISOString() : String(r.createdAt),
|
|
13
|
+
}));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { BetterAuthLoginHistoryRepository } from "./login-history-repository.js";
|
|
3
|
+
function makePool(rows) {
|
|
4
|
+
return {
|
|
5
|
+
query: vi.fn().mockResolvedValue({ rows }),
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
describe("BetterAuthLoginHistoryRepository", () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
vi.clearAllMocks();
|
|
11
|
+
});
|
|
12
|
+
it("returns empty array when user has no sessions", async () => {
|
|
13
|
+
const pool = makePool([]);
|
|
14
|
+
const repo = new BetterAuthLoginHistoryRepository(pool);
|
|
15
|
+
const result = await repo.findByUserId("no-such-user");
|
|
16
|
+
expect(result).toEqual([]);
|
|
17
|
+
});
|
|
18
|
+
it("returns sessions ordered by createdAt DESC", async () => {
|
|
19
|
+
const pool = makePool([
|
|
20
|
+
{ ipAddress: "5.6.7.8", userAgent: "Chrome/120", createdAt: new Date("2026-01-02") },
|
|
21
|
+
{ ipAddress: "1.2.3.4", userAgent: "Mozilla/5.0", createdAt: new Date("2026-01-01") },
|
|
22
|
+
]);
|
|
23
|
+
const repo = new BetterAuthLoginHistoryRepository(pool);
|
|
24
|
+
const result = await repo.findByUserId("user-1");
|
|
25
|
+
expect(result).toHaveLength(2);
|
|
26
|
+
expect(result[0].ip).toBe("5.6.7.8");
|
|
27
|
+
expect(result[1].ip).toBe("1.2.3.4");
|
|
28
|
+
});
|
|
29
|
+
it("respects the limit parameter", async () => {
|
|
30
|
+
const pool = makePool([
|
|
31
|
+
{ ipAddress: "1.1.1.1", userAgent: null, createdAt: new Date() },
|
|
32
|
+
{ ipAddress: "2.2.2.2", userAgent: null, createdAt: new Date() },
|
|
33
|
+
{ ipAddress: "3.3.3.3", userAgent: null, createdAt: new Date() },
|
|
34
|
+
]);
|
|
35
|
+
const repo = new BetterAuthLoginHistoryRepository(pool);
|
|
36
|
+
const result = await repo.findByUserId("user-1", 3);
|
|
37
|
+
expect(pool.query).toHaveBeenCalledWith(expect.any(String), ["user-1", 3]);
|
|
38
|
+
expect(result).toHaveLength(3);
|
|
39
|
+
});
|
|
40
|
+
it("does not return sessions for other users", async () => {
|
|
41
|
+
const pool = makePool([]);
|
|
42
|
+
const repo = new BetterAuthLoginHistoryRepository(pool);
|
|
43
|
+
const result = await repo.findByUserId("user-1");
|
|
44
|
+
expect(result).toEqual([]);
|
|
45
|
+
expect(pool.query).toHaveBeenCalledWith(expect.any(String), ["user-1", 20]);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Auth Middleware — Resolves better-auth sessions for Hono routes.
|
|
3
|
+
*
|
|
4
|
+
* Reads the better-auth session cookie from the request, resolves the user,
|
|
5
|
+
* and sets `c.set("user", ...)` and `c.set("authMethod", "session")` for
|
|
6
|
+
* downstream route handlers.
|
|
7
|
+
*
|
|
8
|
+
* This middleware supports a dual-auth model:
|
|
9
|
+
* - **Session auth** (cookie-based): For browser/UI clients via better-auth
|
|
10
|
+
* - **Bearer token auth** (header-based): For machine-to-machine / API key access
|
|
11
|
+
*
|
|
12
|
+
* If neither is present, the request is rejected with 401.
|
|
13
|
+
*/
|
|
14
|
+
import type { Context, Next } from "hono";
|
|
15
|
+
import type { IApiKeyRepository } from "./api-key-repository.js";
|
|
16
|
+
import type { Auth } from "./better-auth.js";
|
|
17
|
+
import type { AuthUser } from "./index.js";
|
|
18
|
+
export interface SessionAuthEnv {
|
|
19
|
+
Variables: {
|
|
20
|
+
user: AuthUser;
|
|
21
|
+
authMethod: "session" | "api_key";
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Create middleware that authenticates requests via better-auth session cookies.
|
|
26
|
+
*
|
|
27
|
+
* On success, sets:
|
|
28
|
+
* - `c.set("user", { id, roles })` — user context for downstream routes
|
|
29
|
+
* - `c.set("authMethod", "session")` — indicates session-based auth
|
|
30
|
+
*
|
|
31
|
+
* If no valid session cookie is found, returns 401.
|
|
32
|
+
*
|
|
33
|
+
* @param auth - The better-auth instance (use `getAuth()`)
|
|
34
|
+
*/
|
|
35
|
+
export declare function sessionAuth(auth: Auth): (c: Context<SessionAuthEnv>, next: Next) => Promise<void | (Response & import("hono").TypedResponse<{
|
|
36
|
+
error: string;
|
|
37
|
+
}, 401, "json">) | (Response & import("hono").TypedResponse<{
|
|
38
|
+
error: string;
|
|
39
|
+
}, 503, "json">)>;
|
|
40
|
+
/**
|
|
41
|
+
* Create dual-auth middleware that accepts EITHER a better-auth session cookie
|
|
42
|
+
* OR a bearer token. Session cookies are checked first; if absent, falls back
|
|
43
|
+
* to bearer token validation via DB-backed API key lookup.
|
|
44
|
+
*
|
|
45
|
+
* Bearer tokens are hashed with SHA-256 before lookup — raw tokens are never
|
|
46
|
+
* stored or compared in plaintext.
|
|
47
|
+
*
|
|
48
|
+
* @param auth - The better-auth instance
|
|
49
|
+
* @param apiKeyRepo - Optional repository for DB-backed API key lookup
|
|
50
|
+
*/
|
|
51
|
+
export declare function dualAuth(auth: Auth, apiKeyRepo?: IApiKeyRepository): (c: Context<SessionAuthEnv>, next: Next) => Promise<void | (Response & import("hono").TypedResponse<{
|
|
52
|
+
error: string;
|
|
53
|
+
}, 503, "json">) | (Response & import("hono").TypedResponse<{
|
|
54
|
+
error: string;
|
|
55
|
+
}, 401, "json">)>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Auth Middleware — Resolves better-auth sessions for Hono routes.
|
|
3
|
+
*
|
|
4
|
+
* Reads the better-auth session cookie from the request, resolves the user,
|
|
5
|
+
* and sets `c.set("user", ...)` and `c.set("authMethod", "session")` for
|
|
6
|
+
* downstream route handlers.
|
|
7
|
+
*
|
|
8
|
+
* This middleware supports a dual-auth model:
|
|
9
|
+
* - **Session auth** (cookie-based): For browser/UI clients via better-auth
|
|
10
|
+
* - **Bearer token auth** (header-based): For machine-to-machine / API key access
|
|
11
|
+
*
|
|
12
|
+
* If neither is present, the request is rejected with 401.
|
|
13
|
+
*/
|
|
14
|
+
import { createHash } from "node:crypto";
|
|
15
|
+
/**
|
|
16
|
+
* Create middleware that authenticates requests via better-auth session cookies.
|
|
17
|
+
*
|
|
18
|
+
* On success, sets:
|
|
19
|
+
* - `c.set("user", { id, roles })` — user context for downstream routes
|
|
20
|
+
* - `c.set("authMethod", "session")` — indicates session-based auth
|
|
21
|
+
*
|
|
22
|
+
* If no valid session cookie is found, returns 401.
|
|
23
|
+
*
|
|
24
|
+
* @param auth - The better-auth instance (use `getAuth()`)
|
|
25
|
+
*/
|
|
26
|
+
export function sessionAuth(auth) {
|
|
27
|
+
return async (c, next) => {
|
|
28
|
+
try {
|
|
29
|
+
const session = await auth.api.getSession({ headers: c.req.raw.headers });
|
|
30
|
+
if (!session?.user) {
|
|
31
|
+
return c.json({ error: "Authentication required" }, 401);
|
|
32
|
+
}
|
|
33
|
+
const sessionUser = session.user;
|
|
34
|
+
const user = {
|
|
35
|
+
id: sessionUser.id,
|
|
36
|
+
roles: sessionUser.role === "admin" ? ["admin", "user"] : ["user"],
|
|
37
|
+
};
|
|
38
|
+
c.set("user", user);
|
|
39
|
+
c.set("authMethod", "session");
|
|
40
|
+
return next();
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return c.json({ error: "Service unavailable" }, 503);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Create dual-auth middleware that accepts EITHER a better-auth session cookie
|
|
49
|
+
* OR a bearer token. Session cookies are checked first; if absent, falls back
|
|
50
|
+
* to bearer token validation via DB-backed API key lookup.
|
|
51
|
+
*
|
|
52
|
+
* Bearer tokens are hashed with SHA-256 before lookup — raw tokens are never
|
|
53
|
+
* stored or compared in plaintext.
|
|
54
|
+
*
|
|
55
|
+
* @param auth - The better-auth instance
|
|
56
|
+
* @param apiKeyRepo - Optional repository for DB-backed API key lookup
|
|
57
|
+
*/
|
|
58
|
+
export function dualAuth(auth, apiKeyRepo) {
|
|
59
|
+
return async (c, next) => {
|
|
60
|
+
// 1. Try session cookie first
|
|
61
|
+
try {
|
|
62
|
+
const session = await auth.api.getSession({ headers: c.req.raw.headers });
|
|
63
|
+
if (session?.user) {
|
|
64
|
+
const sessionUser = session.user;
|
|
65
|
+
const user = {
|
|
66
|
+
id: sessionUser.id,
|
|
67
|
+
roles: sessionUser.role === "admin" ? ["admin", "user"] : ["user"],
|
|
68
|
+
};
|
|
69
|
+
c.set("user", user);
|
|
70
|
+
c.set("authMethod", "session");
|
|
71
|
+
return next();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return c.json({ error: "Service unavailable" }, 503);
|
|
76
|
+
}
|
|
77
|
+
// 2. Fall back to bearer token (DB-backed lookup)
|
|
78
|
+
const authHeader = c.req.header("Authorization");
|
|
79
|
+
if (authHeader && apiKeyRepo) {
|
|
80
|
+
const trimmed = authHeader.trim();
|
|
81
|
+
if (trimmed.toLowerCase().startsWith("bearer ")) {
|
|
82
|
+
const token = trimmed.slice(7).trim();
|
|
83
|
+
if (token) {
|
|
84
|
+
const keyHash = createHash("sha256").update(token).digest("hex");
|
|
85
|
+
try {
|
|
86
|
+
const apiUser = await apiKeyRepo.findByHash(keyHash);
|
|
87
|
+
if (apiUser) {
|
|
88
|
+
c.set("user", { ...apiUser, roles: [...apiUser.roles] });
|
|
89
|
+
c.set("authMethod", "api_key");
|
|
90
|
+
return next();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return c.json({ error: "Service unavailable" }, 503);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return c.json({ error: "Authentication required" }, 401);
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|