import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { InputOTP, InputOTPGroup, InputOTPSlot, } from "@/components/ui/input-otp"; import { authClient } from "@/lib/auth-client"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { Fingerprint, QrCode } from "lucide-react"; import QRCode from "qrcode"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; const PasswordSchema = z.object({ password: z.string().min(8, { message: "Password is required", }), issuer: z.string().optional(), }); const PinSchema = z.object({ pin: z.string().min(6, { message: "Pin is required", }), }); type TwoFactorSetupData = { qrCodeUrl: string; secret: string; totpURI: string; }; type PasswordForm = z.infer; type PinForm = z.infer; export const Enable2FA = () => { const utils = api.useUtils(); const [data, setData] = useState(null); const [backupCodes, setBackupCodes] = useState([]); const [isDialogOpen, setIsDialogOpen] = useState(false); const [step, setStep] = useState<"password" | "verify">("password"); const [isPasswordLoading, setIsPasswordLoading] = useState(false); const handlePasswordSubmit = async (formData: PasswordForm) => { setIsPasswordLoading(true); try { const { data: enableData, error } = await authClient.twoFactor.enable({ password: formData.password, issuer: formData.issuer, }); if (!enableData) { throw new Error(error?.message || "Error enabling 2FA"); } if (enableData.backupCodes) { setBackupCodes(enableData.backupCodes); } if (enableData.totpURI) { const qrCodeUrl = await QRCode.toDataURL(enableData.totpURI); setData({ qrCodeUrl, secret: enableData.totpURI.split("secret=")[1]?.split("&")[0] || "", totpURI: enableData.totpURI, }); setStep("verify"); toast.success("Scan the QR code with your authenticator app"); } else { throw new Error("No TOTP URI received from server"); } } catch (error) { toast.error( error instanceof Error ? error.message : "Error setting up 2FA", ); passwordForm.setError("password", { message: error instanceof Error ? error.message : "Error setting up 2FA", }); } finally { setIsPasswordLoading(false); } }; const handleVerifySubmit = async (formData: PinForm) => { try { const result = await authClient.twoFactor.verifyTotp({ code: formData.pin, }); if (result.error) { if (result.error.code === "INVALID_TWO_FACTOR_AUTHENTICATION") { pinForm.setError("pin", { message: "Invalid code. Please try again.", }); toast.error("Invalid verification code"); return; } throw result.error; } if (!result.data) { throw new Error("No response received from server"); } toast.success("2FA configured successfully"); utils.user.get.invalidate(); setIsDialogOpen(false); } catch (error) { if (error instanceof Error) { const errorMessage = error.message === "Failed to fetch" ? "Connection error. Please check your internet connection." : error.message; pinForm.setError("pin", { message: errorMessage, }); toast.error(errorMessage); } else { pinForm.setError("pin", { message: "Error verifying code", }); toast.error("Error verifying 2FA code"); } } }; const passwordForm = useForm({ resolver: zodResolver(PasswordSchema), defaultValues: { password: "", }, }); const pinForm = useForm({ resolver: zodResolver(PinSchema), defaultValues: { pin: "", }, }); useEffect(() => { if (!isDialogOpen) { setStep("password"); setData(null); setBackupCodes([]); passwordForm.reset(); pinForm.reset(); } }, [isDialogOpen, passwordForm, pinForm]); return ( 2FA Setup {step === "password" ? "Enter your password to begin 2FA setup" : "Scan the QR code and verify with your authenticator app"} {step === "password" ? (
( Password Enter your password to enable 2FA )} /> ( Issuer Enter your password to enable 2FA )} /> ) : (
{data?.qrCodeUrl ? ( <>
Scan this QR code with your authenticator app 2FA QR Code
Can't scan the QR code? {data.secret}
{backupCodes && backupCodes.length > 0 && (

Backup Codes

{backupCodes.map((code, index) => ( {code} ))}

Save these backup codes in a secure place. You can use them to access your account if you lose access to your authenticator device.

)} ) : (
)}
( Verification Code Enter the 6-digit code from your authenticator app )} /> )}
); };