Skip to content

Commit e02c704

Browse files
authored
Merge pull request #9 from Mra1k3r0/master
fix(proxy): rate limiter no longer blocks auth
2 parents 0be1aaa + 272626e commit e02c704

4 files changed

Lines changed: 57 additions & 50 deletions

File tree

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
import { NextResponse } from "next/server";
22

3-
export default function HeadlessBrowserCheck(req: Request) {
4-
const ua = req.headers.get("user-agent") || "";
5-
const headlessPatterns = [
6-
"HeadlessChrome",
7-
"PhantomJS",
8-
"SlimerJS",
9-
"Puppeteer",
10-
"Playwright",
11-
"Chrome-Lighthouse",
12-
];
3+
const HEADLESS_PATTERNS = [
4+
"HeadlessChrome",
5+
"PhantomJS",
6+
"SlimerJS",
7+
"Puppeteer",
8+
"Playwright",
9+
"Chrome-Lighthouse",
10+
] as const;
1311

14-
const isHeadless = headlessPatterns.some((p) =>
12+
// block bots, pass through real browsers
13+
export default function HeadlessBrowserCheck(
14+
req: Request,
15+
): NextResponse | undefined {
16+
const ua = req.headers.get("user-agent") ?? "";
17+
const isHeadless = HEADLESS_PATTERNS.some((p) =>
1518
ua.toLowerCase().includes(p.toLowerCase()),
1619
);
1720

@@ -24,4 +27,6 @@ export default function HeadlessBrowserCheck(req: Request) {
2427
},
2528
);
2629
}
30+
31+
return undefined;
2732
}

app/lib/proxy/rate-limiter.ts

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,42 @@
11
import { NextRequest, NextResponse } from "next/server";
22
import { checkRateLimit } from "../rate-limit";
33

4-
export default function RateLimiter(request: NextRequest) {
5-
if (/api\//.test(request.nextUrl.pathname)) {
6-
const forwardedFor = request.headers.get("x-forwarded-for");
7-
const ip = forwardedFor?.split(",")[0] || "unknown";
8-
const origin = request.headers.get("origin");
9-
10-
const allowedOrigins =
11-
process.env.NODE_ENV === "development"
12-
? [/^http:\/\/localhost:\d+$/, /^http:\/\/127\.0\.0\.1:\d+$/]
13-
: [/^https?:\/\/devpulse-waka\.vercel\.app(:\d+)?$/];
14-
15-
const isAllowed1 =
16-
!origin || allowedOrigins.some((pattern) => pattern.test(origin));
17-
18-
if (!isAllowed1) {
19-
return NextResponse.json(
20-
{ error: "Hehe you're going too far naah..." },
21-
{ status: 403 },
22-
);
23-
}
24-
25-
const maxRequest = /api\/(login|signup)/.test(request.nextUrl.pathname)
26-
? 5
27-
: 10;
28-
29-
const window = /api\/(login|signup)/.test(request.nextUrl.pathname)
30-
? 60 * 60 * 1000
31-
: 5 * 60 * 1000;
32-
33-
const isAllowed = checkRateLimit(ip, maxRequest, window);
34-
35-
if (!isAllowed) {
36-
return NextResponse.json({ error: "Too many requests" }, { status: 429 });
37-
}
4+
export default function RateLimiter(
5+
request: NextRequest,
6+
): NextResponse | undefined {
7+
if (!/api\//.test(request.nextUrl.pathname)) {
8+
return undefined;
389
}
3910

40-
return NextResponse.next();
11+
const forwardedFor = request.headers.get("x-forwarded-for");
12+
const ip = forwardedFor?.split(",")[0]?.trim() || "unknown";
13+
const origin = request.headers.get("origin");
14+
15+
const allowedOrigins =
16+
process.env.NODE_ENV === "development"
17+
? [/^http:\/\/localhost:\d+$/, /^http:\/\/127\.0\.0\.1:\d+$/]
18+
: [/^https?:\/\/devpulse-waka\.vercel\.app(:\d+)?$/];
19+
20+
const isOriginAllowed =
21+
!origin || allowedOrigins.some((pattern) => pattern.test(origin));
22+
23+
if (!isOriginAllowed) {
24+
return NextResponse.json(
25+
{ error: "Hehe you're going too far naah..." },
26+
{ status: 403 },
27+
);
28+
}
29+
30+
const isAuthEndpoint = /api\/(login|signup)/.test(request.nextUrl.pathname);
31+
const maxRequests = isAuthEndpoint ? 5 : 10;
32+
const windowMs = isAuthEndpoint ? 60 * 60 * 1000 : 5 * 60 * 1000;
33+
34+
const withinLimit = checkRateLimit(ip, maxRequests, windowMs);
35+
36+
if (!withinLimit) {
37+
return NextResponse.json({ error: "Too many requests" }, { status: 429 });
38+
}
39+
40+
// let it through so auth still runs
41+
return undefined;
4142
}

app/lib/rate-limit.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// in-memory so on serverless each instance has its own count (redis/kv if you need it shared)
12
const ipRequests: Record<string, { count: number; firstRequest: number }> = {};
23

34
export function checkRateLimit(ip: string, maxRequests: number, rateLimitWindow: number): boolean {
@@ -11,13 +12,12 @@ export function checkRateLimit(ip: string, maxRequests: number, rateLimitWindow:
1112
const data = ipRequests[ip];
1213
if (now - data.firstRequest < rateLimitWindow) {
1314
if (data.count >= maxRequests) {
14-
return false; // ❌ over the limit
15+
return false;
1516
}
1617
data.count++;
1718
return true;
1819
}
1920

20-
// reset window
2121
ipRequests[ip] = { count: 1, firstRequest: now };
2222
return true;
2323
}

proxy.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@ import RateLimiter from "./app/lib/proxy/rate-limiter";
55

66
const env = process.env.NODE_ENV;
77

8+
// headless → rate limit → auth; only return when we block or redirect
89
export async function proxy(req: NextRequest) {
910
const headlessResponse = headlessBrowserCheck(req);
1011
if (headlessResponse) return headlessResponse;
1112

1213
if (env === "production") {
13-
const rateLimiter = RateLimiter(req);
14-
if (rateLimiter) return rateLimiter;
14+
const rateLimitResponse = RateLimiter(req);
15+
if (rateLimitResponse) return rateLimitResponse;
1516
}
1617

1718
const authResponse = await auth(req);

0 commit comments

Comments
 (0)