import { ref, computed } from 'vue' const userToken = ref(null) const refreshToken = ref(null) const userProfile = ref(null) const userRoles = ref([]) const isAuthenticated = ref(false) const isInitialized = ref(false) let sessionPollTimer = null const extractRolesFromToken = (token) => { if (!token) return [] try { const payloadBase64 = token.split('.')[1] const base64 = payloadBase64.replace(/-/g, '+').replace(/_/g, '/') const jsonPayload = decodeURIComponent( atob(base64) .split('') .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)) .join('') ) const payload = JSON.parse(jsonPayload) const realmRoles = payload?.realm_access?.roles || [] const clientRoles = Object.values(payload?.resource_access || {}) .flatMap(client => client.roles || []) const directRoles = payload?.roles || [] return [...realmRoles, ...clientRoles, ...directRoles].map(r => String(r).toLowerCase()) } catch (e) { console.warn('Failed to parse roles from token:', e) return [] } } const getStoredRefreshToken = () => { if (refreshToken.value) { return refreshToken.value } if (import.meta.client) { return localStorage.getItem('keycloak_refresh_token') } return null } export const useAuth = () => { const fetchUserProfile = async () => { if (!userToken.value) return try { const profile = await $fetch('/api/user/profile', { headers: { Authorization: `Bearer ${userToken.value}` } }) userProfile.value = profile } catch (err) { console.error('Failed to fetch user profile:', err) } } const setSession = (data) => { userToken.value = data.access_token userRoles.value = extractRolesFromToken(data.access_token) if (data.refresh_token) { refreshToken.value = data.refresh_token if (import.meta.client) { localStorage.setItem('keycloak_refresh_token', data.refresh_token) } } isAuthenticated.value = true } const clearSession = () => { stopLiveSessionPolling() userToken.value = null refreshToken.value = null userProfile.value = null userRoles.value = [] isAuthenticated.value = false if (import.meta.client) { localStorage.removeItem('keycloak_refresh_token') } } const exchangeCodeForToken = async (code) => { try { const data = await $fetch('/api/auth/token', { method: 'POST', body: { code, redirectUri: `${window.location.origin}/oauth` } }) setSession(data) window.history.replaceState({}, document.title, window.location.pathname) await fetchUserProfile() startLiveSessionPolling() return { success: true, isFirstLogin: data.isFirstLogin } } catch (error) { console.error('Code exchange failed:', error) clearSession() return { success: false, isFirstLogin: false } } finally { isInitialized.value = true } } const silentRefresh = async () => { const storedRefreshToken = getStoredRefreshToken() if (!storedRefreshToken) { clearSession() isInitialized.value = true return false } try { const data = await $fetch('/api/auth/refresh', { method: 'POST', body: { refreshToken: storedRefreshToken } }) setSession(data) await fetchUserProfile() startLiveSessionPolling() return true } catch (error) { console.warn('Silent refresh failed:', error) clearSession() return false } finally { isInitialized.value = true } } const verifySession = async () => { if (!userToken.value) { return await silentRefresh() } try { const res = await $fetch('/api/auth/check-session', { method: 'POST', body: { token: userToken.value } }) if (res.active) { isAuthenticated.value = true if (!userProfile.value) { await fetchUserProfile() } return true } else { return await silentRefresh() } } catch (e) { return await silentRefresh() } finally { isInitialized.value = true } } const initializeAuth = async ({ code, redirectTo = '/oauth', replace = false } = {}) => { if (code) { const result = await exchangeCodeForToken(code) if (!result.success) { await navigateTo(redirectTo, { replace }) return false } if (result.isFirstLogin) { await navigateTo('/welcome', { replace: true }) return true } return true } if (isAuthenticated.value) { const valid = await verifySession() if (!valid) { await navigateTo(redirectTo, { replace }) return false } return true } const success = await silentRefresh() if (!success) { await navigateTo(redirectTo, { replace }) return false } return true } const startLiveSessionPolling = () => { if (import.meta.server) return stopLiveSessionPolling() sessionPollTimer = setInterval(async () => { if (isAuthenticated.value) { const valid = await verifySession() if (!valid) { handleLocalLogout() } } }, 5000) } const stopLiveSessionPolling = () => { if (sessionPollTimer) { clearInterval(sessionPollTimer) sessionPollTimer = null } } const handleLocalLogout = () => { clearSession() isInitialized.value = true navigateTo('/oauth') } const logout = async () => { try { await $fetch('/api/auth/logout', { method: 'POST', body: { refreshToken: refreshToken.value } }) } catch (e) { console.warn('Server logout failed:', e) } finally { handleLocalLogout() } } return { userToken, token: userToken, userProfile, userRoles, isAuthenticated, isInitialized, exchangeCodeForToken, initializeAuth, silentRefresh, verifySession, fetchUserProfile, logout } }