Merge pull request #5735 from imrja8/fix/2fa-password-manager-autofill

Fix/2fa password manager autofill
This commit is contained in:
Yash Kumar
2026-10-23 19:52:47 +05:30
committed by GitHub
+48 -31
View File
@@ -5,7 +5,6 @@ import {
} from "@dokploy/server";
import { validateRequest } from "@dokploy/server/lib/auth";
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
import { REGEXP_ONLY_DIGITS } from "input-otp";
import { Fingerprint } from "lucide-react";
import type { GetServerSidePropsContext } from "next";
import Link from "next/link";
@@ -38,14 +37,9 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/components/ui/input-otp";
import { Label } from "@/components/ui/label";
import { authClient } from "@/lib/auth-client";
import { cn } from "@/lib/utils";
import { appRouter } from "@/server/api/root";
import { api } from "@/utils/api";
import { generateServerSideHelper } from "@/utils/create-server-helpers";
@@ -77,6 +71,9 @@ export default function Home({ IS_CLOUD, enforceSSO }: Props) {
const [isTwoFactor, setIsTwoFactor] = useState(false);
const [error, setError] = useState<string | null>(null);
const [twoFactorCode, setTwoFactorCode] = useState("");
const [twoFactorCaret, setTwoFactorCaret] = useState(0);
// Clamped so a caret left over from before the code was cleared can't point past it.
const twoFactorCursor = Math.min(twoFactorCaret, twoFactorCode.length);
const [isBackupCodeModalOpen, setIsBackupCodeModalOpen] = useState(false);
const [backupCode, setBackupCode] = useState("");
const loginForm = useForm<LoginForm>({
@@ -183,12 +180,15 @@ export default function Home({ IS_CLOUD, enforceSSO }: Props) {
toast.error("Please enter a valid 6-digit code");
return;
}
await verifyTwoFactorCode(twoFactorCode);
};
const verifyTwoFactorCode = async (code: string) => {
if (isTwoFactorLoading) return;
setIsTwoFactorLoading(true);
try {
const { error } = await authClient.twoFactor.verifyTotp({
code: twoFactorCode.replace(/\s/g, ""),
});
const { error } = await authClient.twoFactor.verifyTotp({ code });
if (error) {
toast.error(error.message);
@@ -346,27 +346,44 @@ export default function Home({ IS_CLOUD, enforceSSO }: Props) {
>
<div className="flex flex-col gap-2">
<Label htmlFor="totp-code">2FA Code</Label>
<InputOTP
id="totp-code"
name="totp"
value={twoFactorCode}
onChange={setTwoFactorCode}
maxLength={6}
pattern={REGEXP_ONLY_DIGITS}
autoFocus
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
{/* Visible input so password managers can autofill it */}
<div className="group relative isolate w-56">
<Input
id="totp-code"
name="totp"
value={twoFactorCode}
onChange={(e) => {
const code = e.target.value.replace(/\D/g, "");
setTwoFactorCode(code);
setTwoFactorCaret(e.target.selectionStart ?? code.length);
if (code.length === 6) verifyTwoFactorCode(code);
}}
onSelect={(e) =>
setTwoFactorCaret(e.currentTarget.selectionStart ?? 0)
}
autoComplete="one-time-code"
inputMode="numeric"
maxLength={6}
readOnly={isTwoFactorLoading}
autoFocus
className={cn(
"h-8 border-0 py-0 pr-0 pl-[calc((2rem_-_1ch)/2)] font-mono text-base tracking-[calc(2rem_-_1ch)] focus-visible:ring-0 md:text-base dark:bg-transparent",
twoFactorCursor === 6 && "caret-transparent",
)}
/>
<div
aria-hidden
className="pointer-events-none absolute inset-y-0 left-0 -z-10 flex"
>
{[0, 1, 2, 3, 4, 5].map((slot) => (
<div
key={slot}
data-active={slot === Math.min(twoFactorCursor, 5)}
className="relative size-8 border-y border-r border-input first:rounded-l-lg first:border-l last:rounded-r-lg dark:bg-input/30 group-focus-within:data-[active=true]:z-10 group-focus-within:data-[active=true]:border-ring group-focus-within:data-[active=true]:ring-3 group-focus-within:data-[active=true]:ring-ring/50"
/>
))}
</div>
</div>
<CardDescription>
Enter the 6-digit code from your authenticator app
</CardDescription>