Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | /** * Middleware Rate Limiting API — Limite les requetes par plan utilisateur. * * Utilise les limites du plan depuis la DB (via getPlanLimits) : * - api_access : boolean — bloque si false * - api_requests_per_hour : nombre — rate limit horaire * * Tracking en memoire des requetes par utilisateur pour affichage * du compteur dans l'UI (GET /api/v1/me/rate-limit). * * Retourne les headers standard X-RateLimit-* et 429 si depassement. * * @module middleware/apiRateLimit */ const rateLimit = require('express-rate-limit'); const { getPlanLimits } = require('../config/plans'); /** * Fallback hardcode (utilise si la DB est inaccessible). */ const PLAN_LIMITS = { free: 0, pro: 1000, business: 5000, enterprise: 20000, }; // Stores par plan — un rate limiter par niveau de limite const stores = {}; // ─── Tracking des requetes par utilisateur ────────────────────────────────── // Map<userId, { count, limit, windowStart }> const usageTracker = new Map(); const WINDOW_MS = 60 * 60 * 1000; // 1 heure /** * Incremente le compteur pour un utilisateur. */ function trackRequest(userId, limit) { const now = Date.now(); const entry = usageTracker.get(userId); if (!entry || now - entry.windowStart >= WINDOW_MS) { // Nouvelle fenetre usageTracker.set(userId, { count: 1, limit, windowStart: now }); } else { entry.count++; entry.limit = limit; } } /** * Retourne l'usage actuel d'un utilisateur. * @param {string} userId * @returns {{ used: number, limit: number, remaining: number, reset_at: string } | null} */ function getUsage(userId) { const entry = usageTracker.get(userId); if (!entry) return null; const now = Date.now(); if (now - entry.windowStart >= WINDOW_MS) { // Fenetre expiree return { used: 0, limit: entry.limit, remaining: entry.limit, reset_at: new Date(now + WINDOW_MS).toISOString() }; } const remaining = Math.max(0, entry.limit - entry.count); const resetAt = new Date(entry.windowStart + WINDOW_MS).toISOString(); return { used: entry.count, limit: entry.limit, remaining, reset_at: resetAt }; } /** * Cree ou retourne le rate limiter pour une limite donnee. * @param {number} maxPerHour * @returns {Function} */ function getLimiter(maxPerHour) { if (!stores[maxPerHour]) { stores[maxPerHour] = rateLimit({ windowMs: WINDOW_MS, max: maxPerHour, standardHeaders: true, legacyHeaders: false, keyGenerator: (req) => req.user?.id || req.ip, message: { error: { code: 'RATE_LIMIT_EXCEEDED', message: `Limite de ${maxPerHour} requetes/heure atteinte.`, details: { limit: maxPerHour }, }, }, }); } return stores[maxPerHour]; } /** * Middleware API rate limiting par plan. * Verifie api_access, puis applique api_requests_per_hour. */ async function apiRateLimiter(req, res, next) { const plan = req.user?.plan || 'free'; let apiAccess = false; let limit = 0; try { const limits = await getPlanLimits(plan); apiAccess = limits.api_access ?? false; limit = limits.api_requests_per_hour ?? 0; } catch { // Fallback hardcode si DB inaccessible apiAccess = plan !== 'free'; limit = PLAN_LIMITS[plan] ?? 0; } // Pas d'acces API pour ce plan if (!apiAccess || limit === 0) { return res.status(403).json({ error: { code: 'API_ACCESS_DENIED', message: 'L\'acces API necessite un plan Pro ou superieur.', }, }); } // Tracker la requete trackRequest(req.user.id, limit); // Appliquer le rate limiter du plan const limiter = getLimiter(limit); limiter(req, res, next); } module.exports = { apiRateLimiter, getUsage, PLAN_LIMITS }; |