249 lines
6.8 KiB
Markdown
249 lines
6.8 KiB
Markdown
# 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|