1702235c59
- POST /satellites/accounts (ensureAccount): cria/reativa User por email, idempotente; gate aceita chave MESTRA ou chave de satélite (nwk_) válida — o satélite espelha suas próprias contas (scoreodonto) - POST /sessions/:id/connect (reconectar/re-scan) e /sessions/:id/disconnect (encerrar sem remover) — antes o satélite recebia 404 no botão Conectar - inclui proxy/re-download de mídia (/media/:id/download) da integração satélite Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
180 lines
8.1 KiB
JavaScript
180 lines
8.1 KiB
JavaScript
"use strict";
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.ensureAccount = ensureAccount;
|
|
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 bcryptjs_1 = __importDefault(require("bcryptjs"));
|
|
const apikey_auth_1 = require("./apikey-auth");
|
|
const genKey = () => `nwk_${crypto_1.default.randomUUID().replace(/-/g, '')}`;
|
|
// ─── Provisionamento de conta (auto-provisioning de satélites) ─────────────────
|
|
// Garante que exista um User no motor para um dado email. Idempotente: se a conta
|
|
// já existe, apenas a reativa (se inativa) e retorna. A conta é "headless" — o
|
|
// acesso se dá via satélite pela chave mestra + x-nw-email; a senha é aleatória
|
|
// (a pessoa nunca loga direto no motor). Chamado pelo scoreodonto ao criar a
|
|
// conta lá (eager) e como self-heal quando o motor devolve "conta não encontrada".
|
|
async function ensureAccount(prisma, input) {
|
|
const email = String(input.email ?? '').toLowerCase().trim();
|
|
if (!email || !email.includes('@')) {
|
|
throw Object.assign(new Error('email válido é obrigatório'), { statusCode: 400 });
|
|
}
|
|
const existing = await prisma.user.findUnique({
|
|
where: { email }, select: { id: true, email: true, isActive: true },
|
|
});
|
|
if (existing) {
|
|
if (!existing.isActive)
|
|
await prisma.user.update({ where: { id: existing.id }, data: { isActive: true } });
|
|
return { id: existing.id, email: existing.email, created: false };
|
|
}
|
|
// Senha aleatória (conta headless — nunca usada para login direto).
|
|
const passwordHash = await bcryptjs_1.default.hash(`${crypto_1.default.randomUUID()}${crypto_1.default.randomUUID()}`, 10);
|
|
const user = await prisma.user.create({
|
|
data: { name: (input.name || email).slice(0, 120), email, passwordHash, isActive: true },
|
|
select: { id: true, email: true },
|
|
});
|
|
return { id: user.id, email: user.email, created: true };
|
|
}
|
|
// 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)();
|
|
// Auto-provisiona (ou reativa) a conta de um email. Idempotente.
|
|
// Gate PRÓPRIO (definido antes do gate mestra abaixo): aceita a chave mestra OU
|
|
// uma chave de satélite válida (nwk_) — pois auto-provisionar as contas dos seus
|
|
// próprios usuários é justamente a função de um satélite confiável (ex.: scoreodonto).
|
|
router.post('/accounts', async (req, res) => {
|
|
const key = req.headers['x-nw-key']?.trim() ?? '';
|
|
const allowed = (0, apikey_auth_1.isMasterKey)(key) || (key.startsWith('nwk_') && !!(await (0, apikey_auth_1.resolvePairKey)(prisma, key)));
|
|
if (!allowed) {
|
|
res.status(403).json({ error: 'Chave inválida para provisionar conta.' });
|
|
return;
|
|
}
|
|
try {
|
|
res.json(await ensureAccount(prisma, req.body ?? {}));
|
|
}
|
|
catch (e) {
|
|
res.status(e.statusCode ?? 500).json({ error: e.message });
|
|
}
|
|
});
|
|
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
|