feat(auth): add remembered local login to the frontend

This commit is contained in:
2026-08-17 04:52:39 +02:00
parent 8c67cb75dc
commit 202822f3ba
36 changed files with 2649 additions and 163 deletions
@@ -4,8 +4,17 @@ import { http, HttpResponse } from "msw";
import { server } from "../test/msw";
import { useSessionStore } from "../store/sessionStore";
import { WidgetHost } from "./WidgetHost";
import { setAuthState } from "../auth/authState";
beforeEach(() => useSessionStore.getState().resetSession());
const userA = {
issuer: "local", subject: "user-a", roles: ["user"] as const,
permissions: ["session.use"], isAdmin: false, csrfToken: "a".repeat(43), session: null,
};
beforeEach(() => {
useSessionStore.getState().resetSession();
setAuthState(userA);
});
test("a gate choice shows progress, prevents duplicate clicks, and stays silent on success", async () => {
let release!: () => void;
@@ -48,3 +57,38 @@ test("a gate choice shows progress, prevents duplicate clicks, and stays silent
expect(screen.getByRole("button", { name: "Next answer" })).toBeEnabled();
expect(screen.queryByRole("status")).not.toBeInTheDocument();
});
test("a delayed gate response from user A cannot clear user B's pending gate", async () => {
let release!: () => void;
let started!: () => void;
let settled!: () => void;
const held = new Promise<void>((resolve) => { release = resolve; });
const requestStarted = new Promise<void>((resolve) => { started = resolve; });
const requestSettled = new Promise<void>((resolve) => { settled = resolve; });
server.use(http.post("/api/sessions/s1/response", async () => {
started();
try {
await held;
return new HttpResponse(null, { status: 204 });
} finally {
settled();
}
}));
setAuthState(userA);
useSessionStore.setState({
pendingWidget: { id: "gate-a", widget: "select", options: [{ id: "a", label: "A answer" }] },
});
render(<WidgetHost sessionId="s1" />);
await userEvent.click(screen.getByRole("button", { name: "A answer" }));
await requestStarted;
act(() => setAuthState({ ...userA, subject: "user-b", csrfToken: "b".repeat(43) }));
act(() => useSessionStore.setState({
pendingWidget: { id: "gate-b", widget: "select", options: [{ id: "b", label: "B answer" }] },
}));
release();
await act(async () => { await requestSettled; });
expect(useSessionStore.getState().pendingWidget?.id).toBe("gate-b");
expect(useSessionStore.getState().lastUserEntry).toBeNull();
});