frontend_nyla/src/hooks/useAuthentication.ts

1019 lines
No EOL
37 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState } from 'react';
import { useMsal } from '@azure/msal-react';
import api from '../api';
import { useApiRequest } from './useApi';
import { addCSRFTokenToHeaders } from '../utils/csrfUtils';
import { getApiBaseUrl } from '../../config/config';
// Regular authentication
interface LoginResponse {
accessToken: string;
tokenType: string;
label?: any;
fieldLabels?: any;
}
export function useAuth() {
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const login = async (username: string, password: string): Promise<LoginResponse> => {
setIsLoading(true);
setError(null);
try {
// Create the form data in the exact format FastAPI OAuth2 expects
const params = new URLSearchParams();
params.append('username', username);
params.append('password', password);
params.append('grant_type', 'password');
params.append('scope', '');
params.append('client_id', '');
params.append('client_secret', '');
// Prepare headers with CSRF token if available
const headers: Record<string, string> = {
'Content-Type': 'application/x-www-form-urlencoded'
};
// Add CSRF token if available (for new security implementation)
addCSRFTokenToHeaders(headers);
// Log the request details for debugging
console.log('🔍 Login request details:', {
url: '/api/local/login',
headers: headers,
hasParams: !!params,
paramsSize: params.toString().length,
paramsContent: params.toString()
});
// Use the existing api instance with custom headers for this request
const response = await api.post('/api/local/login', params, {
headers
});
// Tokens are automatically set in httpOnly cookies by backend
if (response.data.type === 'local_auth_success') {
if (response.data.authenticationAuthority) {
localStorage.setItem('auth_authority', response.data.authenticationAuthority);
}
console.log('✅ Local authentication successful - tokens set in httpOnly cookies');
// CRITICAL: Immediately fetch user data after successful login
try {
console.log('🔄 Fetching user data immediately after login...');
const userResponse = await api.get('/api/local/me');
if (userResponse.data) {
// Cache user data in localStorage for privilege checkers and language
localStorage.setItem('currentUser', JSON.stringify(userResponse.data));
console.log('✅ User data fetched and cached:', {
username: userResponse.data.username,
privilege: userResponse.data.privilege,
language: userResponse.data.language
});
}
} catch (userError) {
console.error('❌ Failed to fetch user data after login:', userError);
// Don't block login flow, but log the error
}
return response.data;
}
throw new Error('Login failed');
} catch (error: any) {
let errorMessage = 'An error occurred during login';
console.error('❌ Login error details:', {
status: error.response?.status,
statusText: error.response?.statusText,
data: error.response?.data,
message: error.message,
headers: error.response?.headers,
config: {
url: error.config?.url,
method: error.config?.method,
headers: error.config?.headers
}
});
// Additional debugging for CSRF-related errors
if (error.response?.status === 500) {
console.error('🚨 500 Error - Possible causes:');
console.error('1. Backend CSRF validation not implemented');
console.error('2. Backend expecting different CSRF token format');
console.error('3. Backend server error');
console.error('4. Check backend logs for detailed error information');
console.error('💡 To temporarily bypass CSRF, set CSRF_BYPASS_FOR_TESTING = true in csrfUtils.ts');
}
if (error.response) {
// Handle different error response formats
if (error.response.data?.detail) {
if (Array.isArray(error.response.data.detail)) {
errorMessage = error.response.data.detail.map((err: any) => err.msg || err).join(', ');
} else {
errorMessage = error.response.data.detail;
}
} else if (error.response.data?.message) {
errorMessage = error.response.data.message;
} else if (error.response.status === 500) {
errorMessage = 'Server error during login. Please try again.';
} else {
errorMessage = 'Invalid username or password';
}
} else if (error.request) {
errorMessage = 'No response received from server';
} else {
errorMessage = error.message;
}
setError(errorMessage);
throw error;
} finally {
setIsLoading(false);
}
};
return {
login,
error,
isLoading
};
}
// Microsoft Authentication
interface MsalAuthResponse {
accessToken: string;
tokenType: string;
user: {
username: string;
email: string;
fullName: string;
mandateId: number;
};
}
export function useMsalAuth() {
const [msalError, setMsalError] = useState<string | null>(null);
const [isMsalLoading, setIsMsalLoading] = useState(false);
const loginWithMsal = async (): Promise<MsalAuthResponse> => {
setIsMsalLoading(true);
setMsalError(null);
try {
return new Promise((resolve, reject) => {
const backendUrl = getApiBaseUrl();
const loginUrl = `${backendUrl}/api/msft/login?state=login`;
console.log('🔐 Starting MSAL authentication...');
console.log('🌐 Backend URL:', backendUrl);
console.log('🔗 Login URL:', loginUrl);
console.log('🍪 Current cookies before auth:', document.cookie || 'No cookies');
// Open popup to backend Microsoft login route
console.log('🚀 Opening Microsoft auth popup...');
const popup = window.open(
loginUrl,
'msft-login',
'width=500,height=600,scrollbars=yes,resizable=yes,top=100,left=100'
);
console.log('🪟 Popup window object:', popup ? 'Created successfully' : 'Failed to create');
if (!popup) {
const errorMsg = 'Popup was blocked by browser. Please allow popups for this site and try again.';
console.error('❌ Popup blocked:', errorMsg);
setMsalError(errorMsg);
setIsMsalLoading(false);
reject(new Error('Popup was blocked'));
return;
}
console.log('✅ Popup opened successfully');
// Listen for messages from the popup
const messageListener = (event: MessageEvent) => {
// Filter out React DevTools messages
if (event.data?.source?.includes('react-devtools') ||
event.data?.source?.includes('devtools') ||
event.data?.hello === true) {
return; // Ignore React DevTools messages
}
console.log('📨 Received message from Microsoft auth popup:', {
origin: event.origin,
data: event.data,
dataType: typeof event.data,
hasType: !!event.data?.type,
messageKeys: event.data ? Object.keys(event.data) : 'No data object',
timestamp: new Date().toISOString()
});
// Verify origin for security
const apiUrl = new URL(backendUrl);
if (event.origin !== apiUrl.origin) {
console.warn('⚠️ Message from unauthorized origin:', event.origin, 'Expected:', apiUrl.origin);
return;
}
if (event.data.type === 'msft_auth_success') {
console.log('✅ MSAL authentication successful');
console.log('📋 Full event data received:', event.data);
// Store debug info in localStorage for persistence across navigation
const debugInfo = {
timestamp: new Date().toISOString(),
eventData: event.data,
eventDataKeys: Object.keys(event.data),
hasAuthenticationAuthority: !!event.data.authenticationAuthority,
cookiesBeforeAuth: document.cookie || 'No cookies',
authFlow: 'msft_popup_success'
};
localStorage.setItem('msft_auth_debug', JSON.stringify(debugInfo));
// Tokens are automatically set in httpOnly cookies by backend
if (event.data.authenticationAuthority) {
localStorage.setItem('auth_authority', event.data.authenticationAuthority);
console.log('✅ Auth authority set:', event.data.authenticationAuthority);
} else {
// Fallback: set 'msft' as the auth authority for Microsoft login
localStorage.setItem('auth_authority', 'msft');
console.log('⚠️ authenticationAuthority not in event data, setting fallback: msft');
console.log('📋 Available event.data properties:', Object.keys(event.data));
}
console.log('✅ Microsoft authentication successful - tokens set in httpOnly cookies');
// CRITICAL: Immediately fetch user data after successful login
// Wait a bit for cookies to be properly set
setTimeout(async () => {
try {
console.log('🔄 Fetching user data immediately after Microsoft login...');
const userResponse = await api.get('/api/msft/me');
if (userResponse.data) {
// Cache user data in localStorage for privilege checkers and language
localStorage.setItem('currentUser', JSON.stringify(userResponse.data));
console.log('✅ User data fetched and cached:', {
username: userResponse.data.username,
privilege: userResponse.data.privilege,
language: userResponse.data.language
});
}
} catch (userError) {
console.error('❌ Failed to fetch user data after Microsoft login:', userError);
// Store debug info
const allCookies = document.cookie;
const hasAccessToken = allCookies.includes('access_token');
const hasRefreshToken = allCookies.includes('refresh_token');
const cookieInfo = {
allCookies: allCookies || 'No cookies visible',
hasAccessToken,
hasRefreshToken,
authAuthority: localStorage.getItem('auth_authority'),
timestamp: new Date().toISOString(),
userFetchError: userError
};
localStorage.setItem('msft_cookie_debug', JSON.stringify(cookieInfo));
console.log('🍪 Cookie check after Microsoft auth:', cookieInfo);
}
}, 500);
// Clean up
window.removeEventListener('message', messageListener);
popup.close();
setIsMsalLoading(false);
// Resolve with the response data
resolve(event.data);
} else if (event.data.type === 'msft_connection_error') {
console.error('❌ MSAL connection error:', event.data.error);
// Handle error
window.removeEventListener('message', messageListener);
popup.close();
setIsMsalLoading(false);
setMsalError(event.data.error || 'Microsoft authentication failed');
reject(new Error(event.data.error || 'Microsoft authentication failed'));
}
};
// Add message listener
window.addEventListener('message', messageListener);
// Handle popup closing without completing auth
let popupClosedManually = false;
const checkClosed = setInterval(() => {
if (popup.closed) {
clearInterval(checkClosed);
window.removeEventListener('message', messageListener);
setIsMsalLoading(false);
if (!popupClosedManually) {
console.warn('⚠️ Popup was closed before authentication completed');
setMsalError('Authentication was cancelled - popup was closed before completing login');
} else {
console.log(' Popup closed after successful authentication');
}
if (!popupClosedManually) {
reject(new Error('Authentication was cancelled'));
}
}
}, 1000);
// Set a timeout to detect if popup doesn't load
const loadTimeout = setTimeout(() => {
if (!popup.closed) {
console.warn('⚠️ Popup did not load within 10 seconds');
popup.close();
clearInterval(checkClosed);
window.removeEventListener('message', messageListener);
setIsMsalLoading(false);
setMsalError('Authentication timeout - please check your internet connection and try again');
reject(new Error('Authentication timeout'));
}
}, 60000);
// Override popup.close to mark as manually closed
const originalClose = popup.close;
popup.close = function() {
popupClosedManually = true;
clearTimeout(loadTimeout);
return originalClose.call(this);
};
});
} catch (error: any) {
console.error('❌ MSAL authentication error:', error);
setMsalError(error.message || 'Microsoft authentication failed');
setIsMsalLoading(false);
throw error;
}
};
return {
loginWithMsal,
error: msalError,
isLoading: isMsalLoading
};
}
// Registration
interface RegisterData {
username: string;
password: string;
email: string;
fullName: string;
language?: string;
enabled?: boolean;
privilege?: string;
}
interface RegisterResponse {
success: boolean;
message?: string;
user?: {
id: string;
username: string;
email: string;
fullName: string;
language: string;
enabled: boolean;
privilege: string;
};
}
export function useRegister() {
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const register = async (userData: RegisterData): Promise<RegisterResponse> => {
setIsLoading(true);
setError(null);
try {
// Prepare data to match backend expectations
// Backend expects userData as object and password as embedded field
const dataToSend = {
userData: {
username: userData.username,
email: userData.email,
fullName: userData.fullName,
language: userData.language || 'de',
enabled: userData.enabled !== undefined ? userData.enabled : true,
privilege: userData.privilege || 'user'
},
password: userData.password
};
// Prepare headers with CSRF token if available
const headers: Record<string, string> = {
'Content-Type': 'application/json'
};
// Add CSRF token if available (for new security implementation)
addCSRFTokenToHeaders(headers);
const response = await api.post('/api/local/register', dataToSend, {
headers
});
return {
success: true,
message: 'Registration successful',
user: response.data
};
} catch (error: any) {
let errorMessage = 'An error occurred during registration';
if (error.response) {
// Handle validation errors from FastAPI
if (error.response.data?.detail) {
if (Array.isArray(error.response.data.detail)) {
// Handle FastAPI validation errors array
errorMessage = error.response.data.detail.map((err: any) => err.msg).join(', ');
} else {
errorMessage = error.response.data.detail;
}
} else {
errorMessage = 'Registration failed';
}
} else if (error.request) {
errorMessage = 'No response received from server';
} else {
errorMessage = error.message;
}
setError(errorMessage);
throw error;
} finally {
setIsLoading(false);
}
};
return {
register,
error,
isLoading
};
}
// Google Authentication
interface GoogleAuthResponse {
accessToken: string;
tokenType: string;
user: {
username: string;
email: string;
fullName: string;
mandateId: number;
};
}
export function useGoogleAuth() {
const [googleError, setGoogleError] = useState<string | null>(null);
const [isGoogleLoading, setIsGoogleLoading] = useState(false);
const loginWithGoogle = async (): Promise<GoogleAuthResponse> => {
setIsGoogleLoading(true);
setGoogleError(null);
try {
return new Promise((resolve, reject) => {
const backendUrl = getApiBaseUrl();
const loginUrl = `${backendUrl}/api/google/login`;
console.log('🔐 Starting Google authentication...');
console.log('🌐 Backend URL:', backendUrl);
console.log('🔗 Login URL:', loginUrl);
// First, get the Google login URL from the backend using fetch to avoid CORS issues
fetch(`${backendUrl}/api/google/login`, {
method: 'GET',
mode: 'cors',
credentials: 'include',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
redirect: 'manual' // Don't follow redirects
})
.then(response => {
console.log('📡 Backend response:', response);
console.log('📊 Response status:', response.status);
console.log('📊 Response type:', response.type);
console.log('📊 Response headers:', response.headers);
// Check if it's a redirect response
if (response.status === 0 || response.type === 'opaque') {
// This might be a CORS issue, try to get the redirect URL from the response
console.log('🔄 CORS/Redirect detected, trying to extract URL from response');
// Try to read the response as text to get the redirect URL
return response.text().then(text => {
console.log('📄 Response text:', text);
// Look for redirect URL in the response
const urlMatch = text.match(/https:\/\/accounts\.google\.com\/o\/oauth2\/auth[^"'\s]*/);
if (urlMatch) {
return { login_url: urlMatch[0] };
}
// If no URL found in text, try to construct it from the error
throw new Error('Could not extract Google OAuth URL from response');
});
} else if (response.status >= 200 && response.status < 300) {
// Normal JSON response
return response.json();
} else if (response.status >= 300 && response.status < 400) {
// Redirect response
const location = response.headers.get('location');
console.log('🔄 Redirect location:', location);
if (location) {
return { login_url: location };
}
throw new Error('Redirect response without location header');
} else {
throw new Error(`HTTP error! status: ${response.status}`);
}
})
.then(data => {
console.log('📄 Response data:', data);
if (data.login_url) {
// Open popup with the Google login URL
const popup = window.open(
data.login_url,
'google-login',
'width=500,height=600,scrollbars=yes,resizable=yes,top=100,left=100'
);
if (!popup) {
const errorMsg = 'Popup was blocked by browser. Please allow popups for this site and try again.';
console.error('❌ Popup blocked:', errorMsg);
setGoogleError(errorMsg);
setIsGoogleLoading(false);
reject(new Error('Popup was blocked'));
return;
}
console.log('✅ Popup opened successfully');
// Listen for messages from the popup
const messageListener = (event: MessageEvent) => {
console.log('📨 Received message from popup:', event.origin, event.data);
// Verify origin for security
const apiUrl = new URL(backendUrl);
if (event.origin !== apiUrl.origin) {
console.warn('⚠️ Message from unauthorized origin:', event.origin, 'Expected:', apiUrl.origin);
return;
}
if (event.data.type === 'google_auth_success') {
console.log('✅ Google authentication successful');
console.log('📋 Full event data received:', event.data);
// Tokens are automatically set in httpOnly cookies by backend
if (event.data.authenticationAuthority) {
localStorage.setItem('auth_authority', event.data.authenticationAuthority);
console.log('✅ Auth authority set:', event.data.authenticationAuthority);
} else {
// Fallback: set 'google' as the auth authority for Google login
localStorage.setItem('auth_authority', 'google');
console.log('⚠️ authenticationAuthority not in event data, setting fallback: google');
console.log('📋 Available event.data properties:', Object.keys(event.data));
}
console.log('✅ Google authentication successful - tokens set in httpOnly cookies');
// Clean up
window.removeEventListener('message', messageListener);
popup.close();
setIsGoogleLoading(false);
// Resolve with the response data
resolve(event.data);
} else if (event.data.type === 'google_connection_error') {
console.error('❌ Google connection error:', event.data.error);
// Handle error
window.removeEventListener('message', messageListener);
popup.close();
setIsGoogleLoading(false);
setGoogleError(event.data.error || 'Google authentication failed');
reject(new Error(event.data.error || 'Google authentication failed'));
}
};
// Add message listener
window.addEventListener('message', messageListener);
// Handle popup closing without completing auth
let popupClosedManually = false;
const checkClosed = setInterval(() => {
if (popup.closed) {
clearInterval(checkClosed);
window.removeEventListener('message', messageListener);
setIsGoogleLoading(false);
if (!popupClosedManually) {
console.warn('⚠️ Popup was closed before authentication completed');
setGoogleError('Authentication was cancelled - popup was closed before completing login');
} else {
console.log(' Popup closed after successful authentication');
}
if (!popupClosedManually) {
reject(new Error('Authentication was cancelled'));
}
}
}, 1000);
// Set a timeout to detect if popup doesn't load
const loadTimeout = setTimeout(() => {
if (!popup.closed) {
console.warn('⚠️ Popup did not load within 60 seconds');
popup.close();
clearInterval(checkClosed);
window.removeEventListener('message', messageListener);
setIsGoogleLoading(false);
setGoogleError('Authentication timeout - please check your internet connection and try again');
reject(new Error('Authentication timeout'));
}
}, 60000);
// Override popup.close to mark as manually closed
const originalClose = popup.close;
popup.close = function() {
popupClosedManually = true;
clearTimeout(loadTimeout);
return originalClose.call(this);
};
} else {
throw new Error('No login URL received from backend');
}
})
.catch(error => {
console.error('❌ Failed to get Google login URL:', error);
console.log('🔄 Attempting fallback approach...');
// Fallback: Try to construct the Google OAuth URL directly
// This is a temporary solution until the backend is fixed
const fallbackGoogleUrl = `${backendUrl}/api/google/login`;
console.log('🔄 Using fallback URL:', fallbackGoogleUrl);
// Open popup with the fallback URL (let the backend handle the redirect)
const popup = window.open(
fallbackGoogleUrl,
'google-login',
'width=500,height=600,scrollbars=yes,resizable=yes,top=100,left=100'
);
if (!popup) {
const errorMsg = 'Popup was blocked by browser. Please allow popups for this site and try again.';
console.error('❌ Popup blocked:', errorMsg);
setGoogleError(errorMsg);
setIsGoogleLoading(false);
reject(new Error('Popup was blocked'));
return;
}
console.log('✅ Popup opened successfully with fallback URL');
// Listen for messages from the popup
const messageListener = (event: MessageEvent) => {
console.log('📨 Received message from popup:', event.origin, event.data);
// Verify origin for security
const apiUrl = new URL(backendUrl);
if (event.origin !== apiUrl.origin) {
console.warn('⚠️ Message from unauthorized origin:', event.origin, 'Expected:', apiUrl.origin);
return;
}
if (event.data.type === 'google_auth_success') {
console.log('✅ Google authentication successful');
console.log('📋 Full event data received:', event.data);
// Tokens are automatically set in httpOnly cookies by backend
if (event.data.authenticationAuthority) {
localStorage.setItem('auth_authority', event.data.authenticationAuthority);
console.log('✅ Auth authority set:', event.data.authenticationAuthority);
} else {
// Fallback: set 'google' as the auth authority for Google login
localStorage.setItem('auth_authority', 'google');
console.log('⚠️ authenticationAuthority not in event data, setting fallback: google');
console.log('📋 Available event.data properties:', Object.keys(event.data));
}
console.log('✅ Google authentication successful - tokens set in httpOnly cookies');
// CRITICAL: Immediately fetch user data after successful login
// Wait a bit for cookies to be properly set
setTimeout(async () => {
try {
console.log('🔄 Fetching user data immediately after Google login...');
const userResponse = await api.get('/api/google/me');
if (userResponse.data) {
// Cache user data in localStorage for privilege checkers and language
localStorage.setItem('currentUser', JSON.stringify(userResponse.data));
console.log('✅ User data fetched and cached:', {
username: userResponse.data.username,
privilege: userResponse.data.privilege,
language: userResponse.data.language
});
}
} catch (userError) {
console.error('❌ Failed to fetch user data after Google login:', userError);
// Don't block login flow, but log the error
}
}, 500);
// Clean up
window.removeEventListener('message', messageListener);
popup.close();
setIsGoogleLoading(false);
// Resolve with the response data
resolve(event.data);
} else if (event.data.type === 'google_connection_error') {
console.error('❌ Google connection error:', event.data.error);
// Handle error
window.removeEventListener('message', messageListener);
popup.close();
setIsGoogleLoading(false);
setGoogleError(event.data.error || 'Google authentication failed');
reject(new Error(event.data.error || 'Google authentication failed'));
}
};
// Add message listener
window.addEventListener('message', messageListener);
// Handle popup closing without completing auth
let popupClosedManually = false;
const checkClosed = setInterval(() => {
if (popup.closed) {
clearInterval(checkClosed);
window.removeEventListener('message', messageListener);
setIsGoogleLoading(false);
if (!popupClosedManually) {
console.warn('⚠️ Popup was closed before authentication completed');
setGoogleError('Authentication was cancelled - popup was closed before completing login');
} else {
console.log(' Popup closed after successful authentication');
}
if (!popupClosedManually) {
reject(new Error('Authentication was cancelled'));
}
}
}, 1000);
// Set a timeout to detect if popup doesn't load
const loadTimeout = setTimeout(() => {
if (!popup.closed) {
console.warn('⚠️ Popup did not load within 60 seconds');
popup.close();
clearInterval(checkClosed);
window.removeEventListener('message', messageListener);
setIsGoogleLoading(false);
setGoogleError('Authentication timeout - please check your internet connection and try again');
reject(new Error('Authentication timeout'));
}
}, 60000);
// Override popup.close to mark as manually closed
const originalClose = popup.close;
popup.close = function() {
popupClosedManually = true;
clearTimeout(loadTimeout);
return originalClose.call(this);
};
});
});
} catch (error: any) {
console.error('❌ Google authentication error:', error);
setGoogleError(error.message || 'Google authentication failed');
setIsGoogleLoading(false);
throw error;
}
};
return {
loginWithGoogle,
error: googleError,
isLoading: isGoogleLoading
};
}
// Microsoft Registration
interface MsalRegisterData {
username: string;
email: string;
fullName: string;
language?: string;
}
export function useMsalRegister() {
const { instance, accounts } = useMsal();
const { request, isLoading, error } = useApiRequest<MsalRegisterData, any>();
const registerWithMsal = async (): Promise<RegisterResponse> => {
try {
if (!accounts || accounts.length === 0) {
// If not signed in with Microsoft, sign in first
await instance.loginPopup({
scopes: ['user.read']
});
}
// Get the current account
const currentAccount = instance.getAllAccounts()[0];
if (!currentAccount) {
throw new Error('No Microsoft account found');
}
// Prepare user data from Microsoft account
const userData: MsalRegisterData = {
username: currentAccount.username,
email: currentAccount.username,
fullName: currentAccount.name || currentAccount.username,
language: 'de'
};
// Register the user through our backend
const response = await request({
url: '/api/msft/register',
method: 'post',
data: userData,
additionalConfig: {
headers: {
'Content-Type': 'application/json'
}
}
});
return {
success: true,
message: 'Registration successful',
user: response
};
} catch (error: any) {
throw error;
}
};
return {
registerWithMsal,
error,
isLoading
};
}
// Username availability check
export function useUsernameAvailability() {
const [isChecking, setIsChecking] = useState(false);
const [error, setError] = useState<string | null>(null);
const checkAvailability = async (username: string, authenticationAuthority: string = 'local'): Promise<{
username: string;
authenticationAuthority: string;
available: boolean;
message: string;
}> => {
setIsChecking(true);
setError(null);
try {
const response = await api.get('/api/local/available', {
params: {
username,
authenticationAuthority
}
});
return response.data;
} catch (error: any) {
let errorMessage = 'Failed to check username availability';
if (error.response) {
errorMessage = error.response.data?.detail || errorMessage;
}
setError(errorMessage);
throw error;
} finally {
setIsChecking(false);
}
};
return {
checkAvailability,
isChecking,
error
};
}
// Logout function
export function useLogout() {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const logout = async (): Promise<void> => {
setIsLoading(true);
setError(null);
try {
// Call logout endpoint to clear JWT tokens on server
await api.post('/api/local/logout');
// Clear local storage (user data and auth_authority)
// Note: JWT tokens are now stored in httpOnly cookies and cleared by backend
localStorage.removeItem('currentUser');
localStorage.removeItem('auth_authority');
// Redirect to login page
window.location.href = '/login?logout=true';
} catch (error: any) {
let errorMessage = 'Logout failed';
if (error.response) {
errorMessage = error.response.data?.detail || errorMessage;
}
setError(errorMessage);
// Even if logout fails on server, clear local data and redirect
// Note: JWT tokens are now stored in httpOnly cookies and cleared by backend
localStorage.removeItem('currentUser');
localStorage.removeItem('auth_authority');
window.location.href = '/login?logout=true';
} finally {
setIsLoading(false);
}
};
return {
logout,
isLoading,
error
};
}
// Get current user
export function useCurrentUser() {
const [user, setUser] = useState<any>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const getCurrentUser = async (): Promise<any> => {
setIsLoading(true);
setError(null);
try {
// 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:', endpoint, 'auth authority:', authAuthority);
const response = await api.get(endpoint);
setUser(response.data);
return response.data;
} catch (error: any) {
let errorMessage = 'Failed to get current user';
if (error.response) {
errorMessage = error.response.data?.detail || errorMessage;
}
setError(errorMessage);
throw error;
} finally {
setIsLoading(false);
}
};
return {
user,
getCurrentUser,
isLoading,
error
};
}