mirror of
https://github.com/screentinker/screentinker.git
synced 2026-06-20 05:02:54 -06:00
fix(server): rate-limit per endpoint, not the stripped req.path (#100)
app.use('/api/auth/login', rateLimit(...)) etc. keyed on req.path, which Express strips to
'/' for mounted middleware - so /login, /register, /totp/verify shared ONE per-IP counter
(coupled limits; the new /totp/verify brute-force limit was not actually independent). Key
on originalUrl instead. Also adds the /api/auth/totp/verify 10/min limit (tightening #2).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c02086e305
commit
c38d8dc0e6
|
|
@ -271,7 +271,12 @@ app.use('/socket.io-client', express.static(
|
|||
const rateLimits = new Map();
|
||||
function rateLimit(windowMs, maxRequests) {
|
||||
return (req, res, next) => {
|
||||
const key = getClientIp(req) + req.path;
|
||||
// #100: key on the FULL path, not req.path. These limiters are mounted via
|
||||
// app.use('/api/auth/login', ...) etc., and Express strips the mount path, so
|
||||
// req.path was '/' for ALL of them - i.e. /login, /register, /totp/verify shared
|
||||
// ONE per-IP counter (coupled limits; the /totp/verify brute-force limit wasn't
|
||||
// actually independent). originalUrl keeps each endpoint's limit separate.
|
||||
const key = getClientIp(req) + (req.originalUrl || req.url || req.path).split('?')[0];
|
||||
const now = Date.now();
|
||||
const windowStart = now - windowMs;
|
||||
let hits = rateLimits.get(key) || [];
|
||||
|
|
@ -292,6 +297,10 @@ function rateLimit(windowMs, maxRequests) {
|
|||
// Auth routes (public, rate limited)
|
||||
app.use('/api/auth/login', rateLimit(60000, 10)); // 10 attempts per minute
|
||||
app.use('/api/auth/register', rateLimit(60000, 5)); // 5 registrations per minute
|
||||
// #100 (tightening #2): the TOTP verify endpoint is the brute-force surface for a
|
||||
// 6-digit code. Cap attempts/min here; the per-user lockout (lib/totp-lockout) sits
|
||||
// on top in the handler.
|
||||
app.use('/api/auth/totp/verify', rateLimit(60000, 10));
|
||||
// Admin password-reset endpoint: even if an admin's session is compromised,
|
||||
// cap the blast radius to 20 resets/min/IP. Express matches the longest
|
||||
// path prefix first, so this fires before /api/auth catches the request.
|
||||
|
|
|
|||
Loading…
Reference in a new issue