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 3a99544..f0a110f 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,7 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildApiKeyAuth = buildApiKeyAuth; +exports.resolvePairKey = resolvePairKey; exports.isMasterKey = isMasterKey; exports.resolveMasterEmail = resolveMasterEmail; exports.resolveApiKey = resolveApiKey; @@ -69,6 +70,20 @@ function buildApiKeyAuth(prisma) { next(); return; } + // ── Chave de SATÉLITE (PluginPair) ───────────────────────────────────────── + // Cada satélite registrado tem sua própria integration_key (nwk_...). Resolve + // o tenant dono do par; honra revoke e atualiza lastSeenAt. É o registry — e + // não uma única chave mestra global — autenticando o satélite. + if (key.startsWith('nwk_')) { + const tenantId = await resolvePairKey(prisma, key); + if (!tenantId) { + res.status(401).json({ error: 'Chave de satélite inválida ou revogada' }); + 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) { @@ -97,6 +112,30 @@ function buildApiKeyAuth(prisma) { next(); }; } +/** + * Resolve uma chave de SATÉLITE (PluginPair, nwk_...) para o tenantId dono do par. + * Retorna null se não existir, estiver revogada ou a conta inativa. Atualiza + * lastSeenAt em background e usa o mesmo cache das demais chaves. + */ +// Throttle das escritas de lastSeenAt (não cacheia a DECISÃO de auth — a checagem +// de revoke bate no banco a cada request, garantindo revoke instantâneo). +const pairTouch = new Map(); +async function resolvePairKey(prisma, key) { + if (!key || !key.startsWith('nwk_')) + return null; + const pair = await prisma.pluginPair.findUnique({ + where: { integrationKey: key }, + select: { userId: true, revokedAt: true, user: { select: { isActive: true } } }, + }); + if (!pair || pair.revokedAt || !pair.user.isActive) + return null; + const now = Date.now(); + if (now - (pairTouch.get(key) ?? 0) > 60000) { + pairTouch.set(key, now); + prisma.pluginPair.update({ where: { integrationKey: key }, data: { lastSeenAt: new Date() } }).catch(() => { }); + } + return pair.userId; +} /** * 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 @@ -132,6 +171,9 @@ async function resolveMasterEmail(prisma, email) { async function resolveApiKey(prisma, key) { if (!key) return null; + // Chave de satélite (PluginPair) — resolve pelo registry. + if (key.startsWith('nwk_')) + return resolvePairKey(prisma, key); const cached = keyCache.get(key); if (cached && Date.now() - cached.cachedAt < KEY_CACHE_TTL) { if (cached.expiresAt && cached.expiresAt < new Date()) 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 66bcf1f..3ed8547 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 7d49273..c3d093e 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 @@ -88,6 +88,17 @@ export function buildApiKeyAuth(prisma: PrismaClient) { next(); return } + // ── Chave de SATÉLITE (PluginPair) ───────────────────────────────────────── + // Cada satélite registrado tem sua própria integration_key (nwk_...). Resolve + // o tenant dono do par; honra revoke e atualiza lastSeenAt. É o registry — e + // não uma única chave mestra global — autenticando o satélite. + if (key.startsWith('nwk_')) { + const tenantId = await resolvePairKey(prisma, key) + if (!tenantId) { res.status(401).json({ error: 'Chave de satélite inválida ou revogada' }); 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) { @@ -121,6 +132,29 @@ export function buildApiKeyAuth(prisma: PrismaClient) { } } +/** + * Resolve uma chave de SATÉLITE (PluginPair, nwk_...) para o tenantId dono do par. + * Retorna null se não existir, estiver revogada ou a conta inativa. Atualiza + * lastSeenAt em background e usa o mesmo cache das demais chaves. + */ +// Throttle das escritas de lastSeenAt (não cacheia a DECISÃO de auth — a checagem +// de revoke bate no banco a cada request, garantindo revoke instantâneo). +const pairTouch = new Map() +export async function resolvePairKey(prisma: PrismaClient, key: string): Promise { + if (!key || !key.startsWith('nwk_')) return null + const pair = await prisma.pluginPair.findUnique({ + where: { integrationKey: key }, + select: { userId: true, revokedAt: true, user: { select: { isActive: true } } }, + }) + if (!pair || pair.revokedAt || !pair.user.isActive) return null + const now = Date.now() + if (now - (pairTouch.get(key) ?? 0) > 60_000) { + pairTouch.set(key, now) + prisma.pluginPair.update({ where: { integrationKey: key }, data: { lastSeenAt: new Date() } }).catch(() => {}) + } + return pair.userId +} + /** * 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 @@ -160,6 +194,8 @@ export async function resolveApiKey( key: string, ): Promise { if (!key) return null + // Chave de satélite (PluginPair) — resolve pelo registry. + if (key.startsWith('nwk_')) return resolvePairKey(prisma, key) const cached = keyCache.get(key) if (cached && Date.now() - cached.cachedAt < KEY_CACHE_TTL) { if (cached.expiresAt && cached.expiresAt < new Date()) return null diff --git a/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/satellites.js b/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/satellites.js new file mode 100644 index 0000000..31a7b0c --- /dev/null +++ b/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/satellites.js @@ -0,0 +1,133 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.provisionSatellite = provisionSatellite; +exports.listSatellites = listSatellites; +exports.revokeSatellite = revokeSatellite; +exports.rotateSatelliteKey = rotateSatelliteKey; +exports.buildSatelliteRoutes = buildSatelliteRoutes; +const express_1 = require("express"); +const crypto_1 = __importDefault(require("crypto")); +const apikey_auth_1 = require("./apikey-auth"); +const genKey = () => `nwk_${crypto_1.default.randomUUID().replace(/-/g, '')}`; +// A resolução da chave de satélite para o auth vive em apikey-auth.resolvePairKey +// (co-localizada com os demais resolvers + cache). Aqui ficam só as operações de gestão. +// ─── Operações de gestão ────────────────────────────────────────────────────── +async function provisionSatellite(prisma, input) { + const email = String(input.email ?? '').toLowerCase().trim(); + if (!email || !input.clientSystem || !input.clientName) { + throw Object.assign(new Error('email, clientSystem e clientName obrigatórios'), { statusCode: 400 }); + } + const user = await prisma.user.findUnique({ + where: { email }, select: { id: true, isActive: true, name: true, email: true }, + }); + if (!user || !user.isActive) + throw Object.assign(new Error(`Conta não encontrada/inativa: ${email}`), { statusCode: 404 }); + // Revoga par anterior do mesmo sistema (evita duplicatas ativas). + await prisma.pluginPair.updateMany({ + where: { userId: user.id, clientSystem: input.clientSystem, revokedAt: null }, + data: { revokedAt: new Date() }, + }); + const integrationKey = genKey(); + const pair = await prisma.pluginPair.create({ + data: { + userId: user.id, + clientSystem: input.clientSystem, + clientName: input.clientName, + clientUrl: input.clientUrl || null, + integrationKey, + }, + }); + return { + id: pair.id, + integration_key: integrationKey, + account: { name: user.name, email: user.email }, + clientSystem: input.clientSystem, + clientName: input.clientName, + }; +} +async function listSatellites(prisma) { + const pairs = await prisma.pluginPair.findMany({ + orderBy: { createdAt: 'desc' }, + select: { + id: true, clientSystem: true, clientName: true, clientUrl: true, + lastSeenAt: true, revokedAt: true, createdAt: true, + user: { select: { email: true, isActive: true } }, + }, + }); + return pairs.map((p) => ({ + id: p.id, + clientSystem: p.clientSystem, + clientName: p.clientName, + clientUrl: p.clientUrl, + account: p.user.email, + active: !p.revokedAt && p.user.isActive, + revokedAt: p.revokedAt, + lastSeenAt: p.lastSeenAt, + createdAt: p.createdAt, + })); +} +async function revokeSatellite(prisma, id) { + const r = await prisma.pluginPair.updateMany({ where: { id, revokedAt: null }, data: { revokedAt: new Date() } }); + if (!r.count) + throw Object.assign(new Error('Satélite não encontrado ou já revogado'), { statusCode: 404 }); + return { ok: true }; +} +async function rotateSatelliteKey(prisma, id) { + const pair = await prisma.pluginPair.findFirst({ where: { id, revokedAt: null }, select: { id: true } }); + if (!pair) + throw Object.assign(new Error('Satélite não encontrado'), { statusCode: 404 }); + const integrationKey = genKey(); + await prisma.pluginPair.update({ where: { id }, data: { integrationKey } }); + return { id, integration_key: integrationKey }; +} +// ─── Router de gestão (superadmin via chave mestra) ─────────────────────────── +// Montado FORA do authMiddleware do ext-api: tem gate próprio (chave mestra, +// sem exigir x-nw-email como o fluxo de operação). +function buildSatelliteRoutes(prisma) { + const router = (0, express_1.Router)(); + router.use((req, res, next) => { + const key = req.headers['x-nw-key']?.trim() ?? ''; + if (!(0, apikey_auth_1.isMasterKey)(key)) { + res.status(403).json({ error: 'Apenas a chave mestra gerencia satélites.' }); + return; + } + next(); + }); + router.get('/', async (_req, res) => { + try { + res.json({ satellites: await listSatellites(prisma) }); + } + catch (e) { + res.status(500).json({ error: e.message }); + } + }); + router.post('/', async (req, res) => { + try { + res.json(await provisionSatellite(prisma, req.body ?? {})); + } + catch (e) { + res.status(e.statusCode ?? 500).json({ error: e.message }); + } + }); + router.post('/:id/revoke', async (req, res) => { + try { + res.json(await revokeSatellite(prisma, req.params['id'])); + } + catch (e) { + res.status(e.statusCode ?? 500).json({ error: e.message }); + } + }); + router.post('/:id/rotate', async (req, res) => { + try { + res.json(await rotateSatelliteKey(prisma, req.params['id'])); + } + catch (e) { + res.status(e.statusCode ?? 500).json({ error: e.message }); + } + }); + return router; +} +//# sourceMappingURL=satellites.js.map \ No newline at end of file diff --git a/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/satellites.js.map b/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/satellites.js.map new file mode 100644 index 0000000..dcc2d61 --- /dev/null +++ b/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/satellites.js.map @@ -0,0 +1 @@ +{"version":3,"file":"satellites.js","sourceRoot":"","sources":["satellites.ts"],"names":[],"mappings":";;;;;AAyBA,gDAmCC;AAED,wCAoBC;AAED,0CAIC;AAED,gDAMC;AAKD,oDA8BC;AArHD,qCAA6D;AAC7D,oDAA2B;AAC3B,+CAA2C;AAE3C,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,gBAAM,CAAC,UAAU,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,CAAA;AAEnE,kFAAkF;AAClF,yFAAyF;AAEzF,iFAAiF;AAE1E,KAAK,UAAU,kBAAkB,CACtC,MAAoB,EACpB,KAA6F;IAE7F,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAA;IAC5D,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,YAAY,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;QACvD,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,+CAA+C,CAAC,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAA;IACtG,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;QACxC,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;KAChF,CAAC,CAAA;IACF,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ;QAAE,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,iCAAiC,KAAK,EAAE,CAAC,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAA;IAE1H,kEAAkE;IAClE,MAAM,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC;QACjC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,SAAS,EAAE,IAAI,EAAE;QAC7E,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,EAAE;KAChC,CAAC,CAAA;IACF,MAAM,cAAc,GAAG,MAAM,EAAE,CAAA;IAC/B,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC;QAC1C,IAAI,EAAE;YACJ,MAAM,EAAE,IAAI,CAAC,EAAE;YACf,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,IAAI;YAClC,cAAc;SACf;KACF,CAAC,CAAA;IACF,OAAO;QACL,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,eAAe,EAAE,cAAc;QAC/B,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE;QAC/C,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,UAAU,EAAE,KAAK,CAAC,UAAU;KAC7B,CAAA;AACH,CAAC;AAEM,KAAK,UAAU,cAAc,CAAC,MAAoB;IACvD,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC;QAC7C,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE;QAC9B,MAAM,EAAE;YACN,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI;YAC/D,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI;YAClD,IAAI,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;SAClD;KACF,CAAC,CAAA;IACF,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACvB,EAAE,EAAE,CAAC,CAAC,EAAE;QACR,YAAY,EAAE,CAAC,CAAC,YAAY;QAC5B,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK;QACrB,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ;QACvC,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,SAAS,EAAE,CAAC,CAAC,SAAS;KACvB,CAAC,CAAC,CAAA;AACL,CAAC;AAEM,KAAK,UAAU,eAAe,CAAC,MAAoB,EAAE,EAAU;IACpE,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,EAAE,EAAE,CAAC,CAAA;IACjH,IAAI,CAAC,CAAC,CAAC,KAAK;QAAE,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,wCAAwC,CAAC,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAA;IAC3G,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;AACrB,CAAC;AAEM,KAAK,UAAU,kBAAkB,CAAC,MAAoB,EAAE,EAAU;IACvE,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAA;IACxG,IAAI,CAAC,IAAI;QAAE,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,yBAAyB,CAAC,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAA;IACzF,MAAM,cAAc,GAAG,MAAM,EAAE,CAAA;IAC/B,MAAM,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,cAAc,EAAE,EAAE,CAAC,CAAA;IAC3E,OAAO,EAAE,EAAE,EAAE,eAAe,EAAE,cAAc,EAAE,CAAA;AAChD,CAAC;AAED,iFAAiF;AACjF,6EAA6E;AAC7E,mDAAmD;AACnD,SAAgB,oBAAoB,CAAC,MAAoB;IACvD,MAAM,MAAM,GAAG,IAAA,gBAAM,GAAE,CAAA;IAEvB,MAAM,CAAC,GAAG,CAAC,CAAC,GAAY,EAAE,GAAa,EAAE,IAAI,EAAE,EAAE;QAC/C,MAAM,GAAG,GAAI,GAAG,CAAC,OAAO,CAAC,UAAU,CAAwB,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;QACzE,IAAI,CAAC,IAAA,yBAAW,EAAC,GAAG,CAAC,EAAE,CAAC;YAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,2CAA2C,EAAE,CAAC,CAAC;YAAC,OAAM;QAAC,CAAC;QAC/G,IAAI,EAAE,CAAA;IACR,CAAC,CAAC,CAAA;IAEF,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;QAClC,IAAI,CAAC;YAAC,GAAG,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QAAC,CAAC;QAC9D,OAAO,CAAM,EAAE,CAAC;YAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;QAAC,CAAC;IAC/D,CAAC,CAAC,CAAA;IAEF,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QAClC,IAAI,CAAC;YAAC,GAAG,CAAC,IAAI,CAAC,MAAM,kBAAkB,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAA;QAAC,CAAC;QAClE,OAAO,CAAM,EAAE,CAAC;YAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;QAAC,CAAC;IAC/E,CAAC,CAAC,CAAA;IAEF,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QAC5C,IAAI,CAAC;YAAC,GAAG,CAAC,IAAI,CAAC,MAAM,eAAe,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,CAAE,CAAC,CAAC,CAAA;QAAC,CAAC;QAClE,OAAO,CAAM,EAAE,CAAC;YAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;QAAC,CAAC;IAC/E,CAAC,CAAC,CAAA;IAEF,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QAC5C,IAAI,CAAC;YAAC,GAAG,CAAC,IAAI,CAAC,MAAM,kBAAkB,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,CAAE,CAAC,CAAC,CAAA;QAAC,CAAC;QACrE,OAAO,CAAM,EAAE,CAAC;YAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;QAAC,CAAC;IAC/E,CAAC,CAAC,CAAA;IAEF,OAAO,MAAM,CAAA;AACf,CAAC"} \ No newline at end of file diff --git a/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/satellites.ts b/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/satellites.ts new file mode 100644 index 0000000..e6b8b80 --- /dev/null +++ b/newwhats.clube67.com/newwhats.local/plugins/ext-api/backend/satellites.ts @@ -0,0 +1,132 @@ +/** + * Gerenciador de plugins/satélites do motor. + * + * Um "satélite" é um sistema cliente (ex.: scoreodonto) registrado como PluginPair, + * com integration_key própria (nwk_...), clientUrl e clientSystem. Este módulo: + * (a) resolve a chave do satélite → tenant no auth do ext-api (honra revoke, + * atualiza lastSeenAt) — tornando o REGISTRY o gatekeeper, em vez de uma + * única EXT_MASTER_KEY global; + * (b) expõe gestão (listar / provisionar / revogar / rotacionar) para superadmin + * (autenticado pela chave mestra). + * + * A chave mestra (EXT_MASTER_KEY) permanece para bootstrap/superadmin. + */ +import type { PrismaClient } from '@prisma/client' +import { Router, type Request, type Response } from 'express' +import crypto from 'crypto' +import { isMasterKey } from './apikey-auth' + +const genKey = () => `nwk_${crypto.randomUUID().replace(/-/g, '')}` + +// A resolução da chave de satélite para o auth vive em apikey-auth.resolvePairKey +// (co-localizada com os demais resolvers + cache). Aqui ficam só as operações de gestão. + +// ─── Operações de gestão ────────────────────────────────────────────────────── + +export async function provisionSatellite( + prisma: PrismaClient, + input: { email: string; clientSystem: string; clientName: string; clientUrl?: string | null }, +) { + const email = String(input.email ?? '').toLowerCase().trim() + if (!email || !input.clientSystem || !input.clientName) { + throw Object.assign(new Error('email, clientSystem e clientName obrigatórios'), { statusCode: 400 }) + } + const user = await prisma.user.findUnique({ + where: { email }, select: { id: true, isActive: true, name: true, email: true }, + }) + if (!user || !user.isActive) throw Object.assign(new Error(`Conta não encontrada/inativa: ${email}`), { statusCode: 404 }) + + // Revoga par anterior do mesmo sistema (evita duplicatas ativas). + await prisma.pluginPair.updateMany({ + where: { userId: user.id, clientSystem: input.clientSystem, revokedAt: null }, + data: { revokedAt: new Date() }, + }) + const integrationKey = genKey() + const pair = await prisma.pluginPair.create({ + data: { + userId: user.id, + clientSystem: input.clientSystem, + clientName: input.clientName, + clientUrl: input.clientUrl || null, + integrationKey, + }, + }) + return { + id: pair.id, + integration_key: integrationKey, + account: { name: user.name, email: user.email }, + clientSystem: input.clientSystem, + clientName: input.clientName, + } +} + +export async function listSatellites(prisma: PrismaClient) { + const pairs = await prisma.pluginPair.findMany({ + orderBy: { createdAt: 'desc' }, + select: { + id: true, clientSystem: true, clientName: true, clientUrl: true, + lastSeenAt: true, revokedAt: true, createdAt: true, + user: { select: { email: true, isActive: true } }, + }, + }) + return pairs.map((p) => ({ + id: p.id, + clientSystem: p.clientSystem, + clientName: p.clientName, + clientUrl: p.clientUrl, + account: p.user.email, + active: !p.revokedAt && p.user.isActive, + revokedAt: p.revokedAt, + lastSeenAt: p.lastSeenAt, + createdAt: p.createdAt, + })) +} + +export async function revokeSatellite(prisma: PrismaClient, id: string) { + const r = await prisma.pluginPair.updateMany({ where: { id, revokedAt: null }, data: { revokedAt: new Date() } }) + if (!r.count) throw Object.assign(new Error('Satélite não encontrado ou já revogado'), { statusCode: 404 }) + return { ok: true } +} + +export async function rotateSatelliteKey(prisma: PrismaClient, id: string) { + const pair = await prisma.pluginPair.findFirst({ where: { id, revokedAt: null }, select: { id: true } }) + if (!pair) throw Object.assign(new Error('Satélite não encontrado'), { statusCode: 404 }) + const integrationKey = genKey() + await prisma.pluginPair.update({ where: { id }, data: { integrationKey } }) + return { id, integration_key: integrationKey } +} + +// ─── Router de gestão (superadmin via chave mestra) ─────────────────────────── +// Montado FORA do authMiddleware do ext-api: tem gate próprio (chave mestra, +// sem exigir x-nw-email como o fluxo de operação). +export function buildSatelliteRoutes(prisma: PrismaClient): Router { + const router = Router() + + router.use((req: Request, res: Response, next) => { + const key = (req.headers['x-nw-key'] as string | undefined)?.trim() ?? '' + if (!isMasterKey(key)) { res.status(403).json({ error: 'Apenas a chave mestra gerencia satélites.' }); return } + next() + }) + + router.get('/', async (_req, res) => { + try { res.json({ satellites: await listSatellites(prisma) }) } + catch (e: any) { res.status(500).json({ error: e.message }) } + }) + + router.post('/', async (req, res) => { + try { res.json(await provisionSatellite(prisma, req.body ?? {})) } + catch (e: any) { res.status(e.statusCode ?? 500).json({ error: e.message }) } + }) + + router.post('/:id/revoke', async (req, res) => { + try { res.json(await revokeSatellite(prisma, req.params['id']!)) } + catch (e: any) { res.status(e.statusCode ?? 500).json({ error: e.message }) } + }) + + router.post('/:id/rotate', async (req, res) => { + try { res.json(await rotateSatelliteKey(prisma, req.params['id']!)) } + catch (e: any) { res.status(e.statusCode ?? 500).json({ error: e.message }) } + }) + + return router +} 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 9061beb..37e228b 100644 --- a/newwhats.clube67.com/newwhats.local/plugins/ext-api/index.js +++ b/newwhats.clube67.com/newwhats.local/plugins/ext-api/index.js @@ -7,6 +7,7 @@ const apikey_auth_1 = require("./backend/apikey-auth"); const routes_1 = require("./backend/routes"); const ws_bridge_1 = require("./backend/ws-bridge"); const webhook_dispatcher_1 = require("./backend/webhook-dispatcher"); +const satellites_1 = require("./backend/satellites"); const manifest_json_1 = __importDefault(require("./manifest.json")); const plugin = { manifest: manifest_json_1.default, @@ -19,6 +20,10 @@ const plugin = { } const authMiddleware = (0, apikey_auth_1.buildApiKeyAuth)(prisma); const extRouter = (0, routes_1.buildExtRoutes)(prisma, manager, db, config, hooks, io); + // Gerenciador de satélites — montado ANTES do authMiddleware (gate próprio + // de chave mestra, sem exigir x-nw-email). Superadmin gerencia os PluginPairs. + app.use('/api/ext/v1/satellites', (0, satellites_1.buildSatelliteRoutes)(prisma)); + logger.info('[ext-api] Gerenciador de satélites em /api/ext/v1/satellites (chave mestra)'); 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 eb632e9..c7c9eb5 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,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 +{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";;;;;AAKA,uDAAuD;AACvD,6CAAiD;AACjD,mDAAmD;AACnD,qEAAqE;AACrE,qDAA2D;AAE3D,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,2EAA2E;QAC3E,+EAA+E;QAC/E,GAAG,CAAC,GAAG,CAAC,wBAAwB,EAAE,IAAA,iCAAoB,EAAC,MAAM,CAAC,CAAC,CAAA;QAC/D,MAAM,CAAC,IAAI,CAAC,6EAA6E,CAAC,CAAA;QAE1F,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 diff --git a/newwhats.clube67.com/newwhats.local/plugins/ext-api/index.ts b/newwhats.clube67.com/newwhats.local/plugins/ext-api/index.ts index 19f5773..ef25b92 100644 --- a/newwhats.clube67.com/newwhats.local/plugins/ext-api/index.ts +++ b/newwhats.clube67.com/newwhats.local/plugins/ext-api/index.ts @@ -7,6 +7,7 @@ import { buildApiKeyAuth } from './backend/apikey-auth' import { buildExtRoutes } from './backend/routes' import { buildWsBridge } from './backend/ws-bridge' import { buildWebhookDispatcher } from './backend/webhook-dispatcher' +import { buildSatelliteRoutes } from './backend/satellites' import { WhatsAppConnectionManager } from '../../backend/src/modules/whatsapp/connection/WhatsAppConnectionManager' import manifest from './manifest.json' @@ -34,6 +35,11 @@ const plugin: PluginInstance = { const authMiddleware = buildApiKeyAuth(prisma) const extRouter = buildExtRoutes(prisma, manager as WhatsAppConnectionManager, db, config, hooks, io) + // Gerenciador de satélites — montado ANTES do authMiddleware (gate próprio + // de chave mestra, sem exigir x-nw-email). Superadmin gerencia os PluginPairs. + app.use('/api/ext/v1/satellites', buildSatelliteRoutes(prisma)) + logger.info('[ext-api] Gerenciador de satélites em /api/ext/v1/satellites (chave mestra)') + app.use('/api/ext/v1', authMiddleware, extRouter) logger.info('[ext-api] Rotas REST registradas em /api/ext/v1')