import { useState, useCallback, useRef } from "react"; import { api } from "@/lib/api"; export function useTwoFactor() { const [twoFactorEnabled, setTwoFactorEnabled] = useState(false); const [loadingTwoFactor, setLoadingTwoFactor] = useState(false); const [settingUpTwoFactor, setSettingUpTwoFactor] = useState(false); const [twoFactorSecret, setTwoFactorSecret] = useState(""); const [twoFactorQR, setTwoFactorQR] = useState(""); const [twoFactorToken, setTwoFactorToken] = useState(""); const [disablingTwoFactor, setDisablingTwoFactor] = useState(false); const [disableTwoFactorPassword, setDisableTwoFactorPassword] = useState(""); const [disableTwoFactorToken, setDisableTwoFactorToken] = useState(""); const [recoveryCodes, setRecoveryCodes] = useState([]); const [showRecoveryCodes, setShowRecoveryCodes] = useState(false); // Retry tracking to prevent infinite loops on failure const retryCountRef = useRef(0); const maxRetries = 3; const hasFailedRef = useRef(false); const load2FAStatus = useCallback(async () => { // Don't retry if we've already failed too many times if (hasFailedRef.current) { return; } try { setLoadingTwoFactor(true); const status = await api.get("/auth/2fa/status"); setTwoFactorEnabled(status.enabled); // Reset retry count on success retryCountRef.current = 0; } catch (error) { console.error("Failed to load 2FA status:", error); retryCountRef.current++; // Stop retrying after max attempts if (retryCountRef.current >= maxRetries) { hasFailedRef.current = true; console.warn(`2FA status load failed after ${maxRetries} attempts, giving up`); } } finally { setLoadingTwoFactor(false); } }, []); const setup2FA = async () => { try { setLoadingTwoFactor(true); const response = await api.post("/auth/2fa/setup", {}); setTwoFactorSecret(response.secret); setTwoFactorQR(response.qrCode); setSettingUpTwoFactor(true); } catch (error: any) { console.error("Failed to setup 2FA:", error); throw error; // Let caller handle display } finally { setLoadingTwoFactor(false); } }; const enable2FA = async (token: string) => { try { setLoadingTwoFactor(true); const response = await api.post("/auth/2fa/enable", { secret: twoFactorSecret, token, }); setRecoveryCodes(response.recoveryCodes); setShowRecoveryCodes(true); setTwoFactorEnabled(true); setSettingUpTwoFactor(false); setTwoFactorToken(""); } catch (error: any) { console.error("Failed to enable 2FA:", error); throw error; // Let caller handle display } finally { setLoadingTwoFactor(false); } }; const disable2FA = async (password: string, token: string) => { try { setDisablingTwoFactor(true); await api.post("/auth/2fa/disable", { password, token, }); setTwoFactorEnabled(false); setDisableTwoFactorPassword(""); setDisableTwoFactorToken(""); } catch (error: any) { console.error("Failed to disable 2FA:", error); throw error; // Let caller handle display } finally { setDisablingTwoFactor(false); } }; const cancel2FASetup = () => { setSettingUpTwoFactor(false); setTwoFactorToken(""); setTwoFactorSecret(""); setTwoFactorQR(""); }; const closeRecoveryCodes = () => { setShowRecoveryCodes(false); setRecoveryCodes([]); }; return { twoFactorEnabled, loadingTwoFactor, settingUpTwoFactor, twoFactorSecret, twoFactorQR, twoFactorToken, disablingTwoFactor, disableTwoFactorPassword, disableTwoFactorToken, recoveryCodes, showRecoveryCodes, setTwoFactorToken, setDisableTwoFactorPassword, setDisableTwoFactorToken, load2FAStatus, setup2FA, enable2FA, disable2FA, cancel2FASetup, closeRecoveryCodes, }; }