67 lines
1.9 KiB
JavaScript
67 lines
1.9 KiB
JavaScript
const rateLimit = require('express-rate-limit');
|
|
const redisClient = require('../config/redis');
|
|
|
|
// In-memory fallback store if Redis is unavailable
|
|
const memoryStore = new Map();
|
|
|
|
async function getRateLimitStore() {
|
|
// Custom Redis store
|
|
return {
|
|
async increment(key) {
|
|
try {
|
|
const current = await redisClient.get(key);
|
|
const count = current ? parseInt(current) + 1 : 1;
|
|
await redisClient.setEx(key, 3600, count.toString()); // 1 hour TTL
|
|
return { totalHits: count, resetTime: null };
|
|
} catch (error) {
|
|
// Fallback to memory store
|
|
const count = (memoryStore.get(key) || 0) + 1;
|
|
memoryStore.set(key, count);
|
|
setTimeout(() => memoryStore.delete(key), 3600000); // 1 hour
|
|
return { totalHits: count, resetTime: null };
|
|
}
|
|
},
|
|
async decrement(key) {
|
|
try {
|
|
await redisClient.decr(key);
|
|
} catch (error) {
|
|
const count = memoryStore.get(key) || 0;
|
|
memoryStore.set(key, Math.max(0, count - 1));
|
|
}
|
|
},
|
|
async resetKey(key) {
|
|
try {
|
|
await redisClient.del(key);
|
|
} catch (error) {
|
|
memoryStore.delete(key);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
const createRateLimiter = (maxRequests = 1000, windowMs = 3600000) => {
|
|
return rateLimit({
|
|
windowMs, // 1 hour default
|
|
max: maxRequests,
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
keyGenerator: (req) => {
|
|
// Use API key if available, otherwise IP
|
|
return req.apiKeyInfo?.id || req.ip;
|
|
},
|
|
handler: (req, res) => {
|
|
res.status(429).json({
|
|
error: 'Too many requests',
|
|
message: 'Rate limit exceeded. Please try again later.',
|
|
retryAfter: Math.ceil(windowMs / 1000)
|
|
});
|
|
},
|
|
skip: (req) => {
|
|
// Skip rate limiting for health checks
|
|
return req.path === '/health';
|
|
}
|
|
});
|
|
};
|
|
|
|
module.exports = createRateLimiter;
|