Skip to content

Commit 39c22eb

Browse files
committed
Add per-IP rate limiting to parents signup endpoint
1 parent 7121c77 commit 39c22eb

2 files changed

Lines changed: 30 additions & 0 deletions

File tree

app/api/parents-signup/route.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,27 @@ function clientIp(req: NextRequest) {
1818
return req.headers.get("x-real-ip") ?? undefined;
1919
}
2020

21+
const RATE_LIMIT_WINDOW_MS = 10 * 60 * 1000;
22+
const RATE_LIMIT_MAX = 5;
23+
const rateLimitHits = new Map<string, number[]>();
24+
25+
function isRateLimited(key: string) {
26+
const now = Date.now();
27+
const recent = (rateLimitHits.get(key) ?? []).filter(
28+
(t) => now - t < RATE_LIMIT_WINDOW_MS,
29+
);
30+
recent.push(now);
31+
rateLimitHits.set(key, recent);
32+
33+
if (rateLimitHits.size > 5000) {
34+
for (const [k, hits] of rateLimitHits) {
35+
if (now - hits[hits.length - 1] > RATE_LIMIT_WINDOW_MS) rateLimitHits.delete(k);
36+
}
37+
}
38+
39+
return recent.length > RATE_LIMIT_MAX;
40+
}
41+
2142
export async function POST(req: NextRequest) {
2243
const key = apiKey();
2344
if (!key) {
@@ -38,6 +59,10 @@ export async function POST(req: NextRequest) {
3859

3960
const ip = clientIp(req);
4061

62+
if (isRateLimited(ip ?? "unknown")) {
63+
return NextResponse.json({ error: "Too many requests" }, { status: 429 });
64+
}
65+
4166
const res = await fetch(
4267
`https://api.airtable.com/v0/${BASE_ID}/${encodeURIComponent(TABLE_NAME)}`,
4368
{

components/parents-email-signup.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ export function ParentsEmailSignup() {
2626
headers: { "Content-Type": "application/json" },
2727
body: JSON.stringify({ email: email.trim() }),
2828
});
29+
if (res.status === 429) {
30+
setStatus("error");
31+
setErrorMsg("Too many attempts — try again later");
32+
return;
33+
}
2934
if (!res.ok) throw new Error();
3035
setStatus("success");
3136
setEmail("");

0 commit comments

Comments
 (0)