Description
The rate limiter in src/middleware/rateLimit.ts stores buckets in a per-process Map, and its own docstring states a "horizontally scaled deployment should swap the storage for Redis without touching the middleware's public surface". Today, running multiple API replicas multiplies the effective limit by the replica count and lets the /api/risk/evaluate cap (RATE_LIMIT_MAX_EVALUATE, default 10, from src/config/rateLimit.ts) be bypassed. This issue introduces a pluggable store abstraction with an in-memory default and an optional Redis backend.
Requirements and context
- Extract a
RateLimitStore interface (atomic increment(key, windowMs) returning { count, resetAt }) and refactor createRateLimitMiddleware to depend on it without changing RateLimitOptions, the X-RateLimit-*/Retry-After headers, or the { data, error, retryAfter } 429 envelope.
- Provide an in-memory store (current behavior, preserving the existing
cleanup() sweep) and a Redis store gated by an env var (e.g. RATE_LIMIT_REDIS_URL); when unset, behavior is unchanged.
- Use an atomic Redis primitive (e.g.
INCR + PEXPIRE) so concurrent requests across replicas share one window; reuse the key generators createIpKeyGenerator / createApiKeyKeyGenerator unchanged.
- Validate the new env var in
src/config/env.ts so misconfiguration fails fast, consistent with the existing Zod schema.
- Non-functional: a Redis outage must degrade gracefully (fail-open or fail-closed by config) rather than 500-ing every request; document which mode is default.
Acceptance criteria
Suggested execution
1. Fork the repo and create a branch — git checkout -b feature/redis-rate-limit-store.
2. Implement changes — refactor src/middleware/rateLimit.ts, add stores under src/middleware/ or src/config/, and extend src/config/env.ts.
3. Write/extend tests — Vitest + Supertest; extend tests/middleware/rateLimit.test.ts and tests/config/rateLimit.test.ts, mocking the Redis client for the shared-counter and outage cases.
4. Test and commit —
npm run lint
npm run typecheck
npm test
npm run test:coverage
npm run build
Example commit message
feat(ratelimit): add pluggable store with optional Redis backend
Guidelines
CI enforces a 95% coverage threshold (vitest.config.ts, via npm run test:coverage) — cover the store interface, both backends, and the outage path. Update docs/SECURITY.md and .env.example. Timeframe: 96 hours.
Description
The rate limiter in
src/middleware/rateLimit.tsstores buckets in a per-processMap, and its own docstring states a "horizontally scaled deployment should swap the storage for Redis without touching the middleware's public surface". Today, running multiple API replicas multiplies the effective limit by the replica count and lets the/api/risk/evaluatecap (RATE_LIMIT_MAX_EVALUATE, default 10, fromsrc/config/rateLimit.ts) be bypassed. This issue introduces a pluggable store abstraction with an in-memory default and an optional Redis backend.Requirements and context
RateLimitStoreinterface (atomicincrement(key, windowMs)returning{ count, resetAt }) and refactorcreateRateLimitMiddlewareto depend on it without changingRateLimitOptions, theX-RateLimit-*/Retry-Afterheaders, or the{ data, error, retryAfter }429 envelope.cleanup()sweep) and a Redis store gated by an env var (e.g.RATE_LIMIT_REDIS_URL); when unset, behavior is unchanged.INCR+PEXPIRE) so concurrent requests across replicas share one window; reuse the key generatorscreateIpKeyGenerator/createApiKeyKeyGeneratorunchanged.src/config/env.tsso misconfiguration fails fast, consistent with the existing Zod schema.Acceptance criteria
RateLimitStoreinterface exists with in-memory and Redis implementations.RATE_LIMIT_REDIS_URL(or equivalent) is validated at startup via the env schema.docs/SECURITY.md§5 (rate-limit tuning) documents the Redis option and the degradation behavior.Suggested execution
1. Fork the repo and create a branch —
git checkout -b feature/redis-rate-limit-store.2. Implement changes — refactor
src/middleware/rateLimit.ts, add stores undersrc/middleware/orsrc/config/, and extendsrc/config/env.ts.3. Write/extend tests — Vitest + Supertest; extend
tests/middleware/rateLimit.test.tsandtests/config/rateLimit.test.ts, mocking the Redis client for the shared-counter and outage cases.4. Test and commit —
npm run lint npm run typecheck npm test npm run test:coverage npm run buildExample commit message
Guidelines
CI enforces a 95% coverage threshold (
vitest.config.ts, vianpm run test:coverage) — cover the store interface, both backends, and the outage path. Updatedocs/SECURITY.mdand.env.example. Timeframe: 96 hours.