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

chore(backend): bump allauth version (#9714)

* bump allauth

* add trust

* add device trust handling

* fix style

* Update api_version.py

---------

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
This commit is contained in:
Matthias Mair
2025-06-05 05:27:52 +02:00
committed by GitHub
parent ee67c4f099
commit 71abd489ae
7 changed files with 64 additions and 16 deletions

View File

@ -1,12 +1,15 @@
"""InvenTree API version information."""
# InvenTree API version
INVENTREE_API_VERSION = 343
INVENTREE_API_VERSION = 344
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
v344 -> 2025-06-02 : https://github.com/inventree/InvenTree/pull/9714
- Updates alauth version and adds device trust as a factor
v343 -> 2025-06-02 : https://github.com/inventree/InvenTree/pull/9717
- Add ISO currency codes to the description text for currency options

View File

@ -1327,6 +1327,7 @@ ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True
HEADLESS_ONLY = True
HEADLESS_TOKEN_STRATEGY = 'InvenTree.auth_overrides.DRFTokenStrategy'
HEADLESS_CLIENTS = 'browser'
MFA_ENABLED = get_boolean_setting(
'INVENTREE_MFA_ENABLED', 'mfa_enabled', True
) # TODO re-implement
@ -1336,6 +1337,7 @@ MFA_SUPPORTED_TYPES = get_setting(
['totp', 'recovery_codes'],
typecast=list,
)
MFA_TRUST_ENABLED = True
LOGOUT_REDIRECT_URL = get_setting(
'INVENTREE_LOGOUT_REDIRECT_URL', 'logout_redirect_url', 'index'

View File

@ -423,8 +423,8 @@ django==4.2.21 \
# djangorestframework
# djangorestframework-simplejwt
# drf-spectacular
django-allauth[mfa, openid, saml, socialaccount]==65.4.1 \
--hash=sha256:60b32aef7dbbcc213319aa4fd8f570e985266ea1162ae6ef7a26a24efca85c8c
django-allauth[mfa, openid, saml, socialaccount]==65.9.0 \
--hash=sha256:a06bca9974df44321e94c33bcf770bb6f924d1a44b57defbce4d7ec54a55483e
# via -r src/backend/requirements.in
django-cleanup==9.0.0 \
--hash=sha256:19f8b0e830233f9f0f683b17181f414672a0f48afe3ea3cc80ba47ae40ad880c \

View File

@ -30,6 +30,7 @@ export enum ApiEndpoints {
auth_recovery = 'auth/v1/account/authenticators/recovery-codes',
auth_mfa_reauthenticate = 'auth/v1/auth/2fa/reauthenticate',
auth_totp = 'auth/v1/account/authenticators/totp',
auth_trust = 'auth/v1/auth/2fa/trust',
auth_reauthenticate = 'auth/v1/auth/reauthenticate',
auth_email = 'auth/v1/account/email',
auth_email_verify = 'auth/v1/auth/email/verify',

View File

@ -13,7 +13,8 @@ export enum FlowEnum {
ProviderToken = 'provider_token',
MfaAuthenticate = 'mfa_authenticate',
Reauthenticate = 'reauthenticate',
MfaReauthenticate = 'mfa_reauthenticate'
MfaReauthenticate = 'mfa_reauthenticate',
MfaTrust = 'mfa_trust'
}
export interface Flow {

View File

@ -262,26 +262,19 @@ export function handleReset(
export function handleMfaLogin(
navigate: NavigateFunction,
location: Location<any>,
values: { code: string },
values: { code: string; remember?: boolean },
setError: (message: string | undefined) => void
) {
const { setAuthenticated, fetchUserState } = useUserState.getState();
const { setAuthContext } = useServerApiState.getState();
authApi(apiUrl(ApiEndpoints.auth_login_2fa), undefined, 'post', {
code: values.code
})
.then((response) => {
setError(undefined);
setAuthContext(response.data?.data);
setAuthenticated();
fetchUserState().finally(() => {
observeProfile();
followRedirect(navigate, location?.state);
});
handleSuccessFullAuth(response, navigate, location, setError);
})
.catch((err) => {
// Already logged in, but with a different session
if (err?.response?.status == 409) {
notifications.show({
title: t`Already logged in`,
@ -289,6 +282,19 @@ export function handleMfaLogin(
color: 'red',
autoClose: false
});
// MFA trust flow pending
} else if (err?.response?.status == 401) {
const mfa_trust = err.response.data.data.flows.find(
(flow: any) => flow.id == FlowEnum.MfaTrust
);
if (mfa_trust?.is_pending) {
setAuthContext(err.response.data.data);
authApi(apiUrl(ApiEndpoints.auth_trust), undefined, 'post', {
trust: values.remember ?? false
}).then((response) => {
handleSuccessFullAuth(response, navigate, location, setError);
});
}
} else {
const errors = err.response?.data?.errors;
let msg = t`An error occurred`;
@ -351,6 +357,35 @@ export const checkLoginState = async (
}
};
function handleSuccessFullAuth(
response?: any,
navigate?: NavigateFunction,
location?: Location<any>,
setError?: (message: string | undefined) => void
) {
const { setAuthenticated, fetchUserState } = useUserState.getState();
const { setAuthContext } = useServerApiState.getState();
if (setError) {
// If an error function is provided, clear any previous errors
setError(undefined);
}
if (response.data?.data) {
setAuthContext(response.data?.data);
}
setAuthenticated();
fetchUserState().finally(() => {
observeProfile();
fetchGlobalStates(navigate);
if (navigate) {
followRedirect(navigate, location?.state);
}
});
}
/*
* Return the value of the CSRF cookie, if available
*/

View File

@ -1,6 +1,6 @@
import { t } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import { Button, TextInput } from '@mantine/core';
import { Button, Checkbox, TextInput } from '@mantine/core';
import { useForm } from '@mantine/form';
import { useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
@ -8,7 +8,7 @@ import { handleMfaLogin } from '../../functions/auth';
import { Wrapper } from './Layout';
export default function Mfa() {
const simpleForm = useForm({ initialValues: { code: '' } });
const simpleForm = useForm({ initialValues: { code: '', remember: false } });
const navigate = useNavigate();
const location = useLocation();
const [loginError, setLoginError] = useState<string | undefined>(undefined);
@ -23,6 +23,12 @@ export default function Mfa() {
{...simpleForm.getInputProps('code')}
error={loginError}
/>
<Checkbox
label={t`Remember this device`}
name='remember'
description={t`If enabled, you will not be asked for MFA on this device for 30 days.`}
{...simpleForm.getInputProps('remember', { type: 'checkbox' })}
/>
<Button
type='submit'
onClick={() =>