184 lines
5.2 KiB
TypeScript
184 lines
5.2 KiB
TypeScript
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<User | null>(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
|
|
}
|
|
}
|