// composables/useAuth.ts - Enhanced version with better error handling import type { User, SessionResponse, LoginResponse, LogoutResponse } from '~/types/auth' export const useAuth = () => { const user = ref(null) const accessToken = ref(null) const isLoading = ref(false) const error = ref(null) const isAuthenticated = computed(() => !!user.value) const clearError = () => { error.value = null } const checkAuth = async (): Promise => { try { isLoading.value = true clearError() // The session API returns SessionResponse, or throws 401 if not authenticated // $fetch automatically sends cookies for same-origin requests const response = await $fetch('/api/auth/session', { credentials: 'include' // Explicitly include cookies (though $fetch does this by default) }) // Handle response structure - session API returns { success, user, ... } if (response && response.user) { user.value = response.user accessToken.value = response.accessToken || null return response.user } // If response exists but no user, clear and return null user.value = null accessToken.value = null return null } catch (fetchError: any) { console.error('Session check failed:', fetchError) // 401 errors are expected when not authenticated if (fetchError.statusCode === 401 || fetchError.status === 401) { error.value = null // Don't show error for expected unauthenticated state } else { error.value = 'Failed to check authentication status' } user.value = null accessToken.value = null return null } finally { isLoading.value = false } } const login = async (): Promise => { try { clearError() const response = await $fetch('/api/auth/keycloak-login', { method: 'POST' }) if (response?.success && response?.data?.authUrl) { console.log('🔗 Redirecting to Keycloak login...') window.location.href = response.data.authUrl } else { const errorMsg = response?.error || 'Failed to get authorization URL' error.value = errorMsg throw new Error(errorMsg) } } catch (loginError: any) { console.error('❌ Login error:', loginError) error.value = loginError.message || 'Login failed' throw loginError } } const logout = async (): Promise => { try { isLoading.value = true clearError() console.log('🚪 Starting logout process...') const response = await $fetch('/api/auth/logout', { method: 'POST' }) // Clear user immediately regardless of response user.value = null accessToken.value = null if (response?.success && response?.logoutUrl) { console.log('🔗 Redirecting to Keycloak logout...') // Keycloak will redirect back to the base URL after logout // We can add a query param to detect the logout and show a message // window.location.href = response.logoutUrl window.location.href = "/auth/login?logout=success" } else { const warningMsg = response?.error || response?.message || 'No logout URL received' console.warn('⚠️', warningMsg) error.value = warningMsg await navigateTo('/auth/login') } } catch (logoutError: any) { console.error('❌ Logout error:', logoutError) error.value = logoutError.message || 'Logout failed' user.value = null accessToken.value = null await navigateTo('/auth/login') } finally { isLoading.value = false } } // Helper function to refresh user data const refreshUser = async (): Promise => { const userData = await checkAuth() return !!userData } // Helper function to check if user has specific role const hasRole = (role: string): boolean => { if (!user.value) return false // Check in roles array if (user.value.roles?.includes(role)) return true // Check in realm_access.roles if (user.value.realm_access?.roles?.includes(role)) return true return false } // Helper function to check if user has any of the specified roles const hasAnyRole = (roles: string[]): boolean => { return roles.some(role => hasRole(role)) } return { // State user: readonly(user), accessToken: readonly(accessToken), isAuthenticated, isLoading: readonly(isLoading), error: readonly(error), // Actions checkAuth, login, logout, refreshUser, clearError, // Utilities hasRole, hasAnyRole } }