116 lines
3.2 KiB
TypeScript
116 lines
3.2 KiB
TypeScript
import { useState } from 'react';
|
|
import { pb } from '@/services/pocketbase';
|
|
import * as OTPAuth from 'otpauth';
|
|
|
|
interface MfaConfig {
|
|
userId: string;
|
|
totpSecret: string;
|
|
qrUrl: string;
|
|
isFirstSetup: boolean;
|
|
email: string;
|
|
}
|
|
|
|
export const useLoginFlow = (onLoginSuccess: () => void) => {
|
|
const [step, setStep] = useState<1 | 2>(1);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [error, setError] = useState('');
|
|
const [mfaConfig, setMfaConfig] = useState<MfaConfig | null>(null);
|
|
|
|
// --- SSO ZITADEL ---
|
|
const loginWithZitadel = async () => {
|
|
setError('');
|
|
setIsLoading(true);
|
|
try {
|
|
const authData = await pb.collection('aegis_users').authWithOAuth2({ provider: 'oidc' });
|
|
if (authData) onLoginSuccess();
|
|
} catch (err) {
|
|
console.error(err);
|
|
setError("Échec de la connexion via GISE Identity.");
|
|
pb.authStore.clear();
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
// --- LOCAL LOGIN (Étape 1) ---
|
|
const loginWithLocal = async (email: string, pass: string) => {
|
|
setError('');
|
|
setIsLoading(true);
|
|
try {
|
|
const authData = await pb.collection('aegis_users').authWithPassword(email, pass);
|
|
|
|
if (authData.record.mfa_enabled) {
|
|
let secret = authData.record.totp_secret;
|
|
let qrUrl = '';
|
|
let isFirstSetup = false;
|
|
|
|
// Génération d'un nouveau secret si première fois
|
|
if (!secret) {
|
|
const totp = new OTPAuth.TOTP({
|
|
issuer: 'AEGIS by GISE',
|
|
label: email,
|
|
algorithm: 'SHA1',
|
|
digits: 6,
|
|
period: 30,
|
|
secret: new OTPAuth.Secret({ size: 20 })
|
|
});
|
|
secret = totp.secret.base32;
|
|
qrUrl = totp.toString();
|
|
isFirstSetup = true;
|
|
}
|
|
|
|
setMfaConfig({ userId: authData.record.id, totpSecret: secret, qrUrl, isFirstSetup, email });
|
|
setStep(2);
|
|
} else {
|
|
onLoginSuccess();
|
|
}
|
|
} catch (err) {
|
|
setError("Identifiants incorrects ou accès révoqué.");
|
|
pb.authStore.clear();
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
// --- VALIDATION MFA (Étape 2) ---
|
|
const verifyMfa = async (mfaCode: string) => {
|
|
if (!mfaConfig) return;
|
|
setError('');
|
|
setIsLoading(true);
|
|
|
|
try {
|
|
const totp = new OTPAuth.TOTP({
|
|
issuer: 'AEGIS by GISE',
|
|
label: mfaConfig.email,
|
|
algorithm: 'SHA1',
|
|
digits: 6,
|
|
period: 30,
|
|
secret: OTPAuth.Secret.fromBase32(mfaConfig.totpSecret)
|
|
});
|
|
|
|
const isValid = totp.validate({ token: mfaCode, window: 1 }) !== null;
|
|
|
|
if (isValid) {
|
|
if (mfaConfig.isFirstSetup) {
|
|
await pb.collection('aegis_users').update(mfaConfig.userId, { totp_secret: mfaConfig.totpSecret });
|
|
}
|
|
onLoginSuccess();
|
|
} else {
|
|
setError("Code de sécurité invalide ou expiré.");
|
|
}
|
|
} catch (err) {
|
|
setError("Erreur critique lors de la vérification.");
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const cancelMfa = () => {
|
|
pb.authStore.clear();
|
|
setStep(1);
|
|
setMfaConfig(null);
|
|
setError('');
|
|
};
|
|
|
|
return { step, isLoading, error, mfaConfig, loginWithZitadel, loginWithLocal, verifyMfa, cancelMfa };
|
|
}; |