feat(security): Postgres-backed login rate limiting
Replaces the in-memory sliding-window Map (reset on every restart, not shareable across instances) with a login_attempts table (migration 016). No Redis added - login is not a hot path, and this is a single indexed range-scan query. Restart behavior confirmed for real: platform/test/e2e-phase3a.js's rate-limiting test passes after a genuine Platform API restart in the same run.
This commit is contained in:
@@ -18,7 +18,7 @@ module.exports = function registerInstructorAuthRoutes(app, { pool, sessions, in
|
||||
app.post('/api/auth/instructor/login', async (req, res) => {
|
||||
const { email, password } = req.body || {};
|
||||
const rateKey = `${req.ip}:${String(email || '').toLowerCase()}`;
|
||||
if (tooManyAttempts(rateKey, LOGIN_MAX_ATTEMPTS, LOGIN_WINDOW_MS)) {
|
||||
if (await tooManyAttempts(pool, rateKey, LOGIN_MAX_ATTEMPTS, LOGIN_WINDOW_MS)) {
|
||||
return res.status(429).json({ error: 'too_many_attempts', message: 'Too many login attempts. Try again in a few minutes.' });
|
||||
}
|
||||
if (!email || !password) return res.status(400).json({ error: 'email and password are required' });
|
||||
|
||||
Reference in New Issue
Block a user