interface User { id: string email: string username: string name: string authMethod: string } interface AuthState { user: User | null authenticated: boolean } export const useCustomAuth = () => { const user = ref(null) const authenticated = ref(false) const loading = ref(true) const refreshing = ref(false) // Get auth state from middleware cache (no API calls!) const syncFromCache = () => { try { const nuxtApp = useNuxtApp() // Try to get from auth state cache first const authState = nuxtApp.payload?.data?.authState if (authState && typeof authState === 'object') { user.value = authState.user || null authenticated.value = authState.authenticated || false loading.value = false console.log('[CUSTOM_AUTH] Session synced from cache:', { authenticated: authenticated.value, userId: user.value?.id }) return true } // Fallback to session cache const sessionCache = nuxtApp.payload?.data?.['auth:session:cache'] if (sessionCache && typeof sessionCache === 'object') { user.value = sessionCache.user || null authenticated.value = sessionCache.authenticated || false loading.value = false console.log('[CUSTOM_AUTH] Session synced from session cache:', { authenticated: authenticated.value, userId: user.value?.id }) return true } // No cache available console.log('[CUSTOM_AUTH] No cache available, setting defaults') user.value = null authenticated.value = false loading.value = false return false } catch (error) { console.error('[CUSTOM_AUTH] Error syncing from cache:', error) user.value = null authenticated.value = false loading.value = false return false } } // Simple check auth that only uses cache const checkAuth = async (skipRetry = false) => { loading.value = true const synced = syncFromCache() if (!synced) { console.warn('[CUSTOM_AUTH] No auth cache available, user may need to refresh') } } // Refresh token with better error handling const refreshToken = async () => { if (refreshing.value) return false try { refreshing.value = true console.log('[CUSTOM_AUTH] Attempting token refresh...') const response = await $fetch<{ success: boolean; expiresAt?: number }>('/api/auth/refresh', { method: 'POST', retry: 2, retryDelay: 1000 }) if (response.success) { console.log('[CUSTOM_AUTH] Token refresh successful') await checkAuth(true) // Re-check auth state after refresh, skip retry to avoid loops return true } return false } catch (error) { console.error('[CUSTOM_AUTH] Token refresh failed:', error) // Check if it's a 401 (invalid refresh token) vs other errors if ((error as any)?.status === 401) { console.log('[CUSTOM_AUTH] Refresh token invalid, clearing auth state') user.value = null authenticated.value = false return false } // For other errors (network issues, 502, etc.), don't clear auth state immediately // The auto-refresh plugin will handle retries console.log('[CUSTOM_AUTH] Network error during refresh, keeping auth state') return false } finally { refreshing.value = false } } // Login with Keycloak const login = () => { const authUrl = 'https://auth.portnimara.dev/realms/client-portal/protocol/openid-connect/auth?' + new URLSearchParams({ client_id: 'client-portal', redirect_uri: 'https://client.portnimara.dev/api/auth/keycloak/callback', response_type: 'code', scope: 'openid profile email', state: Math.random().toString(36).substring(2) }).toString() console.log('[CUSTOM_AUTH] Redirecting to Keycloak login:', authUrl) navigateTo(authUrl, { external: true }) } // Logout const logout = async () => { try { console.log('[CUSTOM_AUTH] Initiating logout...') // Clear local state immediately user.value = null authenticated.value = false // Redirect to logout endpoint await navigateTo('/api/auth/logout', { external: true }) } catch (error) { console.error('[CUSTOM_AUTH] Logout failed:', error) // Fallback: redirect to login await navigateTo('/login') } } // Initialize auth state on composable creation onMounted(() => { checkAuth() }) // Watch for auth state changes in the cache (client-side only) if (process.client) { watch( () => { const nuxtApp = useNuxtApp() return nuxtApp.payload?.data?.authState }, (newAuthState) => { if (newAuthState && typeof newAuthState === 'object') { console.log('[CUSTOM_AUTH] Auth state changed, syncing from cache') syncFromCache() } }, { deep: false } ) } return { user: readonly(user), authenticated: readonly(authenticated), loading: readonly(loading), refreshing: readonly(refreshing), login, logout, checkAuth, refreshToken } }