103 lines
3.9 KiB
JavaScript
103 lines
3.9 KiB
JavaScript
"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
|