From 335d87ef16b2760c107641104bba3a2771ecc6ab Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 5 Sep 2025 16:07:32 +1000 Subject: [PATCH] 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 --- src/frontend/lib/types/Auth.tsx | 11 ++ src/frontend/src/components/nav/MainMenu.tsx | 123 +++++++++--------- src/frontend/src/functions/auth.tsx | 2 + .../AccountSettings/SecurityContent.tsx | 68 +++++++++- src/frontend/src/states/ServerApiState.tsx | 2 +- 5 files changed, 141 insertions(+), 65 deletions(-) diff --git a/src/frontend/lib/types/Auth.tsx b/src/frontend/lib/types/Auth.tsx index 5f6b0670cf..959b64e5fe 100644 --- a/src/frontend/lib/types/Auth.tsx +++ b/src/frontend/lib/types/Auth.tsx @@ -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 }; } diff --git a/src/frontend/src/components/nav/MainMenu.tsx b/src/frontend/src/components/nav/MainMenu.tsx index e18bf98b13..63407c756f 100644 --- a/src/frontend/src/components/nav/MainMenu.tsx +++ b/src/frontend/src/components/nav/MainMenu.tsx @@ -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 ( - - - - - {username() ? ( - - {username()} - - ) : ( - - )} - - - - - - - Settings - - } - component={Link} - to='/settings/user' - > - Account Settings - - {user?.is_staff && ( + <> + + + + + {username() ? ( + + {username()} + + ) : ( + + )} + + + + + + + Settings + } + leftSection={} component={Link} - to='/settings/system' + to='/settings/user' > - System Settings + Account Settings - )} - {user?.is_staff && ( + {user?.is_staff && ( + } + component={Link} + to='/settings/system' + > + System Settings + + )} + {user?.is_staff && ( + } + component={Link} + to='/settings/admin' + > + Admin Center + + )} + {user?.is_staff && } } - component={Link} - to='/settings/admin' + onClick={toggleColorScheme} + leftSection={ + colorScheme === 'dark' ? : + } + c={ + colorScheme === 'dark' + ? vars.colors.yellow[4] + : vars.colors.blue[6] + } > - Admin Center + Change Color Mode - )} - {user?.is_staff && } - : } - c={ - colorScheme === 'dark' ? vars.colors.yellow[4] : vars.colors.blue[6] - } - > - Change Color Mode - - - } - onClick={() => { - doLogout(navigate); - }} - > - Logout - - - + + } + onClick={() => { + doLogout(navigate); + }} + > + Logout + + + + ); } diff --git a/src/frontend/src/functions/auth.tsx b/src/frontend/src/functions/auth.tsx index 7a2158e6f5..fec5f68cf9 100644 --- a/src/frontend/src/functions/auth.tsx +++ b/src/frontend/src/functions/auth.tsx @@ -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'); }; diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx index f08d810b47..faf1210fe3 100644 --- a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx +++ b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx @@ -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 ( - + {t`Email Addresses`} @@ -90,11 +95,64 @@ export function SecurityContent() { + {user.isSuperuser() && ( + + + {t`Session Information`} + + + + + + )} ); } +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 ( + + + + + + + + + + + {t`Timestamp`} + {t`Method`} + + + + {auth_context?.methods?.map((method: any, index: number) => ( + + {parseDate(method.at)} + {method.method} + + ))} + +
+
+ ); +} + function EmailSection() { const [selectedEmail, setSelectedEmail] = useState(''); 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(); diff --git a/src/frontend/src/states/ServerApiState.tsx b/src/frontend/src/states/ServerApiState.tsx index e2ace8c689..6e0428f7f9 100644 --- a/src/frontend/src/states/ServerApiState.tsx +++ b/src/frontend/src/states/ServerApiState.tsx @@ -14,7 +14,7 @@ interface ServerApiStateProps { fetchServerApiState: () => Promise; 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;