From 58f471cbb7235d045256de9305504dbb95b985ae Mon Sep 17 00:00:00 2001 From: Florian Date: Fri, 30 Oct 2026 10:38:30 +0100 Subject: [PATCH] fix(overpass): send OVERPASS_URL credentials as Basic auth fetch refuses a URL carrying credentials, so queries to an OVERPASS_URL like https://user:pass@host always failed, with the password in the error. Send them as an Authorization header through the shared core helper. --- packages/core/src/utils/overpass/client.test.ts | 15 +++++++++++++++ packages/core/src/utils/overpass/client.ts | 9 +++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) 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),