fix(frontend): hide logout outside local auth
This commit is contained in:
@@ -13,18 +13,20 @@ function renderShell(user: {
|
||||
isAdmin: boolean;
|
||||
roles: readonly ("user" | "admin")[];
|
||||
permissions: readonly string[];
|
||||
}) {
|
||||
}, canLogout = true) {
|
||||
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
setAuthState({
|
||||
issuer: "local", ...user, csrfToken: null, session: null,
|
||||
});
|
||||
return render(<QueryClientProvider client={client}><AppShell /></QueryClientProvider>);
|
||||
return render(<QueryClientProvider client={client}><AppShell canLogout={canLogout} /></QueryClientProvider>);
|
||||
}
|
||||
|
||||
function KeyedAuthenticatedShell() {
|
||||
const user = useAuthUser();
|
||||
const generation = useAuthGeneration();
|
||||
return user ? <AppShell key={`${user.issuer}:${user.subject}:${generation}`} /> : null;
|
||||
return user
|
||||
? <AppShell key={`${user.issuer}:${user.subject}:${generation}`} canLogout />
|
||||
: null;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -63,6 +65,25 @@ describe("authenticated shell permissions", () => {
|
||||
expect(screen.getByRole("button", { name: "All sessions" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("hides logout outside local authentication while preserving the identity", async () => {
|
||||
let logoutCalls = 0;
|
||||
server.use(http.post("/api/auth/logout", () => {
|
||||
logoutCalls += 1;
|
||||
return new HttpResponse(null, { status: 204 });
|
||||
}));
|
||||
|
||||
renderShell({
|
||||
subject: "portal-user",
|
||||
isAdmin: false,
|
||||
roles: ["user"],
|
||||
permissions: ["session.use"],
|
||||
}, false);
|
||||
|
||||
expect(await screen.findByText("portal-user")).toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Log out" })).not.toBeInTheDocument();
|
||||
expect(logoutCalls).toBe(0);
|
||||
});
|
||||
|
||||
test("logout revokes the cookie session and clears in-memory auth", async () => {
|
||||
const user = {
|
||||
issuer: "local" as const,
|
||||
@@ -112,7 +133,7 @@ describe("authenticated shell permissions", () => {
|
||||
|
||||
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
setAuthState(userA);
|
||||
render(<QueryClientProvider client={client}><AppShell /></QueryClientProvider>);
|
||||
render(<QueryClientProvider client={client}><AppShell canLogout /></QueryClientProvider>);
|
||||
await userEvent.click(screen.getByRole("button", { name: "Log out" }));
|
||||
await started;
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { workspacePreferences } from "../workspaces/preferences";
|
||||
|
||||
function renderShell() {
|
||||
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
return render(<QueryClientProvider client={client}><AppShell /></QueryClientProvider>);
|
||||
return render(<QueryClientProvider client={client}><AppShell canLogout={false} /></QueryClientProvider>);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -22,7 +22,7 @@ const adminUser: AuthenticatedUser = {
|
||||
function wrap(user: AuthenticatedUser = regularUser) {
|
||||
if (user !== regularUser) setAuthState(user);
|
||||
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
return render(<QueryClientProvider client={client}><AppShell /></QueryClientProvider>);
|
||||
return render(<QueryClientProvider client={client}><AppShell canLogout={false} /></QueryClientProvider>);
|
||||
}
|
||||
|
||||
const LIST = [
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useSessionStore } from "../store/sessionStore";
|
||||
|
||||
function renderShell() {
|
||||
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
return render(<QueryClientProvider client={client}><AppShell /></QueryClientProvider>);
|
||||
return render(<QueryClientProvider client={client}><AppShell canLogout={false} /></QueryClientProvider>);
|
||||
}
|
||||
|
||||
function deferred() {
|
||||
|
||||
@@ -36,7 +36,11 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import type { CSSProperties } from "react";
|
||||
import { captureAuthOperation, isAuthOperationCurrent, StaleAuthOperationError, type AuthOperationGuard } from "../auth/authOperation";
|
||||
|
||||
export function AppShell() {
|
||||
interface AppShellProps {
|
||||
canLogout: boolean;
|
||||
}
|
||||
|
||||
export function AppShell({ canLogout }: AppShellProps) {
|
||||
const authenticatedUser = useAuthUser();
|
||||
const [panelSession, setPanelSession] = useState<SessionSummary | null>(null);
|
||||
const {
|
||||
@@ -717,7 +721,11 @@ export function AppShell() {
|
||||
<span className="min-w-0 truncate text-xs text-muted-foreground" title={authenticatedUser.displayName ?? authenticatedUser.subject}>
|
||||
{authenticatedUser.displayName ?? authenticatedUser.subject}
|
||||
</span>
|
||||
<Button variant="ghost" size="xs" onClick={() => { void signOut().catch(() => undefined); }}>Log out</Button>
|
||||
{canLogout && (
|
||||
<Button variant="ghost" size="xs" onClick={() => { void signOut().catch(() => undefined); }}>
|
||||
Log out
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user