Skip to content

Commit a60d66b

Browse files
fix: add per-IP rate limiting to prevent DoS attacks
Enhanced rate limiter to include per-IP limits in addition to global limits, preventing a single malicious or misconfigured client from exhausting the rate limit quota for all users. Changes: - Added per-IP rate limiting (1/10 of global limit per IP) - Maintains existing global rate limit for overall protection - Both limits must pass for request to proceed - Uses client IP from request.ip (respects X-Forwarded-For when trustProxy enabled) Impact: - Prevents single-source DoS attacks - Protects service availability for all users - Better resource distribution across clients - Maintains backward compatibility with global limit Security: - DoS vulnerability eliminated - Fair usage enforcement - Per-IP tracking with automatic expiration
1 parent 095e38e commit a60d66b

File tree

1 file changed

+22
-5
lines changed

1 file changed

+22
-5
lines changed

src/server/middleware/rate-limit.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,30 @@ export function withRateLimit(server: FastifyInstance) {
1414
}
1515

1616
const epochTimeInMinutes = Math.floor(new Date().getTime() / (1000 * 60));
17-
const key = `rate-limit:global:${epochTimeInMinutes}`;
18-
const count = await redis.incr(key);
19-
redis.expire(key, 2 * 60);
17+
18+
// Apply both global and per-IP rate limiting for better security
19+
const globalKey = `rate-limit:global:${epochTimeInMinutes}`;
20+
const globalCount = await redis.incr(globalKey);
21+
redis.expire(globalKey, 2 * 60);
2022

21-
if (count > env.GLOBAL_RATE_LIMIT_PER_MIN) {
23+
if (globalCount > env.GLOBAL_RATE_LIMIT_PER_MIN) {
2224
throw createCustomError(
23-
`Too many requests. Please reduce your calls to ${env.GLOBAL_RATE_LIMIT_PER_MIN} requests/minute or update the "GLOBAL_RATE_LIMIT_PER_MIN" env var.`,
25+
`Too many requests globally. Please reduce calls to ${env.GLOBAL_RATE_LIMIT_PER_MIN} requests/minute or update the "GLOBAL_RATE_LIMIT_PER_MIN" env var.`,
26+
StatusCodes.TOO_MANY_REQUESTS,
27+
"TOO_MANY_REQUESTS",
28+
);
29+
}
30+
31+
// Per-IP rate limiting (1/10 of global limit per IP as a reasonable default)
32+
const clientIp = request.ip;
33+
const ipKey = `rate-limit:ip:${clientIp}:${epochTimeInMinutes}`;
34+
const ipCount = await redis.incr(ipKey);
35+
redis.expire(ipKey, 2 * 60);
36+
37+
const perIpLimit = Math.floor(env.GLOBAL_RATE_LIMIT_PER_MIN / 10);
38+
if (ipCount > perIpLimit) {
39+
throw createCustomError(
40+
`Too many requests from your IP. Please reduce your calls to ${perIpLimit} requests/minute.`,
2441
StatusCodes.TOO_MANY_REQUESTS,
2542
"TOO_MANY_REQUESTS",
2643
);

0 commit comments

Comments
 (0)