import type { NextFunction, Request, Response } from 'express' import { config } from '../config/index.js' import { verificarToken, type TokenPayload } from '../services/auth.service.js' // Rutas públicas: no requieren token aunque AUTH_ENABLED=true. Se comparan // contra req.path, que aquí ya está relativo al mount /api. // Monday no puede mandar JWT, así que su webhook queda abierto (se valida por // firma/challenge dentro del controlador). const RUTAS_PUBLICAS: ReadonlyArray<{ metodo: string; ruta: string }> = [ { metodo: 'GET', ruta: '/health' }, { metodo: 'POST', ruta: '/auth/login' }, { metodo: 'POST', ruta: '/webhooks/monday' }, ] // Rutas públicas con parámetros (se comparan contra req.path relativo a /api). // El endpoint público de cotización es el destino del QR del PDF: el cliente // final lo abre sin sesión; devuelve solo datos comerciales, nunca del cliente. const PATRONES_PUBLICOS: ReadonlyArray<{ metodo: string; patron: RegExp }> = [ { metodo: 'GET', patron: /^\/cotizaciones\/[^/]+\/publico$/ }, // Liga remota de firma: públicos pero con token propio (scope 'firma-remota') // que se verifica dentro del controlador; no aceptan tokens de login. { metodo: 'GET', patron: /^\/clientes\/publico\/[^/]+$/ }, { metodo: 'POST', patron: /^\/clientes\/publico\/[^/]+\/firma$/ }, { metodo: 'POST', patron: /^\/clientes\/publico\/[^/]+\/documentos$/ }, ] declare module 'express-serve-static-core' { interface Request { usuarioToken?: TokenPayload } } /** * Exige Bearer JWT en todo /api salvo las rutas públicas. * Con AUTH_ENABLED=false (tests) deja pasar todo. */ export function authMiddleware(req: Request, res: Response, next: NextFunction): void { if (!config.auth.enabled) { next() return } const publica = RUTAS_PUBLICAS.some((r) => r.metodo === req.method && req.path === r.ruta) || PATRONES_PUBLICOS.some((r) => r.metodo === req.method && r.patron.test(req.path)) if (publica) { next() return } const header = req.headers.authorization const token = header?.startsWith('Bearer ') ? header.slice('Bearer '.length).trim() : null if (!token) { res.status(401).json({ error: 'Token requerido' }) return } try { req.usuarioToken = verificarToken(token) next() } catch { res.status(401).json({ error: 'Token inválido o expirado' }) } }