@@ -58,7 +58,11 @@ import { hasProductUpdatesOptOut } from "./product-updates";
5858import { reportHandledError } from "@uploads/observability" ;
5959import { setIssueContext } from "@uploads/observability/issues" ;
6060import { buildTokenGrantAfterHandler , captureRefreshTokenPriorState } from "./oauth-observability" ;
61- import { createDurableRateLimitStorage , type RateLimitNamespaceLike } from "./rate-limit" ;
61+ import {
62+ createBindingRateLimitStorage ,
63+ rateLimitTiers ,
64+ type RateLimitBindings ,
65+ } from "./rate-limit" ;
6266import * as schema from "./schema" ;
6367import { stripePluginOrNone } from "./stripe-plugin" ;
6468import { authTrustedOrigins , isTrustedOrigin } from "./trusted-origins" ;
@@ -523,7 +527,8 @@ async function enforceLastAdminGuard(
523527}
524528
525529export type AuthEnv = GitHubCredentialsEnv &
526- DashApiKeyEnv & {
530+ DashApiKeyEnv &
531+ RateLimitBindings & {
527532 DB : D1Database ;
528533 EMAIL ?: import ( "./email" ) . EmailBinding ;
529534 BETTER_AUTH_URL ?: string ;
@@ -536,11 +541,6 @@ export type AuthEnv = GitHubCredentialsEnv &
536541 ENVIRONMENT ?: string ;
537542 BETTER_AUTH_TRUSTED_ORIGINS ?: string ;
538543 AUTH_RATE_LIMIT_DISABLED ?: string ;
539- /**
540- * Durable Object namespace backing the rate limiter (src/rate-limit-do.ts).
541- * Without it, Better Auth's in-memory storage is used.
542- */
543- RATE_LIMIT ?: RateLimitNamespaceLike ;
544544 /** Ephemeral flag passed only by `pnpm dev:stack`; never configure in prod. */
545545 LOCAL_STACK ?: string ;
546546 /** Gates stripePluginOrNone and feeds the memoization key. */
@@ -557,15 +557,16 @@ export type AuthEnv = GitHubCredentialsEnv &
557557export type BetterAuthInstance = ReturnType < typeof buildAuth > ;
558558
559559/**
560- * Rate-limit storage: the Durable Object when RATE_LIMIT is bound, Better
560+ * Rate-limit storage: the Rate Limiting bindings when all are bound, Better
561561 * Auth's in-memory Map otherwise. Both branches return the same shape so
562562 * callers don't have to narrow a union.
563563 */
564564function rateLimitStorage ( env : AuthEnv ) : {
565- customStorage ?: ReturnType < typeof createDurableRateLimitStorage > ;
565+ customStorage ?: ReturnType < typeof createBindingRateLimitStorage > ;
566566 storage ?: "memory" ;
567567} {
568- if ( env . RATE_LIMIT ) return { customStorage : createDurableRateLimitStorage ( env . RATE_LIMIT ) } ;
568+ const tiers = rateLimitTiers ( env ) ;
569+ if ( tiers ) return { customStorage : createBindingRateLimitStorage ( tiers ) } ;
569570 return { storage : "memory" } ;
570571}
571572
@@ -1035,13 +1036,12 @@ function buildAuth(
10351036 //
10361037 // D1 is kept out of the rate-limit path: a D1 stall once hung every
10371038 // /api/auth/* request. /get-session is exempt entirely (customRules), and
1038- // the remaining low-frequency endpoints use one Durable Object counter
1039- // per key (src/rate-limit-do.ts), an exact fleet-wide fixed window. The
1040- // wrapper fails open and races the DO call against a ~1.5s timer, since
1041- // rate limiting is abuse-shaping, not a security boundary.
1039+ // the remaining endpoints use the per-colo Rate Limiting bindings
1040+ // (src/rate-limit.ts). The wrapper fails open, since rate limiting is
1041+ // abuse-shaping, not a security boundary.
10421042 //
1043- // Without the binding (tests, bare local envs) Better Auth's in-memory
1044- // Map is used. KV is too coarse (1 write/sec/key) for a shared counter.
1043+ // Without the bindings (tests, bare local envs) Better Auth's in-memory
1044+ // Map is used.
10451045 rateLimit : {
10461046 enabled : isProduction && env . AUTH_RATE_LIMIT_DISABLED !== "true" ,
10471047 ...rateLimitStorage ( env ) ,
@@ -1103,9 +1103,9 @@ function cacheKey(
11031103 githubClientSecret : github ?. clientSecret ?? null ,
11041104 dashApiKey,
11051105 hasEmail : Boolean ( env . EMAIL ) ,
1106- // Presence flips the limiter between DO -backed customStorage and Better
1107- // Auth's memory fallback, so it has to be part of the key.
1108- hasRateLimitDO : Boolean ( env . RATE_LIMIT ) ,
1106+ // Presence flips the limiter between binding -backed customStorage and
1107+ // Better Auth's memory fallback, so it has to be part of the key.
1108+ hasRateLimiters : Boolean ( rateLimitTiers ( env ) ) ,
11091109 } ) ;
11101110}
11111111
0 commit comments