/** * @fileoverview Simple in-memory per-tenant rate limiter. * @module middleware/rateLimit */ import { AppError, asyncHandler } from '../errors.js'; import { getTenant, resolveLimits } from '../store/tenants.js'; import { createDebugger } from '../debug/logger.js'; const log = createDebugger('billing'); /** @type {Map} */ const windows = new Map(); /** * Reset rate limit windows (tests). * @returns {void} */ export function resetRateLimitWindows() { windows.clear(); } /** * Enforce plan requestsPerMinute using a 60s sliding fixed window. */ export const rateLimit = asyncHandler(async (req, res, next) => { const tenantId = req.auth?.tenantId; if (!tenantId) return next(); const tenant = getTenant(tenantId); const limits = tenant ? resolveLimits(tenant) : { requestsPerMinute: 30 }; const rpm = limits.requestsPerMinute ?? 30; const now = Date.now(); const windowMs = 60_000; let entry = windows.get(tenantId); if (!entry || now - entry.windowStart >= windowMs) { entry = { windowStart: now, count: 0 }; windows.set(tenantId, entry); } entry.count += 1; if (entry.count > rpm) { log.debug('rate limited', { tenantId, count: entry.count, rpm }); throw new AppError('Rate limit exceeded', { status: 429, code: 'RATE_LIMITED', details: { requestsPerMinute: rpm }, }); } next(); });