import { ref, computed } from 'vue' // Estado global compartido para auth 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) const isOffline = ref(false) // Estado de conexión // Estado de información del usuario const userInfo = ref(null) // Listener para cambios de autenticación desde otras tabs/ventanas let visibilityChangeListener: (() => void) | null = null let focusListener: (() => void) | null = null export const useAuth = () => { // Detectar cambios en el estado de conexión if (import.meta.client) { const updateOnlineStatus = () => { const wasOffline = isOffline.value isOffline.value = !navigator.onLine if (wasOffline && !isOffline.value) { console.log('[Auth] Connection restored - back online') } else if (!wasOffline && isOffline.value) { console.log('[Auth] Connection lost - now offline') } } // Inicializar estado updateOnlineStatus() // Escuchar eventos de conexión window.addEventListener('online', updateOnlineStatus) window.addEventListener('offline', updateOnlineStatus) } const checkAuth = async (force = false) => { // Si estamos offline, no hacer requests if (isOffline.value && !force) { console.log('[Auth] Skipping check - offline') return } // Si ya sabemos que NO estamos autenticados, no hacer más requests // (evita loops innecesarios) 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 < 30000) { return } isCheckingAuth.value = true try { // Usar endpoint dedicado que lee headers de Authentik desde el servidor // Este endpoint tiene headers no-cache y verifica la sesión en tiempo real const response = await $fetch<{ authenticated: boolean; user: any | null; timestamp: string }>('/api/auth/status', { // No usar redirect: 'error' aquí porque queremos la respuesta del servidor // El endpoint responde con authenticated: false si no hay sesión }) const wasAuthenticated = isAuthenticated.value isAuthenticated.value = response.authenticated authChecked.value = true lastCheckTime.value = now // Log cambios de estado if (wasAuthenticated !== isAuthenticated.value) { console.log('[Auth] Status changed:', isAuthenticated.value ? 'authenticated' : 'unauthenticated') if (response.user) { console.log('[Auth] User:', response.user.username) } } } catch (error: any) { console.warn('[Auth] Check failed:', error.message || error) // Verificar si estamos offline AHORA (pudo cambiar durante el request) if (!navigator.onLine) { console.log('[Auth] Offline detected - maintaining current auth state') isOffline.value = true // NO cambiar isAuthenticated cuando estamos offline // Mantener el último estado conocido } else { // Online pero el fetch falló = problema de autenticación const wasAuthenticated = isAuthenticated.value console.log('[Auth] Online but fetch failed - marking as unauthenticated') isAuthenticated.value = false authChecked.value = true if (wasAuthenticated) { console.log('[Auth] Status changed: authenticated → unauthenticated') } } lastCheckTime.value = now } finally { isCheckingAuth.value = false } } const triggerAuth = async () => { console.log('[Auth] Triggering authentication flow...') // Desregistrar el Service Worker temporalmente para evitar conflictos con Authentik if ('serviceWorker' in navigator) { try { const registrations = await navigator.serviceWorker.getRegistrations() console.log('[Auth] Unregistering Service Workers before auth...') await Promise.all(registrations.map(reg => reg.unregister())) } catch (error) { console.warn('[Auth] Could not unregister SW:', error) } } // Forzar navegación completa a la raíz // Authentik interceptará y redirigirá al login si no estamos autenticados console.log('[Auth] Forcing hard navigation to trigger Authentik...') window.location.href = window.location.origin + '/' } const fetchUserInfo = async () => { if (!isAuthenticated.value) { console.log('[Auth] Cannot fetch user info - not authenticated') return } try { const response = await $fetch<{ authenticated: boolean; user: any | null }>('/api/auth/status') if (response.authenticated && response.user) { userInfo.value = response.user console.log('[Auth] User info loaded:', response.user) } else { userInfo.value = null } } catch (error) { console.warn('[Auth] Failed to fetch user info:', error) userInfo.value = null } } const logout = async () => { console.log('[Auth] Logging out...') // Marcar como no autenticado y desregistrar SW await markUnauthenticated() // Limpiar información del usuario userInfo.value = null // Navegar directamente al servidor de Authentik para logout // Authentik tiene un flujo de invalidación por defecto que cierra la sesión console.log('[Auth] Navigating to Authentik logout...') window.location.href = 'https://authentik.nucleoriofrio.com/if/flow/default-invalidation-flow/' } const markUnauthenticated = async () => { // Helper para marcar como no autenticado (útil cuando detectamos 401/403) // IMPORTANTE: NO llamar esto si estamos offline, solo si hay un error real de auth // Si estamos offline, NO marcar como no autenticado if (isOffline.value || !navigator.onLine) { console.log('[Auth] Skipping markUnauthenticated - offline mode') return } console.log('[Auth] Marking as unauthenticated') isAuthenticated.value = false authChecked.value = true lastCheckTime.value = Date.now() // Desregistrar Service Worker para evitar conflictos con Authentik if ('serviceWorker' in navigator) { try { const registrations = await navigator.serviceWorker.getRegistrations() if (registrations.length > 0) { console.log('[Auth] Unregistering Service Worker due to auth loss...') await Promise.all(registrations.map(reg => reg.unregister())) console.log('[Auth] Service Worker unregistered - page will need reload for re-auth') } } catch (error) { console.warn('[Auth] Could not unregister SW:', error) } } } const setupVisibilityListener = () => { // DESHABILITADO: Los listeners causan problemas con Authentik // Cuando no estamos autenticados, los listeners intentan hacer fetch // y Authentik redirige, causando errores de CORS // En su lugar, confiamos en que el usuario haga click en "Reautenticar" console.log('[Auth] Visibility listeners disabled to avoid conflicts with Authentik') return /* CÓDIGO DESHABILITADO // Re-chequea auth cuando la pestaña vuelve a ser visible // (útil si el usuario se autentica en otra pestaña) if (typeof document !== 'undefined' && !visibilityChangeListener) { visibilityChangeListener = () => { 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(false) } } document.addEventListener('visibilitychange', visibilityChangeListener) } // También chequea cuando la ventana recibe foco if (typeof window !== 'undefined' && !focusListener) { focusListener = () => { if (isAuthenticated.value) { // Solo chequear si creemos estar autenticados console.log('[Auth] Window focused, checking auth...') checkAuth(false) } } window.addEventListener('focus', focusListener) } */ } const cleanupListeners = () => { if (visibilityChangeListener) { document.removeEventListener('visibilitychange', visibilityChangeListener) visibilityChangeListener = null } if (focusListener) { window.removeEventListener('focus', focusListener) focusListener = null } } const authStatus = computed(() => { // Si estamos offline, mostrar estado offline if (isOffline.value) return 'offline' if (!authChecked.value) return 'unknown' return isAuthenticated.value ? 'authenticated' : 'unauthenticated' }) return { isAuthenticated, authChecked, isCheckingAuth, isOffline, authStatus, userInfo, checkAuth, triggerAuth, fetchUserInfo, logout, markUnauthenticated, setupVisibilityListener, cleanupListeners } }