mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-03 03:03:24 -04: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";
|
||||
|
||||
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", () => {
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
@@ -49,14 +61,7 @@ describe("fetchBrowserJson loopback auth", () => {
|
||||
});
|
||||
|
||||
it("adds bearer auth for loopback absolute HTTP URLs", async () => {
|
||||
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);
|
||||
const fetchMock = stubJsonFetchOk();
|
||||
|
||||
const res = await fetchBrowserJson<{ ok: boolean }>("http://127.0.0.1:18888/");
|
||||
expect(res.ok).toBe(true);
|
||||
@@ -67,14 +72,7 @@ describe("fetchBrowserJson loopback auth", () => {
|
||||
});
|
||||
|
||||
it("does not inject auth for non-loopback absolute URLs", async () => {
|
||||
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);
|
||||
const fetchMock = stubJsonFetchOk();
|
||||
|
||||
await fetchBrowserJson<{ ok: boolean }>("http://example.com/");
|
||||
|
||||
@@ -84,14 +82,7 @@ describe("fetchBrowserJson loopback auth", () => {
|
||||
});
|
||||
|
||||
it("keeps caller-supplied auth header", async () => {
|
||||
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);
|
||||
const fetchMock = stubJsonFetchOk();
|
||||
|
||||
await fetchBrowserJson<{ ok: boolean }>("http://localhost:18888/", {
|
||||
headers: {
|
||||
@@ -103,4 +94,14 @@ describe("fetchBrowserJson loopback auth", () => {
|
||||
const headers = new Headers(init?.headers);
|
||||
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 {
|
||||
try {
|
||||
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 {
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user