2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-13 18:45:40 +00:00

feat(frontend): make mfa login faster (#9595)

* add hidden field and logic to reduce clicks for mfa logins

* refactor to seperate function to reduce complexity

* fix missing imports

* fix style

---------

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
This commit is contained in:
Matthias Mair
2025-06-06 07:47:42 +02:00
committed by GitHub
parent 9138bad8bc
commit 5dc6b7cf51
2 changed files with 69 additions and 26 deletions

View File

@ -1,3 +1,4 @@
import { ApiEndpoints, apiUrl } from '@lib/index';
import { t } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import {
@ -8,16 +9,14 @@ import {
Loader,
PasswordInput,
Stack,
TextInput
TextInput,
VisuallyHidden
} from '@mantine/core';
import { useForm } from '@mantine/form';
import { useDisclosure } from '@mantine/hooks';
import { showNotification } from '@mantine/notifications';
import { useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { apiUrl } from '@lib/functions/Api';
import { showNotification } from '@mantine/notifications';
import { useShallow } from 'zustand/react/shallow';
import { api } from '../../App';
import {
@ -33,7 +32,7 @@ import { SsoButton } from '../buttons/SSOButton';
export function AuthenticationForm() {
const classicForm = useForm({
initialValues: { username: '', password: '' }
initialValues: { username: '', password: '', code: '' }
});
const simpleForm = useForm({ initialValues: { email: '' } });
const [classicLoginMode, setMode] = useDisclosure(true);
@ -58,7 +57,9 @@ export function AuthenticationForm() {
doBasicLogin(
classicForm.values.username,
classicForm.values.password,
navigate
navigate,
classicForm.values.code
)
.then((success) => {
setIsLoggingIn(false);
@ -140,6 +141,13 @@ export function AuthenticationForm() {
placeholder={t`Your password`}
{...classicForm.getInputProps('password')}
/>
<VisuallyHidden>
<TextInput
name='TOTP'
{...classicForm.getInputProps('code')}
hidden={true}
/>
</VisuallyHidden>
{password_forgotten_enabled() === true && (
<Group justify='space-between' mt='0'>
<Anchor

View File

@ -62,11 +62,12 @@ function post(path: string, params: any, method = 'post') {
* If login is successful, an API token will be returned.
* This API token is used for any future API requests.
*/
export const doBasicLogin = async (
export async function doBasicLogin(
username: string,
password: string,
navigate: NavigateFunction
) => {
navigate: NavigateFunction,
code?: string
) {
const { getHost } = useLocalState.getState();
const { clearUserState, setAuthenticated, fetchUserState } =
useUserState.getState();
@ -104,16 +105,9 @@ export const doBasicLogin = async (
success = true;
}
})
.catch((err) => {
.catch(async (err) => {
if (err?.response?.status == 401) {
setAuthContext(err.response.data?.data);
const mfa_flow = err.response.data.data.flows.find(
(flow: any) => flow.id == FlowEnum.MfaAuthenticate
);
if (mfa_flow && mfa_flow.is_pending == true) {
success = true;
navigate('/mfa');
}
await handlePossibleMFAError(err);
} else if (err?.response?.status == 409) {
notifications.show({
title: t`Already logged in`,
@ -133,7 +127,40 @@ export const doBasicLogin = async (
clearUserState();
}
return success;
};
async function handlePossibleMFAError(err: any) {
setAuthContext(err.response.data?.data);
const mfa_flow = err.response.data.data.flows.find(
(flow: any) => flow.id == FlowEnum.MfaAuthenticate
);
if (mfa_flow?.is_pending) {
// MFA is required - we might already have a code
if (code && code.length > 0) {
const rslt = await handleMfaLogin(
navigate,
undefined,
{ code: code },
() => {}
);
if (rslt) {
setAuthenticated(true);
loginDone = true;
success = true;
notifications.show({
title: t`MFA Login successful`,
message: t`MFA details were automatically provided in the browser`,
color: 'green'
});
}
}
// No code or success - off to the mfa page
if (!loginDone) {
success = true;
navigate('/mfa');
}
}
}
}
/**
* Logout the user from the current session
@ -259,19 +286,25 @@ export function handleReset(
});
}
export function handleMfaLogin(
export async function handleMfaLogin(
navigate: NavigateFunction,
location: Location<any>,
location: Location<any> | undefined,
values: { code: string; remember?: boolean },
setError: (message: string | undefined) => void
) {
const { setAuthContext } = useServerApiState.getState();
authApi(apiUrl(ApiEndpoints.auth_login_2fa), undefined, 'post', {
code: values.code
})
const result = await authApi(
apiUrl(ApiEndpoints.auth_login_2fa),
undefined,
'post',
{
code: values.code
}
)
.then((response) => {
handleSuccessFullAuth(response, navigate, location, setError);
return true;
})
.catch((err) => {
// Already logged in, but with a different session
@ -304,7 +337,9 @@ export function handleMfaLogin(
}
setError(msg);
}
return false;
});
return result;
}
/**
@ -380,7 +415,7 @@ function handleSuccessFullAuth(
observeProfile();
fetchGlobalStates(navigate);
if (navigate) {
if (navigate && location) {
followRedirect(navigate, location?.state);
}
});