diff --git a/src/shared/components/common/totp-modal.tsx b/src/shared/components/common/totp-modal.tsx index 72ebbb64..124155f6 100644 --- a/src/shared/components/common/totp-modal.tsx +++ b/src/shared/components/common/totp-modal.tsx @@ -31,6 +31,11 @@ function handleInput( { modal, i }: { modal: TotpModal; i: number }, event: any, ) { + if (isNaN(event.target.value)) { + event.preventDefault(); + return; + } + modal.setState(prev => ({ ...prev, totp: prev.totp + event.target.value })); document.getElementById(`totp-input-${i + 1}`)?.focus(); @@ -114,16 +119,17 @@ export default class TotpModal extends Component< tabIndex={-1} aria-hidden aria-labelledby="#totpModalTitle" + data-bs-backdrop="static" >

{type === "generate" - ? "Generate TOTP" + ? "Enable 2 Factor Authentication" : type === "remove" - ? "Remove TOTP" - : "Enter TOTP"} + ? "Disable 2 Factor Authentication" + : "Enter 2FA Token"}

-
+
{type === "generate" && ( -
+
- + or scan this QR code in your authenticator app - + interface State { loginRes: RequestState; form: { - username_or_email?: string; - password?: string; - totp_2fa_token?: string; + username_or_email: string; + password: string; }; - showTotp: boolean; siteRes: GetSiteResponse; } +async function handleLoginSuccess(i: Login, loginRes: LoginResponse) { + UserService.Instance.login({ + res: loginRes, + }); + const site = await HttpService.client.getSite(); + + if (site.state === "success") { + UserService.Instance.myUserInfo = site.data.my_user; + } + + const { prev } = getLoginQueryParams(); + + prev + ? i.props.history.replace(prev) + : i.props.history.action === "PUSH" + ? i.props.history.back() + : i.props.history.replace("/"); +} + async function handleLoginSubmit(i: Login, event: any) { event.preventDefault(); - const { password, totp_2fa_token, username_or_email } = i.state.form; + const { password, username_or_email } = i.state.form; if (username_or_email && password) { i.setState({ loginRes: { state: "loading" } }); @@ -43,39 +61,23 @@ async function handleLoginSubmit(i: Login, event: any) { const loginRes = await HttpService.client.login({ username_or_email, password, - totp_2fa_token, }); switch (loginRes.state) { case "failed": { if (loginRes.msg === "missing_totp_token") { - i.setState({ showTotp: true }); - toast(I18NextService.i18n.t("enter_two_factor_code"), "info"); + const Modal = (await import("bootstrap/js/dist/modal")).default; + const modal = new Modal(document.getElementById("totpModal")!); + modal.show(); } else { toast(I18NextService.i18n.t(loginRes.msg), "danger"); } - i.setState({ loginRes: { state: "failed", msg: loginRes.msg } }); + i.setState({ loginRes }); break; } case "success": { - UserService.Instance.login({ - res: loginRes.data, - }); - const site = await HttpService.client.getSite(); - - if (site.state === "success") { - UserService.Instance.myUserInfo = site.data.my_user; - } - - const { prev } = getLoginQueryParams(); - - prev - ? i.props.history.replace(prev) - : i.props.history.action === "PUSH" - ? i.props.history.back() - : i.props.history.replace("/"); - + handleLoginSuccess(i, loginRes.data); break; } } @@ -88,10 +90,6 @@ function handleLoginUsernameChange(i: Login, event: any) { ); } -function handleLoginTotpChange(i: Login, event: any) { - i.setState(prevState => (prevState.form.totp_2fa_token = event.target.value)); -} - function handleLoginPasswordChange(i: Login, event: any) { i.setState(prevState => (prevState.form.password = event.target.value)); } @@ -104,13 +102,17 @@ export class Login extends Component< state: State = { loginRes: { state: "empty" }, - form: {}, - showTotp: false, + form: { + username_or_email: "", + password: "", + }, siteRes: this.isoData.site_res, }; constructor(props: any, context: any) { super(props, context); + + this.handleSubmitTotp = this.handleSubmitTotp.bind(this); } componentDidMount() { @@ -137,6 +139,7 @@ export class Login extends Component< title={this.documentTitle} path={this.context.router.route.match.url} /> +
{this.loginForm()}
@@ -144,6 +147,23 @@ export class Login extends Component< ); } + async handleSubmitTotp(totp: string) { + const loginRes = await HttpService.client.login({ + password: this.state.form.password, + username_or_email: this.state.form.username_or_email, + totp_2fa_token: totp, + }); + + const succeeded = loginRes.state === "success"; + if (succeeded) { + handleLoginSuccess(this, loginRes.data); + } else { + toast("Invalid 2FA Token", "danger"); + } + + return succeeded; + } + loginForm() { return (
@@ -178,28 +198,6 @@ export class Login extends Component< showForgotLink />
- {this.state.showTotp && ( -
- -
- -
-
- )}