243 lines
6.2 KiB
TypeScript
243 lines
6.2 KiB
TypeScript
import React, { useCallback, useEffect, useState } from "react";
|
|
import { Link, Navigate } from "react-router-dom";
|
|
import Joi from "joi";
|
|
import authentication from "../services/authenticationService";
|
|
import { InputType } from "../../../components/common/Input";
|
|
import { ButtonType } from "../../../components/common/Button";
|
|
import { useForm } from "../../../components/common/useForm";
|
|
import {
|
|
renderButton,
|
|
renderError,
|
|
renderInput,
|
|
} from "../../../components/common/formHelpers";
|
|
|
|
const LoginForm: React.FC = () => {
|
|
const [isInNextStage, setIsInNextStage] = useState(false);
|
|
const [emailSent, setEmailSent] = useState(false);
|
|
const passwordMaxLength = 255;
|
|
|
|
const form = useForm({
|
|
loaded: true,
|
|
data: {
|
|
username: "",
|
|
password: "",
|
|
tfaNeeded: false,
|
|
requestTfaRemoval: false,
|
|
securityCode: "",
|
|
},
|
|
errors: {},
|
|
redirect: "",
|
|
});
|
|
|
|
const schema = {
|
|
username: Joi.string()
|
|
.required()
|
|
.email({ tlds: { allow: false } })
|
|
.label("Email"),
|
|
password: Joi.string().required().label("Password"),
|
|
tfaNeeded: Joi.boolean().required(),
|
|
requestTfaRemoval: Joi.boolean().required(),
|
|
securityCode: Joi.string().allow("").label("Authenticate"),
|
|
};
|
|
|
|
form.schema = schema;
|
|
|
|
useEffect(() => {
|
|
window.location.replace("/login");
|
|
}, []);
|
|
|
|
const performLogin = useCallback(
|
|
async (data: any) => {
|
|
try {
|
|
const result = await authentication.login(
|
|
data.username,
|
|
data.password,
|
|
data.securityCode,
|
|
data.requestTfaRemoval,
|
|
);
|
|
|
|
switch (result) {
|
|
case 1: {
|
|
const nextData = { ...form.state.data };
|
|
|
|
if (!nextData.tfaNeeded) {
|
|
nextData.tfaNeeded = true;
|
|
form.setState({ data: nextData });
|
|
}
|
|
break;
|
|
}
|
|
case 2:
|
|
window.location.href = "/";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} catch (ex: any) {
|
|
form.handleGeneralError(ex);
|
|
}
|
|
},
|
|
[form],
|
|
);
|
|
|
|
const doSubmit = async () => {
|
|
const { data } = form.state;
|
|
await performLogin(data);
|
|
};
|
|
|
|
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
|
form.handleSubmit(e, async () => doSubmit());
|
|
};
|
|
|
|
const handleNextClick = async () => {
|
|
const data = { ...form.state.data };
|
|
const validationResult = schema.username.validate(data.username);
|
|
if (validationResult.error === undefined) {
|
|
setIsInNextStage(true);
|
|
}
|
|
};
|
|
|
|
const handleForgetPassword = async () => {
|
|
try {
|
|
await authentication.forgotPassword(form.state.data.username as string);
|
|
setEmailSent(true);
|
|
const nextData = { ...form.state.data, username: "", password: "" };
|
|
form.setState({ data: nextData });
|
|
} catch (ex: any) {
|
|
form.handleGeneralError(ex);
|
|
}
|
|
};
|
|
|
|
const authenticationWorkAround = async () => {
|
|
const data = { ...form.state.data, requestTfaRemoval: true };
|
|
await performLogin(data);
|
|
form.setState({ data });
|
|
};
|
|
|
|
const { tfaNeeded, requestTfaRemoval } = form.state.data as any;
|
|
const result = schema.username.validate(form.state.data.username);
|
|
const validEmail = result.error === undefined;
|
|
|
|
if (authentication.getCurrentUser()) return <Navigate to="/" />;
|
|
|
|
const requestTfaRemovalPanel = (
|
|
<div>
|
|
An email has been sent to you so that you can regain control of your
|
|
account.
|
|
</div>
|
|
);
|
|
|
|
const loginPanel = (
|
|
<>
|
|
<form onSubmit={handleSubmit}>
|
|
{renderInput(
|
|
"username",
|
|
"",
|
|
form.state.data,
|
|
form.state.errors,
|
|
InputType.text,
|
|
isInNextStage,
|
|
"",
|
|
"Email",
|
|
0,
|
|
true,
|
|
"username",
|
|
form.handleChange,
|
|
)}
|
|
{renderInput(
|
|
"password",
|
|
"",
|
|
form.state.data,
|
|
form.state.errors,
|
|
InputType.password,
|
|
emailSent,
|
|
"",
|
|
"Password",
|
|
passwordMaxLength,
|
|
isInNextStage,
|
|
"current-password",
|
|
form.handleChange,
|
|
)}
|
|
{!isInNextStage &&
|
|
renderButton(
|
|
"Next",
|
|
form.state.errors,
|
|
"next",
|
|
handleNextClick,
|
|
"login",
|
|
validEmail,
|
|
ButtonType.primary,
|
|
true,
|
|
)}
|
|
{isInNextStage && (
|
|
<div className="clickables">
|
|
{renderButton(
|
|
"Login",
|
|
form.state.errors,
|
|
"login",
|
|
undefined,
|
|
"login",
|
|
!emailSent,
|
|
)}
|
|
</div>
|
|
)}
|
|
</form>
|
|
{isInNextStage && (
|
|
<div className="forgottenLink">
|
|
{renderButton(
|
|
"Forgotten Password",
|
|
form.state.errors,
|
|
"forgot-password",
|
|
handleForgetPassword,
|
|
"forgot-password",
|
|
validEmail,
|
|
ButtonType.secondary,
|
|
true,
|
|
)}
|
|
</div>
|
|
)}
|
|
{emailSent && (
|
|
<div className="alert alert-info emailSent">
|
|
If you have a registered account, you will receive an email.
|
|
</div>
|
|
)}
|
|
{renderError("_general", form.state.errors)}
|
|
</>
|
|
);
|
|
|
|
const tfaPanel = (
|
|
<form onSubmit={handleSubmit}>
|
|
{renderError("_general", form.state.errors)}
|
|
{renderInput(
|
|
"securityCode",
|
|
"Authenticate",
|
|
form.state.data,
|
|
form.state.errors,
|
|
InputType.text,
|
|
false,
|
|
"",
|
|
"",
|
|
0,
|
|
true,
|
|
undefined,
|
|
form.handleChange,
|
|
)}
|
|
{renderButton("Authenticate", form.state.errors, "authenticate")}
|
|
<Link to="#" onClick={authenticationWorkAround}>
|
|
My Authenticator is not working
|
|
</Link>
|
|
</form>
|
|
);
|
|
|
|
return (
|
|
<div>
|
|
{requestTfaRemoval
|
|
? requestTfaRemovalPanel
|
|
: tfaNeeded
|
|
? tfaPanel
|
|
: loginPanel}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default LoginForm;
|