le-app/app/lib/rate-limit.ts

34 lines
1 KiB
TypeScript

type Bucket = { count: number; resetAt: number };
const buckets = new Map<string, Bucket>();
const MAX_BUCKETS = 10_000;
export function requestIp(req: Request): string {
// The deployment binds Next.js to localhost behind a reverse proxy. The
// proxy must replace client-supplied forwarding headers for this to be safe.
return (
req.headers.get("x-real-ip") ||
req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ||
"unknown"
);
}
export function rateLimit(key: string, max: number, windowMs: number): boolean {
const now = Date.now();
const current = buckets.get(key);
if (!current || current.resetAt <= now) {
if (buckets.size >= MAX_BUCKETS) {
for (const [bucketKey, bucket] of buckets) {
if (bucket.resetAt <= now) buckets.delete(bucketKey);
}
if (buckets.size >= MAX_BUCKETS) buckets.delete(buckets.keys().next().value as string);
}
buckets.set(key, { count: 1, resetAt: now + windowMs });
return true;
}
if (current.count >= max) return false;
current.count += 1;
return true;
}