Files
lidify/frontend/features/settings/hooks/useTwoFactor.ts
2025-12-25 18:58:06 -06:00

138 lines
4.5 KiB
TypeScript

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<string[]>([]);
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,
};
}