62 lines
1.7 KiB
JavaScript
62 lines
1.7 KiB
JavaScript
|
|
/**
|
||
|
|
* 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}
|
||
|
|
}
|
||
|
|
}
|