Rules Hub
Coding Rules Library
Rule priority, scope & exceptions
Use this to align rules with the senior-level structure (P0/P1/P2, scope, exceptions/tradeoffs).
Apply stricter, identity-keyed rate limits to auth and expensive endpoints
Beyond a baseline per-IP limiter, protect login, password reset, OTP/verification, and costly operations with tighter limits keyed on the account/target identifier (and a backoff/lockout), to stop credential stuffing and resource abuse.
Bad example
| 1 | // One loose global limiter for the whole app, keyed only on IP |
| 2 | const limiter = rateLimit({ windowMs: 60_000, max: 1000 }); |
| 3 | app.use(limiter); |
| 4 |
|
| 5 | app.post('/login', login); // 1000 attempts/min/IP |
| 6 | app.post('/reset-password', reset); // unlimited password-reset emails within budget |
| 7 | app.post('/verify-otp', verify); // OTP brute force fits easily under 1000/min |
Explanation (EN)
A single 1000/min/IP budget barely slows credential stuffing or OTP brute force, and it's keyed on IP only — a rotating proxy or a botnet sidesteps it entirely. Reset/verify endpoints can also be abused to spam emails/SMS at the victim, an amplification DoS (OWASP API4 / ASVS V2 anti-automation).
Objašnjenje (HR)
Jedinstveni budžet od 1000/min/IP jedva usporava credential stuffing ili OTP brute force, a ključ je samo IP — rotirajući proxy ili botnet ga potpuno zaobilazi. Reset/verify endpointi se također mogu zloupotrijebiti za spamanje email/SMS žrtvi, što je amplifikacijski DoS (OWASP API4 / ASVS V2 anti-automatizacija).
Good example
| 1 | // Baseline IP limiter for everything... |
| 2 | app.use(rateLimit({ windowMs: 60_000, max: 300 })); |
| 3 |
|
| 4 | // ...plus tight, target-keyed limiters on sensitive routes |
| 5 | const loginLimiter = rateLimit({ |
| 6 | windowMs: 15 * 60_000, |
| 7 | max: 5, |
| 8 | keyGenerator: (req) => `${req.ip}:${String(req.body?.username ?? '')}`, |
| 9 | }); |
| 10 | app.post('/login', loginLimiter, login); |
| 11 |
|
| 12 | const resetLimiter = rateLimit({ |
| 13 | windowMs: 60 * 60_000, |
| 14 | max: 3, |
| 15 | keyGenerator: (req) => `reset:${String(req.body?.email ?? '')}`, |
| 16 | }); |
| 17 | app.post('/reset-password', resetLimiter, reset); |
Explanation (EN)
Sensitive endpoints get their own small budgets keyed on the targeted identity (username/email), so attempts against one account are throttled regardless of source IP, and reset/OTP abuse against a victim is capped. The baseline limiter still covers everything else.
Objašnjenje (HR)
Osjetljivi endpointi dobivaju vlastite male budžete vezane uz ciljani identitet (korisničko ime/email), pa su pokušaji protiv jednog računa ograničeni bez obzira na izvorni IP, a zloupotreba reset/OTP-a protiv žrtve je ograničena. Bazni limiter i dalje pokriva sve ostalo.
Exceptions / Tradeoffs (EN)
Use a shared/distributed store (Redis) for the limiter when running multiple instances — an in-memory limiter resets per process and is trivially bypassed by load balancing. If your platform already enforces these limits at the edge/gateway, app-level limiters can be lighter but should still exist as defense-in-depth.
Iznimke / Tradeoffi (HR)
Koristite dijeljeni/distribuirani store (Redis) za limiter kad pokrećete više instanci — in-memory limiter se resetira po procesu i lako se zaobilazi load balancingom. Ako vaša platforma već provodi ove granice na edge/gatewayu, app-level limiteri mogu biti lakši ali bi i dalje trebali postojati kao defense-in-depth.