'use strict'; const { rateLimit, ipKeyGenerator } = require('express-rate-limit'); function makeLimiter(max, windowMs, message) { return rateLimit({ windowMs, max, standardHeaders: 'draft-7', legacyHeaders: false, // Override default handler so the response is always JSON, not HTML handler(req, res) { res.status(429).json({ error: message }); }, }); } // 10 login attempts per 15 minutes per IP — brute-force protection const loginLimiter = makeLimiter( 10, 15 * 60 * 1000, 'Too many login attempts. Please try again in 15 minutes.', ); // 5 FAILED login attempts per 15 minutes per username — layered on top of the // per-IP limiter so a distributed attacker (or many clients behind one NAT/proxy // sharing an IP bucket) cannot brute-force a single account. Successful logins // don't count toward the limit, so legitimate users are unaffected. const loginUsernameLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5, standardHeaders: 'draft-7', legacyHeaders: false, skipSuccessfulRequests: true, keyGenerator: (req) => { const username = String(req.body?.username || '').trim().toLowerCase(); return username ? `user:${username}` : ipKeyGenerator(req); }, handler(req, res) { res.status(429).json({ error: 'Too many failed login attempts for this account. Please try again in 15 minutes.' }); }, }); // 5 password-change attempts per 15 minutes per IP const passwordLimiter = makeLimiter( 5, 15 * 60 * 1000, 'Too many password change attempts. Please try again in 15 minutes.', ); // 20 import preview/apply requests per 15 minutes per IP const importLimiter = makeLimiter( 20, 15 * 60 * 1000, 'Too many import requests. Please try again in 15 minutes.', ); // 30 export requests per 15 minutes per IP const exportLimiter = makeLimiter( 30, 15 * 60 * 1000, 'Too many export requests. Please try again in 15 minutes.', ); // 30 admin mutation actions per 15 minutes per IP (backup/restore/cleanup) const adminActionLimiter = makeLimiter( 30, 15 * 60 * 1000, 'Too many admin actions. Please try again in 15 minutes.', ); // 20 OIDC login/callback requests per 15 minutes per IP const oidcLimiter = makeLimiter( 20, 15 * 60 * 1000, 'Too many authentication requests. Please try again in 15 minutes.', ); // 5 backup operations per 60 minutes per IP (backup creation, restore, import) const backupOperationLimiter = makeLimiter( 5, 60 * 60 * 1000, 'Too many backup operations. Please try again in 60 minutes.', ); // 3 demo data clear operations per 15 minutes per IP const demoDataLimiter = makeLimiter( 3, 15 * 60 * 1000, 'Too many demo data clear operations. Please try again in 15 minutes.', ); // 10 sync/backfill requests per 15 minutes per user — prevents SimpleFIN hammering const syncLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 10, standardHeaders: 'draft-7', legacyHeaders: false, keyGenerator: (req) => req.user?.id?.toString() || ipKeyGenerator(req), handler(req, res) { res.status(429).json({ error: 'Too many sync requests. Please try again in 15 minutes.' }); }, }); // ── Export all limiters plus reset function ──────────────────────────────────── const allLimiters = [ loginLimiter, loginUsernameLimiter, passwordLimiter, importLimiter, exportLimiter, adminActionLimiter, oidcLimiter, backupOperationLimiter, demoDataLimiter, syncLimiter, ]; function resetStores() { for (const limiter of allLimiters) { if (limiter.store.reset) { limiter.store.reset(); } } } module.exports = { loginLimiter, loginUsernameLimiter, passwordLimiter, importLimiter, exportLimiter, adminActionLimiter, oidcLimiter, backupOperationLimiter, demoDataLimiter, syncLimiter, resetStores, };