Skip to content

Commit

Permalink
fix: consistent proxy auth handling (#146)
Browse files Browse the repository at this point in the history
Extracts and reuses the `getBasic` helper function. 

Uses this helper to generate the `Basic` authentication header in the
`proxy` hooks (before, this was done further down the line by
[`http2-wrapper`](https://github.com/szmarczak/http2-wrapper), see the
[related issue](szmarczak/http2-wrapper#108)).
  • Loading branch information
barjin authored Oct 23, 2024
1 parent 5d36db9 commit eea94e5
Show file tree
Hide file tree
Showing 7 changed files with 43 additions and 24 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
4.0.7 / 2024/10/23
====================
- Handles proxy authentication consistently throughout the codebase (solves e.g. this [`http2-wrapper`](https://github.com/szmarczak/http2-wrapper/issues/108) issue).

4.0.6 / 2024/05/22
====================
- Logging `CONNECT` error response body instead of the length only
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "got-scraping",
"version": "4.0.6",
"version": "4.0.7",
"description": "HTTP client made for scraping based on got.",
"engines": {
"node": ">=16"
Expand Down
19 changes: 3 additions & 16 deletions src/agent/h1-proxy-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import https from 'node:https';
import { isIPv6 } from 'node:net';
import tls, { type ConnectionOptions } from 'node:tls';
import { URL } from 'node:url';
import { buildBasicAuthHeader } from '../auth.js';

interface AgentOptions extends http.AgentOptions {
proxy: string | URL;
Expand All @@ -30,20 +31,6 @@ const getPort = (url: URL): number => {
throw new Error(`Unexpected protocol: ${url.protocol}`);
};

const getBasic = (url: URL): string => {
let basic = '';
if (url.username || url.password) {
const username = decodeURIComponent(url.username);
const password = decodeURIComponent(url.password);

basic = Buffer.from(`${username}:${password}`).toString('base64');

return `Basic ${basic}`;
}

return basic;
};

export class HttpRegularProxyAgent extends http.Agent {
proxy!: URL;

Expand Down Expand Up @@ -76,7 +63,7 @@ export class HttpRegularProxyAgent extends http.Agent {

request.path = url.href;

const basic = getBasic(this.proxy);
const basic = buildBasicAuthHeader(this.proxy);
if (basic) {
request.setHeader('proxy-authorization', basic);
}
Expand Down Expand Up @@ -114,7 +101,7 @@ export class HttpProxyAgent extends http.Agent {
host: hostport,
};

const basic = getBasic(this.proxy);
const basic = buildBasicAuthHeader(this.proxy);
if (basic) {
headers['proxy-authorization'] = basic;
headers.authorization = basic;
Expand Down
18 changes: 18 additions & 0 deletions src/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Returns the Basic auth string based on the `username` and `password` parts of the given URL.
* If the URL does not contain neither username nor password, returns `null`.
* @param url URL object to process
* @returns `Basic BASE64` string
*/
export function buildBasicAuthHeader(url: URL): string | null {
if (!url.username && !url.password) {
return null;
}

const username = decodeURIComponent(url.username ?? '');
const password = decodeURIComponent(url.password ?? '');

const basic = Buffer.from(`${username}:${password}`).toString('base64');

return `Basic ${basic}`;
}
10 changes: 10 additions & 0 deletions src/hooks/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import http2, { auto } from 'http2-wrapper';
import { URL } from 'node:url';
import { HttpProxyAgent, HttpRegularProxyAgent, HttpsProxyAgent } from '../agent/h1-proxy-agent.js';
import { TransformHeadersAgent } from '../agent/transform-headers-agent.js';
import { buildBasicAuthHeader } from '../auth.js';

const {
HttpOverHttp2,
Expand Down Expand Up @@ -37,10 +38,19 @@ function validateProxyProtocol(protocol: string) {
async function getAgents(parsedProxyUrl: URL, rejectUnauthorized: boolean) {
// Sockets must not be reused, the proxy server may rotate upstream proxies as well.

const headers: Record<string, string> = {};
const basic = buildBasicAuthHeader(parsedProxyUrl);

if (basic) {
headers.authorization = basic;
headers['proxy-authorization'] = basic;
}

// `http2-wrapper` Agent options
const wrapperOptions = {
proxyOptions: {
url: parsedProxyUrl,
headers,

// Based on the got https.rejectUnauthorized option
rejectUnauthorized,
Expand Down
10 changes: 5 additions & 5 deletions src/resolve-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { type Headers } from 'got';
import { auto, type ResolveProtocolConnectFunction, type ResolveProtocolFunction } from 'http2-wrapper';
import QuickLRU from 'quick-lru';
import { ProxyError } from './hooks/proxy.js';
import { buildBasicAuthHeader } from './auth.js';

const connect = async (proxyUrl: string, options: tls.ConnectionOptions, callback: () => void) => new Promise<TLSSocket>((resolve, reject) => {
let host = `${options.host}:${options.port}`;
Expand All @@ -20,12 +21,11 @@ const connect = async (proxyUrl: string, options: tls.ConnectionOptions, callbac
};

const url = new URL(proxyUrl);
const username = decodeURIComponent(url.username);
const password = decodeURIComponent(url.password);
const basic = buildBasicAuthHeader(url);

if (username || password) {
headers.authorization = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
headers['proxy-authorization'] = headers.authorization;
if (basic) {
headers.authorization = basic;
headers['proxy-authorization'] = basic;
}

const request = await auto(url, {
Expand Down

0 comments on commit eea94e5

Please sign in to comment.