[PUI] Auth behaviour controls (#6314)

* optimize login layout

* move auth/reg up

* [PUI] Registration
Fixes #6282

* [PUI] Registration
Fixes #6282

* fix type

* Add auth settings to API state

* control showing of registration via state

* small cleanups

* Added registration and password forgotten to auth endpoint

* control password forgotten via state

* cleanup imports

* bump api version
This commit is contained in:
Matthias Mair 2024-01-23 12:27:41 +00:00 committed by GitHub
parent f35ce29612
commit 8f6893a6b2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 69 additions and 24 deletions

View File

@ -1,11 +1,14 @@
"""InvenTree API version information.""" """InvenTree API version information."""
# InvenTree API version # InvenTree API version
INVENTREE_API_VERSION = 162 INVENTREE_API_VERSION = 163
"""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 = """
v163 -> 2024-01-22 : https://github.com/inventree/InvenTree/pull/6314
- Extends API endpoint to expose auth configuration information for signin pages
v162 -> 2024-01-14 : https://github.com/inventree/InvenTree/pull/6230 v162 -> 2024-01-14 : https://github.com/inventree/InvenTree/pull/6230
- Adds API endpoints to provide information on background tasks - Adds API endpoints to provide information on background tasks

View File

@ -153,6 +153,10 @@ class SocialProviderListView(ListAPI):
'sso_registration': InvenTree.sso.registration_enabled(), 'sso_registration': InvenTree.sso.registration_enabled(),
'mfa_required': InvenTreeSetting.get_setting('LOGIN_ENFORCE_MFA'), 'mfa_required': InvenTreeSetting.get_setting('LOGIN_ENFORCE_MFA'),
'providers': provider_list, 'providers': provider_list,
'registration_enabled': InvenTreeSetting.get_setting('LOGIN_ENABLE_REG'),
'password_forgotten_enabled': InvenTreeSetting.get_setting(
'LOGIN_ENABLE_PWD_FORGOT'
),
} }
return Response(data) return Response(data)

View File

@ -19,7 +19,7 @@ import { useNavigate } from 'react-router-dom';
import { api } from '../../App'; import { api } from '../../App';
import { ApiPaths } from '../../enums/ApiEndpoints'; import { ApiPaths } from '../../enums/ApiEndpoints';
import { doClassicLogin, doSimpleLogin } from '../../functions/auth'; import { doClassicLogin, doSimpleLogin } from '../../functions/auth';
import { apiUrl } from '../../states/ApiState'; import { apiUrl, useServerApiState } from '../../states/ApiState';
export function AuthenticationForm() { export function AuthenticationForm() {
const classicForm = useForm({ const classicForm = useForm({
@ -27,6 +27,7 @@ export function AuthenticationForm() {
}); });
const simpleForm = useForm({ initialValues: { email: '' } }); const simpleForm = useForm({ initialValues: { email: '' } });
const [classicLoginMode, setMode] = useDisclosure(true); const [classicLoginMode, setMode] = useDisclosure(true);
const [auth_settings] = useServerApiState((state) => [state.auth_settings]);
const navigate = useNavigate(); const navigate = useNavigate();
const [isLoggingIn, setIsLoggingIn] = useState<boolean>(false); const [isLoggingIn, setIsLoggingIn] = useState<boolean>(false);
@ -96,17 +97,19 @@ export function AuthenticationForm() {
placeholder={t`Your password`} placeholder={t`Your password`}
{...classicForm.getInputProps('password')} {...classicForm.getInputProps('password')}
/> />
<Group position="apart" mt="0"> {auth_settings?.password_forgotten_enabled === true && (
<Anchor <Group position="apart" mt="0">
component="button" <Anchor
type="button" component="button"
color="dimmed" type="button"
size="xs" color="dimmed"
onClick={() => navigate('/reset-password')} size="xs"
> onClick={() => navigate('/reset-password')}
<Trans>Reset password</Trans> >
</Anchor> <Trans>Reset password</Trans>
</Group> </Anchor>
</Group>
)}
</Stack> </Stack>
) : ( ) : (
<Stack> <Stack>
@ -248,6 +251,9 @@ export function ModeSelector({
loginMode: boolean; loginMode: boolean;
setMode: any; setMode: any;
}) { }) {
const [auth_settings] = useServerApiState((state) => [state.auth_settings]);
if (auth_settings?.registration_enabled === false) return null;
return ( return (
<Text ta="center" size={'xs'} mt={'md'}> <Text ta="center" size={'xs'} mt={'md'}>
{loginMode ? ( {loginMode ? (
@ -258,7 +264,7 @@ export function ModeSelector({
type="button" type="button"
color="dimmed" color="dimmed"
size="xs" size="xs"
onClick={() => setMode.toggle()} onClick={() => setMode.close()}
> >
<Trans>Register</Trans> <Trans>Register</Trans>
</Anchor> </Anchor>
@ -269,7 +275,7 @@ export function ModeSelector({
type="button" type="button"
color="dimmed" color="dimmed"
size="xs" size="xs"
onClick={() => setMode.toggle()} onClick={() => setMode.open()}
> >
<Trans>Go back to login</Trans> <Trans>Go back to login</Trans>
</Anchor> </Anchor>

View File

@ -7,7 +7,7 @@ import { statusCodeList } from '../defaults/backendMappings';
import { emptyServerAPI } from '../defaults/defaults'; import { emptyServerAPI } from '../defaults/defaults';
import { ApiPaths } from '../enums/ApiEndpoints'; import { ApiPaths } from '../enums/ApiEndpoints';
import { ModelType } from '../enums/ModelType'; import { ModelType } from '../enums/ModelType';
import { ServerAPIProps } from './states'; import { AuthProps, ServerAPIProps } from './states';
type StatusLookup = Record<ModelType | string, StatusCodeListInterface>; type StatusLookup = Record<ModelType | string, StatusCodeListInterface>;
@ -16,6 +16,7 @@ interface ServerApiStateProps {
setServer: (newServer: ServerAPIProps) => void; setServer: (newServer: ServerAPIProps) => void;
fetchServerApiState: () => void; fetchServerApiState: () => void;
status?: StatusLookup; status?: StatusLookup;
auth_settings?: AuthProps;
} }
export const useServerApiState = create<ServerApiStateProps>()( export const useServerApiState = create<ServerApiStateProps>()(
@ -32,14 +33,27 @@ export const useServerApiState = create<ServerApiStateProps>()(
}) })
.catch(() => {}); .catch(() => {});
// Fetch status data for rendering labels // Fetch status data for rendering labels
await api.get(apiUrl(ApiPaths.global_status)).then((response) => { await api
const newStatusLookup: StatusLookup = {} as StatusLookup; .get(apiUrl(ApiPaths.global_status))
for (const key in response.data) { .then((response) => {
newStatusLookup[statusCodeList[key] || key] = const newStatusLookup: StatusLookup = {} as StatusLookup;
response.data[key].values; for (const key in response.data) {
} newStatusLookup[statusCodeList[key] || key] =
set({ status: newStatusLookup }); response.data[key].values;
}); }
set({ status: newStatusLookup });
})
.catch(() => {});
// Fetch login/SSO behaviour
await api
.get(apiUrl(ApiPaths.sso_providers), {
headers: { Authorization: '' }
})
.then((response) => {
set({ auth_settings: response.data });
})
.catch(() => {});
}, },
status: undefined status: undefined
}), }),

View File

@ -40,6 +40,24 @@ export interface ServerAPIProps {
default_locale: null | string; default_locale: null | string;
} }
export interface AuthProps {
sso_enabled: boolean;
sso_registration: boolean;
mfa_required: boolean;
providers: Provider[];
registration_enabled: boolean;
password_forgotten_enabled: boolean;
}
export interface Provider {
id: string;
name: string;
configured: boolean;
login: string;
connect: string;
display_name: string;
}
// Type interface defining a single 'setting' object // Type interface defining a single 'setting' object
export interface Setting { export interface Setting {
pk: number; pk: number;