# Rate Limiting & OTP Throttling Implementation ## Overview This document explains the secure rate limiting and OTP throttling implementation added to the authentication service. ## Architecture ### Components Added 1. **Redis Client** (`src/services/redisClient.js`) - Manages Redis connection with graceful fallback to in-memory storage - Supports `REDIS_URL` or `REDIS_HOST`/`REDIS_PORT` configuration 2. **Rate Limiting Middleware** (`src/middleware/rateLimitMiddleware.js`) - Per-phone rate limiting for OTP requests - Per-IP rate limiting for OTP requests - Active OTP checking (2-minute no-resend rule) - Failed verification attempt tracking 3. **Updated OTP Service** (`src/services/otpService.js`) - Changed OTP expiry from 10 minutes to 2 minutes (120 seconds) - Enhanced attempt tracking with generic error responses 4. **Updated Auth Routes** (`src/routes/authRoutes.js`) - Integrated all rate limiting middleware - Updated error responses to be generic ## Rate Limiting Rules ### POST /auth/request-otp **Per Phone Number:** - Max 3 requests per 10 minutes - Max 10 requests per 24 hours - **No resend if active OTP exists** (within 2 minutes of generation) **Per IP Address:** - Max 20 requests per 10 minutes - Max 100 requests per 24 hours **Response on Limit Exceeded:** ```json { "success": false, "message": "Too many OTP requests. Please try again later." } ``` HTTP Status: `429 Too Many Requests` **Response if Active OTP Exists:** ```json { "success": false, "message": "An OTP is already active. Please wait a moment before requesting a new one." } ``` HTTP Status: `429 Too Many Requests` ### POST /auth/verify-otp **Per OTP:** - Max 5 verification attempts per OTP - After 5 failed attempts, OTP is invalidated **Per Phone Number:** - Max 10 failed verifications per hour - If exceeded, all verification attempts are blocked temporarily **Response on Failure:** ```json { "success": false, "message": "OTP invalid or expired. Please request a new one." } ``` HTTP Status: `400 Bad Request` **Response on Too Many Failed Attempts:** ```json { "success": false, "message": "Too many attempts. Please try again later." } ``` HTTP Status: `429 Too Many Requests` ## OTP Validity - **OTP expires after 2 minutes (120 seconds)** from generation - **No new OTP can be sent** to the same phone number while an active OTP exists - Once verified successfully, OTP is immediately deleted ## Redis Setup ### Configuration The system uses Redis for rate limiting counters with automatic TTL expiration. If Redis is not available, it falls back to in-memory storage. **Environment Variables:** ```bash # Option 1: Full Redis URL REDIS_URL=redis://localhost:6379 # or with password REDIS_URL=redis://:password@localhost:6379 # Option 2: Separate host/port REDIS_HOST=localhost REDIS_PORT=6379 REDIS_PASSWORD=your_password # Optional ``` ### Redis Keys Used - `otp_req:phone:{phone}:10min` - Phone requests (10 min window) - `otp_req:phone:{phone}:day` - Phone requests (24 hour window) - `otp_req:ip:{ip}:10min` - IP requests (10 min window) - `otp_req:ip:{ip}:day` - IP requests (24 hour window) - `otp_active:phone:{phone}` - Active OTP marker (2 min TTL) - `otp_verify_failed:phone:{phone}:hour` - Failed verifications (1 hour window) All keys automatically expire based on their TTL. ## Configuration All limits are configurable via environment variables: ```bash # OTP Request Limits OTP_REQ_PHONE_10MIN_LIMIT=3 # Default: 3 OTP_REQ_PHONE_DAY_LIMIT=10 # Default: 10 OTP_REQ_IP_10MIN_LIMIT=20 # Default: 20 OTP_REQ_IP_DAY_LIMIT=100 # Default: 100 # OTP Verification Limits OTP_VERIFY_MAX_ATTEMPTS=5 # Default: 5 OTP_VERIFY_FAILED_PER_HOUR_LIMIT=10 # Default: 10 # OTP Validity OTP_TTL_SECONDS=120 # Default: 120 (2 minutes) # Proxy Support (for correct IP detection) TRUST_PROXY=true # Set if behind reverse proxy ``` ## Implementation Details ### Redis Client Initialization The Redis client is initialized in `src/index.js`: ```javascript const { initRedis } = require('./services/redisClient'); initRedis().catch((err) => { console.warn('Redis initialization warning:', err.message); }); ``` If Redis is unavailable, the system logs a warning and continues with in-memory fallback. ### Rate Limiting Logic 1. **Request Flow for `/auth/request-otp`:** ``` Request → checkActiveOtpForPhone → rateLimitRequestOtpByPhone → rateLimitRequestOtpByIp → createOtp ``` 2. **Request Flow for `/auth/verify-otp`:** ``` Request → rateLimitVerifyOtpByPhone → verifyOtp → (on failure) incrementFailedVerify ``` ### Memory Store Fallback When Redis is not available, the system uses an in-memory store that: - Maintains counters with expiration timestamps - Automatically cleans up expired entries every minute - Works identically to Redis for rate limiting purposes - **Note:** In-memory store is per-process and doesn't persist across restarts ### Error Handling All rate limiting middleware uses a "fail open" strategy: - If Redis errors occur, the request is allowed to proceed - Errors are logged but don't block legitimate users - This ensures availability even if rate limiting infrastructure fails ## Security Features 1. **Generic Error Messages:** All error responses use generic messages to avoid information leakage 2. **No Information Leakage:** Errors don't distinguish between "wrong OTP", "expired OTP", or "max attempts" 3. **Automatic Cleanup:** Expired OTPs and rate limit counters are automatically cleaned up 4. **IP-based Protection:** Prevents abuse from single IP addresses 5. **Phone-based Protection:** Prevents abuse targeting specific phone numbers ## Testing To test the rate limiting: 1. **Test Active OTP Rule:** ```bash # Request OTP curl -X POST http://localhost:3000/auth/request-otp \ -H "Content-Type: application/json" \ -d '{"phone_number": "+1234567890"}' # Immediately try again (should be blocked) curl -X POST http://localhost:3000/auth/request-otp \ -H "Content-Type: application/json" \ -d '{"phone_number": "+1234567890"}' ``` 2. **Test Rate Limits:** ```bash # Send multiple requests quickly (should hit limit after 3) for i in {1..5}; do curl -X POST http://localhost:3000/auth/request-otp \ -H "Content-Type: application/json" \ -d '{"phone_number": "+1234567890"}' sleep 1 done ``` ## Dependencies Added to `package.json`: - `redis`: ^4.7.0 Install with: ```bash npm install ``` ## Notes - The system gracefully degrades if Redis is unavailable - All rate limits are enforced independently (phone and IP limits both apply) - OTP expiry time changed from 10 minutes to 2 minutes - SMS message updated to reflect 2-minute expiry - Existing JWT and user creation logic remains unchanged