import type { Request, Response, NextFunction } from "express"; import { pool } from "../db/pool.ts"; export interface PlatformUser { id: number; business_id: number | null; email: string; name: string; role: "admin" | "owner" | "employee"; employee_id: number | null; avatar_color: string; } export interface AuthedRequest extends Request { user?: PlatformUser; } /** * DEUDA CONOCIDA: el token es el id del usuario en texto plano y la contraseña * se compara sin hashear. Se porta tal cual desde el backend de demo para no * romper `src/lib/api.ts`, el AuthProvider y los .mjs de prueba en el mismo * cambio. Endurecerlo es un entregable propio: bcrypt/Argon2id + sesión real + * los cinco sitios a la vez. */ export async function authRequired( req: AuthedRequest, res: Response, next: NextFunction ) { const header = req.header("authorization") || ""; const token = header.startsWith("Bearer ") ? header.slice(7) : req.header("x-user-id"); if (!token) { err(res, 401, "No autorizado"); return; } const userId = Number(token); if (!Number.isFinite(userId)) { err(res, 401, "Token inválido"); return; } const { rows } = await pool.query( `SELECT id, business_id, email, name, role, employee_id, avatar_color FROM users WHERE id = $1`, [userId] ); if (!rows[0]) { err(res, 401, "Usuario no encontrado"); return; } req.user = rows[0]; next(); } export function ownerOnly(req: AuthedRequest, res: Response, next: NextFunction) { if (req.user?.role !== "owner") { err(res, 403, "Solo la administradora puede realizar esta acción"); return; } next(); } /** * Administración de la plataforma: opera todas las cuentas y su `business_id` * es NULL. * * No se confunde con `ownerOnly`, que manda dentro de UN negocio. Son dos * autoridades distintas: la dueña de un spa no debe poder dar de alta cuentas * ajenas ni ver las credenciales de nadie. */ export function adminOnly(req: AuthedRequest, res: Response, next: NextFunction) { if (req.user?.role !== "admin") { err(res, 403, "Solo la administración de la plataforma puede realizar esta acción"); return; } next(); } export function err(res: Response, status: number, message: string) { return res.status(status).json({ error: message }); } /** Envuelve un handler async para que un rechazo no cuelgue la petición. */ export function h(fn: (req: AuthedRequest, res: Response) => Promise) { return (req: AuthedRequest, res: Response, next: NextFunction) => { fn(req, res).catch(next); }; }