Explorar o código

add 2fa form to login

Milo Schwartz hai 6 meses
pai
achega
9c37036a39
Modificáronse 1 ficheiros con 162 adicións e 56 borrados
  1. 162 56
      src/components/LoginForm.tsx

+ 162 - 56
src/components/LoginForm.tsx

@@ -12,14 +12,14 @@ import {
     FormField,
     FormField,
     FormItem,
     FormItem,
     FormLabel,
     FormLabel,
-    FormMessage,
+    FormMessage
 } from "@/components/ui/form";
 } from "@/components/ui/form";
 import {
 import {
     Card,
     Card,
     CardContent,
     CardContent,
     CardDescription,
     CardDescription,
     CardHeader,
     CardHeader,
-    CardTitle,
+    CardTitle
 } from "@/components/ui/card";
 } from "@/components/ui/card";
 import { Alert, AlertDescription } from "@/components/ui/alert";
 import { Alert, AlertDescription } from "@/components/ui/alert";
 import { LoginResponse } from "@server/routers/auth";
 import { LoginResponse } from "@server/routers/auth";
@@ -29,6 +29,12 @@ import { formatAxiosError } from "@app/lib/utils";
 import { LockIcon } from "lucide-react";
 import { LockIcon } from "lucide-react";
 import { createApiClient } from "@app/api";
 import { createApiClient } from "@app/api";
 import { useEnvContext } from "@app/hooks/useEnvContext";
 import { useEnvContext } from "@app/hooks/useEnvContext";
+import {
+    InputOTP,
+    InputOTPGroup,
+    InputOTPSeparator,
+    InputOTPSlot
+} from "./ui/input-otp";
 
 
 type LoginFormProps = {
 type LoginFormProps = {
     redirect?: string;
     redirect?: string;
@@ -39,7 +45,11 @@ const formSchema = z.object({
     email: z.string().email({ message: "Invalid email address" }),
     email: z.string().email({ message: "Invalid email address" }),
     password: z
     password: z
         .string()
         .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) {
 export default function LoginForm({ redirect, onLogin }: LoginFormProps) {
@@ -50,17 +60,26 @@ export default function LoginForm({ redirect, onLogin }: LoginFormProps) {
     const [error, setError] = useState<string | null>(null);
     const [error, setError] = useState<string | null>(null);
     const [loading, setLoading] = useState(false);
     const [loading, setLoading] = useState(false);
 
 
+    const [mfaRequested, setMfaRequested] = useState(false);
+
     const form = useForm<z.infer<typeof formSchema>>({
     const form = useForm<z.infer<typeof formSchema>>({
         resolver: zodResolver(formSchema),
         resolver: zodResolver(formSchema),
         defaultValues: {
         defaultValues: {
             email: "",
             email: "",
-            password: "",
-        },
+            password: ""
+        }
     });
     });
 
 
-    async function onSubmit(values: z.infer<typeof formSchema>) {
-        const { email, password } = values;
+    const mfaForm = useForm<z.infer<typeof mfaSchema>>({
+        resolver: zodResolver(mfaSchema),
+        defaultValues: {
+            code: ""
+        }
+    });
 
 
+    async function onSubmit(values: any) {
+        const { email, password } = form.getValues();
+        const { code } = mfaForm.getValues()
 
 
         setLoading(true);
         setLoading(true);
 
 
@@ -68,18 +87,30 @@ export default function LoginForm({ redirect, onLogin }: LoginFormProps) {
             .post<AxiosResponse<LoginResponse>>("/auth/login", {
             .post<AxiosResponse<LoginResponse>>("/auth/login", {
                 email,
                 email,
                 password,
                 password,
+                code
             })
             })
             .catch((e) => {
             .catch((e) => {
                 console.error(e);
                 console.error(e);
                 setError(
                 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);
             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) {
                 if (redirect) {
                     router.push(`/auth/verify-email?redirect=${redirect}`);
                     router.push(`/auth/verify-email?redirect=${redirect}`);
                 } else {
                 } else {
@@ -97,51 +128,126 @@ export default function LoginForm({ redirect, onLogin }: LoginFormProps) {
     }
     }
 
 
     return (
     return (
-        <Form {...form}>
-            <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
-                <FormField
-                    control={form.control}
-                    name="email"
-                    render={({ field }) => (
-                        <FormItem>
-                            <FormLabel>Email</FormLabel>
-                            <FormControl>
-                                <Input
-                                    placeholder="Enter your email"
-                                    {...field}
-                                />
-                            </FormControl>
-                            <FormMessage />
-                        </FormItem>
-                    )}
-                />
-                <FormField
-                    control={form.control}
-                    name="password"
-                    render={({ field }) => (
-                        <FormItem>
-                            <FormLabel>Password</FormLabel>
-                            <FormControl>
-                                <Input
-                                    type="password"
-                                    placeholder="Enter your password"
-                                    {...field}
-                                />
-                            </FormControl>
-                            <FormMessage />
-                        </FormItem>
-                    )}
-                />
-                {error && (
-                    <Alert variant="destructive">
-                        <AlertDescription>{error}</AlertDescription>
-                    </Alert>
-                )}
-                <Button type="submit" className="w-full" loading={loading}>
-                    <LockIcon className="w-4 h-4 mr-2" />
-                    Login
-                </Button>
-            </form>
-        </Form>
+        <div>
+            {!mfaRequested && (
+                <Form {...form}>
+                    <form
+                        onSubmit={form.handleSubmit(onSubmit)}
+                        className="space-y-8"
+                    >
+                        <FormField
+                            control={form.control}
+                            name="email"
+                            render={({ field }) => (
+                                <FormItem>
+                                    <FormLabel>Email</FormLabel>
+                                    <FormControl>
+                                        <Input
+                                            placeholder="Enter your email"
+                                            {...field}
+                                        />
+                                    </FormControl>
+                                    <FormMessage />
+                                </FormItem>
+                            )}
+                        />
+                        <FormField
+                            control={form.control}
+                            name="password"
+                            render={({ field }) => (
+                                <FormItem>
+                                    <FormLabel>Password</FormLabel>
+                                    <FormControl>
+                                        <Input
+                                            type="password"
+                                            placeholder="Enter your password"
+                                            {...field}
+                                        />
+                                    </FormControl>
+                                    <FormMessage />
+                                </FormItem>
+                            )}
+                        />
+                        {error && (
+                            <Alert variant="destructive">
+                                <AlertDescription>{error}</AlertDescription>
+                            </Alert>
+                        )}
+                        <Button
+                            type="submit"
+                            className="w-full"
+                            loading={loading}
+                        >
+                            <LockIcon className="w-4 h-4 mr-2" />
+                            Login
+                        </Button>
+                    </form>
+                </Form>
+            )}
+
+            {mfaRequested && (
+                <Form {...mfaForm}>
+                    <form
+                        onSubmit={mfaForm.handleSubmit(onSubmit)}
+                        className="space-y-8"
+                    >
+                        <FormField
+                            control={mfaForm.control}
+                            name="code"
+                            render={({ field }) => (
+                                <FormItem>
+                                    <FormLabel>Authenticator Code</FormLabel>
+                                    <FormControl>
+                                        <div className="flex justify-center">
+                                            <InputOTP maxLength={6} {...field}>
+                                                <InputOTPGroup>
+                                                    <InputOTPSlot index={0} />
+                                                    <InputOTPSlot index={1} />
+                                                    <InputOTPSlot index={2} />
+                                                </InputOTPGroup>
+                                                <InputOTPSeparator />
+                                                <InputOTPGroup>
+                                                    <InputOTPSlot index={3} />
+                                                    <InputOTPSlot index={4} />
+                                                    <InputOTPSlot index={5} />
+                                                </InputOTPGroup>
+                                            </InputOTP>
+                                        </div>
+                                    </FormControl>
+                                    <FormMessage />
+                                </FormItem>
+                            )}
+                        />
+                        {error && (
+                            <Alert variant="destructive">
+                                <AlertDescription>{error}</AlertDescription>
+                            </Alert>
+                        )}
+
+                        <div className="space-y-4">
+                            <Button
+                                type="submit"
+                                className="w-full"
+                                loading={loading}
+                            >
+                                <LockIcon className="w-4 h-4 mr-2" />
+                                Submit Code
+                            </Button>
+                            <Button
+                                type="button"
+                                className="w-full"
+                                variant="outline"
+                                onClick={() => {
+                                    setMfaRequested(false);
+                                    mfaForm.reset();
+                                }}
+                            >
+                                Back to Login
+                            </Button>
+                        </div>
+                    </form>
+                </Form>
+            )}
+        </div>
     );
     );
 }
 }