# 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)
