From be2b090ddc8a5a0029e745a3fb367a714b1e43a7 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 8 Feb 2026 13:03:52 -0500 Subject: [PATCH] feat: login unificado BI-CCC com deteccao automatica de role - Adiciona coluna 'role' na tabela agentes (agente|admin) - Migra admins existentes para tabela agentes com role='admin' - Unifica login em /login com redirect baseado em role - Sessao unificada req.session.user com {id, email, nome, role, agente_id} - Middleware requireRole() para proteger rotas por role - Admin panel com selector de role ao criar/editar usuarios - Atualiza branding para "BI - CCC" com logo CambioReal - Redirects: /admin/login -> /login, /admin/logout -> /logout Co-Authored-By: Claude Opus 4.5 --- public/login.html | 34 +- public/logo-small.png | Bin 0 -> 4914 bytes server.js | 240 ++++++- src/admin-auth.js | 37 ++ src/admin-home.js | 433 +++++++++++++ src/admin-panel.js | 536 ++++++++++++++++ src/auth.js | 66 +- src/dashboard.js | 1427 +++++++++++++++++++++++++++++++++++++++-- src/db-local.js | 33 + 9 files changed, 2710 insertions(+), 96 deletions(-) create mode 100644 public/logo-small.png create mode 100644 src/admin-auth.js create mode 100644 src/admin-home.js create mode 100644 src/admin-panel.js diff --git a/public/login.html b/public/login.html index 1a5e3d9..9cbfa0f 100644 --- a/public/login.html +++ b/public/login.html @@ -3,7 +3,7 @@ -BI Agentes — Login +BI - CCC | CambioReal + + + +
+
+

BI - CCC

+
CambioReal Central Command
+
+
+
Admin: ${admin.nome}
+ Gerenciar Agentes + Dashboard Completo + Sair +
+
+ +
+
+

${hoje}

+
Atualizado: ${now}
+
+ +
+
+
+
Total de Ordens Hoje
+
${Number(totalVar) >= 0 ? '+' : ''}${totalVar}%
+
+
${totalHoje}
+
Ontem: ${totalOntem} ordens
+
+
+
+
BRL → USD
+
${Number(brlUsdVar) >= 0 ? '+' : ''}${brlUsdVar}%
+
+
${stats.brlUsd.hoje.qtd}
+
Ontem: ${stats.brlUsd.ontem.qtd}
+
+
+
+
USD → BRL
+
${Number(usdBrlVar) >= 0 ? '+' : ''}${usdBrlVar}%
+
+
${stats.usdBrl.hoje.qtd}
+
Ontem: ${stats.usdBrl.ontem.qtd}
+
+
+
+
USD → USD (Balance)
+
${Number(usdUsdVar) >= 0 ? '+' : ''}${usdUsdVar}%
+
+
${stats.usdUsd.hoje.qtd}
+
Ontem: ${stats.usdUsd.ontem.qtd}
+
+
+ +
+
+

Quantidade de Ordens por Fluxo

+
+
+
+

Volume USD por Fluxo

+
+
+
+ +
+
+

BRL → USD (Remessas)

+
+ Ordens Hoje + ${stats.brlUsd.hoje.qtd} +
+
+ Ordens Ontem + ${stats.brlUsd.ontem.qtd} +
+
+ Volume BRL Hoje + ${formatBRL(stats.brlUsd.hoje.total_brl)} +
+
+ Volume USD Hoje + ${formatUSD(stats.brlUsd.hoje.total_usd)} +
+
+ Variacao + ${Number(brlUsdVar) >= 0 ? '+' : ''}${brlUsdVar}% +
+
+
+

USD → BRL (Recebimentos)

+
+ Ordens Hoje + ${stats.usdBrl.hoje.qtd} +
+
+ Ordens Ontem + ${stats.usdBrl.ontem.qtd} +
+
+ Volume BRL Hoje + ${formatBRL(stats.usdBrl.hoje.total_brl)} +
+
+ Volume USD Hoje + ${formatUSD(stats.usdBrl.hoje.total_usd)} +
+
+ Variacao + ${Number(usdBrlVar) >= 0 ? '+' : ''}${usdBrlVar}% +
+
+
+

USD → USD (Balance)

+
+ Ordens Hoje + ${stats.usdUsd.hoje.qtd} +
+
+ Ordens Ontem + ${stats.usdUsd.ontem.qtd} +
+
+ Volume USD Hoje + ${formatUSD(stats.usdUsd.hoje.total_usd)} +
+
+ Volume USD Ontem + ${formatUSD(stats.usdUsd.ontem.total_usd)} +
+
+ Variacao + ${Number(usdUsdVar) >= 0 ? '+' : ''}${usdUsdVar}% +
+
+
+ + +
+ + + + + +`; +} + +module.exports = { buildAdminHomeHTML }; diff --git a/src/admin-panel.js b/src/admin-panel.js new file mode 100644 index 0000000..ff2a8bb --- /dev/null +++ b/src/admin-panel.js @@ -0,0 +1,536 @@ +/** + * Admin Panel - HTML builder for agent management + */ + +function buildAdminHTML(agentes, admin) { + const now = new Date().toLocaleString('pt-BR'); + return ` + + + + +BI - CCC - CambioReal Central Command + + + + + +
+
+

BI - CCC

+
CambioReal Central Command
+
+
+
Admin: ${admin.nome}
+ Dashboard Geral + Sair +
+
+ +
+
+ +
+

Usuarios Cadastrados (${agentes.length})

+ +
+ +
+
+ + + + + + + + + + + + + + + ${agentes.map(a => ` + + + + + + + + + + + `).join('')} + +
IDNomeE-mailRoleAgente IDStatusCriado emAcoes
${a.id}${a.nome}${a.email}${a.role === 'admin' ? 'Admin' : 'Agente'}${a.role === 'admin' ? '-' : a.agente_id}${a.ativo ? 'Ativo' : 'Inativo'}${a.created_at ? new Date(a.created_at).toLocaleDateString('pt-BR') : '-'} + ${a.role === 'agente' ? `Emular` : ''} + + + +
+
+
+
+ + + + + + + + + + + +`; +} + +module.exports = { buildAdminHTML }; diff --git a/src/auth.js b/src/auth.js index b70380e..949c91e 100644 --- a/src/auth.js +++ b/src/auth.js @@ -1,18 +1,33 @@ /** - * Autenticação — login/logout com bcrypt + express-session + * Autenticacao Unificada — login/logout com bcrypt + express-session + * Suporta roles: 'agente' | 'admin' */ const bcrypt = require('bcrypt'); const db = require('./db-local'); const SALT_ROUNDS = 10; -async function createAgente(email, senha, agenteId, nome) { +/** + * Cria um novo usuario (agente ou admin) + */ +async function createUser(email, senha, nome, role = 'agente', agenteId = 0) { const hash = await bcrypt.hash(senha, SALT_ROUNDS); return db.prepare( - 'INSERT INTO agentes (email, senha_hash, agente_id, nome) VALUES (?, ?, ?, ?)' - ).run(email, hash, agenteId, nome); + 'INSERT INTO agentes (email, senha_hash, agente_id, nome, role) VALUES (?, ?, ?, ?, ?)' + ).run(email, hash, agenteId, nome, role); } +/** + * Cria agente (retrocompatibilidade) + */ +async function createAgente(email, senha, agenteId, nome) { + return createUser(email, senha, nome, 'agente', agenteId); +} + +/** + * Autentica usuario por email/senha + * Retorna usuario com role ou null se invalido + */ async function authenticate(email, senha) { const row = db.prepare( 'SELECT * FROM agentes WHERE email = ? AND ativo = 1' @@ -22,9 +37,48 @@ async function authenticate(email, senha) { return match ? row : null; } +/** + * Middleware que verifica autenticacao e roles + * Uso: requireRole() - qualquer usuario logado + * requireRole('admin') - apenas admins + * requireRole('agente', 'admin') - ambos + */ +function requireRole(...roles) { + return (req, res, next) => { + if (!req.session?.user) { + return res.redirect('/login'); + } + if (roles.length && !roles.includes(req.session.user.role)) { + return res.status(403).send('Acesso negado'); + } + next(); + }; +} + +/** + * Middleware simples - apenas verifica se esta logado + * Retrocompatibilidade com requireAuth + */ function requireAuth(req, res, next) { - if (req.session && req.session.agente) return next(); + if (req.session && req.session.user) return next(); res.redirect('/login'); } -module.exports = { createAgente, authenticate, requireAuth }; +/** + * Atualiza senha de usuario + */ +async function updatePassword(id, novaSenha) { + const hash = await bcrypt.hash(novaSenha, SALT_ROUNDS); + return db.prepare( + 'UPDATE agentes SET senha_hash = ? WHERE id = ?' + ).run(hash, id); +} + +module.exports = { + createUser, + createAgente, + authenticate, + requireAuth, + requireRole, + updatePassword +}; diff --git a/src/dashboard.js b/src/dashboard.js index 503c9bd..ec2f3ed 100644 --- a/src/dashboard.js +++ b/src/dashboard.js @@ -2,14 +2,15 @@ * Gera HTML do dashboard — parametrizado por agente */ -function buildHTML(data, agente) { +function buildHTML(data, agente, isAgentDashboard = true, diasPeriodo = null, asyncLoad = false, isEmulating = false) { const now = new Date().toLocaleString('pt-BR'); + const isAdminDash = diasPeriodo !== null; return ` -BI — ${agente.nome} +BI - CCC — ${agente.nome}