feat(auth): login JWT en API y las 4 apps
- Modelo Usuario + seed desde lista de vendedores (18 usuarios, bcrypt cost 12) - POST /api/auth/login + /api/auth/me; middleware Bearer en todo /api excepto health, login y webhook Monday; flag AUTH_ENABLED - Username = nombre completo normalizado (case/acentos insensible), password inicial Frasa2026, admin vía ADMIN_PASSWORD - Apps: LoginScreen + AuthGate (StudioBlank), token en localStorage, header Authorization en todas las llamadas, logout en 401 - Tests: api 136, cotizador 71, alta 65, cobranza 71, agente-ia 13, landing 4
This commit is contained in:
50
api-gateway/src/middleware/auth.middleware.ts
Normal file
50
api-gateway/src/middleware/auth.middleware.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
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' },
|
||||
]
|
||||
|
||||
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)
|
||||
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' })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user