-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathproxy.ts
More file actions
146 lines (128 loc) · 3.71 KB
/
proxy.ts
File metadata and controls
146 lines (128 loc) · 3.71 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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import {
apiRateLimiter,
chatRateLimiter,
strictRateLimiter,
pageRateLimiter,
} from "@/lib/rate-limiter";
/**
* Get client IP address from request
* Handles various proxy headers
*/
function getClientIP(request: NextRequest): string {
// Check various headers for real IP (in order of trust)
const forwardedFor = request.headers.get("x-forwarded-for");
if (forwardedFor) {
// Take the first IP if there are multiple (client IP is first)
return forwardedFor.split(",")[0].trim();
}
const realIP = request.headers.get("x-real-ip");
if (realIP) {
return realIP.trim();
}
// Vercel-specific header
const vercelForwardedFor = request.headers.get("x-vercel-forwarded-for");
if (vercelForwardedFor) {
return vercelForwardedFor.split(",")[0].trim();
}
// Cloudflare header
const cfConnectingIP = request.headers.get("cf-connecting-ip");
if (cfConnectingIP) {
return cfConnectingIP.trim();
}
// Fallback to a default (shouldn't happen in production)
return "unknown";
}
/**
* Create a rate limit exceeded response
*/
function rateLimitResponse(resetIn: number): NextResponse {
return new NextResponse(
JSON.stringify({
error: "Too Many Requests",
message: "You have exceeded the rate limit. Please try again later.",
retryAfter: Math.ceil(resetIn / 1000),
}),
{
status: 429,
headers: {
"Content-Type": "application/json",
"Retry-After": String(Math.ceil(resetIn / 1000)),
"X-RateLimit-Remaining": "0",
},
}
);
}
/**
* Paths that should be excluded from rate limiting
*/
const EXCLUDED_PATHS = [
"/_next", // Next.js internal
"/favicon.ico",
"/manifest.json",
"/robots.txt",
"/sitemap.xml",
];
/**
* Check if path should be excluded from rate limiting
*/
function isExcludedPath(pathname: string): boolean {
return EXCLUDED_PATHS.some(
(excluded) => pathname === excluded || pathname.startsWith(`${excluded}/`)
);
}
export function proxy(request: NextRequest) {
const { pathname } = request.nextUrl;
// Skip rate limiting for excluded paths
if (isExcludedPath(pathname)) {
return NextResponse.next();
}
// Skip rate limiting for static files
if (
pathname.match(/\.(ico|png|jpg|jpeg|gif|svg|webp|css|js|woff|woff2|ttf|otf)$/)
) {
return NextResponse.next();
}
const clientIP = getClientIP(request);
let result;
// Apply different rate limits based on route type
if (pathname.startsWith("/api/chat")) {
// Chat/AI endpoints - stricter limits
result = chatRateLimiter.check(clientIP);
} else if (pathname.startsWith("/api/auth")) {
// Auth endpoints - very strict limits
result = strictRateLimiter.check(clientIP);
} else if (pathname.startsWith("/api/")) {
// General API endpoints
result = apiRateLimiter.check(clientIP);
} else {
// Page requests - more lenient
result = pageRateLimiter.check(clientIP);
}
if (!result.allowed) {
console.warn(
`[Rate Limit] Blocked request from ${clientIP} to ${pathname}`
);
return rateLimitResponse(result.resetIn);
}
// Add rate limit headers to response
const response = NextResponse.next();
response.headers.set("X-RateLimit-Remaining", String(result.remaining));
response.headers.set(
"X-RateLimit-Reset",
String(Math.ceil((Date.now() + result.resetIn) / 1000))
);
return response;
}
export const config = {
matcher: [
/*
* Match all request paths except:
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico, sitemap.xml, robots.txt (metadata files)
*/
"/((?!_next/static|_next/image).*)",
],
};