From f1c194dedfff5ac2b25b25b4a601ebf9ec2c3964 Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Thu, 2 Dec 2021 15:38:36 +0200 Subject: [PATCH] fix(audit): audit should work with a proxy (#4057) close #3755 --- .changeset/brown-spies-stare.md | 5 ++++ .changeset/giant-toes-notice.md | 5 ++++ .changeset/hungry-dragons-wait.md | 5 ++++ packages/audit/src/index.ts | 6 ++-- packages/fetch/src/fetchFromRegistry.ts | 28 ++++++++++++++---- packages/fetch/src/index.ts | 3 +- packages/plugin-commands-audit/src/audit.ts | 32 ++++++++++++++++++++- 7 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 .changeset/brown-spies-stare.md create mode 100644 .changeset/giant-toes-notice.md create mode 100644 .changeset/hungry-dragons-wait.md diff --git a/.changeset/brown-spies-stare.md b/.changeset/brown-spies-stare.md new file mode 100644 index 0000000000..20389c786c --- /dev/null +++ b/.changeset/brown-spies-stare.md @@ -0,0 +1,5 @@ +--- +"@pnpm/audit": minor +--- + +Added options for the HTTP agent. diff --git a/.changeset/giant-toes-notice.md b/.changeset/giant-toes-notice.md new file mode 100644 index 0000000000..f824c65431 --- /dev/null +++ b/.changeset/giant-toes-notice.md @@ -0,0 +1,5 @@ +--- +"@pnpm/fetch": minor +--- + +Add `fetchWithAgent()`. diff --git a/.changeset/hungry-dragons-wait.md b/.changeset/hungry-dragons-wait.md new file mode 100644 index 0000000000..9da7b39270 --- /dev/null +++ b/.changeset/hungry-dragons-wait.md @@ -0,0 +1,5 @@ +--- +"pnpm": patch +--- + +`pnpm audit` should work when a proxy is configured for the registry [#3755](https://github.com/pnpm/pnpm/issues/3755). diff --git a/packages/audit/src/index.ts b/packages/audit/src/index.ts index f54a151127..339e020f7b 100644 --- a/packages/audit/src/index.ts +++ b/packages/audit/src/index.ts @@ -1,5 +1,5 @@ import PnpmError from '@pnpm/error' -import fetch, { RetryTimeoutOptions } from '@pnpm/fetch' +import { AgentOptions, fetchWithAgent, RetryTimeoutOptions } from '@pnpm/fetch' import { Lockfile } from '@pnpm/lockfile-types' import { DependenciesField } from '@pnpm/types' import lockfileToAuditTree from './lockfileToAuditTree' @@ -10,6 +10,7 @@ export * from './types' export default async function audit ( lockfile: Lockfile, opts: { + agentOptions?: AgentOptions include?: { [dependenciesField in DependenciesField]: boolean } registry: string retry?: RetryTimeoutOptions @@ -19,7 +20,8 @@ export default async function audit ( const auditTree = lockfileToAuditTree(lockfile, { include: opts.include }) const registry = opts.registry.endsWith('/') ? opts.registry : `${opts.registry}/` const auditUrl = `${registry}-/npm/v1/security/audits` - const res = await fetch(auditUrl, { + const res = await fetchWithAgent(auditUrl, { + agentOptions: opts.agentOptions ?? {}, body: JSON.stringify(auditTree), headers: { 'Content-Type': 'application/json' }, method: 'post', diff --git a/packages/fetch/src/fetchFromRegistry.ts b/packages/fetch/src/fetchFromRegistry.ts index 5f61f79f6d..6c21cdb336 100644 --- a/packages/fetch/src/fetchFromRegistry.ts +++ b/packages/fetch/src/fetchFromRegistry.ts @@ -1,7 +1,7 @@ import { URL } from 'url' import { FetchFromRegistry } from '@pnpm/fetching-types' import npmRegistryAgent, { AgentOptions } from '@pnpm/npm-registry-agent' -import fetch, { isRedirect, Response } from './fetch' +import fetch, { isRedirect, Response, RequestInfo, RequestInit } from './fetch' const USER_AGENT = 'pnpm' // or maybe make it `${pkg.name}/${pkg.version} (+https://npm.im/${pkg.name})` @@ -9,6 +9,23 @@ const CORGI_DOC = 'application/vnd.npm.install-v1+json; q=1.0, application/json; const JSON_DOC = 'application/json' const MAX_FOLLOWED_REDIRECTS = 20 +export type FetchWithAgentOptions = RequestInit & { + agentOptions: AgentOptions +} + +export function fetchWithAgent (url: RequestInfo, opts: FetchWithAgentOptions) { + const agent = npmRegistryAgent(url.toString(), { + ...opts.agentOptions, + strictSsl: opts.agentOptions.strictSsl ?? true, + } as any) // eslint-disable-line + const headers = opts.headers ?? {} + headers['connection'] = agent ? 'keep-alive' : 'close' + return fetch(url, { + ...opts, + agent, + }) +} + export { AgentOptions } export default function ( @@ -31,17 +48,16 @@ export default function ( let urlObject = new URL(url) const originalHost = urlObject.host while (true) { - const agent = npmRegistryAgent(urlObject.href, { + const agentOptions = { ...defaultOpts, ...opts, strictSsl: defaultOpts.strictSsl ?? true, - } as any) // eslint-disable-line - headers['connection'] = agent ? 'keep-alive' : 'close' + } as any // eslint-disable-line // We should pass a URL object to node-fetch till this is not resolved: // https://github.com/bitinn/node-fetch/issues/245 - const response = await fetch(urlObject, { - agent, + const response = await fetchWithAgent(urlObject, { + agentOptions, // if verifying integrity, node-fetch must not decompress compress: opts?.compress ?? false, headers, diff --git a/packages/fetch/src/index.ts b/packages/fetch/src/index.ts index 7fbd94844a..d6c36274b8 100644 --- a/packages/fetch/src/index.ts +++ b/packages/fetch/src/index.ts @@ -1,11 +1,12 @@ import { FetchFromRegistry } from '@pnpm/fetching-types' import fetch, { RetryTimeoutOptions } from './fetch' -import createFetchFromRegistry, { AgentOptions } from './fetchFromRegistry' +import createFetchFromRegistry, { fetchWithAgent, AgentOptions } from './fetchFromRegistry' export default fetch export { AgentOptions, createFetchFromRegistry, + fetchWithAgent, FetchFromRegistry, RetryTimeoutOptions, } diff --git a/packages/plugin-commands-audit/src/audit.ts b/packages/plugin-commands-audit/src/audit.ts index 4e4585cc40..2a89ac12ea 100644 --- a/packages/plugin-commands-audit/src/audit.ts +++ b/packages/plugin-commands-audit/src/audit.ts @@ -106,7 +106,25 @@ export async function handler ( json?: boolean lockfileDir?: string registries: Registries - } & Pick + } & Pick ) { const lockfile = await readWantedLockfile(opts.lockfileDir ?? opts.dir, { ignoreIncompatible: true }) if (lockfile == null) { @@ -120,6 +138,18 @@ export async function handler ( let auditReport!: AuditReport try { auditReport = await audit(lockfile, { + agentOptions: { + ca: opts.ca, + cert: opts.cert, + httpProxy: opts.httpProxy, + httpsProxy: opts.httpsProxy, + key: opts.key, + localAddress: opts.localAddress, + maxSockets: opts.maxSockets, + noProxy: opts.noProxy, + strictSsl: opts.strictSsl, + timeout: opts.fetchTimeout, + }, include, registry: opts.registries.default, retry: {