fix(webui): gate bootstrap API token issuance

This commit is contained in:
chengyongru
2026-07-08 21:01:48 +08:00
committed by Xubin Ren
parent 7204d88a4c
commit 88143a8bf0
16 changed files with 219 additions and 85 deletions
+6
View File
@@ -177,10 +177,12 @@ vi.mock("@/hooks/useTheme", async () => {
vi.mock("@/lib/bootstrap", () => ({
fetchBootstrap: vi.fn().mockResolvedValue({
token: "tok",
api_token: "api-tok",
ws_path: "/",
expires_in: 300,
}),
deriveWsUrl: vi.fn(() => "ws://test"),
consumeUrlBootstrapSecret: vi.fn(() => ""),
loadSavedSecret: vi.fn(() => ""),
saveSecret: vi.fn(),
clearSavedSecret: vi.fn(),
@@ -239,6 +241,7 @@ describe("App layout", () => {
localStorage.removeItem("nanobot-webui.sidebar.session-updates.v1");
vi.mocked(fetchBootstrap).mockReset().mockResolvedValue({
token: "tok",
api_token: "api-tok",
ws_path: "/",
expires_in: 300,
});
@@ -723,6 +726,7 @@ describe("App layout", () => {
];
vi.mocked(fetchBootstrap).mockResolvedValue({
token: "tok",
api_token: "api-tok",
ws_path: "/",
expires_in: 300,
runtime_surface: "native",
@@ -2147,11 +2151,13 @@ describe("App layout", () => {
vi.mocked(fetchBootstrap)
.mockResolvedValueOnce({
token: "tok-1",
api_token: "api-tok-1",
ws_path: "/",
expires_in: 30,
})
.mockResolvedValueOnce({
token: "tok-2",
api_token: "api-tok-2",
ws_path: "/",
expires_in: 300,
});
+26 -1
View File
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { deriveWsUrl, fetchBootstrap } from "@/lib/bootstrap";
import { consumeUrlBootstrapSecret, deriveWsUrl, fetchBootstrap } from "@/lib/bootstrap";
describe("bootstrap helpers", () => {
afterEach(() => {
@@ -50,4 +50,29 @@ describe("bootstrap helpers", () => {
await pending;
});
it("rejects bootstrap responses without an API token", async () => {
vi.stubGlobal(
"fetch",
vi.fn(async () => ({
ok: true,
json: async () => ({ token: "ws-token", ws_path: "/", expires_in: 300 }),
})),
);
await expect(fetchBootstrap()).rejects.toThrow(
"bootstrap response missing api_token",
);
});
it("consumes bootstrap secrets from the URL fragment", () => {
window.history.replaceState(
null,
"",
"/#/settings?bootstrapSecret=s3cret&section=models",
);
expect(consumeUrlBootstrapSecret()).toBe("s3cret");
expect(window.location.hash).toBe("#/settings?section=models");
});
});