webui/src/modules/frame/components/LoginForm.tsx

246 lines
6.4 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";
import { useTranslation } from "react-i18next";
import { Namespaces } from "../../../i18n/i18n";
const LoginForm: React.FC = () => {
const { t } = useTranslation<typeof Namespaces.Common>();
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(t("Email")),
password: Joi.string().required().label(t("Password")),
tfaNeeded: Joi.boolean().required(),
requestTfaRemoval: Joi.boolean().required(),
securityCode: Joi.string().allow("").label(t("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,
"",
t("Email") as string,
0,
true,
"username",
form.handleChange,
)}
{renderInput(
"password",
"",
form.state.data,
form.state.errors,
InputType.password,
emailSent,
"",
t("Password") as string,
passwordMaxLength,
isInNextStage,
"current-password",
form.handleChange,
)}
{!isInNextStage &&
renderButton(
t("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(
t("ForgottenPassword") as string,
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",
t("Authenticate") as string,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderButton(t("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;