const rateLimit = require('express-rate-limit'); // In-memory store for per-recipient email tracking const emailTracker = new Map(); // Helper function to clean up expired entries function cleanupExpiredEntries() { const now = Date.now(); for (const [key, timestamp] of emailTracker.entries()) { if (now - timestamp > 5 * 60 * 1000) { // 5 minutes emailTracker.delete(key); } } } // Clean up expired entries every minute setInterval(cleanupExpiredEntries, 60 * 1000); // General API rate limiter const general = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // limit each IP to 100 requests per windowMs message: { error: 'Too many requests from this IP, please try again later.', retryAfter: 15 * 60 // 15 minutes in seconds }, standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers // Use a custom key generator that's safer with trust proxy keyGenerator: (req) => { // Fallback to connection remote address if req.ip is not available return req.ip || req.connection?.remoteAddress || 'unknown'; }, }); // Email sending rate limiter (general - keeps existing behavior) const email = rateLimit({ windowMs: 60 * 60 * 1000, // 1 hour max: 10, // limit each IP to 10 emails per hour message: { error: 'Too many emails sent from this IP, please try again later.', retryAfter: 60 * 60 // 1 hour in seconds }, standardHeaders: true, legacyHeaders: false, skipSuccessfulRequests: false, // Don't skip counting successful requests // Use a custom key generator that's safer with trust proxy keyGenerator: (req) => { // Fallback to connection remote address if req.ip is not available return req.ip || req.connection?.remoteAddress || 'unknown'; }, }); // Custom middleware for per-recipient email rate limiting const perRecipientEmailLimiter = (req, res, next) => { const clientIp = req.ip || req.connection?.remoteAddress || 'unknown'; const recipientEmail = req.body.recipientEmail; if (!recipientEmail) { return next(); // Let validation middleware handle missing recipient } const trackingKey = `${clientIp}:${recipientEmail}`; const now = Date.now(); const lastSent = emailTracker.get(trackingKey); if (lastSent && (now - lastSent) < 5 * 60 * 1000) { // 5 minutes const timeRemaining = Math.ceil((5 * 60 * 1000 - (now - lastSent)) / 1000); return res.status(429).json({ success: false, error: 'Rate limit exceeded', message: `You can only send one email per representative every 5 minutes. Please wait ${Math.ceil(timeRemaining / 60)} more minutes before sending another email to this representative.`, retryAfter: timeRemaining, rateLimitType: 'per-recipient' }); } // Store the current timestamp for this IP-recipient combination emailTracker.set(trackingKey, now); next(); }; // Represent API rate limiter (more restrictive) const representAPI = rateLimit({ windowMs: 60 * 1000, // 1 minute max: 60, // match the Represent API limit of 60 requests per minute message: { error: 'Represent API rate limit exceeded, please try again later.', retryAfter: 60 // 1 minute in seconds }, standardHeaders: true, legacyHeaders: false, // Use a custom key generator that's safer with trust proxy keyGenerator: (req) => { // Fallback to connection remote address if req.ip is not available return req.ip || req.connection?.remoteAddress || 'unknown'; }, }); module.exports = { general, email, perRecipientEmailLimiter, representAPI };