Files
worldmonitor/api/_rate-limit.js
Haozhe Wu 3a2a83d31f fix(security): prefer cf-connecting-ip over x-real-ip in JS rate limiter (#2106)
Behind Cloudflare -> Vercel, x-real-ip is the CF edge IP shared across
many users, not the actual client. The TS rate limiter correctly
prioritizes cf-connecting-ip. Align the JS version.

Without this fix, all users behind the same CF edge share a single
rate limit bucket, making the limit either too restrictive or too lenient.

Co-authored-by: warren618 <warren618@users.noreply.github.com>
Co-authored-by: Elie Habib <elie.habib@gmail.com>
2026-03-23 09:22:45 +04:00

59 lines
1.5 KiB
JavaScript

import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
import { jsonResponse } from './_json-response.js';
let ratelimit = null;
function getRatelimit() {
if (ratelimit) return ratelimit;
const url = process.env.UPSTASH_REDIS_REST_URL;
const token = process.env.UPSTASH_REDIS_REST_TOKEN;
if (!url || !token) return null;
ratelimit = new Ratelimit({
redis: new Redis({ url, token }),
limiter: Ratelimit.slidingWindow(600, '60 s'),
prefix: 'rl',
analytics: false,
});
return ratelimit;
}
function getClientIp(request) {
// With Cloudflare proxy -> Vercel, x-real-ip is the CF edge IP (shared
// across users). cf-connecting-ip is the actual client IP — prefer it.
// (Matches server/_shared/rate-limit.ts)
return (
request.headers.get('cf-connecting-ip') ||
request.headers.get('x-real-ip') ||
request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ||
'0.0.0.0'
);
}
export async function checkRateLimit(request, corsHeaders) {
const rl = getRatelimit();
if (!rl) return null;
const ip = getClientIp(request);
try {
const { success, limit, reset } = await rl.limit(ip);
if (!success) {
return jsonResponse({ error: 'Too many requests' }, 429, {
'X-RateLimit-Limit': String(limit),
'X-RateLimit-Remaining': '0',
'X-RateLimit-Reset': String(reset),
'Retry-After': String(Math.ceil((reset - Date.now()) / 1000)),
...corsHeaders,
});
}
return null;
} catch {
return null;
}
}