Actualizar server.js
This commit is contained in:
48
server.js
48
server.js
@@ -40,7 +40,7 @@ async function autoUpdateDB() {
|
|||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
full_name TEXT NOT NULL,
|
full_name TEXT NOT NULL,
|
||||||
phone TEXT UNIQUE NOT NULL,
|
phone TEXT NOT NULL, -- ELIMINADO 'UNIQUE' EN DEFINICIÓN (Se gestiona abajo)
|
||||||
email TEXT UNIQUE NOT NULL,
|
email TEXT UNIQUE NOT NULL,
|
||||||
dni TEXT,
|
dni TEXT,
|
||||||
address TEXT,
|
address TEXT,
|
||||||
@@ -80,6 +80,15 @@ async function autoUpdateDB() {
|
|||||||
);
|
);
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
// --- CORRECCIÓN MULTITENANT ---
|
||||||
|
// Eliminamos la restricción global de teléfono único si existe para permitir
|
||||||
|
// que el mismo teléfono esté en varias empresas.
|
||||||
|
try {
|
||||||
|
await client.query(`ALTER TABLE users DROP CONSTRAINT IF EXISTS users_phone_key`);
|
||||||
|
} catch (e) {
|
||||||
|
// Ignoramos si no existe o hay error menor
|
||||||
|
}
|
||||||
|
|
||||||
// Parches
|
// Parches
|
||||||
await client.query(`
|
await client.query(`
|
||||||
DO $$ BEGIN
|
DO $$ BEGIN
|
||||||
@@ -156,16 +165,18 @@ app.post("/auth/register", async (req, res) => {
|
|||||||
const passwordHash = await bcrypt.hash(password, 10);
|
const passwordHash = await bcrypt.hash(password, 10);
|
||||||
await client.query('BEGIN');
|
await client.query('BEGIN');
|
||||||
|
|
||||||
const checkUser = await client.query("SELECT * FROM users WHERE email = $1 OR phone = $2", [email, p]);
|
// Verificamos Email (Que debe ser único globalmente para login)
|
||||||
|
const checkUser = await client.query("SELECT * FROM users WHERE email = $1", [email]);
|
||||||
let userId;
|
let userId;
|
||||||
|
|
||||||
if (checkUser.rowCount > 0) {
|
if (checkUser.rowCount > 0) {
|
||||||
const existing = checkUser.rows[0];
|
const existing = checkUser.rows[0];
|
||||||
|
// Si existe y ya está verificado, error.
|
||||||
if (existing.is_verified) {
|
if (existing.is_verified) {
|
||||||
await client.query('ROLLBACK');
|
await client.query('ROLLBACK');
|
||||||
return res.status(409).json({ ok: false, error: "Usuario ya registrado." });
|
return res.status(409).json({ ok: false, error: "El email ya está registrado." });
|
||||||
}
|
}
|
||||||
await client.query("UPDATE users SET full_name=$1, address=$2, dni=$3, password_hash=$4 WHERE id=$5", [fullName, address, dni, passwordHash, existing.id]);
|
await client.query("UPDATE users SET full_name=$1, address=$2, dni=$3, password_hash=$4, phone=$5 WHERE id=$6", [fullName, address, dni, passwordHash, p, existing.id]);
|
||||||
userId = existing.id;
|
userId = existing.id;
|
||||||
} else {
|
} else {
|
||||||
const insert = await client.query(
|
const insert = await client.query(
|
||||||
@@ -217,15 +228,11 @@ app.post("/auth/login", async (req, res) => {
|
|||||||
} catch(e) { res.status(500).json({ ok: false, error: "Error login" }); }
|
} catch(e) { res.status(500).json({ ok: false, error: "Error login" }); }
|
||||||
});
|
});
|
||||||
|
|
||||||
// 🆕 NUEVO: RECUPERAR CONTRASEÑA 🆕
|
// RECUPERAR CONTRASEÑA
|
||||||
|
|
||||||
// Paso 1: Solicitar código con DNI y Teléfono
|
|
||||||
app.post("/auth/forgot-password", async (req, res) => {
|
app.post("/auth/forgot-password", async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { dni, phone } = req.body;
|
const { dni, phone } = req.body;
|
||||||
const p = normalizePhone(phone);
|
const p = normalizePhone(phone);
|
||||||
|
|
||||||
// Buscamos usuario por DNI y Teléfono
|
|
||||||
const userQuery = await pool.query("SELECT id FROM users WHERE dni = $1 AND phone = $2", [dni, p]);
|
const userQuery = await pool.query("SELECT id FROM users WHERE dni = $1 AND phone = $2", [dni, p]);
|
||||||
|
|
||||||
if (userQuery.rowCount === 0) {
|
if (userQuery.rowCount === 0) {
|
||||||
@@ -235,9 +242,8 @@ app.post("/auth/forgot-password", async (req, res) => {
|
|||||||
const userId = userQuery.rows[0].id;
|
const userId = userQuery.rows[0].id;
|
||||||
const code = genCode6();
|
const code = genCode6();
|
||||||
const codeHash = await bcrypt.hash(code, 10);
|
const codeHash = await bcrypt.hash(code, 10);
|
||||||
const expiresAt = new Date(Date.now() + 10 * 60 * 1000); // 10 min
|
const expiresAt = new Date(Date.now() + 10 * 60 * 1000);
|
||||||
|
|
||||||
// Guardamos con propósito 'password_reset'
|
|
||||||
await pool.query("INSERT INTO login_codes (user_id, phone, code_hash, purpose, expires_at) VALUES ($1, $2, $3, 'password_reset', $4)",
|
await pool.query("INSERT INTO login_codes (user_id, phone, code_hash, purpose, expires_at) VALUES ($1, $2, $3, 'password_reset', $4)",
|
||||||
[userId, p, codeHash, expiresAt]);
|
[userId, p, codeHash, expiresAt]);
|
||||||
|
|
||||||
@@ -250,14 +256,12 @@ app.post("/auth/forgot-password", async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Paso 2: Resetear contraseña
|
|
||||||
app.post("/auth/reset-password", async (req, res) => {
|
app.post("/auth/reset-password", async (req, res) => {
|
||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
try {
|
try {
|
||||||
const { phone, code, newPassword } = req.body;
|
const { phone, code, newPassword } = req.body;
|
||||||
const p = normalizePhone(phone);
|
const p = normalizePhone(phone);
|
||||||
|
|
||||||
// Buscar código válido para reset
|
|
||||||
const codeQuery = await client.query(`
|
const codeQuery = await client.query(`
|
||||||
SELECT lc.*, u.id as uid
|
SELECT lc.*, u.id as uid
|
||||||
FROM login_codes lc
|
FROM login_codes lc
|
||||||
@@ -275,7 +279,6 @@ app.post("/auth/reset-password", async (req, res) => {
|
|||||||
const valid = await bcrypt.compare(String(code), row.code_hash);
|
const valid = await bcrypt.compare(String(code), row.code_hash);
|
||||||
if (!valid) return res.status(400).json({ ok: false, error: "Código incorrecto" });
|
if (!valid) return res.status(400).json({ ok: false, error: "Código incorrecto" });
|
||||||
|
|
||||||
// Cambiar contraseña
|
|
||||||
const newHash = await bcrypt.hash(newPassword, 10);
|
const newHash = await bcrypt.hash(newPassword, 10);
|
||||||
|
|
||||||
await client.query('BEGIN');
|
await client.query('BEGIN');
|
||||||
@@ -339,6 +342,7 @@ app.get("/admin/users", authMiddleware, async (req, res) => {
|
|||||||
} catch (e) { res.status(500).json({ ok: false, error: "Error listar users" }); }
|
} catch (e) { res.status(500).json({ ok: false, error: "Error listar users" }); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// CREAR USUARIO (CORREGIDO MULTITENANT)
|
||||||
app.post("/admin/users", authMiddleware, async (req, res) => {
|
app.post("/admin/users", authMiddleware, async (req, res) => {
|
||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
try {
|
try {
|
||||||
@@ -346,6 +350,16 @@ app.post("/admin/users", authMiddleware, async (req, res) => {
|
|||||||
if (!email || !password || !fullName || !phone) return res.status(400).json({ ok: false, error: "Faltan datos" });
|
if (!email || !password || !fullName || !phone) return res.status(400).json({ ok: false, error: "Faltan datos" });
|
||||||
const p = normalizePhone(phone);
|
const p = normalizePhone(phone);
|
||||||
const passwordHash = await bcrypt.hash(password, 10);
|
const passwordHash = await bcrypt.hash(password, 10);
|
||||||
|
|
||||||
|
// NUEVO: Verificamos duplicado SOLO dentro de mi empresa
|
||||||
|
const checkDup = await client.query(
|
||||||
|
"SELECT id FROM users WHERE (phone=$1 OR email=$2) AND owner_id=$3",
|
||||||
|
[p, email, req.user.accountId]
|
||||||
|
);
|
||||||
|
if (checkDup.rowCount > 0) {
|
||||||
|
return res.status(400).json({ ok: false, error: "❌ Este empleado ya existe en tu empresa (Email o Teléfono)." });
|
||||||
|
}
|
||||||
|
|
||||||
await client.query('BEGIN');
|
await client.query('BEGIN');
|
||||||
const insert = await client.query(
|
const insert = await client.query(
|
||||||
`INSERT INTO users (full_name, email, password_hash, role, phone, is_verified, owner_id) VALUES ($1, $2, $3, $4, $5, TRUE, $6) RETURNING id`,
|
`INSERT INTO users (full_name, email, password_hash, role, phone, is_verified, owner_id) VALUES ($1, $2, $3, $4, $5, TRUE, $6) RETURNING id`,
|
||||||
@@ -359,9 +373,9 @@ app.post("/admin/users", authMiddleware, async (req, res) => {
|
|||||||
res.json({ ok: true, msg: "Usuario creado" });
|
res.json({ ok: true, msg: "Usuario creado" });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await client.query('ROLLBACK');
|
await client.query('ROLLBACK');
|
||||||
if (e.code === '23505') {
|
// Mantenemos el check global de email único, pero permitimos teléfonos duplicados en distintas empresas
|
||||||
if (e.constraint && e.constraint.includes('email')) return res.status(400).json({ ok: false, error: "❌ Email existe." });
|
if (e.code === '23505' && e.constraint.includes('email')) {
|
||||||
if (e.constraint && e.constraint.includes('phone')) return res.status(400).json({ ok: false, error: "❌ Teléfono existe." });
|
return res.status(400).json({ ok: false, error: "❌ El Email ya está usado en el sistema." });
|
||||||
}
|
}
|
||||||
res.status(500).json({ ok: false, error: "Error creando usuario" });
|
res.status(500).json({ ok: false, error: "Error creando usuario" });
|
||||||
} finally { client.release(); }
|
} finally { client.release(); }
|
||||||
|
|||||||
Reference in New Issue
Block a user