290 lines
No EOL
7.3 KiB
TypeScript
290 lines
No EOL
7.3 KiB
TypeScript
import { useState } from 'react';
|
|
import axios from 'axios';
|
|
import { useMsal } from '@azure/msal-react';
|
|
import api from '../api';
|
|
import { useApiRequest } from './useApi';
|
|
|
|
// 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', '');
|
|
|
|
// Create a custom axios instance for this request
|
|
const instance = axios.create({
|
|
baseURL: import.meta.env.VITE_API_BASE_URL,
|
|
withCredentials: true,
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|
}
|
|
});
|
|
|
|
const response = await instance.post('/api/token', params);
|
|
|
|
// Store the entire auth response
|
|
if (response.data.accessToken) {
|
|
localStorage.setItem('auth_data', JSON.stringify(response.data));
|
|
}
|
|
|
|
return response.data;
|
|
} 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 { instance, accounts } = useMsal();
|
|
const { request, isLoading, error } = useApiRequest<null, MsalAuthResponse>();
|
|
const [msalError, setMsalError] = useState<string | null>(null);
|
|
const [isMsalLoading, setIsMsalLoading] = useState(false);
|
|
|
|
const loginWithMsal = async (): Promise<MsalAuthResponse> => {
|
|
setIsMsalLoading(true);
|
|
setMsalError(null);
|
|
|
|
try {
|
|
let msalToken;
|
|
|
|
// If we have an account, try to get the token silently
|
|
if (accounts.length > 0) {
|
|
const silentRequest = {
|
|
scopes: ['user.read'],
|
|
account: accounts[0]
|
|
};
|
|
|
|
try {
|
|
const response = await instance.acquireTokenSilent(silentRequest);
|
|
msalToken = response.accessToken;
|
|
} catch (e) {
|
|
// If silent token acquisition fails, fall back to popup
|
|
const response = await instance.acquireTokenPopup(silentRequest);
|
|
msalToken = response.accessToken;
|
|
}
|
|
} else {
|
|
// No account, do popup login
|
|
const response = await instance.loginPopup({
|
|
scopes: ['user.read']
|
|
});
|
|
|
|
if (response.account) {
|
|
const tokenResponse = await instance.acquireTokenSilent({
|
|
scopes: ['user.read'],
|
|
account: response.account
|
|
});
|
|
msalToken = tokenResponse.accessToken;
|
|
} else {
|
|
throw new Error('Failed to get account after login');
|
|
}
|
|
}
|
|
|
|
// Exchange MSAL token for backend token
|
|
const response = await api.post('/api/msft/token', null, {
|
|
headers: {
|
|
'Authorization': `Bearer ${msalToken}`
|
|
}
|
|
});
|
|
|
|
// Store the backend token
|
|
if (response.data.accessToken) {
|
|
localStorage.setItem('auth_data', JSON.stringify(response.data));
|
|
}
|
|
|
|
return response.data;
|
|
} catch (error: any) {
|
|
let errorMessage = 'MSAL Login fehlgeschlagen';
|
|
|
|
if (error.response) {
|
|
errorMessage = error.response.data?.detail || error.response.data?.message || errorMessage;
|
|
} else if (error.request) {
|
|
errorMessage = 'Keine Antwort vom Server erhalten';
|
|
} else {
|
|
errorMessage = error.message || errorMessage;
|
|
}
|
|
|
|
setMsalError(errorMessage);
|
|
throw new Error(errorMessage);
|
|
} finally {
|
|
setIsMsalLoading(false);
|
|
}
|
|
};
|
|
|
|
return {
|
|
loginWithMsal,
|
|
error: msalError || error,
|
|
isLoading: isMsalLoading || isLoading
|
|
};
|
|
}
|
|
|
|
// Registration
|
|
interface RegisterData {
|
|
username: string;
|
|
password: string;
|
|
email: string;
|
|
fullName: string;
|
|
language?: string;
|
|
}
|
|
|
|
interface RegisterResponse {
|
|
success: boolean;
|
|
message?: string;
|
|
user?: {
|
|
username: string;
|
|
email: string;
|
|
fullName: string;
|
|
};
|
|
}
|
|
|
|
export function useRegister() {
|
|
const { request, isLoading, error } = useApiRequest<RegisterData, any>();
|
|
|
|
const register = async (userData: RegisterData): Promise<RegisterResponse> => {
|
|
try {
|
|
// Add default language if not provided
|
|
const dataToSend = {
|
|
...userData,
|
|
language: userData.language || 'de'
|
|
};
|
|
|
|
const response = await request({
|
|
url: '/api/users/register',
|
|
method: 'post',
|
|
data: dataToSend,
|
|
additionalConfig: {
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
}
|
|
});
|
|
|
|
return {
|
|
success: true,
|
|
message: 'Registration successful',
|
|
user: response
|
|
};
|
|
} catch (error: any) {
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
return {
|
|
register,
|
|
error,
|
|
isLoading
|
|
};
|
|
}
|
|
|
|
// 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/users/register-with-msal',
|
|
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
|
|
};
|
|
}
|