import React, { useState } from 'react'; import { pb } from '../../config/pocketbase'; import * as OTPAuth from 'otpauth'; import { QRCodeSVG } from 'qrcode.react'; interface LoginProps { onLoginSuccess: () => void; } export default function Login({ onLoginSuccess }: LoginProps) { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [mfaCode, setMfaCode] = useState(''); const [error, setError] = useState(''); const [isLoading, setIsLoading] = useState(false); const [step, setStep] = useState<1 | 2>(1); // États pour la cryptographie TOTP (Flux Local) const [userId, setUserId] = useState(''); const [totpSecret, setTotpSecret] = useState(''); const [qrUrl, setQrUrl] = useState(''); const [isFirstSetup, setIsFirstSetup] = useState(false); // -------------------------------------------------------- // 1 Connexion Zitadel (SSO) // -------------------------------------------------------- const handleZitadelLogin = async () => { setError(''); setIsLoading(true); try { // PocketBase gère automatiquement la popup vers Zitadel et le retour du token const authData = await pb.collection('aegis_users').authWithOAuth2({ provider: 'oidc' }); console.log("Données d'authentification SSO :", authData); if (authData) { // Zitadel a déjà géré la sécurité et le MFA de son côté. // On ouvre directement le coffre-fort. onLoginSuccess(); } } catch (err: any) { console.error("Erreur d'authentification SSO :", err); setError("Échec de la connexion sécurisée via GISE Identity."); pb.authStore.clear(); } finally { setIsLoading(false); } }; // -------------------------------------------------------- // 2 Connexion Email - Mot de passe // -------------------------------------------------------- const handleLocalLogin = async (e: React.FormEvent) => { e.preventDefault(); setError(''); setIsLoading(true); try { const authData = await pb.collection('aegis_users').authWithPassword(email, password); if (authData.record.mfa_enabled) { setUserId(authData.record.id); if (!authData.record.totp_secret) { const totp = new OTPAuth.TOTP({ issuer: 'AEGIS by GISE', label: email, algorithm: 'SHA1', digits: 6, period: 30, secret: new OTPAuth.Secret({ size: 20 }) }); setTotpSecret(totp.secret.base32); setQrUrl(totp.toString()); setIsFirstSetup(true); } else { setTotpSecret(authData.record.totp_secret); setIsFirstSetup(false); } setStep(2); // On passe à l'étape MFA locale } else { onLoginSuccess(); } } catch (err: any) { setError("Identifiants institutionnels incorrects ou accès révoqué."); pb.authStore.clear(); } finally { setIsLoading(false); } }; const handleFinalStep = async (e: React.FormEvent) => { e.preventDefault(); setError(''); setIsLoading(true); try { const totp = new OTPAuth.TOTP({ issuer: 'AEGIS by GISE', label: email, algorithm: 'SHA1', digits: 6, period: 30, secret: OTPAuth.Secret.fromBase32(totpSecret) }); const isValid = totp.validate({ token: mfaCode, window: 1 }) !== null; if (isValid) { if (isFirstSetup) { await pb.collection('aegis_users').update(userId, { totp_secret: totpSecret }); } onLoginSuccess(); } else { setError("Code de sécurité invalide ou expiré."); setMfaCode(''); } } catch (err) { setError("Une erreur critique est survenue lors de la vérification."); } finally { setIsLoading(false); } }; const handleCancelMFA = () => { pb.authStore.clear(); setStep(1); setPassword(''); setMfaCode(''); setError(''); setIsFirstSetup(false); }; return (
Accès restreint