mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 13:15:43 +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:
		| @@ -1,12 +1,15 @@ | |||||||
| """InvenTree API version information.""" | """InvenTree API version information.""" | ||||||
|  |  | ||||||
| # InvenTree API version | # 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.""" | """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" | ||||||
|  |  | ||||||
|  |  | ||||||
| INVENTREE_API_TEXT = """ | 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 | v343 -> 2025-06-02 : https://github.com/inventree/InvenTree/pull/9717 | ||||||
|     - Add ISO currency codes to the description text for currency options |     - Add ISO currency codes to the description text for currency options | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1327,6 +1327,7 @@ ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True | |||||||
|  |  | ||||||
| HEADLESS_ONLY = True | HEADLESS_ONLY = True | ||||||
| HEADLESS_TOKEN_STRATEGY = 'InvenTree.auth_overrides.DRFTokenStrategy' | HEADLESS_TOKEN_STRATEGY = 'InvenTree.auth_overrides.DRFTokenStrategy' | ||||||
|  | HEADLESS_CLIENTS = 'browser' | ||||||
| MFA_ENABLED = get_boolean_setting( | MFA_ENABLED = get_boolean_setting( | ||||||
|     'INVENTREE_MFA_ENABLED', 'mfa_enabled', True |     'INVENTREE_MFA_ENABLED', 'mfa_enabled', True | ||||||
| )  # TODO re-implement | )  # TODO re-implement | ||||||
| @@ -1336,6 +1337,7 @@ MFA_SUPPORTED_TYPES = get_setting( | |||||||
|     ['totp', 'recovery_codes'], |     ['totp', 'recovery_codes'], | ||||||
|     typecast=list, |     typecast=list, | ||||||
| ) | ) | ||||||
|  | MFA_TRUST_ENABLED = True | ||||||
|  |  | ||||||
| LOGOUT_REDIRECT_URL = get_setting( | LOGOUT_REDIRECT_URL = get_setting( | ||||||
|     'INVENTREE_LOGOUT_REDIRECT_URL', 'logout_redirect_url', 'index' |     'INVENTREE_LOGOUT_REDIRECT_URL', 'logout_redirect_url', 'index' | ||||||
|   | |||||||
| @@ -423,8 +423,8 @@ django==4.2.21 \ | |||||||
|     #   djangorestframework |     #   djangorestframework | ||||||
|     #   djangorestframework-simplejwt |     #   djangorestframework-simplejwt | ||||||
|     #   drf-spectacular |     #   drf-spectacular | ||||||
| django-allauth[mfa, openid, saml, socialaccount]==65.4.1 \ | django-allauth[mfa, openid, saml, socialaccount]==65.9.0 \ | ||||||
|     --hash=sha256:60b32aef7dbbcc213319aa4fd8f570e985266ea1162ae6ef7a26a24efca85c8c |     --hash=sha256:a06bca9974df44321e94c33bcf770bb6f924d1a44b57defbce4d7ec54a55483e | ||||||
|     # via -r src/backend/requirements.in |     # via -r src/backend/requirements.in | ||||||
| django-cleanup==9.0.0 \ | django-cleanup==9.0.0 \ | ||||||
|     --hash=sha256:19f8b0e830233f9f0f683b17181f414672a0f48afe3ea3cc80ba47ae40ad880c \ |     --hash=sha256:19f8b0e830233f9f0f683b17181f414672a0f48afe3ea3cc80ba47ae40ad880c \ | ||||||
|   | |||||||
| @@ -30,6 +30,7 @@ export enum ApiEndpoints { | |||||||
|   auth_recovery = 'auth/v1/account/authenticators/recovery-codes', |   auth_recovery = 'auth/v1/account/authenticators/recovery-codes', | ||||||
|   auth_mfa_reauthenticate = 'auth/v1/auth/2fa/reauthenticate', |   auth_mfa_reauthenticate = 'auth/v1/auth/2fa/reauthenticate', | ||||||
|   auth_totp = 'auth/v1/account/authenticators/totp', |   auth_totp = 'auth/v1/account/authenticators/totp', | ||||||
|  |   auth_trust = 'auth/v1/auth/2fa/trust', | ||||||
|   auth_reauthenticate = 'auth/v1/auth/reauthenticate', |   auth_reauthenticate = 'auth/v1/auth/reauthenticate', | ||||||
|   auth_email = 'auth/v1/account/email', |   auth_email = 'auth/v1/account/email', | ||||||
|   auth_email_verify = 'auth/v1/auth/email/verify', |   auth_email_verify = 'auth/v1/auth/email/verify', | ||||||
|   | |||||||
| @@ -13,7 +13,8 @@ export enum FlowEnum { | |||||||
|   ProviderToken = 'provider_token', |   ProviderToken = 'provider_token', | ||||||
|   MfaAuthenticate = 'mfa_authenticate', |   MfaAuthenticate = 'mfa_authenticate', | ||||||
|   Reauthenticate = 'reauthenticate', |   Reauthenticate = 'reauthenticate', | ||||||
|   MfaReauthenticate = 'mfa_reauthenticate' |   MfaReauthenticate = 'mfa_reauthenticate', | ||||||
|  |   MfaTrust = 'mfa_trust' | ||||||
| } | } | ||||||
|  |  | ||||||
| export interface Flow { | export interface Flow { | ||||||
|   | |||||||
| @@ -262,26 +262,19 @@ export function handleReset( | |||||||
| export function handleMfaLogin( | export function handleMfaLogin( | ||||||
|   navigate: NavigateFunction, |   navigate: NavigateFunction, | ||||||
|   location: Location<any>, |   location: Location<any>, | ||||||
|   values: { code: string }, |   values: { code: string; remember?: boolean }, | ||||||
|   setError: (message: string | undefined) => void |   setError: (message: string | undefined) => void | ||||||
| ) { | ) { | ||||||
|   const { setAuthenticated, fetchUserState } = useUserState.getState(); |  | ||||||
|   const { setAuthContext } = useServerApiState.getState(); |   const { setAuthContext } = useServerApiState.getState(); | ||||||
|  |  | ||||||
|   authApi(apiUrl(ApiEndpoints.auth_login_2fa), undefined, 'post', { |   authApi(apiUrl(ApiEndpoints.auth_login_2fa), undefined, 'post', { | ||||||
|     code: values.code |     code: values.code | ||||||
|   }) |   }) | ||||||
|     .then((response) => { |     .then((response) => { | ||||||
|       setError(undefined); |       handleSuccessFullAuth(response, navigate, location, setError); | ||||||
|       setAuthContext(response.data?.data); |  | ||||||
|       setAuthenticated(); |  | ||||||
|  |  | ||||||
|       fetchUserState().finally(() => { |  | ||||||
|         observeProfile(); |  | ||||||
|         followRedirect(navigate, location?.state); |  | ||||||
|       }); |  | ||||||
|     }) |     }) | ||||||
|     .catch((err) => { |     .catch((err) => { | ||||||
|  |       // Already logged in, but with a different session | ||||||
|       if (err?.response?.status == 409) { |       if (err?.response?.status == 409) { | ||||||
|         notifications.show({ |         notifications.show({ | ||||||
|           title: t`Already logged in`, |           title: t`Already logged in`, | ||||||
| @@ -289,6 +282,19 @@ export function handleMfaLogin( | |||||||
|           color: 'red', |           color: 'red', | ||||||
|           autoClose: false |           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 { |       } else { | ||||||
|         const errors = err.response?.data?.errors; |         const errors = err.response?.data?.errors; | ||||||
|         let msg = t`An error occurred`; |         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 |  * Return the value of the CSRF cookie, if available | ||||||
|  */ |  */ | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import { t } from '@lingui/core/macro'; | import { t } from '@lingui/core/macro'; | ||||||
| import { Trans } from '@lingui/react/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 { useForm } from '@mantine/form'; | ||||||
| import { useState } from 'react'; | import { useState } from 'react'; | ||||||
| import { useLocation, useNavigate } from 'react-router-dom'; | import { useLocation, useNavigate } from 'react-router-dom'; | ||||||
| @@ -8,7 +8,7 @@ import { handleMfaLogin } from '../../functions/auth'; | |||||||
| import { Wrapper } from './Layout'; | import { Wrapper } from './Layout'; | ||||||
|  |  | ||||||
| export default function Mfa() { | export default function Mfa() { | ||||||
|   const simpleForm = useForm({ initialValues: { code: '' } }); |   const simpleForm = useForm({ initialValues: { code: '', remember: false } }); | ||||||
|   const navigate = useNavigate(); |   const navigate = useNavigate(); | ||||||
|   const location = useLocation(); |   const location = useLocation(); | ||||||
|   const [loginError, setLoginError] = useState<string | undefined>(undefined); |   const [loginError, setLoginError] = useState<string | undefined>(undefined); | ||||||
| @@ -23,6 +23,12 @@ export default function Mfa() { | |||||||
|         {...simpleForm.getInputProps('code')} |         {...simpleForm.getInputProps('code')} | ||||||
|         error={loginError} |         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 |       <Button | ||||||
|         type='submit' |         type='submit' | ||||||
|         onClick={() => |         onClick={() => | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user