import type { User } from '~/utils/types'; export const useAuth = () => { // Use useState for SSR compatibility - prevents hydration mismatches const user = useState('auth.user', () => null); const isAuthenticated = computed(() => !!user.value); const loading = ref(false); const error = ref(null); // Enhanced role checking method - supports both realm roles and legacy groups const hasRole = (roleName: string): boolean => { if (!user.value) return false; // Get roles from user token (Keycloak format) const userToken = user.value as any; // Cast for accessing token properties // Check realm roles first (new system) const realmRoles = userToken.realm_access?.roles || []; if (realmRoles.includes(roleName)) { return true; } // Check client roles (new system) const clientRoles = userToken.resource_access || {}; for (const clientId in clientRoles) { const roles = clientRoles[clientId]?.roles || []; if (roles.includes(roleName)) { return true; } } // Fallback to legacy group system const groups = user.value.groups || []; return groups.includes(roleName) || groups.includes(`/${roleName}`); }; // Enhanced tier-based computed properties with role support const isUser = computed(() => { // Check new realm roles first if (hasRole('monaco-user')) return true; // Fallback to legacy tier system return user.value?.tier === 'user'; }); const isBoard = computed(() => { // Check new realm roles first if (hasRole('monaco-board')) return true; // Fallback to legacy tier system return user.value?.tier === 'board'; }); const isAdmin = computed(() => { // Check new realm roles first if (hasRole('monaco-admin')) return true; // Fallback to legacy tier system return user.value?.tier === 'admin'; }); // Enhanced tier computation with role priority const userTier = computed(() => { if (hasRole('monaco-admin')) return 'admin'; if (hasRole('monaco-board')) return 'board'; if (hasRole('monaco-user')) return 'user'; // Fallback to legacy tier system return user.value?.tier || 'user'; }); const firstName = computed(() => { if (user.value?.firstName) return user.value.firstName; if (user.value?.name) return user.value.name.split(' ')[0]; return 'User'; }); // Enhanced helper methods const hasTier = (requiredTier: 'user' | 'board' | 'admin') => { // Use computed userTier which handles both new and legacy systems return userTier.value === requiredTier; }; const hasGroup = (groupName: string) => { return user.value?.groups?.includes(groupName) || false; }; // New helper methods for realm roles const hasRealmRole = (roleName: string): boolean => { if (!user.value) return false; const userToken = user.value as any; const realmRoles = userToken.realm_access?.roles || []; return realmRoles.includes(roleName); }; const hasClientRole = (roleName: string, clientId?: string): boolean => { if (!user.value) return false; const userToken = user.value as any; const clientRoles = userToken.resource_access || {}; if (clientId) { // Check specific client const roles = clientRoles[clientId]?.roles || []; return roles.includes(roleName); } else { // Check all clients for (const cId in clientRoles) { const roles = clientRoles[cId]?.roles || []; if (roles.includes(roleName)) { return true; } } return false; } }; // Get all user roles (combines realm and client roles) const getAllRoles = (): string[] => { if (!user.value) return []; const userToken = user.value as any; const roles: string[] = []; // Add realm roles const realmRoles = userToken.realm_access?.roles || []; roles.push(...realmRoles); // Add client roles const clientRoles = userToken.resource_access || {}; for (const clientId in clientRoles) { const clientRolesList = clientRoles[clientId]?.roles || []; roles.push(...clientRolesList); } // Add legacy groups for compatibility const groups = user.value.groups || []; roles.push(...groups); return [...new Set(roles)]; // Remove duplicates }; // Direct login method const login = async (credentials: { username: string; password: string; rememberMe?: boolean }) => { loading.value = true; error.value = null; try { console.log('🔄 Starting login request...'); const response = await $fetch<{ success: boolean; redirectTo?: string; user?: User; }>('/api/auth/direct-login', { method: 'POST', body: credentials, timeout: 30000 // 30 second timeout }); console.log('✅ Login response received:', response); if (response.success) { // Add a small delay to ensure cookie is set before checking session console.log('⏳ Waiting for cookie to be set...'); await new Promise(resolve => setTimeout(resolve, 200)); // After successful login, get the user data from the session console.log('🔄 Getting user data from session...'); // Try multiple times in case of timing issues let sessionSuccess = false; let attempts = 0; const maxAttempts = 3; while (!sessionSuccess && attempts < maxAttempts) { attempts++; console.log(`🔄 Session check attempt ${attempts}/${maxAttempts}`); sessionSuccess = await checkAuth(); if (!sessionSuccess && attempts < maxAttempts) { console.log('⏳ Session not ready, waiting 500ms...'); await new Promise(resolve => setTimeout(resolve, 500)); } } if (sessionSuccess) { console.log('👤 User data retrieved from session:', user.value); // Return redirect URL for the component to handle console.log('✅ Login successful, returning redirect URL:', response.redirectTo || '/dashboard'); return { success: true, redirectTo: response.redirectTo || '/dashboard' }; } else { console.warn('❌ Failed to get user data from session after login'); // Still return success with redirect since login was successful on server return { success: true, redirectTo: '/dashboard' }; } } console.warn('❌ Login response indicates failure:', response); return { success: false, error: 'Login failed' }; } catch (err: any) { console.error('❌ Login error caught:', err); // Handle different types of errors let errorMessage = 'Login failed'; if (err.status === 502) { errorMessage = 'Server temporarily unavailable. Please try again.'; } else if (err.status === 401) { errorMessage = 'Invalid username or password'; } else if (err.status === 429) { errorMessage = 'Too many login attempts. Please try again later.'; } else if (err.data?.message) { errorMessage = err.data.message; } else if (err.message) { errorMessage = err.message; } error.value = errorMessage; return { success: false, error: errorMessage }; } finally { loading.value = false; } }; // OAuth login method (fallback) const loginOAuth = () => { return navigateTo('/api/auth/login'); }; // Password reset method const requestPasswordReset = async (email: string) => { loading.value = true; error.value = null; try { const response = await $fetch<{ success: boolean; message: string; }>('/api/auth/forgot-password', { method: 'POST', body: { email } }); return { success: true, message: response.message }; } catch (err: any) { error.value = err.data?.message || 'Password reset failed'; return { success: false, error: error.value }; } finally { loading.value = false; } }; // Check authentication status - simple and reliable const checkAuth = async () => { try { console.log('🔄 Performing session check...'); const response = await $fetch<{ authenticated: boolean; user: User | null; }>('/api/auth/session'); if (response.authenticated && response.user) { user.value = response.user; return true; } else { user.value = null; return false; } } catch (err) { console.error('Auth check error:', err); user.value = null; return false; } }; // Logout method const logout = async () => { try { await $fetch('/api/auth/logout', { method: 'POST' }); user.value = null; await navigateTo('/login'); } catch (err) { console.error('Logout error:', err); user.value = null; await navigateTo('/login'); } }; return { // State user: readonly(user), isAuthenticated, loading: readonly(loading), error: readonly(error), // Tier-based properties userTier, isUser, isBoard, isAdmin, firstName, // Helper methods hasTier, hasGroup, hasRole, // Enhanced with realm role support hasRealmRole, hasClientRole, getAllRoles, // Actions login, loginOAuth, logout, requestPasswordReset, checkAuth, }; };