From 9c37036a39b0179ee85a6ae0955e0dbc824f34ec Mon Sep 17 00:00:00 2001 From: Milo Schwartz Date: Sun, 22 Dec 2024 14:38:17 -0500 Subject: [PATCH] add 2fa form to login --- src/components/LoginForm.tsx | 218 ++++++++++++++++++++++++++--------- 1 file changed, 162 insertions(+), 56 deletions(-) diff --git a/src/components/LoginForm.tsx b/src/components/LoginForm.tsx index 9da6f15..8d8e2e6 100644 --- a/src/components/LoginForm.tsx +++ b/src/components/LoginForm.tsx @@ -12,14 +12,14 @@ import { FormField, FormItem, FormLabel, - FormMessage, + FormMessage } from "@/components/ui/form"; import { Card, CardContent, CardDescription, CardHeader, - CardTitle, + CardTitle } from "@/components/ui/card"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { LoginResponse } from "@server/routers/auth"; @@ -29,6 +29,12 @@ import { formatAxiosError } from "@app/lib/utils"; import { LockIcon } from "lucide-react"; import { createApiClient } from "@app/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; +import { + InputOTP, + InputOTPGroup, + InputOTPSeparator, + InputOTPSlot +} from "./ui/input-otp"; type LoginFormProps = { redirect?: string; @@ -39,7 +45,11 @@ const formSchema = z.object({ email: z.string().email({ message: "Invalid email address" }), password: z .string() - .min(8, { message: "Password must be at least 8 characters" }), + .min(8, { message: "Password must be at least 8 characters" }) +}); + +const mfaSchema = z.object({ + code: z.string().length(6, { message: "Invalid code" }) }); export default function LoginForm({ redirect, onLogin }: LoginFormProps) { @@ -50,17 +60,26 @@ export default function LoginForm({ redirect, onLogin }: LoginFormProps) { const [error, setError] = useState(null); const [loading, setLoading] = useState(false); + const [mfaRequested, setMfaRequested] = useState(false); + const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { email: "", - password: "", - }, + password: "" + } }); - async function onSubmit(values: z.infer) { - const { email, password } = values; + const mfaForm = useForm>({ + resolver: zodResolver(mfaSchema), + defaultValues: { + code: "" + } + }); + async function onSubmit(values: any) { + const { email, password } = form.getValues(); + const { code } = mfaForm.getValues() setLoading(true); @@ -68,18 +87,30 @@ export default function LoginForm({ redirect, onLogin }: LoginFormProps) { .post>("/auth/login", { email, password, + code }) .catch((e) => { console.error(e); setError( - formatAxiosError(e, "An error occurred while logging in"), + formatAxiosError(e, "An error occurred while logging in") ); }); - if (res && res.status === 200) { + if (res) { setError(null); - if (res.data?.data?.emailVerificationRequired) { + const data = res.data.data; + + console.log(data); + + if (data?.codeRequested) { + setMfaRequested(true); + setLoading(false); + mfaForm.reset(); + return; + } + + if (data?.emailVerificationRequired) { if (redirect) { router.push(`/auth/verify-email?redirect=${redirect}`); } else { @@ -97,51 +128,126 @@ export default function LoginForm({ redirect, onLogin }: LoginFormProps) { } return ( -
- - ( - - Email - - - - - - )} - /> - ( - - Password - - - - - - )} - /> - {error && ( - - {error} - - )} - - - +
+ {!mfaRequested && ( +
+ + ( + + Email + + + + + + )} + /> + ( + + Password + + + + + + )} + /> + {error && ( + + {error} + + )} + + + + )} + + {mfaRequested && ( +
+ + ( + + Authenticator Code + +
+ + + + + + + + + + + + + +
+
+ +
+ )} + /> + {error && ( + + {error} + + )} + +
+ + +
+ + + )} +
); }