fix: evitar error CORS al expirar sesión de Authentik
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 28s

Cuando la sesión de Authentik expira, los fetch() a /api/music recibían
un redirect (302) a la página de login. Por defecto, fetch intenta seguir
el redirect pero falla por CORS porque Authentik no tiene el header
Access-Control-Allow-Origin.

La solución es usar redirect: 'error' en todos los fetch() a endpoints
protegidos, lo que convierte los redirects en errores que podemos capturar
y manejar apropiadamente. Esto coincide con la estrategia que ya usa
useAuth.ts.

Cambios:
- stores/music.ts: Agregar redirect: 'error' a fetchTracks() y cacheByName()
- pages/index.vue: Agregar redirect: 'error' a playTrack()
- Mejorar detección de errores de autenticación para incluir 'Failed to fetch'
  y errores de tipo TypeError relacionados con redirects
This commit is contained in:
2025-10-17 03:40:12 -06:00
parent 25aace816f
commit b46d15145f
2 changed files with 31 additions and 11 deletions

View File

@@ -197,7 +197,10 @@ const playTrack = async (track, index) => {
// Fetch and preload entire song into memory // Fetch and preload entire song into memory
const encodedName = encodeURIComponent(track.name) const encodedName = encodeURIComponent(track.name)
const response = await fetch(`/api/music/${encodedName}`) const response = await fetch(`/api/music/${encodedName}`, {
credentials: 'include',
redirect: 'error' // No seguir redirects de Authentik - convertir en error
})
// Check for authentication errors // Check for authentication errors
if (!response.ok) { if (!response.ok) {
@@ -235,8 +238,13 @@ const playTrack = async (track, index) => {
loadingTrack.value = null loadingTrack.value = null
// Don't try fallback streaming if it's an auth error (it will fail too) // Don't try fallback streaming if it's an auth error (it will fail too)
if (error.message && (error.message.includes('401') || error.message.includes('403'))) { // Esto incluye errores de redirect de Authentik
console.warn('[PlayTrack] Skipping fallback due to auth error') if (error.message && (error.message.includes('401') ||
error.message.includes('403') ||
error.message.includes('Failed to fetch') ||
(error.name === 'TypeError' && error.message.includes('redirect')))) {
console.warn('[PlayTrack] Skipping fallback due to auth error:', error.message)
musicStore.error = error.message || 'Authentication error'
return return
} }

View File

@@ -44,17 +44,22 @@ export const useMusicStore = defineStore('music', {
this.loading = true this.loading = true
this.error = null this.error = null
try { try {
const response = await $fetch<{ tracks: Track[] }>('/api/music') const response = await $fetch<{ tracks: Track[] }>('/api/music', {
credentials: 'include',
redirect: 'error' // No seguir redirects de Authentik - convertir en error
})
this.tracks = response.tracks || [] this.tracks = response.tracks || []
} catch (e: any) { } catch (e: any) {
const errorMsg = e?.message || 'Failed to load tracks' const errorMsg = e?.message || 'Failed to load tracks'
this.error = errorMsg this.error = errorMsg
// Check if it's an auth error // Check if it's an auth error (including redirect attempts from Authentik)
if (e?.statusCode === 401 || e?.statusCode === 403 || if (e?.statusCode === 401 || e?.statusCode === 403 ||
errorMsg.includes('401') || errorMsg.includes('403') || errorMsg.includes('401') || errorMsg.includes('403') ||
errorMsg.includes('Unauthorized')) { errorMsg.includes('Unauthorized') ||
console.warn('[Music Store] Authentication error detected') errorMsg.includes('Failed to fetch') ||
(e?.cause?.name === 'TypeError' && e?.cause?.message?.includes('redirect'))) {
console.warn('[Music Store] Authentication error detected:', errorMsg)
// The useAuth composable will be notified via watch in components // The useAuth composable will be notified via watch in components
} }
} finally { } finally {
@@ -128,7 +133,10 @@ export const useMusicStore = defineStore('music', {
async cacheByName(name: string, duration?: number): Promise<boolean> { async cacheByName(name: string, duration?: number): Promise<boolean> {
try { try {
const encodedName = encodeURIComponent(name) const encodedName = encodeURIComponent(name)
const response = await fetch(`/api/music/${encodedName}`) const response = await fetch(`/api/music/${encodedName}`, {
credentials: 'include',
redirect: 'error' // No seguir redirects de Authentik - convertir en error
})
if (!response.ok) { if (!response.ok) {
const errorMsg = `HTTP ${response.status}` const errorMsg = `HTTP ${response.status}`
@@ -147,9 +155,13 @@ export const useMusicStore = defineStore('music', {
return true return true
} catch (e: any) { } catch (e: any) {
console.error('[Music Store] Cache failed:', e) console.error('[Music Store] Cache failed:', e)
// Propagate auth errors
if (e?.message?.includes('401') || e?.message?.includes('403')) { // Propagate auth errors (including redirect attempts from Authentik)
this.error = e.message if (e?.message?.includes('401') ||
e?.message?.includes('403') ||
e?.message?.includes('Failed to fetch') ||
(e?.name === 'TypeError' && e?.message?.includes('redirect'))) {
this.error = e.message || 'Authentication error'
} }
return false return false
} }