From 71152b664be2713167dfdac8ae814578469db8ca Mon Sep 17 00:00:00 2001
From: Mohammed Imran
Date: Fri, 17 Oct 2025 23:08:54 +0530
Subject: [PATCH 1/4] feature: enhance 2FA management UI and logic in profile
settings
- Replaced AlertDialog with Dialog for managing 2FA settings, improving user experience.
- Introduced multi-step dialog flow for verifying identity, managing actions, and displaying backup codes.
---
.../settings/profile/disable-2fa.tsx | 355 ++++++++++++++----
.../settings/profile/profile-form.tsx | 48 +--
2 files changed, 309 insertions(+), 94 deletions(-)
diff --git a/apps/dokploy/components/dashboard/settings/profile/disable-2fa.tsx b/apps/dokploy/components/dashboard/settings/profile/disable-2fa.tsx
index 4055d4079..aea342b18 100644
--- a/apps/dokploy/components/dashboard/settings/profile/disable-2fa.tsx
+++ b/apps/dokploy/components/dashboard/settings/profile/disable-2fa.tsx
@@ -1,17 +1,28 @@
import { zodResolver } from "@hookform/resolvers/zod";
-import { useState } from "react";
+import { KeyRound, RefreshCw, ShieldOff } from "lucide-react";
+import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import {
AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
+ AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
- AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
import {
Form,
FormControl,
@@ -32,11 +43,17 @@ const PasswordSchema = z.object({
});
type PasswordForm = z.infer;
+type Step = "password" | "actions" | "backup-codes";
export const Disable2FA = () => {
const utils = api.useUtils();
- const [isOpen, setIsOpen] = useState(false);
- const [isLoading, setIsLoading] = useState(false);
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
+ const [step, setStep] = useState("password");
+ const [password, setPassword] = useState("");
+ const [backupCodes, setBackupCodes] = useState([]);
+ const [showDisableConfirm, setShowDisableConfirm] = useState(false);
+ const [isDisabling, setIsDisabling] = useState(false);
+ const [isRegenerating, setIsRegenerating] = useState(false);
const form = useForm({
resolver: zodResolver(PasswordSchema),
@@ -45,91 +62,287 @@ export const Disable2FA = () => {
},
});
- const handleSubmit = async (formData: PasswordForm) => {
- setIsLoading(true);
+ useEffect(() => {
+ if (!isDialogOpen) {
+ setStep("password");
+ setPassword("");
+ setBackupCodes([]);
+ form.reset();
+ }
+ }, [isDialogOpen, form]);
+
+ const handlePasswordSubmit = async (formData: PasswordForm) => {
+ setIsRegenerating(true);
try {
- const result = await authClient.twoFactor.disable({
+ // Verify password by attempting to generate backup codes
+ // This validates the password and checks if 2FA is enabled
+ const result = await authClient.twoFactor.generateBackupCodes({
password: formData.password,
});
if (result.error) {
- form.setError("password", {
- message: result.error.message,
- });
+ form.setError("password", { message: result.error.message });
+ toast.error(result.error.message);
+ return;
+ }
+
+ // If we get here, password is correct
+ setPassword(formData.password);
+ setStep("actions");
+ } catch (error) {
+ form.setError("password", {
+ message: error instanceof Error ? error.message : "Incorrect password",
+ });
+ toast.error("Incorrect password");
+ } finally {
+ setIsRegenerating(false);
+ }
+ };
+
+ const handleRegenerateBackupCodes = async () => {
+ setIsRegenerating(true);
+ try {
+ const result = await authClient.twoFactor.generateBackupCodes({
+ password,
+ });
+
+ if (result.error) {
+ toast.error(result.error.message);
+ return;
+ }
+
+ if (result.data?.backupCodes) {
+ setBackupCodes(result.data.backupCodes);
+ setStep("backup-codes");
+ toast.success("Backup codes regenerated successfully");
+ }
+ } catch (error) {
+ toast.error(
+ error instanceof Error
+ ? error.message
+ : "Failed to regenerate backup codes",
+ );
+ } finally {
+ setIsRegenerating(false);
+ }
+ };
+
+ const handleDisable2FA = async () => {
+ setIsDisabling(true);
+ try {
+ const result = await authClient.twoFactor.disable({
+ password,
+ });
+
+ if (result.error) {
toast.error(result.error.message);
return;
}
toast.success("2FA disabled successfully");
utils.user.get.invalidate();
- setIsOpen(false);
- } catch {
- form.setError("password", {
- message: "Connection error. Please try again.",
- });
- toast.error("Connection error. Please try again.");
+ setIsDialogOpen(false);
+ setShowDisableConfirm(false);
+ } catch (error) {
+ toast.error("Failed to disable 2FA. Please try again.");
} finally {
- setIsLoading(false);
+ setIsDisabling(false);
+ }
+ };
+
+ const handleCloseDialog = () => {
+ if (step === "backup-codes") {
+ setStep("actions");
+ } else {
+ setIsDialogOpen(false);
}
};
return (
-
-
-
-
-
-
- Are you absolutely sure?
-
- This action cannot be undone. This will permanently disable
- Two-Factor Authentication for your account.
-
-
+ <>
+
+
+
+
+
+