import { useState, useEffect } from 'react'; import { useApiRequest } from './useApi'; // User interfaces export interface User { id: string; username: string; email: string; fullName: string; language: string; enabled: boolean; privilege: string; authenticationAuthority: string; mandateId: string; } export type UserUpdateData = Partial>; // Current user hook export function useCurrentUser() { const [user, setUser] = useState(null); const { request, isLoading, error } = useApiRequest(); const fetchCurrentUser = async (retryCount = 0) => { try { // Check if we already have user data from JWT token const cachedUser = localStorage.getItem('currentUser'); if (cachedUser) { try { const userData = JSON.parse(cachedUser); setUser(userData); console.log('✅ Using cached user data from JWT:', userData); return; } catch (error) { console.error('Error parsing cached user data:', error); localStorage.removeItem('currentUser'); } } // JWT tokens are now stored in httpOnly cookies, so we fetch user data from API console.log('🍪 JWT tokens are now in httpOnly cookies, fetching user data from API'); // Determine the correct endpoint based on authentication authority const authAuthority = localStorage.getItem('auth_authority'); let endpoint = '/api/local/me'; if (authAuthority === 'msft') { endpoint = '/api/msft/me'; } else if (authAuthority === 'google') { endpoint = '/api/google/me'; } console.log('🔍 Fetching user data from API:', { endpoint, authAuthority, hasAuthCookies: document.cookie.includes('access_token') || document.cookie.includes('refresh_token') }); // Add a small delay to ensure cookies are properly set after authentication if (authAuthority === 'msft' || authAuthority === 'google') { console.log('⏳ Adding delay for OAuth authentication cookie propagation...'); await new Promise(resolve => setTimeout(resolve, 500)); } const data = await request({ url: endpoint, method: 'get' }); setUser(data); // Cache user data in localStorage for privilege checkers localStorage.setItem('currentUser', JSON.stringify(data)); console.log('✅ User data fetched from API and cached:', data); } catch (error: any) { console.error('❌ Failed to fetch user data:', error); // Display stored debug information if this is a Microsoft auth failure const authAuthority = localStorage.getItem('auth_authority'); if (authAuthority === 'msft') { const msftAuthDebug = localStorage.getItem('msft_auth_debug'); const msftCookieDebug = localStorage.getItem('msft_cookie_debug'); console.log('🔍 Microsoft authentication debug information:'); if (msftAuthDebug) { console.log('📋 Auth flow debug:', JSON.parse(msftAuthDebug)); } else { console.log('❌ No Microsoft auth debug info found'); } if (msftCookieDebug) { console.log('🍪 Cookie debug:', JSON.parse(msftCookieDebug)); } else { console.log('❌ No Microsoft cookie debug info found'); } console.log('🔧 Current state:', { authAuthority, currentCookies: document.cookie || 'No cookies', hasAccessToken: document.cookie.includes('access_token'), hasRefreshToken: document.cookie.includes('refresh_token'), retryCount, error: error.message }); } // If authentication failed and we haven't retried yet, try again after a delay const isOAuth = authAuthority === 'msft' || authAuthority === 'google'; const maxRetries = isOAuth ? 2 : 0; // Only retry for OAuth if (retryCount < maxRetries && (error.message?.includes('Not authenticated') || error.message?.includes('Request aborted'))) { console.log(`🔄 Retrying user data fetch (attempt ${retryCount + 1}/${maxRetries + 1}) in 2 seconds...`); setTimeout(() => { fetchCurrentUser(retryCount + 1); }, 2000); return; } // If all retries failed or this is not a retryable error setUser(null); localStorage.removeItem('currentUser'); // If authentication failed after all retries, clear auth data if (error.message?.includes('Not authenticated') || error.message?.includes('401')) { console.log('🔐 Authentication failed after retries - clearing auth data'); // Clean up debug info localStorage.removeItem('msft_auth_debug'); localStorage.removeItem('msft_cookie_debug'); localStorage.removeItem('auth_authority'); localStorage.removeItem('currentUser'); // Trigger a redirect to login by forcing a page reload setTimeout(() => { window.location.href = '/login'; }, 1000); } } }; const logout = async (msalInstance?: any) => { if (!user) { throw new Error('No user to logout'); } try { let logoutEndpoint = '/api/local/logout'; // Determine the correct logout endpoint based on authentication authority if (user.authenticationAuthority === 'msft') { logoutEndpoint = '/api/msft/logout'; } else if (user.authenticationAuthority === 'local') { logoutEndpoint = '/api/local/logout'; } await request({ url: logoutEndpoint, method: 'post' }); // Clear user state after successful logout setUser(null); // Clear any local storage data localStorage.clear(); // Handle MSAL logout for Microsoft authentication if (user.authenticationAuthority === 'msft' && msalInstance) { try { await msalInstance.logoutRedirect({ onRedirectNavigate: () => true }); return; // MSAL will handle the redirect } catch (msalError) { console.error('MSAL logout failed:', msalError); // Continue with regular redirect if MSAL logout fails } } // Redirect to login or home page window.location.href = '/login'; } catch (error) { console.error('Logout failed:', error); throw error; } }; useEffect(() => { // Try to load user from localStorage first for faster initial load const cachedUser = localStorage.getItem('currentUser'); if (cachedUser) { try { const userData = JSON.parse(cachedUser); setUser(userData); } catch (error) { console.error('Error parsing cached user data:', error); localStorage.removeItem('currentUser'); } } // For OAuth authentication, wait a bit longer before fetching user data const authAuthority = localStorage.getItem('auth_authority'); const isOAuth = authAuthority === 'msft' || authAuthority === 'google'; if (isOAuth) { console.log('⏳ OAuth authentication detected, delaying user data fetch...'); // Wait for authentication cookies to be properly set const timer = setTimeout(() => { fetchCurrentUser(); }, 1000); return () => clearTimeout(timer); } else { // For local authentication, fetch immediately fetchCurrentUser(); } }, []); return { user, error, isLoading, refetch: fetchCurrentUser, logout }; } // Organization users hook (list, update, delete) export function useOrgUsers() { const [users, setUsers] = useState([]); const { request, isLoading: loading, error } = useApiRequest(); const fetchUsers = async () => { try { const data = await request({ url: '/api/users/', method: 'get' }); setUsers(data); } catch (error) { // Error is already handled by useApiRequest } }; const updateUser = async (userId: string, userData: User) => { await request({ url: `/api/users/${userId}`, method: 'put', data: userData }); await fetchUsers(); // Refresh the list after update }; const deleteUser = async (userId: string) => { await request({ url: `/api/users/${userId}`, method: 'delete' }); await fetchUsers(); // Refresh the list after deletion }; const getUser = async (userId: string): Promise => { return await request({ url: `/api/users/${userId}`, method: 'get' }); }; const createUser = async (userData: Omit & { password: string }) => { await request({ url: '/api/users', method: 'post', data: userData }); await fetchUsers(); // Refresh the list after creation }; useEffect(() => { fetchUsers(); }, []); return { users, loading, error, refetch: fetchUsers, updateUser, deleteUser, getUser, createUser }; } // Individual user operations hook (for use when you don't need the full list) export function useUser() { const { request, isLoading, error } = useApiRequest(); const getUser = async (userId: string): Promise => { return await request({ url: `/api/users/${userId}`, method: 'get' }); }; const updateUser = async (userId: string, userData: User): Promise => { return await request({ url: `/api/users/${userId}`, method: 'put', data: userData }); }; const deleteUser = async (userId: string): Promise => { await request({ url: `/api/users/${userId}`, method: 'delete' }); }; return { getUser, updateUser, deleteUser, isLoading, error }; }