diff --git a/src/frontend/lib/types/Auth.tsx b/src/frontend/lib/types/Auth.tsx
index 5f6b0670cfea..959b64e5fe47 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 e18bf98b13f9..12595ac87c69 100644
--- a/src/frontend/src/components/nav/MainMenu.tsx
+++ b/src/frontend/src/components/nav/MainMenu.tsx
@@ -1,14 +1,19 @@
import { Trans } from '@lingui/react/macro';
import {
+ Button,
Group,
+ HoverCard,
+ List,
Menu,
Skeleton,
+ Table,
Text,
UnstyledButton,
useMantineColorScheme
} from '@mantine/core';
import {
IconChevronDown,
+ IconInfoCircle,
IconLogout,
IconMoonStars,
IconSettings,
@@ -18,10 +23,15 @@ import {
} from '@tabler/icons-react';
import { Link, useNavigate } from 'react-router-dom';
+import { ApiEndpoints, apiUrl } from '@lib/index';
+import type { AuthContext } from '@lib/types/Auth';
import { useShallow } from 'zustand/react/shallow';
-import { doLogout } from '../../functions/auth';
+import { authApi, doLogout } from '../../functions/auth';
import * as classes from '../../main.css';
+import { parseDate } from '../../pages/Index/Settings/AccountSettings/SecurityContent';
+import { useServerApiState } from '../../states/ApiState';
import { useUserState } from '../../states/UserState';
+import type { ServerAPIProps } from '../../states/states';
import { vars } from '../../theme';
export function MainMenu() {
@@ -30,72 +40,168 @@ export function MainMenu() {
useShallow((state) => [state.user, state.username])
);
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
-
+ const [server, auth_context] = useServerApiState(
+ useShallow((state) => [state.server, state.auth_context])
+ );
return (
-
+ >
+ );
+}
+
+function AuthContextInformation({
+ server,
+ auth_context
+}: Readonly<{
+ server: ServerAPIProps;
+ auth_context: AuthContext | undefined;
+}>) {
+ const [setAuthContext] = useServerApiState(
+ useShallow((state) => [state.setAuthContext])
+ );
+
+ function fetchAuthContext() {
+ authApi(apiUrl(ApiEndpoints.auth_session)).then((resp) => {
+ setAuthContext(resp.data.data);
+ });
+ }
+
+ const rows = [
+ { name: 'Server version', value: server.version },
+ { name: 'API version', value: server.apiVersion },
+ { name: 'User ID', value: auth_context?.user?.id }
+ ];
+ return (
+ <>
+
+
+
+
+ Name
+
+
+ Value
+
+
+
+
+ {rows.map((element) => (
+
+ {element.name}
+ {element.value}
+
+ ))}
+
+
+ {auth_context?.methods && (
+ <>
+
+ Methods
+
+
+
+ {auth_context?.methods.map((method: any) => (
+
+ {parseDate(method.at)}: {method.method}
+
+ ))}
+
+ >
+ )}
+
+ >
);
}
diff --git a/src/frontend/src/functions/auth.tsx b/src/frontend/src/functions/auth.tsx
index 49d583c12364..1f967360c7b7 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 f03a181215b1..22a68199fd69 100644
--- a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx
+++ b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx
@@ -388,9 +388,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) => (
@@ -713,3 +710,6 @@ async function runActionWithFallback(
});
}
}
+
+export const parseDate = (date: number) =>
+ date == null ? 'Never' : new Date(date * 1000).toLocaleString();
diff --git a/src/frontend/src/states/ApiState.tsx b/src/frontend/src/states/ApiState.tsx
index a1b74a5c120d..5ed33e415361 100644
--- a/src/frontend/src/states/ApiState.tsx
+++ b/src/frontend/src/states/ApiState.tsx
@@ -14,7 +14,7 @@ interface ServerApiStateProps {
fetchServerApiState: () => 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;
diff --git a/src/frontend/tests/pages/pui_core.spec.ts b/src/frontend/tests/pages/pui_core.spec.ts
index 290be4784435..cc7f12d0b2f0 100644
--- a/src/frontend/tests/pages/pui_core.spec.ts
+++ b/src/frontend/tests/pages/pui_core.spec.ts
@@ -15,7 +15,7 @@ test('Core User/Group/Contact', async ({ browser }) => {
// users
await loadTab(page, 'Users');
- await page.getByRole('cell', { name: 'admin' }).click();
+ await page.getByRole('cell', { name: 'admin', exact: true }).click();
await page.getByText('User: admin', { exact: true }).waitFor();
await page.getByLabel('User Details').waitFor();
await page.getByLabel('breadcrumb-1-users').click();