All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 24s
Problema: La verificación de sesión usaba HEAD /api/music que no es confiable porque: - Los headers de Authentik solo están disponibles en el servidor - El Service Worker puede servir respuestas cacheadas - No había headers no-cache para evitar respuestas obsoletas Solución: Implementar verificación correcta basada en plantillaNuxtAuthentikProxy: 1. Nuevo endpoint /api/auth/status.get.ts - Lee headers de Authentik directamente desde el servidor - Headers no-cache para verificación en tiempo real - Retorna estado autenticado + info de usuario 2. Actualizar useAuth.ts - checkAuth() ahora usa /api/auth/status - fetchUserInfo() también usa el nuevo endpoint - Lógica simplificada y más confiable Con esto, la verificación de sesión es precisa y en tiempo real, consultando directamente los headers de Authentik en el servidor.
220 lines
7.6 KiB
TypeScript
220 lines
7.6 KiB
TypeScript
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)
|
|
|
|
// 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 = () => {
|
|
const checkAuth = async (force = false) => {
|
|
// 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)
|
|
|
|
const wasAuthenticated = isAuthenticated.value
|
|
|
|
// Si el fetch falla completamente (CORS, redirect, etc), asumir no autenticado
|
|
// Esto pasa cuando Authentik redirige antes de que el endpoint pueda responder
|
|
console.log('[Auth] 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)
|
|
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(() => {
|
|
if (!authChecked.value) return 'unknown'
|
|
return isAuthenticated.value ? 'authenticated' : 'unauthenticated'
|
|
})
|
|
|
|
return {
|
|
isAuthenticated,
|
|
authChecked,
|
|
isCheckingAuth,
|
|
authStatus,
|
|
userInfo,
|
|
checkAuth,
|
|
triggerAuth,
|
|
fetchUserInfo,
|
|
logout,
|
|
markUnauthenticated,
|
|
setupVisibilityListener,
|
|
cleanupListeners
|
|
}
|
|
}
|