Files
aegis/src/hooks/useLoginFlow.ts
T
2026-07-30 14:08:49 +02:00

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