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(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 }; };