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
+68 -2
View File
@@ -1,14 +1,21 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { render, screen, waitFor, within } from "@testing-library/react";
import { act, render, screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { http, HttpResponse } from "msw";
import { beforeEach, expect, test, vi } from "vitest";
import { server } from "../test/msw";
import { canonicalWorkspaceFixture, workspaceRevisionFixture, workspaceSummaryFixture } from "../test/workspace-fixtures";
import { WorkspaceManager } from "./WorkspaceManager";
import { setAuthState } from "../auth/authState";
import { queryClient } from "../app/queryClient";
const workspace = canonicalWorkspaceFixture("psd-clinical");
const revision = workspaceRevisionFixture("psd-clinical");
const authenticatedWorkspaceUser = {
issuer: "local", subject: "workspace-user", roles: ["user"] as const,
permissions: ["workspace.manage", "workspace.secrets.manage"], isAdmin: false,
csrfToken: "w".repeat(43), session: null,
};
const requirement = {
id: "dwh.password",
connector: "dwh",
@@ -34,14 +41,24 @@ function renderManager(onClose = vi.fn()) {
onClose,
...render(
<QueryClientProvider client={client}>
<WorkspaceManager open onClose={onClose} />
<WorkspaceManager open onClose={onClose} canManageWorkspace canManageSecrets />
</QueryClientProvider>,
),
};
}
function renderDeniedManager() {
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
return render(
<QueryClientProvider client={client}>
<WorkspaceManager open onClose={vi.fn()} canManageWorkspace={false} canManageSecrets={false} />
</QueryClientProvider>,
);
}
beforeEach(() => {
localStorage.clear();
setAuthState(authenticatedWorkspaceUser);
server.use(
http.get("/api/workspace-registry/status", () => HttpResponse.json({
branch: "main",
@@ -70,6 +87,17 @@ beforeEach(() => {
);
});
test("defaults workspace mutations and secrets to denied", async () => {
renderDeniedManager();
expect(await screen.findByRole("heading", { name: "Workspace management" })).toBeVisible();
expect(screen.queryByRole("button", { name: "Update workspace repository" })).not.toBeInTheDocument();
await userEvent.click(await screen.findByRole("button", { name: "PSD Clinical" }));
expect(await screen.findByRole("heading", { name: "Workspace-specific actions" })).toBeVisible();
expect(screen.queryByRole("button", { name: "Validate workspace source" })).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Test workspace connections" })).not.toBeInTheDocument();
expect(screen.queryByRole("heading", { name: "Runtime secrets" })).not.toBeInTheDocument();
});
test("uses a tall viewport area and keeps the workspace content scrollable", () => {
renderManager();
@@ -236,6 +264,44 @@ test("secret fields are write-only, clear after blind save, and may be forgotten
expect(await screen.findByText("Not configured")).toBeVisible();
});
test("a delayed runtime-secret save from user A cannot repopulate user B's cache or notice", 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.put("/api/workspaces/psd-clinical/secrets", async () => {
started();
try {
await held;
return HttpResponse.json(runtimeConfiguration(true));
} finally {
settled();
}
}));
queryClient.clear();
setAuthState({ issuer: "local", subject: "user-a", roles: ["user"], permissions: ["workspace.secrets.manage"], isAdmin: false, csrfToken: "a".repeat(43), session: null });
render(
<QueryClientProvider client={queryClient}>
<WorkspaceManager open onClose={vi.fn()} canManageWorkspace canManageSecrets />
</QueryClientProvider>,
);
await userEvent.click(await screen.findByRole("button", { name: "PSD Clinical" }));
const input = await screen.findByLabelText("Data warehouse password");
await userEvent.type(input, "a-secret");
await userEvent.click(screen.getByRole("button", { name: "Save entered secrets" }));
await requestStarted;
act(() => setAuthState({ issuer: "local", subject: "user-b", roles: ["user"], permissions: ["workspace.secrets.manage"], isAdmin: false, csrfToken: "b".repeat(43), session: null }));
expect(queryClient.getQueryData(["workspace-runtime-configuration", "psd-clinical"])).toBeUndefined();
release();
await act(async () => { await requestSettled; });
expect(queryClient.getQueryData(["workspace-runtime-configuration", "psd-clinical"])).toBeUndefined();
expect(screen.queryByText("Runtime secrets saved. Stored values remain hidden.")).not.toBeInTheDocument();
});
test("closing clears unsaved secret fields", async () => {
const user = userEvent.setup();
const onClose = vi.fn();