# Rate Limiting Enhancement — Future Consideration **Date:** 2026-05-08 **Status:** Documented for future implementation **Priority:** Low (current per-IP limits are functional for self-hosted use) --- ## Current Implementation All rate limiters in `middleware/rateLimiter.js` use **per-IP** limiting: ```javascript keyGenerator: (req) => req.ip ``` ### Current Limits | Limiter | Limit | Keyed By | |---------|-------|----------| | `importLimiter` | 20 per 15 min | IP | | `exportLimiter` | 30 per 15 min | IP | | `adminActionLimiter` | 30 per 15 min | IP | | `demoDataLimiter` | 3 per 15 min | IP | | `backupOperationLimiter` | 5 per hour | IP | | `loginLimiter` | 5 per 15 min | IP | | `passwordLimiter` | 3 per hour | IP | | `oidcLimiter` | 10 per 15 min | IP | --- ## Limitations of Per-IP Limiting | Scenario | Problem | |----------|---------| | Shared office network | All users share one rate limit bucket | | VPN users | Multiple users behind same IP share limit | | Same user, multiple devices | Separate limits per device (may be desired) | | Malicious actor with IP rotation | Can bypass limits by rotating IPs | --- ## Recommended Enhancement: Hybrid Per-User + Per-IP ### Implementation ```javascript // middleware/rateLimiter.js const hybridKeyGenerator = (req) => { // Authenticated users get per-user limits if (req.user?.id) { return `user:${req.user.id}`; } // Anonymous requests get per-IP limits return `ip:${req.ip}`; }; // Apply to existing limiters const importLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 20, keyGenerator: hybridKeyGenerator, // <-- Changed standardHeaders: true, legacyHeaders: false, handler: (req, res) => { res.status(429).json({ error: 'Too many import requests. Please try again later.', code: 'RATE_LIMIT_EXCEEDED', retryAfter: Math.ceil(req.rateLimit.resetTime / 1000), }); }, }); ``` ### Benefits | Benefit | Explanation | |---------|-------------| | Fair per-user allocation | Each user gets their own rate limit bucket | | Shared network friendly | Office/VPN users don't share limits | | Abuse resistant | Can't bypass by IP rotation if authenticated | | Backwards compatible | Falls back to IP for anonymous requests | --- ## Alternative: Separate Limits for Auth vs Anonymous ```javascript const createHybridLimiter = (authMax, anonMax, windowMs) => { const authLimiter = rateLimit({ windowMs, max: authMax, keyGenerator: (req) => req.user?.id ? `user:${req.user.id}` : 'anonymous', skip: (req) => !req.user?.id, // Skip if not authenticated }); const anonLimiter = rateLimit({ windowMs, max: anonMax, keyGenerator: (req) => `ip:${req.ip}`, skip: (req) => !!req.user?.id, // Skip if authenticated }); return (req, res, next) => { if (req.user?.id) { return authLimiter(req, res, next); } return anonLimiter(req, res, next); }; }; // Usage: authenticated users get 50/15min, anonymous get 20/15min const importLimiter = createHybridLimiter(50, 20, 15 * 60 * 1000); ``` --- ## When to Implement | Trigger | Action | |---------|--------| | Multi-tenant deployment | Higher priority — many users sharing infrastructure | | API key authentication | Required — API keys need per-key limits | | Public cloud hosting | Recommended — shared IPs common | | Self-hosted, small team | Low priority — current per-IP is adequate | --- ## Related Files - `middleware/rateLimiter.js` — Current rate limiter definitions - `server.js` — Rate limiter application (routes) - `docs/SECURITY.md` — Security documentation --- ## Notes - Current per-IP limits are appropriate for self-hosted SQLite deployment - Hybrid approach should be considered if adding API keys or multi-tenant support - Rate limit storage is in-memory (Redis not required for current scale)