873 lines
No EOL
30 KiB
TypeScript
873 lines
No EOL
30 KiB
TypeScript
import { useState } from 'react';
|
||
|
||
import { useMsal } from '@azure/msal-react';
|
||
import api from '../api';
|
||
import { useApiRequest } from './useApi';
|
||
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 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', '');
|
||
|
||
// Generate a simple CSRF token (in production, this should come from the server)
|
||
const csrfToken = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
||
|
||
// Use the existing api instance with custom headers for this request
|
||
const response = await api.post('/api/local/login', params, {
|
||
headers: {
|
||
'Content-Type': 'application/x-www-form-urlencoded',
|
||
'X-CSRF-Token': csrfToken
|
||
}
|
||
});
|
||
|
||
// Normalize the response structure to match what the frontend expects
|
||
let normalizedAuthData;
|
||
if (response.data.token_data) {
|
||
// Backend returns token_data with tokenAccess field, normalize to accessToken
|
||
normalizedAuthData = {
|
||
accessToken: response.data.token_data.tokenAccess || response.data.access_token,
|
||
tokenType: response.data.token_data.tokenType || 'bearer',
|
||
userId: response.data.token_data.userId,
|
||
expiresAt: response.data.token_data.expiresAt,
|
||
createdAt: response.data.token_data.createdAt
|
||
};
|
||
} else {
|
||
// Fallback to old structure if needed
|
||
normalizedAuthData = {
|
||
accessToken: response.data.access_token,
|
||
tokenType: response.data.token_type || 'bearer'
|
||
};
|
||
}
|
||
|
||
// Store the normalized auth response
|
||
localStorage.setItem('auth_data', JSON.stringify(normalizedAuthData));
|
||
|
||
return {
|
||
accessToken: normalizedAuthData.accessToken,
|
||
tokenType: normalizedAuthData.tokenType
|
||
};
|
||
} catch (error: any) {
|
||
let errorMessage = 'An error occurred during login';
|
||
|
||
if (error.response) {
|
||
errorMessage = error.response.data?.detail || '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);
|
||
|
||
// Open popup to backend Microsoft login route
|
||
const popup = window.open(
|
||
loginUrl,
|
||
'msft-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);
|
||
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) => {
|
||
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 === 'msft_auth_success') {
|
||
console.log('✅ MSAL authentication successful');
|
||
// Store the auth data with normalized field names
|
||
if (event.data.token_data) {
|
||
const normalizedTokenData = {
|
||
accessToken: event.data.token_data.tokenAccess, // Convert tokenAccess to accessToken
|
||
tokenType: event.data.token_data.tokenType,
|
||
userId: event.data.token_data.userId,
|
||
expiresAt: event.data.token_data.expiresAt,
|
||
createdAt: event.data.token_data.createdAt
|
||
};
|
||
localStorage.setItem('auth_data', JSON.stringify(normalizedTokenData));
|
||
console.log('💾 Auth data stored in localStorage');
|
||
}
|
||
|
||
// Clean up
|
||
window.removeEventListener('message', messageListener);
|
||
popup.close();
|
||
setIsMsalLoading(false);
|
||
|
||
// Resolve with the token data
|
||
resolve({
|
||
accessToken: event.data.token_data.tokenAccess,
|
||
tokenType: event.data.token_data.tokenType || 'bearer',
|
||
user: {
|
||
username: '', // Will be populated by the backend
|
||
email: '',
|
||
fullName: '',
|
||
mandateId: 0
|
||
}
|
||
});
|
||
} 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
|
||
};
|
||
|
||
const response = await api.post('/api/local/register', dataToSend, {
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
}
|
||
});
|
||
|
||
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');
|
||
// Store the auth data with normalized field names
|
||
if (event.data.token_data) {
|
||
const normalizedTokenData = {
|
||
accessToken: event.data.token_data.tokenAccess, // Convert tokenAccess to accessToken
|
||
tokenType: event.data.token_data.tokenType,
|
||
userId: event.data.token_data.userId,
|
||
expiresAt: event.data.token_data.expiresAt,
|
||
createdAt: event.data.token_data.createdAt
|
||
};
|
||
localStorage.setItem('auth_data', JSON.stringify(normalizedTokenData));
|
||
console.log('💾 Auth data stored in localStorage');
|
||
}
|
||
|
||
// Clean up
|
||
window.removeEventListener('message', messageListener);
|
||
popup.close();
|
||
setIsGoogleLoading(false);
|
||
|
||
// Resolve with the token data
|
||
resolve({
|
||
accessToken: event.data.token_data.tokenAccess,
|
||
tokenType: event.data.token_data.tokenType || 'bearer',
|
||
user: {
|
||
username: '', // Will be populated by the backend
|
||
email: '',
|
||
fullName: '',
|
||
mandateId: 0
|
||
}
|
||
});
|
||
} 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');
|
||
// Store the auth data with normalized field names
|
||
if (event.data.token_data) {
|
||
const normalizedTokenData = {
|
||
accessToken: event.data.token_data.tokenAccess, // Convert tokenAccess to accessToken
|
||
tokenType: event.data.token_data.tokenType,
|
||
userId: event.data.token_data.userId,
|
||
expiresAt: event.data.token_data.expiresAt,
|
||
createdAt: event.data.token_data.createdAt
|
||
};
|
||
localStorage.setItem('auth_data', JSON.stringify(normalizedTokenData));
|
||
console.log('💾 Auth data stored in localStorage');
|
||
}
|
||
|
||
// Clean up
|
||
window.removeEventListener('message', messageListener);
|
||
popup.close();
|
||
setIsGoogleLoading(false);
|
||
|
||
// Resolve with the token data
|
||
resolve({
|
||
accessToken: event.data.token_data.tokenAccess,
|
||
tokenType: event.data.token_data.tokenType || 'bearer',
|
||
user: {
|
||
username: '', // Will be populated by the backend
|
||
email: '',
|
||
fullName: '',
|
||
mandateId: 0
|
||
}
|
||
});
|
||
} 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 {
|
||
await api.post('/api/local/logout');
|
||
|
||
// Clear local storage
|
||
localStorage.removeItem('auth_data');
|
||
|
||
// Redirect to login page
|
||
window.location.href = '/login';
|
||
} catch (error: any) {
|
||
let errorMessage = 'Logout failed';
|
||
|
||
if (error.response) {
|
||
errorMessage = error.response.data?.detail || errorMessage;
|
||
}
|
||
|
||
setError(errorMessage);
|
||
throw error;
|
||
} 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 {
|
||
const response = await api.get('/api/local/me');
|
||
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
|
||
};
|
||
}
|