2FA client side
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
"framer-motion": "^11.2.10",
|
"framer-motion": "^11.2.10",
|
||||||
"openai": "^4.53.2",
|
"openai": "^4.53.2",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-auth-code-input": "^3.2.1",
|
||||||
"react-chartjs-2": "^5.2.0",
|
"react-chartjs-2": "^5.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hot-toast": "^2.4.1",
|
"react-hot-toast": "^2.4.1",
|
||||||
|
|||||||
@@ -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 { Formik, Form } from "formik";
|
||||||
import * as Yup from "yup";
|
import * as Yup from "yup";
|
||||||
import config from "../config";
|
import config from "../config";
|
||||||
import NextUIFormikInput from "./NextUIFormikInput";
|
import NextUIFormikInput from "./NextUIFormikInput";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { ChevronLeftIcon } from "../icons";
|
import { ChevronLeftIcon } from "../icons";
|
||||||
import { popErrorToast } from "../utilities";
|
import { popErrorToast, popToast } from "../utilities";
|
||||||
import { retrieveUserInformation } from "../security/users";
|
import { retrieveUserInformation } from "../security/users";
|
||||||
import instance from "../security/http";
|
import instance from "../security/http";
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import AuthCode, { AuthCodeRef } from "react-auth-code-input";
|
||||||
|
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
email: Yup.string()
|
email: Yup.string()
|
||||||
@@ -30,14 +40,28 @@ const validationSchema = Yup.object({
|
|||||||
export default function SignInModule() {
|
export default function SignInModule() {
|
||||||
const navigate = useNavigate();
|
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 = {
|
const initialValues = {
|
||||||
email: "",
|
email: "",
|
||||||
password: "",
|
password: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = (values: any): void => {
|
const proceedWithLogin = (values?: any) => {
|
||||||
|
if (!values) {
|
||||||
|
values = userLoginInformation;
|
||||||
|
}
|
||||||
|
console.log("proceeding with login: " + values.email);
|
||||||
instance
|
instance
|
||||||
.post(config.serverAddress + "/users/login", values)
|
.post(config.serverAddress + "/users/login", {
|
||||||
|
email: values.email,
|
||||||
|
password: values.password,
|
||||||
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
console.log("logging in");
|
console.log("logging in");
|
||||||
localStorage.setItem("accessToken", response.data.accessToken);
|
localStorage.setItem("accessToken", response.data.accessToken);
|
||||||
@@ -55,9 +79,52 @@ export default function SignInModule() {
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
popErrorToast(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 (
|
return (
|
||||||
<div className="flex flex-col gap-16">
|
<div className="flex flex-col gap-16">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
@@ -119,6 +186,30 @@ export default function SignInModule() {
|
|||||||
Sign up
|
Sign up
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -544,10 +544,28 @@ router.post("/enable-2fa/:id", async (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/verify-2fa", async (req, res) => {
|
router.post("/has-2fa", async (req, res) => {
|
||||||
const { id, token } = req.body;
|
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) {
|
if (!user) {
|
||||||
console.log("User not found");
|
console.log("User not found");
|
||||||
|
|||||||
Reference in New Issue
Block a user