/** * Simple in-memory rate limiter for upload attempts * Tracks by IP address */ interface RateLimitEntry { count: number; resetAt: number; // Unix timestamp } // In-memory store (use Redis for production multi-instance setups) const rateLimitStore = new Map(); /** * Check if IP address is rate limited * @returns { allowed: boolean, remaining: number } */ export function checkUploadRateLimit(ipAddress: string): { allowed: boolean; remaining: number; resetIn: number } { const maxUploads = parseInt(process.env.UPLOAD_RATE_LIMIT_PER_IP || '5', 10); const windowMs = parseInt(process.env.UPLOAD_RATE_LIMIT_WINDOW_MS || '3600000', 10); // 1 hour const now = Date.now(); const key = `upload:${ipAddress}`; let entry = rateLimitStore.get(key); // Clean up expired entry or create new one if (!entry || now > entry.resetAt) { entry = { count: 0, resetAt: now + windowMs }; rateLimitStore.set(key, entry); } // Check if limit exceeded if (entry.count >= maxUploads) { return { allowed: false, remaining: 0, resetIn: Math.ceil((entry.resetAt - now) / 1000) // seconds }; } // Increment counter entry.count++; rateLimitStore.set(key, entry); return { allowed: true, remaining: maxUploads - entry.count, resetIn: Math.ceil((entry.resetAt - now) / 1000) }; } /** * Periodic cleanup of expired entries (prevent memory leak) * Call this occasionally (e.g., every hour) */ export function cleanupRateLimitStore() { const now = Date.now(); for (const [key, entry] of rateLimitStore.entries()) { if (now > entry.resetAt) { rateLimitStore.delete(key); } } } // Auto-cleanup every 10 minutes setInterval(cleanupRateLimitStore, 10 * 60 * 1000);