Añadido cache: 'no-store' a conAuth() en todas las apps. El navegador cacheaba respuestas GET de la API, causando que los cambios (crear lead, cambiar estado, etc.) no se reflejaran hasta borrar cookies. 6 apps reconstruidas.
114 lines
3.5 KiB
TypeScript
114 lines
3.5 KiB
TypeScript
// Sesión de autenticación contra el API Gateway (JWT guardado en localStorage)
|
|
const API_URL = import.meta.env.VITE_API_URL || '/api'
|
|
const STORAGE_KEY = 'frasa_auth'
|
|
|
|
export interface UsuarioSesion {
|
|
nombre: string
|
|
rol: string
|
|
asignacion: string | null
|
|
// Módulos del ecosistema visibles para el rol (matriz del API; presente en
|
|
// /auth/me y /auth/login desde sep-2026)
|
|
modulos?: string[]
|
|
}
|
|
|
|
export interface Sesion {
|
|
token: string
|
|
usuario: UsuarioSesion
|
|
}
|
|
|
|
/** Error de login con mensaje genérico (no filtra si el usuario existe) */
|
|
export class ErrorLogin extends Error {
|
|
constructor(message = 'Usuario o contraseña incorrectos') {
|
|
super(message)
|
|
this.name = 'ErrorLogin'
|
|
}
|
|
}
|
|
|
|
export function getSesion(): Sesion | null {
|
|
try {
|
|
const raw = localStorage.getItem(STORAGE_KEY)
|
|
if (!raw) return null
|
|
const sesion = JSON.parse(raw) as Sesion
|
|
if (typeof sesion?.token !== 'string' || !sesion.usuario) return null
|
|
return sesion
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
export function getToken(): string | null {
|
|
return getSesion()?.token ?? null
|
|
}
|
|
|
|
export function logout(): void {
|
|
localStorage.removeItem(STORAGE_KEY)
|
|
}
|
|
|
|
/** POST /auth/login; guarda token + usuario en localStorage */
|
|
export async function login(username: string, password: string): Promise<UsuarioSesion> {
|
|
let res: Response
|
|
try {
|
|
res = await fetch(`${API_URL}/auth/login`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ username, password }),
|
|
})
|
|
} catch {
|
|
throw new ErrorLogin('No se pudo conectar con el servidor')
|
|
}
|
|
if (!res.ok) throw new ErrorLogin()
|
|
const data = (await res.json()) as Sesion
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify({ token: data.token, usuario: data.usuario }))
|
|
return data.usuario
|
|
}
|
|
|
|
/**
|
|
* Agrega Authorization: Bearer a un RequestInit si hay sesión.
|
|
* Sin token devuelve el init tal cual (las llamadas siguen iguales que antes).
|
|
*/
|
|
export function conAuth(init: RequestInit = {}): RequestInit {
|
|
const token = getToken()
|
|
if (!token) return init
|
|
return { ...init, cache: "no-store", headers: { ...(init.headers as Record<string, string>), Authorization: `Bearer ${token}` } }
|
|
}
|
|
|
|
/** Si el API responde 401: cierra sesión y recarga para volver al login */
|
|
export function manejar401(res: Response): void {
|
|
if (res.status !== 401) return
|
|
logout()
|
|
if (typeof window !== 'undefined') window.location.reload()
|
|
}
|
|
|
|
/** GET /auth/me — perfil fresco del usuario autenticado (incluye modulos del rol) */
|
|
export async function getMe(): Promise<UsuarioSesion> {
|
|
const res = await fetch(`${API_URL}/auth/me`, conAuth())
|
|
manejar401(res)
|
|
if (!res.ok) throw new Error(`Error ${res.status}`)
|
|
return (await res.json()) as UsuarioSesion
|
|
}
|
|
|
|
/**
|
|
* POST /auth/cambiar-password. Lanza Error con el mensaje del API cuando
|
|
* falla (p. ej. "La contraseña actual no es correcta"); no toca la sesión.
|
|
*/
|
|
export async function cambiarPassword(actual: string, nueva: string): Promise<void> {
|
|
let res: Response
|
|
try {
|
|
res = await fetch(
|
|
`${API_URL}/auth/cambiar-password`,
|
|
conAuth({
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ actual, nueva }),
|
|
}),
|
|
)
|
|
} catch {
|
|
throw new Error('No se pudo conectar con el servidor')
|
|
}
|
|
manejar401(res)
|
|
if (!res.ok) {
|
|
const body = (await res.json().catch(() => null)) as { error?: string } | null
|
|
throw new Error(body?.error ?? `Error ${res.status}`)
|
|
}
|
|
}
|