Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 86 additions & 88 deletions apps/dokploy/components/dashboard/settings/profile/enable-2fa.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,61 +61,17 @@ export const Enable2FA = () => {
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [step, setStep] = useState<"password" | "verify">("password");
const [isPasswordLoading, setIsPasswordLoading] = useState(false);
const [otpValue, setOtpValue] = useState("");

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) => {
const handleVerifySubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
const result = await authClient.twoFactor.verifyTotp({
code: formData.pin,
code: otpValue,
});

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;
}
Expand All @@ -137,15 +93,11 @@ export const Enable2FA = () => {
? "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", {
description: error instanceof Error ? error.message : "Unknown error",
});
toast.error("Error verifying 2FA code");
}
}
};
Expand All @@ -169,10 +121,62 @@ export const Enable2FA = () => {
setStep("password");
setData(null);
setBackupCodes([]);
passwordForm.reset();
pinForm.reset();
setOtpValue("");
passwordForm.reset({
password: "",
issuer: "",
});
}
}, [isDialogOpen, passwordForm, pinForm]);
}, [isDialogOpen, passwordForm]);

useEffect(() => {
if (step === "verify") {
setOtpValue("");
}
}, [step]);

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

return (
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
Expand Down Expand Up @@ -233,7 +237,8 @@ export const Enable2FA = () => {
/>
</FormControl>
<FormDescription>
Enter your password to enable 2FA
Use a custom issuer to identify the service you're
authenticating with.
</FormDescription>
<FormMessage />
</FormItem>
Expand All @@ -250,11 +255,7 @@ export const Enable2FA = () => {
</Form>
) : (
<Form {...pinForm}>
<form
id="pin-form"
onSubmit={pinForm.handleSubmit(handleVerifySubmit)}
className="space-y-6"
>
<form onSubmit={handleVerifySubmit} className="space-y-6">
<div className="flex flex-col gap-6 justify-center items-center">
{data?.qrCodeUrl ? (
<>
Expand Down Expand Up @@ -306,36 +307,33 @@ export const Enable2FA = () => {
)}
</div>

<FormField
control={pinForm.control}
name="pin"
render={({ field }) => (
<FormItem className="flex flex-col justify-center items-center">
<FormLabel>Verification Code</FormLabel>
<FormControl>
<InputOTP maxLength={6} {...field}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</FormControl>
<FormDescription>
Enter the 6-digit code from your authenticator app
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<div className="flex flex-col justify-center items-center">
<FormLabel>Verification Code</FormLabel>
<InputOTP
maxLength={6}
value={otpValue}
onChange={setOtpValue}
autoComplete="off"
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
<FormDescription>
Enter the 6-digit code from your authenticator app
</FormDescription>
</div>

<Button
type="submit"
className="w-full"
isLoading={isPasswordLoading}
disabled={otpValue.length !== 6}
>
Enable 2FA
</Button>
Expand Down