Skip to content
Open
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
77 changes: 77 additions & 0 deletions src/features/web2/proxy/Proxy.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Importing Proxy pulls the node runtime in (SharedState -> chain -> logger ->

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Regression Tests Are Not Discovered

The repository's test script uses --testMatch '**/tests/**/*.ts', which does not match this file under src/features/web2/proxy/. The normal test workflow therefore skips these cases, so an authorization-header regression can pass without failing the configured suite.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/features/web2/proxy/Proxy.test.ts
Line: 1

Comment:
**Regression Tests Are Not Discovered**

The repository's test script uses `--testMatch '**/tests/**/*.ts'`, which does not match this file under `src/features/web2/proxy/`. The normal test workflow therefore skips these cases, so an authorization-header regression can pass without failing the configured suite.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code

// PeerManager). Both config objects are passed explicitly below, so none of it
// is actually exercised — stub the modules so the suite stays a unit test.
jest.mock("@/utilities/sharedState", () => ({
__esModule: true,
default: { getInstance: () => ({ PROD: false }) },
}))
jest.mock("@/utilities/logger", () => ({
__esModule: true,
default: { error: jest.fn(), info: jest.fn(), warn: jest.fn(), debug: jest.fn() },
}))
jest.mock("src/libs/crypto/hashing", () => ({
__esModule: true,
default: { sha256: (v: string) => v },
}))

import { Proxy } from "./Proxy"

/**
* Build a Proxy with both config objects supplied so construction never reaches
* SharedState — these tests are about header shaping, not runtime environment.
*/
function makeProxy(requireAuthForAll: boolean) {
return new Proxy(
"test-session-id",
"localhost",
{ requireAuthForAll, exceptions: [] },
{ verifyCertificates: false },
)
}

/** `createHeaders` is private; TS visibility is compile-time only. */
function headersFor(
proxy: Proxy,
targetAuthorization: string,
): Record<string, string> {
return (proxy as any).createHeaders(
"GET",
{},
targetAuthorization,
) as Record<string, string>
}

describe("Proxy outbound Authorization header", () => {
it("is omitted when the caller supplied no token, even in production", () => {
// The regression: `requireAuthForAll` is true on production, and the
// outbound header used to be keyed on it. Every proxied request then
// carried `Bearer undefined`, which GitHub rejects (401 on
// api.github.com, 404 on raw.githubusercontent.com) while permissive
// targets like httpbin ignore it — hence "DAHR works but GitHub 401s".
const headers = headersFor(makeProxy(true), "")

expect(headers).not.toHaveProperty("Authorization")
expect(Object.values(headers).join(" ")).not.toContain("undefined")
})

it("forwards the token the caller did supply", () => {
const headers = headersFor(makeProxy(true), "ghp_realtoken")

expect(headers["Authorization"]).toBe("Bearer ghp_realtoken")
})

it("forwards a supplied token off production too, rather than dropping it", () => {
// Keying the header on the inbound-access flag also meant a token
// passed in development was silently discarded.
const headers = headersFor(makeProxy(false), "ghp_realtoken")

expect(headers["Authorization"]).toBe("Bearer ghp_realtoken")
})

it("still stamps the session id that gates inbound proxy access", () => {
// The inbound control must be untouched by the outbound change.
expect(headersFor(makeProxy(true), "")["x-dahr-session-id"]).toBe(
"test-session-id",
)
})
})
27 changes: 8 additions & 19 deletions src/features/web2/proxy/Proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ export class Proxy {
targetMethod,
targetHeaders,
targetAuthorization,
targetUrl,
)

const req = http.request({
Expand Down Expand Up @@ -429,7 +428,6 @@ export class Proxy {
targetMethod: Web2Method,
targetHeaders: IWeb2Request["raw"]["headers"],
targetAuthorization: string,
targetUrl: string,
): IWeb2Request["raw"]["headers"] {
// Base headers - only essential ones
const headers: IWeb2Request["raw"]["headers"] = {
Expand Down Expand Up @@ -461,8 +459,14 @@ export class Proxy {
headers["Accept-Encoding"] = "identity"
}

// Add Authorization if required
if (this.requiresAuthorization(targetUrl, targetMethod)) {
// Only forward an Authorization the caller actually supplied.
// `requireAuthForAll` governs INBOUND access to this proxy (the
// x-dahr-session-id check in isAuthorizedRequest) and says nothing
// about what the target site should receive. Keying the outbound
// header on it stamps `Bearer undefined` onto every proxied request,
// which any site that validates the header rejects — GitHub answers
// 401 on api.github.com and 404 on raw.githubusercontent.com.
if (targetAuthorization) {
headers["Authorization"] = `Bearer ${targetAuthorization}`
}

Expand Down Expand Up @@ -511,19 +515,4 @@ export class Proxy {
entries.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0))
return entries.map(e => `${e.key}:${e.value}`).join("\n")
}

private requiresAuthorization(url: string, method: Web2Method): boolean {
if (this._authConfig.requireAuthForAll) {
for (const exception of this._authConfig.exceptions) {
if (
exception.urlPattern.test(url) &&
exception.methods.includes(method)
) {
return false
}
}
return true
}
return false
}
}
Loading