diff --git a/web/src/components/AuthPanel.tsx b/web/src/components/AuthPanel.tsx index 1cd29f3..fa93161 100644 --- a/web/src/components/AuthPanel.tsx +++ b/web/src/components/AuthPanel.tsx @@ -14,6 +14,8 @@ export function AuthPanel() { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [otpCode, setOtpCode] = useState(""); + const [recoveryCode, setRecoveryCode] = useState(""); + const [useRecoveryCode, setUseRecoveryCode] = useState(false); const [checkingEmail, setCheckingEmail] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(null); @@ -52,13 +54,18 @@ export function AuthPanel() { return; } - await login(email, password, otpCode.trim() || undefined); + await login( + email, + password, + useRecoveryCode ? undefined : (otpCode.trim() || undefined), + useRecoveryCode ? (recoveryCode.trim() || undefined) : undefined + ); } catch (err) { const message = getErrorMessage(err); if (step === "password" && message.toLowerCase().includes("2fa code required")) { setStep("otp"); setError(null); - setSuccess("Enter 2FA code."); + setSuccess("Enter 2FA code or use a recovery code."); return; } setError(message); @@ -70,6 +77,8 @@ export function AuthPanel() { setStep("email"); setPassword(""); setOtpCode(""); + setRecoveryCode(""); + setUseRecoveryCode(false); setName(""); setUsername(""); setError(null); @@ -142,12 +151,30 @@ export function AuthPanel() { ) : null} {step === "otp" ? ( - setOtpCode(e.target.value.replace(/\D/g, "").slice(0, 8))} - /> + <> + + {useRecoveryCode ? ( + setRecoveryCode(e.target.value.toUpperCase().replace(/[^A-Z0-9-]/g, "").slice(0, 24))} + /> + ) : ( + setOtpCode(e.target.value.replace(/\D/g, "").slice(0, 8))} + /> + )} + ) : null}