Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/lib/auth/cimd-fetch-policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,15 @@ export async function allowCimdMetadataFetch(url: string) {
* Cloudflare's `global_fetch_strictly_public` compatibility flag enforces the
* resolve-once, public-address-only network boundary required by CIMD. Keeping
* the transport in application code also prevents the auth package from
* selecting a non-Worker fetch implementation.
* selecting a non-Worker fetch implementation. Workers do not implement
* `redirect: "error"`, so use `manual`; CIMD still rejects every non-200
* response before reading metadata.
*/
export const fetchCimdMetadataResource: ClientMetadataResourceFetch = (
url,
init,
) => fetch(url, init);
) =>
fetch(
url,
init?.redirect === "error" ? { ...init, redirect: "manual" } : init,
);
35 changes: 33 additions & 2 deletions tests/unit/cimd-fetch-policy.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { resolve4, resolve6 } from "node:dns/promises";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { allowCimdMetadataFetch } from "@/lib/auth/cimd-fetch-policy";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
allowCimdMetadataFetch,
fetchCimdMetadataResource,
} from "@/lib/auth/cimd-fetch-policy";

vi.mock("node:dns/promises", () => ({
resolve4: vi.fn(),
Expand All @@ -16,6 +19,34 @@ describe("CIMD metadata fetch policy", () => {
resolve6Mock.mockReset();
});

afterEach(() => {
vi.unstubAllGlobals();
});

it("adapts redirect rejection to the mode supported by Workers", async () => {
const response = new Response(null, { status: 200 });
const fetchMock = vi.fn().mockResolvedValue(response);
vi.stubGlobal("fetch", fetchMock);
const signal = new AbortController().signal;

await expect(
fetchCimdMetadataResource("https://client.example/oauth.json", {
headers: { accept: "application/json" },
redirect: "error",
signal,
}),
).resolves.toBe(response);

expect(fetchMock).toHaveBeenCalledWith(
"https://client.example/oauth.json",
{
headers: { accept: "application/json" },
redirect: "manual",
signal,
},
);
});

it("accepts hostnames only when every resolved address is public", async () => {
resolve4Mock.mockResolvedValue(["203.0.113.10"]);
resolve6Mock.mockResolvedValue(["2606:4700:4700::1111"]);
Expand Down