"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildApiKeyAuth = buildApiKeyAuth; exports.resolveApiKey = resolveApiKey; // ─── Cache de API key (evita Prisma query em cada requisição) ──────────────── // Chaves são imutáveis durante a vida da sessão — TTL de 5 min é suficiente. const KEY_CACHE_TTL = 5 * 60000; const keyCache = new Map(); setInterval(() => { const now = Date.now(); for (const [k, v] of keyCache.entries()) { if (now - v.cachedAt > KEY_CACHE_TTL) keyCache.delete(k); } }, KEY_CACHE_TTL); // ─── Rate limiter simples em memória (por chave) ───────────────────────────── const RATE_WINDOW_MS = 60000; const RATE_MAX = 120; const rateMap = new Map(); function checkRateLimit(key) { const now = Date.now(); let entry = rateMap.get(key); if (!entry || now > entry.resetAt) { entry = { count: 1, resetAt: now + RATE_WINDOW_MS }; rateMap.set(key, entry); return true; } entry.count++; return entry.count <= RATE_MAX; } // Limpeza periódica para não vazar memória setInterval(() => { const now = Date.now(); for (const [k, v] of rateMap.entries()) { if (now > v.resetAt) rateMap.delete(k); } }, RATE_WINDOW_MS * 2); // ─── Factory ───────────────────────────────────────────────────────────────── function buildApiKeyAuth(prisma) { return async function apiKeyAuth(req, res, next) { const key = req.headers['x-nw-key']?.trim(); if (!key) { res.status(401).json({ error: 'Header x-nw-key ausente' }); return; } if (!checkRateLimit(key)) { res.status(429).json({ error: 'Rate limit excedido (120 req/min por chave)' }); return; } // Verifica cache antes de ir ao banco const cached = keyCache.get(key); if (cached && Date.now() - cached.cachedAt < KEY_CACHE_TTL) { if (cached.expiresAt && cached.expiresAt < new Date()) { res.status(401).json({ error: 'Chave expirada' }); return; } req.extTenantId = cached.tenantId; next(); return; } const apiKey = await prisma.apiKey.findUnique({ where: { key }, select: { tenantId: true, isActive: true, expiresAt: true }, }); if (!apiKey || !apiKey.isActive) { res.status(401).json({ error: 'Chave inválida ou inativa' }); return; } if (apiKey.expiresAt && apiKey.expiresAt < new Date()) { res.status(401).json({ error: 'Chave expirada' }); return; } keyCache.set(key, { tenantId: apiKey.tenantId, expiresAt: apiKey.expiresAt, cachedAt: Date.now() }); req.extTenantId = apiKey.tenantId; next(); }; } /** * Versão standalone que valida a chave sem passar pelo Express. * Usada no handshake do WS (upgrade request). */ async function resolveApiKey(prisma, key) { if (!key) return null; const cached = keyCache.get(key); if (cached && Date.now() - cached.cachedAt < KEY_CACHE_TTL) { if (cached.expiresAt && cached.expiresAt < new Date()) return null; return cached.tenantId; } const apiKey = await prisma.apiKey.findUnique({ where: { key }, select: { tenantId: true, isActive: true, expiresAt: true }, }); if (!apiKey || !apiKey.isActive) return null; if (apiKey.expiresAt && apiKey.expiresAt < new Date()) return null; keyCache.set(key, { tenantId: apiKey.tenantId, expiresAt: apiKey.expiresAt, cachedAt: Date.now() }); return apiKey.tenantId; } //# sourceMappingURL=apikey-auth.js.map