/** * Fabrique de rate-limiter en mémoire. * * Adapté à un déploiement single-process (PM2 sans cluster). * Pour un déploiement multi-instances, remplacer le Map par un store * Redis partagé (ex. @upstash/ratelimit). * * @param {object} options * @param {number} options.windowMs - Durée de la fenêtre en millisecondes * @param {number} options.max - Nombre maximum de requêtes par fenêtre */ export function createRateLimiter({windowMs, max}) { const store = new Map() let lastCleanup = Date.now() /** * Supprime les entrées expirées du store. * Appelé automatiquement une fois par fenêtre temporelle. */ function cleanup(now) { for (const [storeKey, entry] of store) { if (now - entry.start >= windowMs) { store.delete(storeKey) } } lastCleanup = now } /** * Vérifie si la clé (IP:route) dépasse la limite autorisée. * * @param {string} key - Identifiant unique (ex. "1.2.3.4:/api/auth/register") * @returns {{ success: boolean, retryAfter?: number }} */ return key => { const now = Date.now() if (now - lastCleanup >= windowMs) { cleanup(now) } const entry = store.get(key) // Première requête ou fenêtre expirée : on repart à zéro if (!entry || now - entry.start >= windowMs) { store.set(key, {count: 1, start: now}) return {success: true} } // Limite atteinte if (entry.count >= max) { const retryAfter = Math.ceil((entry.start + windowMs - now) / 1000) return {success: false, retryAfter} } // Incrément normal store.set(key, {...entry, count: entry.count + 1}) return {success: true} } }