mirror of
https://github.com/inventree/InvenTree.git
synced 2025-09-13 14:11:37 +00:00
Auth session info (#10271)
* https://github.com/inventree/InvenTree/pull/6293 * refactor to a shared component * refactoring container stuff to a wrapper * move title to wrapper * move logoff and loader to wrapper * mvoe functions to general auth * seperate login and register into seperate pages * unify auth styling * rename component * adapt to new look * check if registration is enabled * feat(frontend):add authentication debug window * clear state on logout * add reload button * reduce diff * export helper * move hover out * only show to superusers * fix state args * fix merge * fix merge * clean up diff * reduce diff * re-diff * fix shallow loading * fix test * fix umport * Move session info to user settings panel * Restrict to superuser accounts --------- Co-authored-by: Matthias Mair <code@mjmair.com>
This commit is contained in:
@@ -1,5 +1,16 @@
|
||||
export interface AuthContext {
|
||||
status: number;
|
||||
user?: {
|
||||
id: number;
|
||||
display: string;
|
||||
has_usable_password: boolean;
|
||||
username: string;
|
||||
};
|
||||
methods?: {
|
||||
method: string;
|
||||
at: number;
|
||||
username: string;
|
||||
}[];
|
||||
data: { flows: Flow[] };
|
||||
meta: { is_authenticated: boolean };
|
||||
}
|
||||
|
@@ -17,7 +17,6 @@ import {
|
||||
IconUserCog
|
||||
} from '@tabler/icons-react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { doLogout } from '../../functions/auth';
|
||||
import * as classes from '../../main.css';
|
||||
@@ -32,70 +31,76 @@ export function MainMenu() {
|
||||
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
|
||||
|
||||
return (
|
||||
<Menu width={260} position='bottom-end'>
|
||||
<Menu.Target>
|
||||
<UnstyledButton className={classes.layoutHeaderUser}>
|
||||
<Group gap={7}>
|
||||
{username() ? (
|
||||
<Text fw={500} size='sm' style={{ lineHeight: 1 }} mr={3}>
|
||||
{username()}
|
||||
</Text>
|
||||
) : (
|
||||
<Skeleton height={20} width={40} radius={vars.radiusDefault} />
|
||||
)}
|
||||
<IconChevronDown />
|
||||
</Group>
|
||||
</UnstyledButton>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Label>
|
||||
<Trans>Settings</Trans>
|
||||
</Menu.Label>
|
||||
<Menu.Item
|
||||
leftSection={<IconUserCog />}
|
||||
component={Link}
|
||||
to='/settings/user'
|
||||
>
|
||||
<Trans>Account Settings</Trans>
|
||||
</Menu.Item>
|
||||
{user?.is_staff && (
|
||||
<>
|
||||
<Menu width={260} position='bottom-end'>
|
||||
<Menu.Target>
|
||||
<UnstyledButton className={classes.layoutHeaderUser}>
|
||||
<Group gap={7}>
|
||||
{username() ? (
|
||||
<Text fw={500} size='sm' style={{ lineHeight: 1 }} mr={3}>
|
||||
{username()}
|
||||
</Text>
|
||||
) : (
|
||||
<Skeleton height={20} width={40} radius={vars.radiusDefault} />
|
||||
)}
|
||||
<IconChevronDown />
|
||||
</Group>
|
||||
</UnstyledButton>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Label>
|
||||
<Trans>Settings</Trans>
|
||||
</Menu.Label>
|
||||
<Menu.Item
|
||||
leftSection={<IconSettings />}
|
||||
leftSection={<IconUserCog />}
|
||||
component={Link}
|
||||
to='/settings/system'
|
||||
to='/settings/user'
|
||||
>
|
||||
<Trans>System Settings</Trans>
|
||||
<Trans>Account Settings</Trans>
|
||||
</Menu.Item>
|
||||
)}
|
||||
{user?.is_staff && (
|
||||
{user?.is_staff && (
|
||||
<Menu.Item
|
||||
leftSection={<IconSettings />}
|
||||
component={Link}
|
||||
to='/settings/system'
|
||||
>
|
||||
<Trans>System Settings</Trans>
|
||||
</Menu.Item>
|
||||
)}
|
||||
{user?.is_staff && (
|
||||
<Menu.Item
|
||||
leftSection={<IconUserBolt />}
|
||||
component={Link}
|
||||
to='/settings/admin'
|
||||
>
|
||||
<Trans>Admin Center</Trans>
|
||||
</Menu.Item>
|
||||
)}
|
||||
{user?.is_staff && <Menu.Divider />}
|
||||
<Menu.Item
|
||||
leftSection={<IconUserBolt />}
|
||||
component={Link}
|
||||
to='/settings/admin'
|
||||
onClick={toggleColorScheme}
|
||||
leftSection={
|
||||
colorScheme === 'dark' ? <IconSun /> : <IconMoonStars />
|
||||
}
|
||||
c={
|
||||
colorScheme === 'dark'
|
||||
? vars.colors.yellow[4]
|
||||
: vars.colors.blue[6]
|
||||
}
|
||||
>
|
||||
<Trans>Admin Center</Trans>
|
||||
<Trans>Change Color Mode</Trans>
|
||||
</Menu.Item>
|
||||
)}
|
||||
{user?.is_staff && <Menu.Divider />}
|
||||
<Menu.Item
|
||||
onClick={toggleColorScheme}
|
||||
leftSection={colorScheme === 'dark' ? <IconSun /> : <IconMoonStars />}
|
||||
c={
|
||||
colorScheme === 'dark' ? vars.colors.yellow[4] : vars.colors.blue[6]
|
||||
}
|
||||
>
|
||||
<Trans>Change Color Mode</Trans>
|
||||
</Menu.Item>
|
||||
<Menu.Divider />
|
||||
<Menu.Item
|
||||
leftSection={<IconLogout />}
|
||||
onClick={() => {
|
||||
doLogout(navigate);
|
||||
}}
|
||||
>
|
||||
<Trans>Logout</Trans>
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
<Menu.Divider />
|
||||
<Menu.Item
|
||||
leftSection={<IconLogout />}
|
||||
onClick={() => {
|
||||
doLogout(navigate);
|
||||
}}
|
||||
>
|
||||
<Trans>Logout</Trans>
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@@ -169,6 +169,7 @@ export async function doBasicLogin(
|
||||
*/
|
||||
export const doLogout = async (navigate: NavigateFunction) => {
|
||||
const { clearUserState, isLoggedIn } = useUserState.getState();
|
||||
const { setAuthContext } = useServerApiState.getState();
|
||||
|
||||
// Logout from the server session
|
||||
if (isLoggedIn() || !!getCsrfCookie()) {
|
||||
@@ -183,6 +184,7 @@ export const doLogout = async (navigate: NavigateFunction) => {
|
||||
|
||||
clearUserState();
|
||||
clearCsrfCookie();
|
||||
setAuthContext(undefined);
|
||||
navigate('/login');
|
||||
};
|
||||
|
||||
|
@@ -5,6 +5,7 @@ import { t } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import {
|
||||
Accordion,
|
||||
ActionIcon,
|
||||
Alert,
|
||||
Badge,
|
||||
Button,
|
||||
@@ -27,15 +28,17 @@ import {
|
||||
IconAlertCircle,
|
||||
IconAt,
|
||||
IconExclamationCircle,
|
||||
IconRefresh,
|
||||
IconX
|
||||
} from '@tabler/icons-react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { api } from '../../../../App';
|
||||
import { StylishText } from '../../../../components/items/StylishText';
|
||||
import { ProviderLogin, authApi } from '../../../../functions/auth';
|
||||
import { useServerApiState } from '../../../../states/ServerApiState';
|
||||
import { useUserState } from '../../../../states/UserState';
|
||||
import { ApiTokenTable } from '../../../../tables/settings/ApiTokenTable';
|
||||
import { QrRegistrationForm } from './QrRegistrationForm';
|
||||
import { useReauth } from './useConfirm';
|
||||
@@ -45,9 +48,11 @@ export function SecurityContent() {
|
||||
useShallow((state) => [state.auth_config, state.sso_enabled])
|
||||
);
|
||||
|
||||
const user = useUserState();
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Accordion multiple defaultValue={['email', 'sso', 'mfa', 'token']}>
|
||||
<Accordion multiple defaultValue={['email']}>
|
||||
<Accordion.Item value='email'>
|
||||
<Accordion.Control>
|
||||
<StylishText size='lg'>{t`Email Addresses`}</StylishText>
|
||||
@@ -90,11 +95,64 @@ export function SecurityContent() {
|
||||
<ApiTokenTable only_myself />
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
{user.isSuperuser() && (
|
||||
<Accordion.Item value='session'>
|
||||
<Accordion.Control>
|
||||
<StylishText size='lg'>{t`Session Information`}</StylishText>
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<AuthContextSection />
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
)}
|
||||
</Accordion>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function AuthContextSection() {
|
||||
const [auth_context, setAuthContext] = useServerApiState(
|
||||
useShallow((state) => [state.auth_context, state.setAuthContext])
|
||||
);
|
||||
|
||||
const fetchAuthContext = useCallback(() => {
|
||||
authApi(apiUrl(ApiEndpoints.auth_session)).then((resp) => {
|
||||
setAuthContext(resp.data.data);
|
||||
});
|
||||
}, [setAuthContext]);
|
||||
|
||||
return (
|
||||
<Stack gap='xs'>
|
||||
<Group>
|
||||
<ActionIcon
|
||||
onClick={fetchAuthContext}
|
||||
variant='transparent'
|
||||
aria-label='refresh-auth-context'
|
||||
>
|
||||
<IconRefresh />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
|
||||
<Table>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>{t`Timestamp`}</Table.Th>
|
||||
<Table.Th>{t`Method`}</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{auth_context?.methods?.map((method: any, index: number) => (
|
||||
<Table.Tr key={`auth-method-${index}`}>
|
||||
<Table.Td>{parseDate(method.at)}</Table.Td>
|
||||
<Table.Td>{method.method}</Table.Td>
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function EmailSection() {
|
||||
const [selectedEmail, setSelectedEmail] = useState<string>('');
|
||||
const [newEmailValue, setNewEmailValue] = useState('');
|
||||
@@ -392,9 +450,6 @@ function MfaSection() {
|
||||
);
|
||||
};
|
||||
|
||||
const parseDate = (date: number) =>
|
||||
date == null ? 'Never' : new Date(date * 1000).toLocaleString();
|
||||
|
||||
const rows = useMemo(() => {
|
||||
if (isLoading || !data) return null;
|
||||
return data.map((token: any) => (
|
||||
@@ -719,3 +774,6 @@ async function runActionWithFallback(
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const parseDate = (date: number) =>
|
||||
date == null ? 'Never' : new Date(date * 1000).toLocaleString();
|
||||
|
@@ -14,7 +14,7 @@ interface ServerApiStateProps {
|
||||
fetchServerApiState: () => Promise<void>;
|
||||
auth_config?: AuthConfig;
|
||||
auth_context?: AuthContext;
|
||||
setAuthContext: (auth_context: AuthContext) => void;
|
||||
setAuthContext: (auth_context: AuthContext | undefined) => void;
|
||||
sso_enabled: () => boolean;
|
||||
registration_enabled: () => boolean;
|
||||
sso_registration_enabled: () => boolean;
|
||||
|
Reference in New Issue
Block a user