143 lines
3.8 KiB
Markdown
143 lines
3.8 KiB
Markdown
# 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)
|