diff --git a/apps/dokploy/components/dashboard/settings/profile/disable-2fa.tsx b/apps/dokploy/components/dashboard/settings/profile/disable-2fa.tsx index 79306bf1..f07aaed6 100644 --- a/apps/dokploy/components/dashboard/settings/profile/disable-2fa.tsx +++ b/apps/dokploy/components/dashboard/settings/profile/disable-2fa.tsx @@ -35,6 +35,7 @@ type PasswordForm = z.infer; export const Disable2FA = () => { const utils = api.useUtils(); + const [isOpen, setIsOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); const form = useForm({ @@ -72,7 +73,7 @@ export const Disable2FA = () => { }; return ( - + @@ -116,6 +117,7 @@ export const Disable2FA = () => { variant="outline" onClick={() => { form.reset(); + setIsOpen(false); }} > Cancel diff --git a/apps/dokploy/pages/index.tsx b/apps/dokploy/pages/index.tsx index 8013c631..cb3684a5 100644 --- a/apps/dokploy/pages/index.tsx +++ b/apps/dokploy/pages/index.tsx @@ -20,16 +20,23 @@ import { InputOTPSlot, } from "@/components/ui/input-otp"; import { Label } from "@/components/ui/label"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; import { authClient } from "@/lib/auth-client"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; import { IS_CLOUD, auth, isAdminPresent } from "@dokploy/server"; import { validateRequest } from "@dokploy/server/lib/auth"; import { zodResolver } from "@hookform/resolvers/zod"; -import { Session, getSessionCookie } from "better-auth"; -import { betterFetch } from "better-auth/react"; import base32 from "hi-base32"; import { REGEXP_ONLY_DIGITS } from "input-otp"; +import { AlertTriangle } from "lucide-react"; import type { GetServerSidePropsContext } from "next"; import Link from "next/link"; import { useRouter } from "next/router"; @@ -48,8 +55,14 @@ const TwoFactorSchema = z.object({ code: z.string().min(6), }); +const BackupCodeSchema = z.object({ + code: z.string().min(8, { + message: "Backup code must be at least 8 characters", + }), +}); + type LoginForm = z.infer; -type TwoFactorForm = z.infer; +type BackupCodeForm = z.infer; interface Props { IS_CLOUD: boolean; @@ -58,9 +71,12 @@ export default function Home({ IS_CLOUD }: Props) { const router = useRouter(); const [isLoginLoading, setIsLoginLoading] = useState(false); const [isTwoFactorLoading, setIsTwoFactorLoading] = useState(false); + const [isBackupCodeLoading, setIsBackupCodeLoading] = useState(false); const [isTwoFactor, setIsTwoFactor] = useState(false); const [error, setError] = useState(null); const [twoFactorCode, setTwoFactorCode] = useState(""); + const [isBackupCodeModalOpen, setIsBackupCodeModalOpen] = useState(false); + const [backupCode, setBackupCode] = useState(""); const loginForm = useForm({ resolver: zodResolver(LoginSchema), @@ -128,15 +144,33 @@ export default function Home({ IS_CLOUD }: Props) { } }; - const convertBase32ToHex = (base32Secret: string) => { + const onBackupCodeSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (backupCode.length < 8) { + toast.error("Please enter a valid backup code"); + return; + } + + setIsBackupCodeLoading(true); try { - // Usar asBytes() para obtener los bytes directamente - const bytes = base32.decode.asBytes(base32Secret.toUpperCase()); - // Convertir bytes a hex - return Buffer.from(bytes).toString("hex"); + const { data, error } = await authClient.twoFactor.verifyBackupCode({ + code: backupCode.trim(), + }); + + if (error) { + toast.error(error.message); + setError( + error.message || "An error occurred while verifying backup code", + ); + return; + } + + toast.success("Logged in successfully"); + router.push("/dashboard/projects"); } catch (error) { - console.error("Error converting base32 to hex:", error); - return base32Secret; // Fallback al valor original si hay error + toast.error("An error occurred while verifying backup code"); + } finally { + setIsBackupCodeLoading(false); } }; @@ -206,56 +240,116 @@ export default function Home({ IS_CLOUD }: Props) { ) : ( -
-
- - - - - - - - - - - - - Enter the 6-digit code from your authenticator app - -
+ <> + +
+ + + + + + + + + + + + + Enter the 6-digit code from your authenticator app + + +
-
- - -
-
+
+ + +
+ + + + + + Enter Backup Code + + Enter one of your backup codes to access your account + + + +
+
+ + setBackupCode(e.target.value)} + placeholder="Enter your backup code" + className="font-mono" + /> + + Enter one of the backup codes you received when setting up + 2FA + +
+ +
+ + +
+
+
+
+ )}