mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 21:14:31 +00:00
fix(browser): handle IPv6 loopback auth and dedupe fetch auth tests
This commit is contained in:
@@ -31,6 +31,18 @@ vi.mock("./routes/dispatcher.js", () => ({
|
|||||||
|
|
||||||
import { fetchBrowserJson } from "./client-fetch.js";
|
import { fetchBrowserJson } from "./client-fetch.js";
|
||||||
|
|
||||||
|
function stubJsonFetchOk() {
|
||||||
|
const fetchMock = vi.fn<(input: RequestInfo | URL, init?: RequestInit) => Promise<Response>>(
|
||||||
|
async () =>
|
||||||
|
new Response(JSON.stringify({ ok: true }), {
|
||||||
|
status: 200,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
vi.stubGlobal("fetch", fetchMock);
|
||||||
|
return fetchMock;
|
||||||
|
}
|
||||||
|
|
||||||
describe("fetchBrowserJson loopback auth", () => {
|
describe("fetchBrowserJson loopback auth", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
@@ -49,14 +61,7 @@ describe("fetchBrowserJson loopback auth", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("adds bearer auth for loopback absolute HTTP URLs", async () => {
|
it("adds bearer auth for loopback absolute HTTP URLs", async () => {
|
||||||
const fetchMock = vi.fn<(input: RequestInfo | URL, init?: RequestInit) => Promise<Response>>(
|
const fetchMock = stubJsonFetchOk();
|
||||||
async () =>
|
|
||||||
new Response(JSON.stringify({ ok: true }), {
|
|
||||||
status: 200,
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
vi.stubGlobal("fetch", fetchMock);
|
|
||||||
|
|
||||||
const res = await fetchBrowserJson<{ ok: boolean }>("http://127.0.0.1:18888/");
|
const res = await fetchBrowserJson<{ ok: boolean }>("http://127.0.0.1:18888/");
|
||||||
expect(res.ok).toBe(true);
|
expect(res.ok).toBe(true);
|
||||||
@@ -67,14 +72,7 @@ describe("fetchBrowserJson loopback auth", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("does not inject auth for non-loopback absolute URLs", async () => {
|
it("does not inject auth for non-loopback absolute URLs", async () => {
|
||||||
const fetchMock = vi.fn<(input: RequestInfo | URL, init?: RequestInit) => Promise<Response>>(
|
const fetchMock = stubJsonFetchOk();
|
||||||
async () =>
|
|
||||||
new Response(JSON.stringify({ ok: true }), {
|
|
||||||
status: 200,
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
vi.stubGlobal("fetch", fetchMock);
|
|
||||||
|
|
||||||
await fetchBrowserJson<{ ok: boolean }>("http://example.com/");
|
await fetchBrowserJson<{ ok: boolean }>("http://example.com/");
|
||||||
|
|
||||||
@@ -84,14 +82,7 @@ describe("fetchBrowserJson loopback auth", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("keeps caller-supplied auth header", async () => {
|
it("keeps caller-supplied auth header", async () => {
|
||||||
const fetchMock = vi.fn<(input: RequestInfo | URL, init?: RequestInit) => Promise<Response>>(
|
const fetchMock = stubJsonFetchOk();
|
||||||
async () =>
|
|
||||||
new Response(JSON.stringify({ ok: true }), {
|
|
||||||
status: 200,
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
vi.stubGlobal("fetch", fetchMock);
|
|
||||||
|
|
||||||
await fetchBrowserJson<{ ok: boolean }>("http://localhost:18888/", {
|
await fetchBrowserJson<{ ok: boolean }>("http://localhost:18888/", {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -103,4 +94,14 @@ describe("fetchBrowserJson loopback auth", () => {
|
|||||||
const headers = new Headers(init?.headers);
|
const headers = new Headers(init?.headers);
|
||||||
expect(headers.get("authorization")).toBe("Bearer caller-token");
|
expect(headers.get("authorization")).toBe("Bearer caller-token");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("injects auth for IPv6 loopback absolute URLs", async () => {
|
||||||
|
const fetchMock = stubJsonFetchOk();
|
||||||
|
|
||||||
|
await fetchBrowserJson<{ ok: boolean }>("http://[::1]:18888/");
|
||||||
|
|
||||||
|
const init = fetchMock.mock.calls[0]?.[1];
|
||||||
|
const headers = new Headers(init?.headers);
|
||||||
|
expect(headers.get("authorization")).toBe("Bearer loopback-token");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,7 +21,11 @@ function isAbsoluteHttp(url: string): boolean {
|
|||||||
function isLoopbackHttpUrl(url: string): boolean {
|
function isLoopbackHttpUrl(url: string): boolean {
|
||||||
try {
|
try {
|
||||||
const host = new URL(url).hostname.trim().toLowerCase();
|
const host = new URL(url).hostname.trim().toLowerCase();
|
||||||
return host === "127.0.0.1" || host === "localhost" || host === "::1";
|
// URL hostnames may keep IPv6 brackets (for example "[::1]"); normalize before checks.
|
||||||
|
const normalizedHost = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
|
||||||
|
return (
|
||||||
|
normalizedHost === "127.0.0.1" || normalizedHost === "localhost" || normalizedHost === "::1"
|
||||||
|
);
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user