From dbb4da8220a6988476419a7d6753c5712d146bbf Mon Sep 17 00:00:00 2001 From: VPS 4 Deploy Agent Date: Wed, 1 Jul 2026 20:55:10 +0200 Subject: [PATCH] feat(ext-api): chave mestra + x-nw-email (middleware REST + WS bridge) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Satélite confiável acessa a conta de qualquer email via EXT_MASTER_KEY: - apikey-auth: helpers isMasterKey/resolveMasterEmail resolvem o tenant pelo User.email; middleware REST refatorado para usá-los. - ws-bridge: handshake do /api/ext/v1/stream trata a chave mestra + x-nw-email (antes só tabela apiKey → o túnel WS tomava 401). Co-Authored-By: Claude Opus 4.8 --- .../plugins/ext-api/backend/apikey-auth.js | 49 +++++++++++++++++++ .../ext-api/backend/apikey-auth.js.map | 2 +- .../plugins/ext-api/backend/apikey-auth.ts | 43 ++++++++++++++++ .../plugins/ext-api/backend/ws-bridge.js | 17 ++++++- .../plugins/ext-api/backend/ws-bridge.js.map | 2 +- .../plugins/ext-api/backend/ws-bridge.ts | 18 +++++-- .../newwhats.local/plugins/ext-api/index.js | 4 +- .../plugins/ext-api/index.js.map | 2 +- 8 files changed, 127 insertions(+), 10 deletions(-) diff --git a/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/apikey-auth.js b/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/apikey-auth.js index 7fe01a0..3a99544 100644 --- a/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/apikey-auth.js +++ b/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/apikey-auth.js @@ -1,6 +1,8 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildApiKeyAuth = buildApiKeyAuth; +exports.isMasterKey = isMasterKey; +exports.resolveMasterEmail = resolveMasterEmail; 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. @@ -48,6 +50,25 @@ function buildApiKeyAuth(prisma) { res.status(429).json({ error: 'Rate limit excedido (120 req/min por chave)' }); return; } + // ── Chave MESTRA + x-nw-email: integração multi-conta por email ──────────── + // Permite que um satélite confiável acesse a conta de QUALQUER email (resolve + // o tenant pelo User.email). O satélite deve enviar SEMPRE o email do usuário + // autenticado dele — nunca um email arbitrário do cliente. + if (isMasterKey(key)) { + const email = req.headers['x-nw-email']?.trim().toLowerCase(); + if (!email) { + res.status(400).json({ error: 'x-nw-email ausente (chave mestra)' }); + return; + } + const tenantId = await resolveMasterEmail(prisma, email); + if (!tenantId) { + res.status(404).json({ error: `Conta não encontrada no motor para ${email}` }); + return; + } + req.extTenantId = tenantId; + next(); + return; + } // Verifica cache antes de ir ao banco const cached = keyCache.get(key); if (cached && Date.now() - cached.cachedAt < KEY_CACHE_TTL) { @@ -76,6 +97,34 @@ function buildApiKeyAuth(prisma) { next(); }; } +/** + * Indica se a chave recebida é a chave MESTRA de integração (EXT_MASTER_KEY). + * A resolução do tenant, nesse caso, é feita por email (x-nw-email) — ver + * resolveMasterEmail — nunca pela tabela apiKey. + */ +function isMasterKey(key) { + const MASTER = process.env.EXT_MASTER_KEY; + return Boolean(MASTER && key === MASTER); +} +/** + * Resolve o tenant a partir do email (fluxo da chave mestra). Compartilhado + * entre o middleware REST e o handshake do WS. Retorna null se o email não + * tiver conta ativa no motor. Usa o mesmo cache (prefixo master:). + */ +async function resolveMasterEmail(prisma, email) { + const e = email?.trim().toLowerCase(); + if (!e) + return null; + const cacheK = `master:${e}`; + const c = keyCache.get(cacheK); + if (c && Date.now() - c.cachedAt < KEY_CACHE_TTL) + return c.tenantId; + const user = await prisma.user.findUnique({ where: { email: e }, select: { id: true, isActive: true } }); + if (!user || !user.isActive) + return null; + keyCache.set(cacheK, { tenantId: user.id, expiresAt: null, cachedAt: Date.now() }); + return user.id; +} /** * Versão standalone que valida a chave sem passar pelo Express. * Usada no handshake do WS (upgrade request). diff --git a/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/apikey-auth.js.map b/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/apikey-auth.js.map index 79d624b..66bcf1f 100644 --- a/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/apikey-auth.js.map +++ b/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/apikey-auth.js.map @@ -1 +1 @@ -{"version":3,"file":"apikey-auth.js","sourceRoot":"","sources":["apikey-auth.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"apikey-auth.js","sourceRoot":"","sources":["apikey-auth.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/apikey-auth.ts b/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/apikey-auth.ts index a38a4c0..7d49273 100644 --- a/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/apikey-auth.ts +++ b/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/apikey-auth.ts @@ -75,6 +75,19 @@ export function buildApiKeyAuth(prisma: PrismaClient) { return } + // ── Chave MESTRA + x-nw-email: integração multi-conta por email ──────────── + // Permite que um satélite confiável acesse a conta de QUALQUER email (resolve + // o tenant pelo User.email). O satélite deve enviar SEMPRE o email do usuário + // autenticado dele — nunca um email arbitrário do cliente. + if (isMasterKey(key)) { + const email = (req.headers['x-nw-email'] as string | undefined)?.trim().toLowerCase() + if (!email) { res.status(400).json({ error: 'x-nw-email ausente (chave mestra)' }); return } + const tenantId = await resolveMasterEmail(prisma, email) + if (!tenantId) { res.status(404).json({ error: `Conta não encontrada no motor para ${email}` }); return } + req.extTenantId = tenantId + next(); return + } + // Verifica cache antes de ir ao banco const cached = keyCache.get(key) if (cached && Date.now() - cached.cachedAt < KEY_CACHE_TTL) { @@ -108,6 +121,36 @@ export function buildApiKeyAuth(prisma: PrismaClient) { } } +/** + * Indica se a chave recebida é a chave MESTRA de integração (EXT_MASTER_KEY). + * A resolução do tenant, nesse caso, é feita por email (x-nw-email) — ver + * resolveMasterEmail — nunca pela tabela apiKey. + */ +export function isMasterKey(key: string): boolean { + const MASTER = process.env.EXT_MASTER_KEY + return Boolean(MASTER && key === MASTER) +} + +/** + * Resolve o tenant a partir do email (fluxo da chave mestra). Compartilhado + * entre o middleware REST e o handshake do WS. Retorna null se o email não + * tiver conta ativa no motor. Usa o mesmo cache (prefixo master:). + */ +export async function resolveMasterEmail( + prisma: PrismaClient, + email: string | undefined | null, +): Promise { + const e = email?.trim().toLowerCase() + if (!e) return null + const cacheK = `master:${e}` + const c = keyCache.get(cacheK) + if (c && Date.now() - c.cachedAt < KEY_CACHE_TTL) return c.tenantId + const user = await prisma.user.findUnique({ where: { email: e }, select: { id: true, isActive: true } }) + if (!user || !user.isActive) return null + keyCache.set(cacheK, { tenantId: user.id, expiresAt: null, cachedAt: Date.now() }) + return user.id +} + /** * Versão standalone que valida a chave sem passar pelo Express. * Usada no handshake do WS (upgrade request). diff --git a/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/ws-bridge.js b/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/ws-bridge.js index ccec411..ab320ce 100644 --- a/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/ws-bridge.js +++ b/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/ws-bridge.js @@ -43,7 +43,20 @@ function buildWsBridge(httpServer, prisma, hooks) { } let tenantId; try { - tenantId = await (0, apikey_auth_1.resolveApiKey)(prisma, apiKey); + // Chave mestra → resolve o tenant pelo x-nw-email do handshake (o túnel do + // satélite injeta o email do usuário autenticado). Chave normal → tabela apiKey. + if ((0, apikey_auth_1.isMasterKey)(apiKey)) { + const email = req.headers['x-nw-email']?.trim().toLowerCase(); + if (!email) { + socket.write('HTTP/1.1 400 Bad Request\r\nContent-Type: text/plain\r\n\r\nx-nw-email ausente (chave mestra)'); + socket.destroy(); + return; + } + tenantId = await (0, apikey_auth_1.resolveMasterEmail)(prisma, email); + } + else { + tenantId = await (0, apikey_auth_1.resolveApiKey)(prisma, apiKey); + } } catch { socket.write('HTTP/1.1 500 Internal Server Error\r\n\r\n'); @@ -51,7 +64,7 @@ function buildWsBridge(httpServer, prisma, hooks) { return; } if (!tenantId) { - socket.write('HTTP/1.1 401 Unauthorized\r\nContent-Type: text/plain\r\n\r\nChave inválida, inativa ou expirada'); + socket.write('HTTP/1.1 401 Unauthorized\r\nContent-Type: text/plain\r\n\r\nChave inválida/inativa ou conta não encontrada'); socket.destroy(); return; } diff --git a/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/ws-bridge.js.map b/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/ws-bridge.js.map index c82e3d8..2c2f146 100644 --- a/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/ws-bridge.js.map +++ b/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/ws-bridge.js.map @@ -1 +1 @@ -{"version":3,"file":"ws-bridge.js","sourceRoot":"","sources":["ws-bridge.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"ws-bridge.js","sourceRoot":"","sources":["ws-bridge.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/ws-bridge.ts b/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/ws-bridge.ts index 8ecc97c..b3d9404 100644 --- a/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/ws-bridge.ts +++ b/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/ws-bridge.ts @@ -23,7 +23,7 @@ import type { Server as HttpServer } from 'http' import type { IncomingMessage } from 'http' import type { PrismaClient } from '@prisma/client' import type { HookBus } from '../../../backend/src/core/hook-bus' -import { resolveApiKey } from './apikey-auth' +import { resolveApiKey, isMasterKey, resolveMasterEmail } from './apikey-auth' const WS_PATH = '/api/ext/v1/stream' const PING_INTERVAL_MS = 25_000 @@ -59,7 +59,19 @@ export function buildWsBridge( let tenantId: string | null try { - tenantId = await resolveApiKey(prisma, apiKey) + // Chave mestra → resolve o tenant pelo x-nw-email do handshake (o túnel do + // satélite injeta o email do usuário autenticado). Chave normal → tabela apiKey. + if (isMasterKey(apiKey)) { + const email = (req.headers['x-nw-email'] as string | undefined)?.trim().toLowerCase() + if (!email) { + socket.write('HTTP/1.1 400 Bad Request\r\nContent-Type: text/plain\r\n\r\nx-nw-email ausente (chave mestra)') + socket.destroy() + return + } + tenantId = await resolveMasterEmail(prisma, email) + } else { + tenantId = await resolveApiKey(prisma, apiKey) + } } catch { socket.write('HTTP/1.1 500 Internal Server Error\r\n\r\n') socket.destroy() @@ -67,7 +79,7 @@ export function buildWsBridge( } if (!tenantId) { - socket.write('HTTP/1.1 401 Unauthorized\r\nContent-Type: text/plain\r\n\r\nChave inválida, inativa ou expirada') + socket.write('HTTP/1.1 401 Unauthorized\r\nContent-Type: text/plain\r\n\r\nChave inválida/inativa ou conta não encontrada') socket.destroy() return } diff --git a/newwhats.clube67.com/newwhats.local/plugins/ext-api/index.js b/newwhats.clube67.com/newwhats.local/plugins/ext-api/index.js index 63061df..9061beb 100644 --- a/newwhats.clube67.com/newwhats.local/plugins/ext-api/index.js +++ b/newwhats.clube67.com/newwhats.local/plugins/ext-api/index.js @@ -11,14 +11,14 @@ const manifest_json_1 = __importDefault(require("./manifest.json")); const plugin = { manifest: manifest_json_1.default, async activate(ctx) { - const { app, prisma, db, config, hooks, logger, httpServer } = ctx; + const { app, prisma, db, config, hooks, logger, httpServer, io } = ctx; // ── REST ──────────────────────────────────────────────────────────────── const manager = globalThis.__whatsAppManager; if (!manager) { logger.warn('[ext-api] WhatsAppConnectionManager não disponível via globalThis.__whatsAppManager — endpoints de send desativados'); } const authMiddleware = (0, apikey_auth_1.buildApiKeyAuth)(prisma); - const extRouter = (0, routes_1.buildExtRoutes)(prisma, manager, db, config, hooks); + const extRouter = (0, routes_1.buildExtRoutes)(prisma, manager, db, config, hooks, io); app.use('/api/ext/v1', authMiddleware, extRouter); logger.info('[ext-api] Rotas REST registradas em /api/ext/v1'); // ── WS Bridge ─────────────────────────────────────────────────────────── diff --git a/newwhats.clube67.com/newwhats.local/plugins/ext-api/index.js.map b/newwhats.clube67.com/newwhats.local/plugins/ext-api/index.js.map index 3784990..eb632e9 100644 --- a/newwhats.clube67.com/newwhats.local/plugins/ext-api/index.js.map +++ b/newwhats.clube67.com/newwhats.local/plugins/ext-api/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";;;;;AAKA,uDAAuD;AACvD,6CAAiD;AACjD,mDAAmD;AACnD,qEAAqE;AAErE,oEAAsC;AAWtC,MAAM,MAAM,GAAmB;IAC7B,QAAQ,EAAE,uBAAe;IAEzB,KAAK,CAAC,QAAQ,CAAC,GAAkB;QAC/B,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,GAAG,CAAA;QAElE,2EAA2E;QAC3E,MAAM,OAAO,GAAG,UAAU,CAAC,iBAAiB,CAAA;QAC5C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,CAAC,IAAI,CAAC,qHAAqH,CAAC,CAAA;QACpI,CAAC;QAED,MAAM,cAAc,GAAG,IAAA,6BAAe,EAAC,MAAM,CAAC,CAAA;QAC9C,MAAM,SAAS,GAAQ,IAAA,uBAAc,EAAC,MAAM,EAAE,OAAoC,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,CAAA;QAEtG,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,cAAc,EAAE,SAAS,CAAC,CAAA;QACjD,MAAM,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAA;QAE9D,2EAA2E;QAC3E,IAAA,yBAAa,EAAC,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,CAAA;QACxC,MAAM,CAAC,IAAI,CAAC,iEAAiE,CAAC,CAAA;QAE9E,2EAA2E;QAC3E,IAAA,2CAAsB,EAAC,MAAM,EAAE,KAAK,CAAC,CAAA;QACrC,MAAM,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAA;IACnD,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,GAAkB;QACjC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,kEAAkE,CAAC,CAAA;IACrF,CAAC;CACF,CAAA;AAED,kBAAe,MAAM,CAAA"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";;;;;AAKA,uDAAuD;AACvD,6CAAiD;AACjD,mDAAmD;AACnD,qEAAqE;AAErE,oEAAsC;AAWtC,MAAM,MAAM,GAAmB;IAC7B,QAAQ,EAAE,uBAAe;IAEzB,KAAK,CAAC,QAAQ,CAAC,GAAkB;QAC/B,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,GAAG,GAAG,CAAA;QAEtE,2EAA2E;QAC3E,MAAM,OAAO,GAAG,UAAU,CAAC,iBAAiB,CAAA;QAC5C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,CAAC,IAAI,CAAC,qHAAqH,CAAC,CAAA;QACpI,CAAC;QAED,MAAM,cAAc,GAAG,IAAA,6BAAe,EAAC,MAAM,CAAC,CAAA;QAC9C,MAAM,SAAS,GAAQ,IAAA,uBAAc,EAAC,MAAM,EAAE,OAAoC,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAA;QAE1G,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,cAAc,EAAE,SAAS,CAAC,CAAA;QACjD,MAAM,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAA;QAE9D,2EAA2E;QAC3E,IAAA,yBAAa,EAAC,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,CAAA;QACxC,MAAM,CAAC,IAAI,CAAC,iEAAiE,CAAC,CAAA;QAE9E,2EAA2E;QAC3E,IAAA,2CAAsB,EAAC,MAAM,EAAE,KAAK,CAAC,CAAA;QACrC,MAAM,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAA;IACnD,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,GAAkB;QACjC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,kEAAkE,CAAC,CAAA;IACrF,CAAC;CACF,CAAA;AAED,kBAAe,MAAM,CAAA"} \ No newline at end of file