diff --git a/packages/core/src/utils/overpass/client.test.ts b/packages/core/src/utils/overpass/client.test.ts index 5d27e0e40..227f90923 100644 --- a/packages/core/src/utils/overpass/client.test.ts +++ b/packages/core/src/utils/overpass/client.test.ts @@ -20,6 +20,21 @@ afterEach(() => { }); describe("overpassQuery", () => { + it("sends OVERPASS_URL credentials as Basic auth, which fetch refuses in the URL", async () => { + vi.stubEnv("OVERPASS_URL", "https://mapper:s3cret@overpass.example.org"); + const fetchMock = vi.fn( + async (_url: string, _init?: RequestInit) => + new Response(JSON.stringify({ elements: [] }), { status: 200 }), + ); + vi.stubGlobal("fetch", fetchMock); + + await expect(overpassQuery(QUERY)).resolves.toMatchObject({ elements: [] }); + expect(fetchMock.mock.calls[0]?.[0]).toBe("https://overpass.example.org/api/interpreter"); + expect(new Headers(fetchMock.mock.calls[0]?.[1]?.headers).get("Authorization")).toBe( + `Basic ${Buffer.from("mapper:s3cret").toString("base64")}`, + ); + }); + it("reports a server that does not answer in time as unavailable, not as a 500", async () => { setOverpassUrl("http://overpass.test"); vi.stubGlobal( diff --git a/packages/core/src/utils/overpass/client.ts b/packages/core/src/utils/overpass/client.ts index f1f2eda48..22b605497 100644 --- a/packages/core/src/utils/overpass/client.ts +++ b/packages/core/src/utils/overpass/client.ts @@ -1,6 +1,7 @@ import { type Attribution, OPENSTREETMAP_ATTRIBUTION } from "@openmapx/mobility-core/attribution"; import { snapshotContributors } from "@openmapx/mobility-core/contributors"; import { type ResolvedPlatformEndpoint, resolvePlatformEndpoint } from "../platform-endpoints"; +import { urlCredentialsAsBasicAuth } from "../url-credentials"; import { USER_AGENT } from "../userAgent"; import type { OverpassResponse } from "./types"; @@ -127,13 +128,17 @@ function overpassErrorPageError(text: string): Error { const OVERPASS_REQUEST_TIMEOUT_MS = 35_000; async function fetchOverpass(url: string, query: string): Promise { - const contributors = snapshotContributors(captureSupplier(url)); + // An operator's `https://user:pass@host` goes as Basic auth: fetch refuses + // a URL carrying credentials and names the whole URL in its error. + const target = urlCredentialsAsBasicAuth(url); + const contributors = snapshotContributors(captureSupplier(target.url)); try { - const res = await fetch(url, { + const res = await fetch(target.url, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", "User-Agent": USER_AGENT, + ...(target.authorization ? { Authorization: target.authorization } : {}), }, body: `data=${encodeURIComponent(query)}`, signal: AbortSignal.timeout(OVERPASS_REQUEST_TIMEOUT_MS),