ui-nyla/src/hooks/useUsers.ts

326 lines
No EOL
9.8 KiB
TypeScript

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<Omit<User, 'id' | 'mandateId'>>;
// Current user hook
export function useCurrentUser() {
const [user, setUser] = useState<User | null>(null);
const { request, isLoading, error } = useApiRequest<null, User>();
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<User[]>([]);
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<User> => {
return await request({
url: `/api/users/${userId}`,
method: 'get'
});
};
const createUser = async (userData: Omit<User, 'id' | 'mandateId'> & { 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<User> => {
return await request({
url: `/api/users/${userId}`,
method: 'get'
});
};
const updateUser = async (userId: string, userData: User): Promise<User> => {
return await request({
url: `/api/users/${userId}`,
method: 'put',
data: userData
});
};
const deleteUser = async (userId: string): Promise<void> => {
await request({
url: `/api/users/${userId}`,
method: 'delete'
});
};
return {
getUser,
updateUser,
deleteUser,
isLoading,
error
};
}