diff --git a/components/AuthIndicator.client.vue b/components/AuthIndicator.client.vue index 2b42a59..8136a1f 100644 --- a/components/AuthIndicator.client.vue +++ b/components/AuthIndicator.client.vue @@ -36,15 +36,19 @@ const musicStore = useMusicStore() // Check auth on mount and setup listeners onMounted(async () => { - await checkAuth(true) + // Asumimos autenticado inicialmente (si la página cargó, pasamos Authentik) + // Solo verificamos para confirmar + await checkAuth(false) setupVisibilityListener() - // Chequea auth cada 30 segundos mientras la pestaña está activa + // Polling más inteligente: solo si estamos autenticados const interval = setInterval(() => { - if (!document.hidden) { - checkAuth() + if (!document.hidden && isAuthenticated.value) { + // Solo hace polling si creemos estar autenticados + // Evita spam de requests cuando ya sabemos que no estamos autenticados + checkAuth(false) } - }, 30000) + }, 60000) // 60 segundos (menos agresivo) onUnmounted(() => { clearInterval(interval) @@ -104,9 +108,9 @@ const statusText = computed(() => { case 'authenticated': return 'Conectado' case 'unauthenticated': - return 'Sin conexión' + return 'Reautenticar' default: - return 'Desconocido' + return 'Verificando...' } }) @@ -115,11 +119,11 @@ const tooltipText = computed(() => { switch (authStatus.value) { case 'authenticated': - return 'Estás autenticado. Puedes descargar música.' + return 'Estás autenticado. Click para verificar estado.' case 'unauthenticated': - return 'No autenticado. Click para iniciar sesión.' + return 'Sesión expirada. Click para iniciar sesión de nuevo.' default: - return 'Estado de autenticación desconocido' + return 'Verificando estado de autenticación...' } }) @@ -127,11 +131,11 @@ const handleClick = () => { if (isCheckingAuth.value) return if (authStatus.value === 'unauthenticated') { - // Redirect to trigger Authentik + // Forzar reload de la página para que Authentik intercepte triggerAuth() } else if (authStatus.value === 'authenticated') { - // Re-check auth status - checkAuth() + // Re-check auth status (forzar, ignorar cache) + checkAuth(true) } } diff --git a/composables/useAuth.ts b/composables/useAuth.ts index 737c62d..84efc6f 100644 --- a/composables/useAuth.ts +++ b/composables/useAuth.ts @@ -1,8 +1,8 @@ -import { ref, computed, onUnmounted } from 'vue' +import { ref, computed } from 'vue' // Estado global compartido para auth -const authChecked = ref(false) -const isAuthenticated = ref(false) +const authChecked = ref(true) // Iniciamos como chequeado +const isAuthenticated = ref(true) // Asumimos autenticado al inicio (si la página cargó, es porque pasamos Authentik) const isCheckingAuth = ref(false) const lastCheckTime = ref(0) @@ -12,10 +12,17 @@ let focusListener: (() => void) | null = null export const useAuth = () => { const checkAuth = async (force = false) => { - // Evitar chequeos duplicados (cache de 10 segundos, reducido) + // Si ya sabemos que NO estamos autenticados, no hacer más requests + // (evita loops de CORS con Authentik redirects) + if (!isAuthenticated.value && !force) { + console.log('[Auth] Skipping check - already known to be unauthenticated') + return + } + + // Evitar chequeos duplicados (cache de 30 segundos) const now = Date.now() if (!force && isCheckingAuth.value) return - if (!force && authChecked.value && now - lastCheckTime.value < 10000) { + if (!force && authChecked.value && now - lastCheckTime.value < 30000) { return } @@ -27,6 +34,7 @@ export const useAuth = () => { method: 'HEAD', // Solo headers, no body credentials: 'include', // Include cookies for auth cache: 'no-store', // No cachear la respuesta + redirect: 'error', // No seguir redirects (error si Authentik intenta redirigir) signal: AbortSignal.timeout(5000) // 5s timeout }) @@ -39,44 +47,35 @@ export const useAuth = () => { if (wasAuthenticated !== isAuthenticated.value) { console.log('[Auth] Status changed:', isAuthenticated.value ? 'authenticated' : 'unauthenticated') } - } catch (error) { - console.warn('[Auth] Failed to check authentication status:', error) - // En caso de error de red, marcamos como no autenticado - isAuthenticated.value = false - authChecked.value = true + } catch (error: any) { + // TypeError: Failed to fetch = probablemente redirect de Authentik (no autenticado) + // o error de red real + console.warn('[Auth] Check failed:', error.message) + + // Si el error es por redirect (Authentik intenta redirigir a login), no estamos autenticados + if (error.message && error.message.includes('redirect')) { + const wasAuthenticated = isAuthenticated.value + isAuthenticated.value = false + authChecked.value = true + if (wasAuthenticated) { + console.log('[Auth] Status changed: unauthenticated (redirect detected)') + } + } + // Si no, mantenemos el estado actual (puede ser error de red temporal) + lastCheckTime.value = now } finally { isCheckingAuth.value = false } } - const triggerAuth = async () => { - console.log('[Auth] Triggering authentication flow...') + const triggerAuth = () => { + console.log('[Auth] Triggering authentication flow - forcing page reload...') - // Primero, hacemos una request al endpoint protegido para forzar - // que Authentik nos redirija a su página de login - try { - const response = await fetch('/api/music', { - method: 'GET', - credentials: 'include', - redirect: 'manual' // No seguir redirects automáticamente - }) - - if (!response.ok && (response.status === 401 || response.status === 403)) { - // Si obtenemos 401/403, forzamos un reload completo de la página - // para que Traefik + Authentik intercepten y redirijan al login - console.log('[Auth] Unauthorized - forcing full page reload to trigger auth') - window.location.href = window.location.href - } else if (response.ok) { - // Si la respuesta es OK, ya estamos autenticados - console.log('[Auth] Already authenticated') - await checkAuth(true) - } - } catch (error) { - console.error('[Auth] Error triggering auth:', error) - // En caso de error, forzamos reload de todas formas - window.location.href = window.location.href - } + // Simplemente recargamos la página completamente + // Si no estamos autenticados, Authentik interceptará y redirigirá al login + // Si ya estamos autenticados, la página se recarga normalmente + window.location.reload() } const markUnauthenticated = () => { @@ -92,9 +91,11 @@ export const useAuth = () => { // (útil si el usuario se autentica en otra pestaña) if (typeof document !== 'undefined' && !visibilityChangeListener) { visibilityChangeListener = () => { - if (!document.hidden) { + if (!document.hidden && isAuthenticated.value) { + // Solo chequear si creemos estar autenticados + // (evita spam de requests fallidas) console.log('[Auth] Tab visible again, checking auth...') - checkAuth(true) + checkAuth(false) } } document.addEventListener('visibilitychange', visibilityChangeListener) @@ -103,8 +104,11 @@ export const useAuth = () => { // También chequea cuando la ventana recibe foco if (typeof window !== 'undefined' && !focusListener) { focusListener = () => { - console.log('[Auth] Window focused, checking auth...') - checkAuth(true) + if (isAuthenticated.value) { + // Solo chequear si creemos estar autenticados + console.log('[Auth] Window focused, checking auth...') + checkAuth(false) + } } window.addEventListener('focus', focusListener) }