feat: agregar sistema de autenticación con usuarios y roles
- Agregar Login con autenticación por DNI y contraseña - Crear tabla de usuarios con roles (admin, médico, enfermero) - Implementar gestión de usuarios (CRUD) para administradores - Agregar control de permisos basado en roles en el menú - Persistir estado de sesión en backend - Inicializar usuario admin por defecto (DNI: 12345678, pass: admin123) - Corregir inicialización de arrays en el store para evitar errores al cargar estado
This commit is contained in:
@@ -2,6 +2,7 @@ import sqlite3 from 'sqlite3';
|
||||
import { open } from 'sqlite';
|
||||
import { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const DB_PATH = __dirname + '/data/hospital.db';
|
||||
@@ -19,6 +20,22 @@ export async function openDb() {
|
||||
);
|
||||
`);
|
||||
|
||||
await db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS usuarios (
|
||||
id TEXT PRIMARY KEY,
|
||||
apellido TEXT NOT NULL,
|
||||
nombre TEXT NOT NULL,
|
||||
dni TEXT UNIQUE NOT NULL,
|
||||
fechaNacimiento TEXT NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
rol TEXT NOT NULL CHECK(rol IN ('admin', 'medico', 'enfermero')),
|
||||
matriculaProfesional TEXT,
|
||||
passwordHash TEXT NOT NULL,
|
||||
areaId TEXT,
|
||||
fechaCreacion TEXT NOT NULL
|
||||
);
|
||||
`);
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
@@ -34,3 +51,84 @@ export async function setValue(key, value) {
|
||||
await db.run('INSERT INTO kv (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value', key, value);
|
||||
await db.close();
|
||||
}
|
||||
|
||||
export async function getUsuarioByDni(dni) {
|
||||
const db = await openDb();
|
||||
const row = await db.get('SELECT * FROM usuarios WHERE dni = ?', dni);
|
||||
await db.close();
|
||||
return row || null;
|
||||
}
|
||||
|
||||
export async function getUsuarioById(id) {
|
||||
const db = await openDb();
|
||||
const row = await db.get('SELECT * FROM usuarios WHERE id = ?', id);
|
||||
await db.close();
|
||||
return row || null;
|
||||
}
|
||||
|
||||
export async function getAllUsuarios() {
|
||||
const db = await openDb();
|
||||
const rows = await db.all('SELECT * FROM usuarios ORDER BY apellido, nombre');
|
||||
await db.close();
|
||||
return rows;
|
||||
}
|
||||
|
||||
export async function createUsuario(usuario) {
|
||||
const db = await openDb();
|
||||
await db.run(
|
||||
`INSERT INTO usuarios (id, apellido, nombre, dni, fechaNacimiento, email, rol, matriculaProfesional, passwordHash, areaId, fechaCreacion)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
usuario.id,
|
||||
usuario.apellido,
|
||||
usuario.nombre,
|
||||
usuario.dni,
|
||||
usuario.fechaNacimiento,
|
||||
usuario.email,
|
||||
usuario.rol,
|
||||
usuario.matriculaProfesional || null,
|
||||
usuario.passwordHash,
|
||||
usuario.areaId || null,
|
||||
usuario.fechaCreacion
|
||||
);
|
||||
await db.close();
|
||||
}
|
||||
|
||||
export async function updateUsuario(id, datos) {
|
||||
const db = await openDb();
|
||||
const fields = [];
|
||||
const values = [];
|
||||
|
||||
if (datos.apellido !== undefined) { fields.push('apellido = ?'); values.push(datos.apellido); }
|
||||
if (datos.nombre !== undefined) { fields.push('nombre = ?'); values.push(datos.nombre); }
|
||||
if (datos.fechaNacimiento !== undefined) { fields.push('fechaNacimiento = ?'); values.push(datos.fechaNacimiento); }
|
||||
if (datos.email !== undefined) { fields.push('email = ?'); values.push(datos.email); }
|
||||
if (datos.rol !== undefined) { fields.push('rol = ?'); values.push(datos.rol); }
|
||||
if (datos.matriculaProfesional !== undefined) { fields.push('matriculaProfesional = ?'); values.push(datos.matriculaProfesional); }
|
||||
if (datos.passwordHash !== undefined) { fields.push('passwordHash = ?'); values.push(datos.passwordHash); }
|
||||
if (datos.areaId !== undefined) { fields.push('areaId = ?'); values.push(datos.areaId); }
|
||||
|
||||
if (fields.length > 0) {
|
||||
values.push(id);
|
||||
await db.run(`UPDATE usuarios SET ${fields.join(', ')} WHERE id = ?`, values);
|
||||
}
|
||||
await db.close();
|
||||
}
|
||||
|
||||
export async function deleteUsuario(id) {
|
||||
const db = await openDb();
|
||||
await db.run('DELETE FROM usuarios WHERE id = ?', id);
|
||||
await db.close();
|
||||
}
|
||||
|
||||
export async function verifyPassword(dni, password) {
|
||||
const db = await openDb();
|
||||
const row = await db.get('SELECT passwordHash FROM usuarios WHERE dni = ?', dni);
|
||||
await db.close();
|
||||
|
||||
if (!row) return false;
|
||||
return bcrypt.compareSync(password, row.passwordHash);
|
||||
}
|
||||
|
||||
export async function hashPassword(password) {
|
||||
return bcrypt.hashSync(password, 10);
|
||||
}
|
||||
|
||||
+200
-1
@@ -1,6 +1,6 @@
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import { getValue, setValue } from './db.js';
|
||||
import { getValue, setValue, getUsuarioByDni, getUsuarioById, getAllUsuarios, createUsuario, updateUsuario, deleteUsuario, verifyPassword, hashPassword } from './db.js';
|
||||
|
||||
const app = express();
|
||||
app.use(cors());
|
||||
@@ -34,3 +34,202 @@ app.put('/api/state', async (req, res) => {
|
||||
app.listen(PORT, () => {
|
||||
console.log(`API server listening on http://localhost:${PORT}`);
|
||||
});
|
||||
|
||||
// Crear usuario admin inicial si no existe
|
||||
async function initAdminUser() {
|
||||
try {
|
||||
const adminExists = await getUsuarioByDni('12345678');
|
||||
if (!adminExists) {
|
||||
const passwordHash = await hashPassword('admin123');
|
||||
const adminUser = {
|
||||
id: generateUUID(),
|
||||
apellido: 'Admin',
|
||||
nombre: 'Sistema',
|
||||
dni: '12345678',
|
||||
fechaNacimiento: '1970-01-01',
|
||||
email: 'admin@hospital.gob.ar',
|
||||
rol: 'admin',
|
||||
matriculaProfesional: null,
|
||||
passwordHash,
|
||||
areaId: null,
|
||||
fechaCreacion: new Date().toISOString().split('T')[0]
|
||||
};
|
||||
await createUsuario(adminUser);
|
||||
console.log('Usuario admin creado: DNI 12345678, Password admin123');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error creating admin user:', err);
|
||||
}
|
||||
}
|
||||
|
||||
initAdminUser();
|
||||
|
||||
// === AUTENTICACIÓN ===
|
||||
|
||||
function generateUUID() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
||||
const r = (Math.random() * 16) | 0;
|
||||
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
app.post('/api/auth/login', async (req, res) => {
|
||||
try {
|
||||
const { dni, password } = req.body;
|
||||
if (!dni || !password) {
|
||||
return res.status(400).json({ error: 'DNI y contraseña requeridos' });
|
||||
}
|
||||
|
||||
const usuario = await getUsuarioByDni(dni);
|
||||
if (!usuario) {
|
||||
return res.status(401).json({ error: 'Usuario no encontrado' });
|
||||
}
|
||||
|
||||
const validPassword = await verifyPassword(dni, password);
|
||||
if (!validPassword) {
|
||||
return res.status(401).json({ error: 'Contraseña incorrecta' });
|
||||
}
|
||||
|
||||
const { passwordHash, ...userWithoutPassword } = usuario;
|
||||
res.json(userWithoutPassword);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Error en autenticación' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/auth/change-password', async (req, res) => {
|
||||
try {
|
||||
const { dni, oldPassword, newPassword } = req.body;
|
||||
if (!dni || !oldPassword || !newPassword) {
|
||||
return res.status(400).json({ error: 'Todos los campos son requeridos' });
|
||||
}
|
||||
|
||||
const validPassword = await verifyPassword(dni, oldPassword);
|
||||
if (!validPassword) {
|
||||
return res.status(401).json({ error: 'Contraseña actual incorrecta' });
|
||||
}
|
||||
|
||||
const newHash = await hashPassword(newPassword);
|
||||
await updateUsuarioByDni(dni, { passwordHash: newHash });
|
||||
res.json({ ok: true });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Error al cambiar contraseña' });
|
||||
}
|
||||
});
|
||||
|
||||
app.put('/api/auth/update-email', async (req, res) => {
|
||||
try {
|
||||
const { dni, newEmail } = req.body;
|
||||
if (!dni || !newEmail) {
|
||||
return res.status(400).json({ error: 'DNI y email son requeridos' });
|
||||
}
|
||||
|
||||
await updateUsuarioByDni(dni, { email: newEmail });
|
||||
res.json({ ok: true });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Error al actualizar email' });
|
||||
}
|
||||
});
|
||||
|
||||
// === GESTIÓN DE USUARIOS (ADMIN) ===
|
||||
|
||||
app.get('/api/usuarios', async (req, res) => {
|
||||
try {
|
||||
const usuarios = await getAllUsuarios();
|
||||
const usuariosSinPassword = usuarios.map(({ passwordHash, ...u }) => u);
|
||||
res.json(usuariosSinPassword);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Error al obtener usuarios' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/usuarios', async (req, res) => {
|
||||
try {
|
||||
const { apellido, nombre, dni, fechaNacimiento, email, rol, matriculaProfesional, password, areaId } = req.body;
|
||||
|
||||
if (!apellido || !nombre || !dni || !fechaNacimiento || !email || !rol || !password) {
|
||||
return res.status(400).json({ error: 'Todos los campos obligatorios deben estar presentes' });
|
||||
}
|
||||
|
||||
const existente = await getUsuarioByDni(dni);
|
||||
if (existente) {
|
||||
return res.status(400).json({ error: 'Ya existe un usuario con ese DNI' });
|
||||
}
|
||||
|
||||
const passwordHash = await hashPassword(password);
|
||||
const usuario = {
|
||||
id: generateUUID(),
|
||||
apellido,
|
||||
nombre,
|
||||
dni,
|
||||
fechaNacimiento,
|
||||
email,
|
||||
rol,
|
||||
matriculaProfesional: matriculaProfesional || null,
|
||||
passwordHash,
|
||||
areaId: areaId || null,
|
||||
fechaCreacion: new Date().toISOString().split('T')[0]
|
||||
};
|
||||
|
||||
await createUsuario(usuario);
|
||||
res.json({ ...usuario, passwordHash: undefined });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Error al crear usuario' });
|
||||
}
|
||||
});
|
||||
|
||||
app.put('/api/usuarios/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { apellido, nombre, fechaNacimiento, email, rol, matriculaProfesional, areaId, password } = req.body;
|
||||
|
||||
const usuario = await getUsuarioById(id);
|
||||
if (!usuario) {
|
||||
return res.status(404).json({ error: 'Usuario no encontrado' });
|
||||
}
|
||||
|
||||
const datos = {};
|
||||
if (apellido !== undefined) datos.apellido = apellido;
|
||||
if (nombre !== undefined) datos.nombre = nombre;
|
||||
if (fechaNacimiento !== undefined) datos.fechaNacimiento = fechaNacimiento;
|
||||
if (email !== undefined) datos.email = email;
|
||||
if (rol !== undefined) datos.rol = rol;
|
||||
if (matriculaProfesional !== undefined) datos.matriculaProfesional = matriculaProfesional;
|
||||
if (areaId !== undefined) datos.areaId = areaId;
|
||||
if (password) {
|
||||
datos.passwordHash = await hashPassword(password);
|
||||
}
|
||||
|
||||
await updateUsuario(id, datos);
|
||||
res.json({ ok: true });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Error al actualizar usuario' });
|
||||
}
|
||||
});
|
||||
|
||||
app.delete('/api/usuarios/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
await deleteUsuario(id);
|
||||
res.json({ ok: true });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Error al eliminar usuario' });
|
||||
}
|
||||
});
|
||||
|
||||
// Helper para actualizar por DNI
|
||||
async function updateUsuarioByDni(dni, datos) {
|
||||
const { getUsuarioByDni, updateUsuario } = await import('./db.js');
|
||||
const usuario = await getUsuarioByDni(dni);
|
||||
if (usuario) {
|
||||
await updateUsuario(usuario.id, datos);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -6,9 +6,11 @@
|
||||
"start": "node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^11.7.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.21.2",
|
||||
"http-proxy-middleware": "^3.0.5"
|
||||
"http-proxy-middleware": "^3.0.5",
|
||||
"sqlite": "^5.1.7",
|
||||
"sqlite3": "^5.1.7"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user