31022c6a6f
- clinica_id (tenant do scoreodonto, clinicas.id) como eixo de isolamento em clinics_devices e images (+índices); clinic_name vira rótulo legível. - Cadastro de device, socket (machine_token) e uploads (direct + legado) gravam clinica_id. - Listagens (/images, /patients, /by-patient, /unique-clinics) filtram por clinica_id. - /clients: lista clientes Windows ativos (inclui autenticados sem client-identify), tempo real + polling, exibe a clínica. - send-image marcado como legado (client real usa Direct Upload). Inclui trabalho de dev acumulado no working tree (não havia commits desde origin/main). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
146 lines
4.5 KiB
JavaScript
146 lines
4.5 KiB
JavaScript
const jwt = require('jsonwebtoken');
|
|
const bcrypt = require('bcrypt');
|
|
const db = require('./database');
|
|
|
|
const JWT_SECRET = process.env.JWT_SECRET || 'sua-chave-secreta-alterar-em-producao';
|
|
const JWT_EXPIRES_IN = '7d';
|
|
|
|
// ================================================================
|
|
// MIDDLEWARE DE AUTENTICAÇÃO
|
|
// ================================================================
|
|
|
|
function authenticateToken(req, res, next) {
|
|
const authHeader = req.headers['authorization'];
|
|
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
|
|
|
|
// Identifica se a rota é uma API (via URL ou cabeçalho Accept)
|
|
const isApiRoute = req.originalUrl.includes('/api') ||
|
|
(req.headers.accept && req.headers.accept.includes('application/json'));
|
|
|
|
// Se não tem token, verifica se está tentando acessar uma página
|
|
if (!token) {
|
|
const isLoginRoute = req.originalUrl.includes('/login') || req.originalUrl === '/auth/login.html';
|
|
|
|
if (isLoginRoute || req.originalUrl === '/status') {
|
|
return next();
|
|
}
|
|
|
|
if (isApiRoute) {
|
|
return res.status(401).json({ error: 'Token não fornecido' });
|
|
}
|
|
|
|
return res.redirect('/login');
|
|
}
|
|
|
|
// Verificar token via JWT (Fluxo padrão)
|
|
jwt.verify(token, JWT_SECRET, async (err, user) => {
|
|
if (err) {
|
|
// Se não for um JWT válido, vamos verificar se é um Token Estático de API
|
|
try {
|
|
const apiToken = await db.get('SELECT * FROM api_tokens WHERE token = $1', [token]);
|
|
if (apiToken) {
|
|
// Token estático encontrado! Atualizar last_used_at em background
|
|
db.run('UPDATE api_tokens SET last_used_at = CURRENT_TIMESTAMP WHERE id = $1', [apiToken.id]).catch(() => {});
|
|
|
|
// Injetar usuário "mock" na requisição com permissão de admin
|
|
req.user = {
|
|
id: -1,
|
|
username: 'api_token',
|
|
is_admin: true,
|
|
email: 'api@system.local'
|
|
};
|
|
return next();
|
|
}
|
|
} catch (dbErr) {
|
|
console.error('Erro ao verificar API Token estático:', dbErr);
|
|
}
|
|
|
|
if (isApiRoute) {
|
|
return res.status(403).json({ error: 'Token inválido' });
|
|
}
|
|
return res.redirect('/login');
|
|
}
|
|
|
|
req.user = user;
|
|
next();
|
|
});
|
|
}
|
|
|
|
// ================================================================
|
|
// HASH DE SENHA
|
|
// ================================================================
|
|
|
|
async function hashPassword(password) {
|
|
const saltRounds = 10;
|
|
return await bcrypt.hash(password, saltRounds);
|
|
}
|
|
|
|
async function verifyPassword(password, hash) {
|
|
return await bcrypt.compare(password, hash);
|
|
}
|
|
|
|
// ================================================================
|
|
// GERAR TOKEN JWT
|
|
// ================================================================
|
|
|
|
function generateToken(user, expiresIn = JWT_EXPIRES_IN) {
|
|
return jwt.sign(
|
|
{
|
|
id: user.id,
|
|
username: user.username,
|
|
email: user.email,
|
|
is_admin: user.is_admin
|
|
},
|
|
JWT_SECRET,
|
|
{ expiresIn: expiresIn }
|
|
);
|
|
}
|
|
|
|
// ================================================================
|
|
// CRIAR USUÁRIO INICIAL
|
|
// ================================================================
|
|
|
|
async function createInitialUser() {
|
|
try {
|
|
// Verificar se usuário já existe
|
|
const existingUser = await db.get('SELECT * FROM users WHERE username = $1', ['rcesar']);
|
|
|
|
if (existingUser) {
|
|
console.log('👤 Usuário admin já existe:', existingUser.username);
|
|
return;
|
|
}
|
|
|
|
const initialPassword = process.env.INITIAL_ADMIN_PASSWORD;
|
|
if (!initialPassword) {
|
|
console.log('⚠️ INITIAL_ADMIN_PASSWORD não definido — usuário admin inicial não será criado automaticamente');
|
|
return;
|
|
}
|
|
|
|
const passwordHash = await hashPassword(initialPassword);
|
|
|
|
await db.run(
|
|
`INSERT INTO users (username, email, password_hash, is_admin, created_at)
|
|
VALUES ($1, $2, $3, true, CURRENT_TIMESTAMP) RETURNING id`,
|
|
['rcesar', 'rcesar@rcesar.com', passwordHash]
|
|
);
|
|
|
|
console.log('✅ Usuário admin criado: rcesar');
|
|
} catch (error) {
|
|
// Ignorar se tabela não existe ainda
|
|
if (error.message.includes('no such table')) {
|
|
console.log('⚠️ Tabela users não existe. Será criada pelo database.js');
|
|
} else {
|
|
console.error('❌ Erro ao criar usuário inicial:', error);
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
authenticateToken,
|
|
hashPassword,
|
|
verifyPassword,
|
|
generateToken,
|
|
createInitialUser,
|
|
JWT_SECRET
|
|
};
|