-
Notifications
You must be signed in to change notification settings - Fork 111
Expand file tree
/
Copy pathrateLimit.ts
More file actions
66 lines (58 loc) · 1.79 KB
/
Copy pathrateLimit.ts
File metadata and controls
66 lines (58 loc) · 1.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import { NextRequest, NextResponse } from 'next/server';
interface RateLimitRecord {
count: number;
ts: number;
}
// In-process store — works for single-instance deployments.
// For multi-instance Vercel Edge, swap to @upstash/ratelimit (see .env.example).
const rateLimitMap = new Map<string, RateLimitRecord>();
// Clean up stale entries every 5 minutes to avoid memory leaks
setInterval(() => {
const now = Date.now();
for (const [key, record] of rateLimitMap.entries()) {
if (now - record.ts > 5 * 60_000) rateLimitMap.delete(key);
}
}, 5 * 60_000);
/**
* Returns a 429 NextResponse if the caller exceeds the limit, otherwise null.
* @param req The incoming NextRequest
* @param key Unique key (e.g. `username:${ip}` or `links:${userId}`)
* @param limit Max requests allowed in the window
* @param windowMs Window size in milliseconds
*/
export function rateLimit(
req: NextRequest,
key: string,
limit: number,
windowMs: number
): NextResponse | null {
const now = Date.now();
const record = rateLimitMap.get(key);
if (!record || now - record.ts > windowMs) {
rateLimitMap.set(key, { count: 1, ts: now });
return null; // allowed
}
if (record.count >= limit) {
return NextResponse.json(
{ error: 'Too many requests. Please slow down.' },
{
status: 429,
headers: {
'Retry-After': String(Math.ceil(windowMs / 1000)),
'X-RateLimit-Limit': String(limit),
'X-RateLimit-Remaining': '0',
},
}
);
}
record.count++;
return null; // allowed
}
/** Extracts the best available IP from the request headers */
export function getIp(req: NextRequest): string {
return (
req.headers.get('x-forwarded-for')?.split(',')[0].trim() ??
req.headers.get('x-real-ip') ??
'unknown'
);
}