2FA client side

This commit is contained in:
2024-08-14 23:11:52 +08:00
parent ffd40f6d39
commit 920cfc4e85
3 changed files with 117 additions and 7 deletions

View File

@@ -20,6 +20,7 @@
"framer-motion": "^11.2.10",
"openai": "^4.53.2",
"react": "^18.2.0",
"react-auth-code-input": "^3.2.1",
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.0",
"react-hot-toast": "^2.4.1",

View File

@@ -1,13 +1,23 @@
import { Button, Link } from "@nextui-org/react";
import {
Button,
Link,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
} from "@nextui-org/react";
import { Formik, Form } from "formik";
import * as Yup from "yup";
import config from "../config";
import NextUIFormikInput from "./NextUIFormikInput";
import { useNavigate } from "react-router-dom";
import { ChevronLeftIcon } from "../icons";
import { popErrorToast } from "../utilities";
import { popErrorToast, popToast } from "../utilities";
import { retrieveUserInformation } from "../security/users";
import instance from "../security/http";
import { useEffect, useRef, useState } from "react";
import AuthCode, { AuthCodeRef } from "react-auth-code-input";
const validationSchema = Yup.object({
email: Yup.string()
@@ -30,14 +40,28 @@ const validationSchema = Yup.object({
export default function SignInModule() {
const navigate = useNavigate();
const [twoFactorModal, setTwoFactorModal] = useState(false);
const [userLoginInformation, setUserLoginInformation] = useState<any>();
const [twoFactorToken, setTwoFactorToken] = useState("");
const [twoFactorVerifying, setTwoFactorVerifying] = useState(false);
const AuthInputRef = useRef<AuthCodeRef>(null);
const initialValues = {
email: "",
password: "",
};
const handleSubmit = (values: any): void => {
const proceedWithLogin = (values?: any) => {
if (!values) {
values = userLoginInformation;
}
console.log("proceeding with login: " + values.email);
instance
.post(config.serverAddress + "/users/login", values)
.post(config.serverAddress + "/users/login", {
email: values.email,
password: values.password,
})
.then((response) => {
console.log("logging in");
localStorage.setItem("accessToken", response.data.accessToken);
@@ -55,9 +79,52 @@ export default function SignInModule() {
})
.catch((error) => {
popErrorToast(error);
setTwoFactorModal(false);
});
};
const handleSubmit = (values: any): void => {
setUserLoginInformation(values);
instance
.post("/users/has-2fa", { email: values.email })
.then((answer) => {
if (answer.data.enabled) {
setTwoFactorModal(true);
} else {
proceedWithLogin(values);
}
})
.catch(() => {
popToast("User not found!", 2);
});
};
useEffect(() => {
if (!(twoFactorToken.length == 6 && !twoFactorVerifying)) {
return;
}
setTwoFactorVerifying(true);
instance
.post("/users/verify-2fa", {
email: userLoginInformation.email,
token: twoFactorToken,
})
.then(() => {
proceedWithLogin();
})
.catch((error) => {
popErrorToast(error);
AuthInputRef.current?.clear();
setTwoFactorToken("");
})
.finally(() => {
AuthInputRef.current?.clear();
setTwoFactorToken("");
setTwoFactorVerifying(false);
});
}, [twoFactorToken]);
return (
<div className="flex flex-col gap-16">
<div className="flex flex-col gap-1">
@@ -119,6 +186,30 @@ export default function SignInModule() {
Sign up
</Link>
</div>
<Modal size="xl" isOpen={twoFactorModal} onOpenChange={setTwoFactorModal}>
<ModalContent>
<ModalHeader>Two-Factor Authentication</ModalHeader>
<ModalBody>
<div className="text-center flex flex-col gap-4">
<AuthCode
containerClassName="flex flex-row gap-4 w-full justify-center"
inputClassName="w-16 h-16 text-4xl text-center rounded-lg my-2 bg-neutral-100 dark:bg-neutral-800 border-2 border-neutral-200 dark:border-neutral-700 "
length={6}
allowedCharacters="numeric"
ref={AuthInputRef}
disabled={twoFactorVerifying}
onChange={(value) => {
setTwoFactorToken(value);
}}
/>
<p className="text-md opacity-50">
Please enter the 6 digits passcode from your authenticator app.
</p>
</div>
</ModalBody>
<ModalFooter></ModalFooter>
</ModalContent>
</Modal>
</div>
);
}

View File

@@ -544,10 +544,28 @@ router.post("/enable-2fa/:id", async (req, res) => {
});
});
router.post("/verify-2fa", async (req, res) => {
const { id, token } = req.body;
router.post("/has-2fa", async (req, res) => {
let email = req.body.email;
const user = await User.findOne({
where: { email: email },
});
const user = await User.findByPk(id);
if (!user) {
return res.status(404).send("User not found");
}
return res.json({
status: "success",
enabled: user.secret ? true : false,
});
});
router.post("/verify-2fa", async (req, res) => {
const { email, token } = req.body;
const user = await User.findOne({
where: { email: email },
});
if (!user) {
console.log("User not found");