verae-middleware/src/middleware/rateLimit.js

56 lines
1.4 KiB
JavaScript

/**
* @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<string, { windowStart: number, count: number }>} */
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();
});