فهرست منبع

rate limit pr ip (#234)

Bernt Christian Egeland 1 ماه پیش
والد
کامیت
f9f9e42dc8
3فایلهای تغییر یافته به همراه20 افزوده شده و 2 حذف شده
  1. 4 1
      src/app/api/public/auth/[...all]/route.ts
  2. 7 0
      src/lib/auth.ts
  3. 9 1
      src/lib/rate-limit.ts

+ 4 - 1
src/app/api/public/auth/[...all]/route.ts

@@ -27,9 +27,12 @@ const authAuditPrefixes = [
 const defaultConfig = { limit: 30, windowMs: 60_000 }
 
 function getRequestIp(request: Request): string | null {
+  // Same precedence as lib/rate-limit.ts: Cloudflare's header cannot be forged
+  // by clients on proxied traffic; the first x-forwarded-for entry can.
   return (
-    request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ||
+    request.headers.get('cf-connecting-ip') ||
     request.headers.get('x-real-ip') ||
+    request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ||
     null
   )
 }

+ 7 - 0
src/lib/auth.ts

@@ -127,6 +127,13 @@ export const auth = betterAuth({
   },
   advanced: {
     useSecureCookies: isProduction,
+    ipAddress: {
+      // Client IP for per-user rate limiting. Cloud traffic arrives through
+      // Cloudflare (cf-connecting-ip); staging and self-hosted installs hit
+      // nginx directly, which sets x-real-ip. Without this, Better Auth falls
+      // back to one shared rate-limit bucket for the entire userbase.
+      ipAddressHeaders: ["cf-connecting-ip", "x-real-ip"],
+    },
   },
   databaseHooks: {
     session: {

+ 9 - 1
src/lib/rate-limit.ts

@@ -25,8 +25,16 @@ export function rateLimit(
   request: Request,
   { limit = 30, windowMs = 60_000 }: { limit?: number; windowMs?: number } = {},
 ): NextResponse | null {
+  // cf-connecting-ip is set by Cloudflare and not client-forgeable on proxied
+  // traffic; x-real-ip is set by nginx for direct/staging traffic. The first
+  // entry of x-forwarded-for is client-controlled (proxies append, not
+  // replace), so it is only a last resort.
   const forwarded = request.headers.get("x-forwarded-for");
-  const ip = forwarded?.split(",")[0]?.trim() || "unknown";
+  const ip =
+    request.headers.get("cf-connecting-ip") ||
+    request.headers.get("x-real-ip") ||
+    forwarded?.split(",")[0]?.trim() ||
+    "unknown";
   const url = new URL(request.url);
   const key = `${ip}:${url.pathname}`;