Solucionar errores de CORS manteniendo seguridad de Authentik
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 46s

PROBLEMA:
- Frontend hacía fetch a APIs protegidas por Authentik
- Cuando sesión expiraba, Authentik redirigía causando error de CORS
- TypeError: Failed to fetch

SOLUCIÓN:
1. Backend inyecta window.__AUTHENTIK_USER__ en HTML inicial (app.js)
   - Servidor lee headers de Authentik y los pasa al frontend
   - Evita fetch innecesario en carga inicial

2. Frontend usa window.__AUTHENTIK_USER__ como fuente principal (useAuthentik.js)
   - Solo hace fetch cuando se fuerza refresh
   - Detecta errores de CORS como señal de sesión expirada
   - Muestra mensaje claro al usuario

3. App.vue detecta errores de autenticación en APIs
   - Cuando fetch falla con CORS, recarga página automáticamente
   - Authentik manejará la re-autenticación

SEGURIDAD:
- Todos los endpoints /api/* siguen protegidos por Authentik
- No se exponen APIs sin autenticación
- Headers de Authentik solo presentes con sesión válida
This commit is contained in:
2025-10-27 15:15:44 -06:00
parent ab0f79e103
commit 1ea50f0aa5
3 changed files with 136 additions and 11 deletions

View File

@@ -188,6 +188,17 @@ const userExpanded = reactive({});
const deviceExpanded = reactive({});
// formulario inline removido: se usa modal con UserForm
// Helper para detectar errores de autenticación
function isAuthError(error) {
// Si es un TypeError de fetch, probablemente es CORS (redirección de Authentik)
return error instanceof TypeError && error.message.includes('fetch');
}
function handleAuthError() {
console.warn('Sesión expirada o error de autenticación, redirigiendo...');
window.location.reload();
}
const showEventFilters = ref(false);
const showUserFilters = ref(false);
const eventFilters = reactive({ text: '', type: '' });
@@ -203,8 +214,17 @@ async function fetchUsers() {
loading.users = true;
try {
const res = await fetch('/api/users');
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
const data = await res.json();
users.value = data.items || [];
} catch (error) {
if (isAuthError(error)) {
handleAuthError();
} else {
console.error('Error fetching users:', error);
}
} finally { loading.users = false; }
}
@@ -212,17 +232,35 @@ async function fetchRequests() {
loading.requests = true;
try {
const res = await fetch('/api/requests');
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
const data = await res.json();
requests.value = data.items || [];
} catch (error) {
if (isAuthError(error)) {
handleAuthError();
} else {
console.error('Error fetching requests:', error);
}
} finally { loading.requests = false; }
}
async function fetchDevices() {
try {
const res = await fetch('/api/devices');
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
const data = await res.json();
devices.value = data.items || [];
} catch {}
} catch (error) {
if (isAuthError(error)) {
handleAuthError();
} else {
console.error('Error fetching devices:', error);
}
}
}
async function toggleDisable(u) {