// Challenge verification: probes the worker's advertise URL to prove it // serves the session secret it registered with. import { proxySecretHeaders, type Transport } from "./proxy.js"; import { discardResponseBody, fetchHeld, readResponseJson } from "./http.js"; export interface ProbeFailure { reason: "unreachable" | "status" | "incomplete" | "mismatch"; status?: number; error: string; } export const verifyChallenge = async ( fetchImpl: Transport.FetchLike, advertiseUrl: string, secret: string, challenge: string, challengeValue: string, timeoutMs: number, ): Promise => { let res: Response; try { res = await fetchHeld( fetchImpl, new Request( `${advertiseUrl}/_pinned/challenge?challenge=${encodeURIComponent(challenge)}`, { headers: proxySecretHeaders(secret), redirect: "manual", signal: AbortSignal.timeout(timeoutMs), }, ), ); } catch { return { reason: "unreachable", error: "challenge verification failed: advertise URL unreachable", }; } if (!res.ok) { await discardResponseBody(res).catch(() => undefined); return { reason: "status", status: res.status, error: `challenge verification failed: worker answered ${res.status}`, }; } // fetch resolves on headers; the body can still stall or drop. Only a // delivered JSON value proves the URL answered someone else's challenge. let body: { value?: unknown } | null; try { body = (await readResponseJson(res)) as { value?: unknown } | null; } catch (err) { const cause = err instanceof Error ? err.name : "Error"; return { reason: "incomplete", error: `challenge verification failed: response body not received (${cause})`, }; } if (body?.value !== challengeValue) { return { reason: "mismatch", error: "challenge verification failed: value mismatch", }; } return null; };