-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmiddleware.ts
More file actions
113 lines (92 loc) · 2.75 KB
/
Copy pathmiddleware.ts
File metadata and controls
113 lines (92 loc) · 2.75 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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import { env } from "@/lib/env";
import { logger } from "@/lib/logger";
import { rateLimit } from "@/lib/rate-limit";
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
const RATE_LIMITED_PATH = /^\/api\//;
const isProduction = process.env.NODE_ENV === "production";
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
if (pathname === "/api/ws/submissions") {
return NextResponse.next();
}
if (!RATE_LIMITED_PATH.test(pathname)) {
return NextResponse.next();
}
if (!isProduction) {
return NextResponse.next();
}
const identifier = getIdentifier(request);
const result = rateLimit({
identifier,
windowInSeconds: env.RATE_LIMIT_WINDOW_SECONDS,
maxRequests: env.RATE_LIMIT_MAX_REQUESTS,
});
if (!result.success) {
logger.warn(
{
identifier,
path: pathname,
retryAt: result.reset,
},
"rate limit exceeded",
);
const retryAfter = Math.max(1, Math.ceil((result.reset - Date.now()) / 1000));
const response = NextResponse.json(
{
error: "Too many requests",
},
{ status: 429 },
);
attachRateLimitHeaders(response, {
remaining: 0,
reset: result.reset,
});
response.headers.set("Retry-After", retryAfter.toString());
return response;
}
const requestId = request.headers.get("x-request-id") ?? crypto.randomUUID();
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-request-id", requestId);
const response = NextResponse.next({
request: {
headers: requestHeaders,
},
});
attachRateLimitHeaders(response, result);
response.headers.set("x-request-id", requestId);
logger.debug(
{
identifier,
path: pathname,
remaining: result.remaining,
reset: result.reset,
requestId,
},
"rate limit check passed",
);
return response;
}
export const config = {
matcher: ["/api/:path*"],
};
function getIdentifier(request: NextRequest) {
const forwardedFor = request.headers.get("x-forwarded-for");
if (forwardedFor) {
const [first] = forwardedFor.split(",");
return first.trim();
}
const realIp = request.headers.get("x-real-ip") ?? request.headers.get("x-vercel-ip");
if (realIp) {
return realIp;
}
return request.nextUrl.hostname ?? "anonymous";
}
function attachRateLimitHeaders(
response: NextResponse,
result: Pick<ReturnType<typeof rateLimit>, "remaining" | "reset">,
) {
response.headers.set("X-RateLimit-Limit", env.RATE_LIMIT_MAX_REQUESTS.toString());
response.headers.set("X-RateLimit-Remaining", Math.max(result.remaining, 0).toString());
response.headers.set("X-RateLimit-Reset", Math.floor(result.reset / 1000).toString());
}