Skip to content

Commit f4cd617

Browse files
authored
Merge pull request #1722 from Dokploy/1715-pin-field-auto-populated-with-issuer-name-and-cannot-be-cleared-during-2fa-setup
Refactor 2FA enablement flow in Enable2FA component
2 parents 8fbad8a + 48cfe66 commit f4cd617

File tree

1 file changed

+86
-88
lines changed

1 file changed

+86
-88
lines changed

apps/dokploy/components/dashboard/settings/profile/enable-2fa.tsx

Lines changed: 86 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -61,61 +61,17 @@ export const Enable2FA = () => {
6161
const [isDialogOpen, setIsDialogOpen] = useState(false);
6262
const [step, setStep] = useState<"password" | "verify">("password");
6363
const [isPasswordLoading, setIsPasswordLoading] = useState(false);
64+
const [otpValue, setOtpValue] = useState("");
6465

65-
const handlePasswordSubmit = async (formData: PasswordForm) => {
66-
setIsPasswordLoading(true);
67-
try {
68-
const { data: enableData, error } = await authClient.twoFactor.enable({
69-
password: formData.password,
70-
issuer: formData.issuer,
71-
});
72-
73-
if (!enableData) {
74-
throw new Error(error?.message || "Error enabling 2FA");
75-
}
76-
77-
if (enableData.backupCodes) {
78-
setBackupCodes(enableData.backupCodes);
79-
}
80-
81-
if (enableData.totpURI) {
82-
const qrCodeUrl = await QRCode.toDataURL(enableData.totpURI);
83-
84-
setData({
85-
qrCodeUrl,
86-
secret: enableData.totpURI.split("secret=")[1]?.split("&")[0] || "",
87-
totpURI: enableData.totpURI,
88-
});
89-
90-
setStep("verify");
91-
toast.success("Scan the QR code with your authenticator app");
92-
} else {
93-
throw new Error("No TOTP URI received from server");
94-
}
95-
} catch (error) {
96-
toast.error(
97-
error instanceof Error ? error.message : "Error setting up 2FA",
98-
);
99-
passwordForm.setError("password", {
100-
message:
101-
error instanceof Error ? error.message : "Error setting up 2FA",
102-
});
103-
} finally {
104-
setIsPasswordLoading(false);
105-
}
106-
};
107-
108-
const handleVerifySubmit = async (formData: PinForm) => {
66+
const handleVerifySubmit = async (e: React.FormEvent) => {
67+
e.preventDefault();
10968
try {
11069
const result = await authClient.twoFactor.verifyTotp({
111-
code: formData.pin,
70+
code: otpValue,
11271
});
11372

11473
if (result.error) {
11574
if (result.error.code === "INVALID_TWO_FACTOR_AUTHENTICATION") {
116-
pinForm.setError("pin", {
117-
message: "Invalid code. Please try again.",
118-
});
11975
toast.error("Invalid verification code");
12076
return;
12177
}
@@ -137,15 +93,11 @@ export const Enable2FA = () => {
13793
? "Connection error. Please check your internet connection."
13894
: error.message;
13995

140-
pinForm.setError("pin", {
141-
message: errorMessage,
142-
});
14396
toast.error(errorMessage);
14497
} else {
145-
pinForm.setError("pin", {
146-
message: "Error verifying code",
98+
toast.error("Error verifying 2FA code", {
99+
description: error instanceof Error ? error.message : "Unknown error",
147100
});
148-
toast.error("Error verifying 2FA code");
149101
}
150102
}
151103
};
@@ -169,10 +121,62 @@ export const Enable2FA = () => {
169121
setStep("password");
170122
setData(null);
171123
setBackupCodes([]);
172-
passwordForm.reset();
173-
pinForm.reset();
124+
setOtpValue("");
125+
passwordForm.reset({
126+
password: "",
127+
issuer: "",
128+
});
174129
}
175-
}, [isDialogOpen, passwordForm, pinForm]);
130+
}, [isDialogOpen, passwordForm]);
131+
132+
useEffect(() => {
133+
if (step === "verify") {
134+
setOtpValue("");
135+
}
136+
}, [step]);
137+
138+
const handlePasswordSubmit = async (formData: PasswordForm) => {
139+
setIsPasswordLoading(true);
140+
try {
141+
const { data: enableData, error } = await authClient.twoFactor.enable({
142+
password: formData.password,
143+
issuer: formData.issuer,
144+
});
145+
146+
if (!enableData) {
147+
throw new Error(error?.message || "Error enabling 2FA");
148+
}
149+
150+
if (enableData.backupCodes) {
151+
setBackupCodes(enableData.backupCodes);
152+
}
153+
154+
if (enableData.totpURI) {
155+
const qrCodeUrl = await QRCode.toDataURL(enableData.totpURI);
156+
157+
setData({
158+
qrCodeUrl,
159+
secret: enableData.totpURI.split("secret=")[1]?.split("&")[0] || "",
160+
totpURI: enableData.totpURI,
161+
});
162+
163+
setStep("verify");
164+
toast.success("Scan the QR code with your authenticator app");
165+
} else {
166+
throw new Error("No TOTP URI received from server");
167+
}
168+
} catch (error) {
169+
toast.error(
170+
error instanceof Error ? error.message : "Error setting up 2FA",
171+
);
172+
passwordForm.setError("password", {
173+
message:
174+
error instanceof Error ? error.message : "Error setting up 2FA",
175+
});
176+
} finally {
177+
setIsPasswordLoading(false);
178+
}
179+
};
176180

177181
return (
178182
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
@@ -233,7 +237,8 @@ export const Enable2FA = () => {
233237
/>
234238
</FormControl>
235239
<FormDescription>
236-
Enter your password to enable 2FA
240+
Use a custom issuer to identify the service you're
241+
authenticating with.
237242
</FormDescription>
238243
<FormMessage />
239244
</FormItem>
@@ -250,11 +255,7 @@ export const Enable2FA = () => {
250255
</Form>
251256
) : (
252257
<Form {...pinForm}>
253-
<form
254-
id="pin-form"
255-
onSubmit={pinForm.handleSubmit(handleVerifySubmit)}
256-
className="space-y-6"
257-
>
258+
<form onSubmit={handleVerifySubmit} className="space-y-6">
258259
<div className="flex flex-col gap-6 justify-center items-center">
259260
{data?.qrCodeUrl ? (
260261
<>
@@ -306,36 +307,33 @@ export const Enable2FA = () => {
306307
)}
307308
</div>
308309

309-
<FormField
310-
control={pinForm.control}
311-
name="pin"
312-
render={({ field }) => (
313-
<FormItem className="flex flex-col justify-center items-center">
314-
<FormLabel>Verification Code</FormLabel>
315-
<FormControl>
316-
<InputOTP maxLength={6} {...field}>
317-
<InputOTPGroup>
318-
<InputOTPSlot index={0} />
319-
<InputOTPSlot index={1} />
320-
<InputOTPSlot index={2} />
321-
<InputOTPSlot index={3} />
322-
<InputOTPSlot index={4} />
323-
<InputOTPSlot index={5} />
324-
</InputOTPGroup>
325-
</InputOTP>
326-
</FormControl>
327-
<FormDescription>
328-
Enter the 6-digit code from your authenticator app
329-
</FormDescription>
330-
<FormMessage />
331-
</FormItem>
332-
)}
333-
/>
310+
<div className="flex flex-col justify-center items-center">
311+
<FormLabel>Verification Code</FormLabel>
312+
<InputOTP
313+
maxLength={6}
314+
value={otpValue}
315+
onChange={setOtpValue}
316+
autoComplete="off"
317+
>
318+
<InputOTPGroup>
319+
<InputOTPSlot index={0} />
320+
<InputOTPSlot index={1} />
321+
<InputOTPSlot index={2} />
322+
<InputOTPSlot index={3} />
323+
<InputOTPSlot index={4} />
324+
<InputOTPSlot index={5} />
325+
</InputOTPGroup>
326+
</InputOTP>
327+
<FormDescription>
328+
Enter the 6-digit code from your authenticator app
329+
</FormDescription>
330+
</div>
334331

335332
<Button
336333
type="submit"
337334
className="w-full"
338335
isLoading={isPasswordLoading}
336+
disabled={otpValue.length !== 6}
339337
>
340338
Enable 2FA
341339
</Button>

0 commit comments

Comments
 (0)