fix(webui): route missing API bootstrap tokens to auth
maintainer edit: handle review feedback by treating bootstrap responses without api_token as auth-required, and remove the obsolete issue_token(api_token=...) compatibility path now that API tokens are issued separately.
This commit is contained in:
@@ -175,6 +175,12 @@ vi.mock("@/hooks/useTheme", async () => {
|
||||
});
|
||||
|
||||
vi.mock("@/lib/bootstrap", () => ({
|
||||
BootstrapAuthRequiredError: class BootstrapAuthRequiredError extends Error {
|
||||
constructor(message = "bootstrap authentication required") {
|
||||
super(message);
|
||||
this.name = "BootstrapAuthRequiredError";
|
||||
}
|
||||
},
|
||||
fetchBootstrap: vi.fn().mockResolvedValue({
|
||||
token: "tok",
|
||||
api_token: "api-tok",
|
||||
@@ -217,7 +223,11 @@ vi.mock("@/lib/nanobot-client", () => {
|
||||
return { NanobotClient: MockClient };
|
||||
});
|
||||
|
||||
import { deriveWsUrl, fetchBootstrap } from "@/lib/bootstrap";
|
||||
import {
|
||||
BootstrapAuthRequiredError,
|
||||
deriveWsUrl,
|
||||
fetchBootstrap,
|
||||
} from "@/lib/bootstrap";
|
||||
import App from "@/App";
|
||||
|
||||
describe("App layout", () => {
|
||||
@@ -271,6 +281,20 @@ describe("App layout", () => {
|
||||
expect(connectSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shows the auth form when bootstrap does not issue an API token", async () => {
|
||||
vi.mocked(fetchBootstrap).mockRejectedValueOnce(
|
||||
new BootstrapAuthRequiredError(
|
||||
"bootstrap authentication required: missing api_token",
|
||||
),
|
||||
);
|
||||
|
||||
render(<App />);
|
||||
|
||||
expect(await screen.findByText("Authentication required")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Invalid password. Try again.")).not.toBeInTheDocument();
|
||||
expect(connectSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shows an invalid-password error after a submitted password is rejected", async () => {
|
||||
vi.mocked(fetchBootstrap).mockRejectedValue(
|
||||
new Error("bootstrap failed: HTTP 401"),
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { consumeUrlBootstrapSecret, deriveWsUrl, fetchBootstrap } from "@/lib/bootstrap";
|
||||
import {
|
||||
BootstrapAuthRequiredError,
|
||||
consumeUrlBootstrapSecret,
|
||||
deriveWsUrl,
|
||||
fetchBootstrap,
|
||||
} from "@/lib/bootstrap";
|
||||
|
||||
describe("bootstrap helpers", () => {
|
||||
afterEach(() => {
|
||||
@@ -51,7 +56,7 @@ describe("bootstrap helpers", () => {
|
||||
await pending;
|
||||
});
|
||||
|
||||
it("rejects bootstrap responses without an API token", async () => {
|
||||
it("treats bootstrap responses without an API token as auth-required", async () => {
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn(async () => ({
|
||||
@@ -60,9 +65,12 @@ describe("bootstrap helpers", () => {
|
||||
})),
|
||||
);
|
||||
|
||||
await expect(fetchBootstrap()).rejects.toThrow(
|
||||
"bootstrap response missing api_token",
|
||||
);
|
||||
const promise = fetchBootstrap();
|
||||
await expect(promise).rejects.toMatchObject({
|
||||
name: "BootstrapAuthRequiredError",
|
||||
message: "bootstrap authentication required: missing api_token",
|
||||
});
|
||||
await expect(promise).rejects.toBeInstanceOf(BootstrapAuthRequiredError);
|
||||
});
|
||||
|
||||
it("consumes bootstrap secrets from the URL fragment", () => {
|
||||
|
||||
Reference in New Issue
Block a user