b6da56617a
Hierarquia tenant_id (usuarios.id do dono) / clinica_id / pc / paciente, espelhando o scoreodonto (conta multi-clínica planejada: um dono gerencia várias clínicas). - Colunas tenant_id em clinics_devices e images (+índices); backfill device + 2776 imgs. - Cadastro de device, socket (machine_token), JWT de client e uploads gravam tenant_id. - Listagens (/images, /patients, /by-patient, /unique-clinics) filtram por tenant_id e/ou clinica_id. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
114 lines
3.7 KiB
JavaScript
114 lines
3.7 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const db = require('../database');
|
|
const bcrypt = require('bcrypt');
|
|
const jwt = require('jsonwebtoken');
|
|
const { JWT_SECRET } = require('../auth');
|
|
|
|
// ==============================================================
|
|
// 1. LOGIN DO CLIENTE (APP DESKTOP)
|
|
// ==============================================================
|
|
router.post('/login', async (req, res) => {
|
|
const { email, password, machine_token } = req.body;
|
|
|
|
if (!email || !password || !machine_token) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Email, Senha e Token da Máquina são obrigatórios'
|
|
});
|
|
}
|
|
|
|
try {
|
|
// 1. Buscar o dispositivo pelo Token
|
|
const device = await db.get(`SELECT * FROM clinics_devices WHERE machine_token = $1`, [machine_token]);
|
|
|
|
if (!device) {
|
|
return res.status(401).json({ success: false, message: 'Token da máquina inválido ou inexistente' });
|
|
}
|
|
|
|
// 2. Verificar se o acesso está ativo
|
|
if (!device.is_active) {
|
|
return res.status(403).json({ success: false, message: 'O acesso desta máquina foi desativado pelo administrador' });
|
|
}
|
|
|
|
// 3. Verificar o Email
|
|
if (device.email !== email) {
|
|
return res.status(401).json({ success: false, message: 'Email incorreto para esta máquina' });
|
|
}
|
|
|
|
// 4. Verificar a Senha
|
|
const validPassword = await bcrypt.compare(password, device.password_hash);
|
|
if (!validPassword) {
|
|
return res.status(401).json({ success: false, message: 'Senha incorreta' });
|
|
}
|
|
|
|
// Atualizar último IP conectado
|
|
const clientIp = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
|
|
await db.run(`UPDATE clinics_devices SET last_ip = $1 WHERE id = $2`, [clientIp, device.id]);
|
|
|
|
// 5. Gerar Token JWT de Sessão
|
|
// O Token vale por 2 horas, obrigando o cliente a re-logar ou pedir refresh
|
|
const sessionToken = jwt.sign(
|
|
{
|
|
clinicId: device.id,
|
|
tenantId: device.tenant_id, // conta/dono (scoreodonto)
|
|
clinicaId: device.clinica_id, // clínica (scoreodonto)
|
|
clinicName: device.clinic_name,
|
|
pcName: device.pc_name,
|
|
role: 'client_device'
|
|
},
|
|
JWT_SECRET,
|
|
{ expiresIn: '2h' }
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Login aprovado',
|
|
token: sessionToken,
|
|
clinic: {
|
|
name: device.clinic_name,
|
|
pc: device.pc_name
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Erro no login do cliente:', error);
|
|
res.status(500).json({ success: false, message: 'Erro interno de servidor' });
|
|
}
|
|
});
|
|
|
|
// ==============================================================
|
|
// 2. MIDDLEWARE DE VALIDAÇÃO (Para usar nas rotas de Upload)
|
|
// ==============================================================
|
|
const requireClientAuth = (req, res, next) => {
|
|
const authHeader = req.headers.authorization;
|
|
|
|
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
return res.status(401).json({ success: false, message: 'Token JWT não fornecido' });
|
|
}
|
|
|
|
const token = authHeader.split(' ')[1];
|
|
|
|
try {
|
|
const decoded = jwt.verify(token, JWT_SECRET);
|
|
if (decoded.role !== 'client_device') {
|
|
return res.status(403).json({ success: false, message: 'Acesso negado: Perfil inválido' });
|
|
}
|
|
|
|
// Anexar info ao request para uso posterior
|
|
req.clientDevice = decoded;
|
|
next();
|
|
} catch (error) {
|
|
if (error.name === 'TokenExpiredError') {
|
|
return res.status(401).json({ success: false, message: 'Sessão expirada. Faça login novamente.', expired: true });
|
|
}
|
|
return res.status(401).json({ success: false, message: 'Token JWT inválido' });
|
|
}
|
|
};
|
|
|
|
module.exports = {
|
|
router,
|
|
requireClientAuth,
|
|
JWT_SECRET
|
|
};
|