flow-nexus
Version:
🚀 AI-Powered Swarm Intelligence Platform - Gamified MCP Development with 70+ Tools
285 lines (249 loc) • 8 kB
JavaScript
/**
* Rate Limiter Middleware for Payment Operations
* Prevents abuse and ensures API stability
*/
import crypto from 'crypto';
// Rate limit configurations per operation
const RATE_LIMITS = {
check_balance: {
requests: 30, // 30 requests
window: 60000, // per minute
blockDuration: 300000, // 5 minute block if exceeded
},
create_payment_link: {
requests: 5, // 5 payment links
window: 3600000, // per hour
blockDuration: 3600000, // 1 hour block
},
configure_auto_refill: {
requests: 10, // 10 config changes
window: 3600000, // per hour
blockDuration: 1800000, // 30 minute block
},
get_payment_history: {
requests: 20, // 20 requests
window: 60000, // per minute
blockDuration: 300000, // 5 minute block
},
create_subscription: {
requests: 3, // 3 subscription attempts
window: 86400000, // per day
blockDuration: 3600000, // 1 hour block
},
reduce_credits: {
requests: 10, // 10 admin operations
window: 3600000, // per hour
blockDuration: 1800000, // 30 minute block
},
global: {
requests: 100, // 100 total payment operations
window: 60000, // per minute
blockDuration: 600000, // 10 minute block
},
};
// In-memory store (use Redis in production)
class RateLimitStore {
constructor() {
this.requests = new Map();
this.blocks = new Map();
// Cleanup old entries every minute
setInterval(() => this.cleanup(), 60000);
}
/**
* Create a unique key for rate limiting
*/
createKey(userId, operation, ip) {
const data = `${userId || 'anonymous'}:${operation}:${ip || 'unknown'}`;
return crypto.createHash('sha256').update(data).digest('hex');
}
/**
* Check if user is blocked
*/
isBlocked(key) {
const blockExpiry = this.blocks.get(key);
if (!blockExpiry) return false;
if (Date.now() > blockExpiry) {
this.blocks.delete(key);
return false;
}
return true;
}
/**
* Block a user for specified duration
*/
block(key, duration) {
this.blocks.set(key, Date.now() + duration);
}
/**
* Record a request
*/
recordRequest(key, window) {
const now = Date.now();
const requests = this.requests.get(key) || [];
// Filter out old requests outside the window
const recentRequests = requests.filter(timestamp =>
now - timestamp < window
);
// Add current request
recentRequests.push(now);
this.requests.set(key, recentRequests);
return recentRequests.length;
}
/**
* Get request count for a key
*/
getRequestCount(key, window) {
const now = Date.now();
const requests = this.requests.get(key) || [];
return requests.filter(timestamp =>
now - timestamp < window
).length;
}
/**
* Clean up old entries
*/
cleanup() {
const now = Date.now();
// Clean expired blocks
for (const [key, expiry] of this.blocks.entries()) {
if (now > expiry) {
this.blocks.delete(key);
}
}
// Clean old requests (older than 24 hours)
const dayAgo = now - 86400000;
for (const [key, requests] of this.requests.entries()) {
const recentRequests = requests.filter(timestamp => timestamp > dayAgo);
if (recentRequests.length === 0) {
this.requests.delete(key);
} else {
this.requests.set(key, recentRequests);
}
}
}
/**
* Get statistics for monitoring
*/
getStats() {
return {
totalKeys: this.requests.size,
blockedUsers: this.blocks.size,
memoryUsage: process.memoryUsage().heapUsed,
};
}
}
// Singleton instance
const store = new RateLimitStore();
/**
* Rate limiter middleware
*/
export function createRateLimiter() {
return {
/**
* Check rate limit for an operation
* @param {string} operation - Operation name
* @param {Object} context - Request context with user info
* @param {string} ip - Client IP address
* @returns {Object} Rate limit result
*/
checkLimit: function(operation, context, ip) {
// Get rate limit config
const config = RATE_LIMITS[operation] || RATE_LIMITS.global;
// Create keys for user and global limits
const userId = context?.user?.id;
const userKey = store.createKey(userId, operation, null);
const ipKey = store.createKey(null, operation, ip);
const globalKey = store.createKey(userId, 'global', ip);
// Check if blocked
if (store.isBlocked(userKey) || store.isBlocked(ipKey) || store.isBlocked(globalKey)) {
return {
allowed: false,
reason: 'Rate limit exceeded - temporarily blocked',
retryAfter: Math.ceil(config.blockDuration / 1000),
blocked: true,
};
}
// Check request counts
const userCount = store.getRequestCount(userKey, config.window);
const ipCount = store.getRequestCount(ipKey, config.window);
const globalCount = store.getRequestCount(globalKey, RATE_LIMITS.global.window);
// Check limits
if (userCount >= config.requests) {
store.block(userKey, config.blockDuration);
return {
allowed: false,
reason: `Too many ${operation} requests - limit is ${config.requests} per ${config.window / 1000} seconds`,
retryAfter: Math.ceil(config.blockDuration / 1000),
limit: config.requests,
remaining: 0,
resetAt: new Date(Date.now() + config.window).toISOString(),
};
}
if (ipCount >= config.requests * 2) { // IP limit is 2x user limit
store.block(ipKey, config.blockDuration);
return {
allowed: false,
reason: 'Too many requests from this IP address',
retryAfter: Math.ceil(config.blockDuration / 1000),
blocked: true,
};
}
if (globalCount >= RATE_LIMITS.global.requests) {
store.block(globalKey, RATE_LIMITS.global.blockDuration);
return {
allowed: false,
reason: 'Too many total requests - please slow down',
retryAfter: Math.ceil(RATE_LIMITS.global.blockDuration / 1000),
blocked: true,
};
}
// Record the request
store.recordRequest(userKey, config.window);
store.recordRequest(ipKey, config.window);
store.recordRequest(globalKey, RATE_LIMITS.global.window);
// Calculate remaining
const remaining = config.requests - userCount - 1;
const resetAt = new Date(Date.now() + config.window);
return {
allowed: true,
limit: config.requests,
remaining: remaining,
resetAt: resetAt.toISOString(),
headers: {
'X-RateLimit-Limit': config.requests.toString(),
'X-RateLimit-Remaining': remaining.toString(),
'X-RateLimit-Reset': resetAt.toISOString(),
},
};
},
/**
* Reset rate limits for a user (admin only)
*/
reset: function(userId, operation) {
const userKey = store.createKey(userId, operation || 'global', null);
store.requests.delete(userKey);
store.blocks.delete(userKey);
return { success: true, message: 'Rate limits reset' };
},
/**
* Get rate limit stats
*/
getStats: function() {
return store.getStats();
},
/**
* Configure rate limits (admin only)
*/
configure: function(operation, config) {
if (RATE_LIMITS[operation]) {
Object.assign(RATE_LIMITS[operation], config);
return { success: true, message: `Rate limits updated for ${operation}` };
}
return { success: false, error: 'Unknown operation' };
},
};
}
// Export singleton instance
export const rateLimiter = createRateLimiter();
// Export for testing
export { RATE_LIMITS, RateLimitStore };