feat(ext-api): gerenciador de satélites — auth por integration_key + gestão
Torna o registry de satélites (PluginPair) o gatekeeper do ext-api, em vez de uma única EXT_MASTER_KEY global: - satellites.ts: serviço + router /api/ext/v1/satellites (listar/provisionar/ revogar/rotacionar), montado fora do authMiddleware com gate de chave mestra. - apikey-auth: resolvePairKey aceita a chave do satélite (nwk_) no REST e no WS, resolvendo o tenant do par; checa revoke no banco a cada request (revoke instantâneo) + throttle de lastSeenAt. - Chave mestra e ApiKey por-tenant seguem funcionando (não-quebra). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.buildApiKeyAuth = buildApiKeyAuth;
|
exports.buildApiKeyAuth = buildApiKeyAuth;
|
||||||
|
exports.resolvePairKey = resolvePairKey;
|
||||||
exports.isMasterKey = isMasterKey;
|
exports.isMasterKey = isMasterKey;
|
||||||
exports.resolveMasterEmail = resolveMasterEmail;
|
exports.resolveMasterEmail = resolveMasterEmail;
|
||||||
exports.resolveApiKey = resolveApiKey;
|
exports.resolveApiKey = resolveApiKey;
|
||||||
@@ -69,6 +70,20 @@ function buildApiKeyAuth(prisma) {
|
|||||||
next();
|
next();
|
||||||
return;
|
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
|
// Verifica cache antes de ir ao banco
|
||||||
const cached = keyCache.get(key);
|
const cached = keyCache.get(key);
|
||||||
if (cached && Date.now() - cached.cachedAt < KEY_CACHE_TTL) {
|
if (cached && Date.now() - cached.cachedAt < KEY_CACHE_TTL) {
|
||||||
@@ -97,6 +112,30 @@ function buildApiKeyAuth(prisma) {
|
|||||||
next();
|
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).
|
* 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
|
* 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) {
|
async function resolveApiKey(prisma, key) {
|
||||||
if (!key)
|
if (!key)
|
||||||
return null;
|
return null;
|
||||||
|
// Chave de satélite (PluginPair) — resolve pelo registry.
|
||||||
|
if (key.startsWith('nwk_'))
|
||||||
|
return resolvePairKey(prisma, key);
|
||||||
const cached = keyCache.get(key);
|
const cached = keyCache.get(key);
|
||||||
if (cached && Date.now() - cached.cachedAt < KEY_CACHE_TTL) {
|
if (cached && Date.now() - cached.cachedAt < KEY_CACHE_TTL) {
|
||||||
if (cached.expiresAt && cached.expiresAt < new Date())
|
if (cached.expiresAt && cached.expiresAt < new Date())
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -88,6 +88,17 @@ export function buildApiKeyAuth(prisma: PrismaClient) {
|
|||||||
next(); return
|
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
|
// Verifica cache antes de ir ao banco
|
||||||
const cached = keyCache.get(key)
|
const cached = keyCache.get(key)
|
||||||
if (cached && Date.now() - cached.cachedAt < KEY_CACHE_TTL) {
|
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<string, number>()
|
||||||
|
export async function resolvePairKey(prisma: PrismaClient, key: string): Promise<string | null> {
|
||||||
|
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).
|
* 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
|
* A resolução do tenant, nesse caso, é feita por email (x-nw-email) — ver
|
||||||
@@ -160,6 +194,8 @@ export async function resolveApiKey(
|
|||||||
key: string,
|
key: string,
|
||||||
): Promise<string | null> {
|
): Promise<string | null> {
|
||||||
if (!key) return null
|
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)
|
const cached = keyCache.get(key)
|
||||||
if (cached && Date.now() - cached.cachedAt < KEY_CACHE_TTL) {
|
if (cached && Date.now() - cached.cachedAt < KEY_CACHE_TTL) {
|
||||||
if (cached.expiresAt && cached.expiresAt < new Date()) return null
|
if (cached.expiresAt && cached.expiresAt < new Date()) return null
|
||||||
|
|||||||
@@ -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
|
||||||
File diff suppressed because one or more lines are too long
@@ -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
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ const apikey_auth_1 = require("./backend/apikey-auth");
|
|||||||
const routes_1 = require("./backend/routes");
|
const routes_1 = require("./backend/routes");
|
||||||
const ws_bridge_1 = require("./backend/ws-bridge");
|
const ws_bridge_1 = require("./backend/ws-bridge");
|
||||||
const webhook_dispatcher_1 = require("./backend/webhook-dispatcher");
|
const webhook_dispatcher_1 = require("./backend/webhook-dispatcher");
|
||||||
|
const satellites_1 = require("./backend/satellites");
|
||||||
const manifest_json_1 = __importDefault(require("./manifest.json"));
|
const manifest_json_1 = __importDefault(require("./manifest.json"));
|
||||||
const plugin = {
|
const plugin = {
|
||||||
manifest: manifest_json_1.default,
|
manifest: manifest_json_1.default,
|
||||||
@@ -19,6 +20,10 @@ const plugin = {
|
|||||||
}
|
}
|
||||||
const authMiddleware = (0, apikey_auth_1.buildApiKeyAuth)(prisma);
|
const authMiddleware = (0, apikey_auth_1.buildApiKeyAuth)(prisma);
|
||||||
const extRouter = (0, routes_1.buildExtRoutes)(prisma, manager, db, config, hooks, io);
|
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);
|
app.use('/api/ext/v1', authMiddleware, extRouter);
|
||||||
logger.info('[ext-api] Rotas REST registradas em /api/ext/v1');
|
logger.info('[ext-api] Rotas REST registradas em /api/ext/v1');
|
||||||
// ── WS Bridge ───────────────────────────────────────────────────────────
|
// ── WS Bridge ───────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -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"}
|
{"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"}
|
||||||
@@ -7,6 +7,7 @@ import { buildApiKeyAuth } from './backend/apikey-auth'
|
|||||||
import { buildExtRoutes } from './backend/routes'
|
import { buildExtRoutes } from './backend/routes'
|
||||||
import { buildWsBridge } from './backend/ws-bridge'
|
import { buildWsBridge } from './backend/ws-bridge'
|
||||||
import { buildWebhookDispatcher } from './backend/webhook-dispatcher'
|
import { buildWebhookDispatcher } from './backend/webhook-dispatcher'
|
||||||
|
import { buildSatelliteRoutes } from './backend/satellites'
|
||||||
import { WhatsAppConnectionManager } from '../../backend/src/modules/whatsapp/connection/WhatsAppConnectionManager'
|
import { WhatsAppConnectionManager } from '../../backend/src/modules/whatsapp/connection/WhatsAppConnectionManager'
|
||||||
import manifest from './manifest.json'
|
import manifest from './manifest.json'
|
||||||
|
|
||||||
@@ -34,6 +35,11 @@ const plugin: PluginInstance = {
|
|||||||
const authMiddleware = buildApiKeyAuth(prisma)
|
const authMiddleware = buildApiKeyAuth(prisma)
|
||||||
const extRouter = buildExtRoutes(prisma, manager as WhatsAppConnectionManager, db, config, hooks, io)
|
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)
|
app.use('/api/ext/v1', authMiddleware, extRouter)
|
||||||
logger.info('[ext-api] Rotas REST registradas em /api/ext/v1')
|
logger.info('[ext-api] Rotas REST registradas em /api/ext/v1')
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user