Smart Library Docs

Rate Limiting & Redis

Distributed, role-aware rate limiting using rate-limiter-flexible with ioredis, including per-endpoint strategies and Cloudflare-aware IP extraction.

Rate Limiting & Redis

The SLMS uses a distributed, enterprise-grade rate limiting system powered by rate-limiter-flexible with ioredis as the backing store. Each endpoint category has its own tuned algorithm and threshold.

Architecture

Incoming Request


┌─────────────────────┐
│  getClientIp(req)   │  ← CF-Connecting-IP → X-Forwarded-For → req.ip
└─────────────────────┘


┌─────────────────────┐
│  Rate Limiter       │  ← Redis (production) / Memory (development fallback)
│  (rate-limiter-     │
│   flexible)         │
└─────────────────────┘

      ├── Allow → Set RateLimit-* headers → next()
      └── Reject → 429 + Retry-After header

Files

FilePurpose
middleware/rateLimiter.jsAll rate limiter definitions and middleware exports
utils/redisClient.jsRedis connection management with environment-aware fallback
utils/getClientIp.jsCloudflare-aware IP extraction helper

Redis Connection Strategy

// utils/redisClient.js
if (process.env.REDIS_URL) {
  // Connect to Redis
} else if (process.env.NODE_ENV === 'production') {
  // FAIL — Redis is mandatory in production
  throw new Error('REDIS_URL is required in production');
} else {
  // Development fallback: in-memory rate limiting
  redisClient = null;
}
EnvironmentBehavior
ProductionRedis is mandatory. Application crashes on startup if REDIS_URL is missing.
DevelopmentRedis is optional. Falls back to in-memory RateLimiterMemory.

Client: ioredis (chosen over redis for better reconnection, cluster support, and Sentinel compatibility).


Rate Limiter Definitions

All limiters use Redis Lua scripts for atomic operations. Keys are prefixed with rl: (e.g., rl:login:email:user@example.com).

Authentication Endpoints

LimiterAlgorithmLimitDurationKeyNotes
loginEmailLimiterSliding Window + Progressive Delay5 req15 minEmailDelays escalate: 1s → 2s → 4s → 8s → reject at 10th
loginIpLimiterFixed Window20 req15 minIPCatches credential stuffing from single IPs
registerLimiterFixed Window3 req1 hourIPPrevents mass account creation
forgotPasswordLimiterFixed Window3 req1 hourEmailPrevents email enumeration
refreshLimiterToken Bucket30 req1 minTokenRate-limits token refresh abuse

API Endpoints (Role-Aware)

The globalApiLimiter applies different thresholds based on the authenticated user's role:

RoleLimitDuration
Anonymous60 req1 min
Student100 req1 min
Faculty150 req1 min
Librarian200 req1 min
Admin300 req1 min

Specialized Endpoints

LimiterLimitDurationKey
searchLimiter (simple)100 req1 minUser ID / IP
searchLimiter (advanced)40 req1 minUser ID / IP
uploadLimiter10 req1 minUser ID / IP
bulkOpsLimiter5 req1 hourUser ID / IP

Response Headers

Every rate-limited response includes standard RateLimit-* headers:

RateLimit-Limit: 100
RateLimit-Remaining: 87
RateLimit-Reset: 1719946500

On rejection, a Retry-After header (in seconds) is also set.


IP Extraction (Cloudflare-Aware)

// utils/getClientIp.js
const getClientIp = (req) => {
  return req.headers['cf-connecting-ip']      // Cloudflare
      || req.headers['x-forwarded-for']?.split(',')[0].trim()
      || req.ip
      || req.connection?.remoteAddress
      || 'unknown';
};

Priority order:

  1. CF-Connecting-IP (Cloudflare's true client IP header)
  2. X-Forwarded-For (first entry from proxy chain)
  3. req.ip (Express, respects trust proxy setting)
  4. req.connection.remoteAddress (direct connection fallback)

Progressive Delay (Login)

The login email limiter implements progressive delay rather than immediate rejection:

Attempt 1-5:  Allowed instantly
Attempt 6:   1 second delay before responding
Attempt 7:   2 second delay
Attempt 8:   4 second delay
Attempt 9:   8 second delay
Attempt 10+: Rejected with 429 + Retry-After

This slows down automated attacks without immediately alerting attackers that rate limiting is active.

On this page