155 lines
4.7 KiB
TypeScript
155 lines
4.7 KiB
TypeScript
// composables/useAuth.ts - Enhanced version with better error handling
|
|
import type { User, SessionResponse, LoginResponse, LogoutResponse } from '~/types/auth'
|
|
|
|
export const useAuth = () => {
|
|
const user = ref<User | null>(null)
|
|
const accessToken = ref<string | null>(null)
|
|
const isLoading = ref(false)
|
|
const error = ref<string | null>(null)
|
|
const isAuthenticated = computed(() => !!user.value)
|
|
|
|
const clearError = () => {
|
|
error.value = null
|
|
}
|
|
|
|
const checkAuth = async (): Promise<User | null> => {
|
|
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<SessionResponse>('/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<void> => {
|
|
try {
|
|
clearError()
|
|
const response = await $fetch<LoginResponse>('/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<void> => {
|
|
try {
|
|
isLoading.value = true
|
|
clearError()
|
|
console.log('🚪 Starting logout process...')
|
|
|
|
const response = await $fetch<LogoutResponse>('/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<boolean> => {
|
|
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
|
|
}
|
|
} |