fix: cli proxy config updates (#6846)

* fix: cli `proxy config` updates

* fix: review comment fixes
This commit is contained in:
lohit
2026-01-19 15:28:20 +00:00
committed by GitHub
parent 4f75474c87
commit 6642f4d0b0
5 changed files with 446 additions and 63 deletions

View File

@@ -22,7 +22,7 @@ const { createFormData } = require('../utils/form-data');
const protocolRegex = /^([-+\w]{1,25})(:?\/\/|:)/;
const { NtlmClient } = require('axios-ntlm');
const { addDigestInterceptor } = require('@usebruno/requests');
const { getCACertificates } = require('@usebruno/requests');
const { getCACertificates, transformProxyConfig } = require('@usebruno/requests');
const { getOAuth2Token } = require('../utils/oauth2');
const { encodeUrl, buildFormUrlEncodedPayload, extractPromptVariables, isFormData } = require('@usebruno/common').utils;
@@ -288,25 +288,27 @@ const runSingleRequest = async function (
let proxyMode = 'off';
let proxyConfig = {};
const collectionProxyConfig = get(brunoConfig, 'proxy', {});
const collectionProxyEnabled = get(collectionProxyConfig, 'enabled', false);
const collectionProxyConfig = transformProxyConfig(get(brunoConfig, 'proxy', {}));
const collectionProxyDisabled = get(collectionProxyConfig, 'disabled', false);
const collectionProxyInherit = get(collectionProxyConfig, 'inherit', true);
const collectionProxyConfigData = get(collectionProxyConfig, 'config', {});
if (noproxy) {
// If noproxy flag is set, don't use any proxy
if (noproxy || collectionProxyDisabled) {
// If noproxy flag is set or collection proxy is disabled, don't use any proxy
proxyMode = 'off';
} else if (collectionProxyEnabled === true) {
// If collection proxy is enabled, use it
proxyConfig = collectionProxyConfig;
} else if (!collectionProxyDisabled && !collectionProxyInherit) {
// Use collection-specific proxy
proxyConfig = collectionProxyConfigData;
proxyMode = 'on';
} else if (collectionProxyEnabled === 'global') {
// If collection proxy is set to 'global', use system proxy
} else if (!collectionProxyDisabled && collectionProxyInherit) {
// Inherit from system proxy
const { http_proxy, https_proxy } = getSystemProxyEnvVariables();
if (http_proxy?.length || https_proxy?.length) {
proxyMode = 'system';
}
} else {
proxyMode = 'off';
// else: no system proxy available, proxyMode stays 'off'
}
// else: collection proxy is disabled, proxyMode stays 'off'
if (proxyMode === 'on') {
const shouldProxy = shouldUseProxy(request.url, get(proxyConfig, 'bypassProxy', ''));
@@ -314,7 +316,7 @@ const runSingleRequest = async function (
const proxyProtocol = interpolateString(get(proxyConfig, 'protocol'), interpolationOptions);
const proxyHostname = interpolateString(get(proxyConfig, 'hostname'), interpolationOptions);
const proxyPort = interpolateString(get(proxyConfig, 'port'), interpolationOptions);
const proxyAuthEnabled = get(proxyConfig, 'auth.enabled', false);
const proxyAuthEnabled = !get(proxyConfig, 'auth.disabled', false);
const socksEnabled = proxyProtocol.includes('socks');
let uriPort = isUndefined(proxyPort) || isNull(proxyPort) ? '' : `:${proxyPort}`;
let proxyUri;

View File

@@ -1,6 +1,6 @@
const path = require('path');
const { isFile, isDirectory } = require('./filesystem');
const { get } = require('lodash');
const { transformProxyConfig } = require('@usebruno/requests');
function transformBrunoConfigBeforeSave(brunoConfig) {
// remove exists from importPaths and protoFiles
@@ -76,55 +76,7 @@ async function transformBrunoConfigAfterRead(brunoConfig, collectionPathname) {
// Migrate proxy configuration from old format to new format
if (brunoConfig.proxy) {
const proxy = brunoConfig.proxy || {};
// Check if this is an old format (has 'enabled' property)
if (proxy.hasOwnProperty('enabled')) {
const enabled = proxy.enabled;
let newProxy = {
inherit: true,
config: {
protocol: proxy.protocol || 'http',
hostname: proxy.hostname || '',
port: proxy.port || null,
auth: {
username: get(proxy, 'auth.username', ''),
password: get(proxy, 'auth.password', '')
},
bypassProxy: proxy.bypassProxy || ''
}
};
// Handle old format: enabled (true | false | 'global')
if (enabled === true) {
newProxy.disabled = false;
newProxy.inherit = false;
} else if (enabled === false) {
newProxy.disabled = true;
newProxy.inherit = false;
} else if (enabled === 'global') {
newProxy.disabled = false;
newProxy.inherit = true;
}
// Migrate auth.enabled to auth.disabled
if (get(proxy, 'auth.enabled') === false) {
newProxy.config.auth.disabled = true;
}
// If auth.enabled is true or undefined, omit disabled (defaults to false)
// Omit disabled: false at top level (optional field)
if (newProxy.disabled === false) {
delete newProxy.disabled;
}
// Omit auth.disabled: false (optional field)
if (newProxy.config.auth.disabled === false) {
delete newProxy.config.auth.disabled;
}
brunoConfig.proxy = newProxy;
}
brunoConfig.proxy = transformProxyConfig(brunoConfig.proxy);
}
return brunoConfig;

View File

@@ -4,6 +4,7 @@ export { WsClient } from './ws/ws-client';
export { default as cookies } from './cookies';
export { getCACertificates } from './utils/ca-cert';
export { transformProxyConfig } from './utils/proxy-util';
export { default as createVaultClient, VaultError } from './utils/node-vault';
export type { VaultClient, VaultConfig, VaultRequestOptions } from './utils/node-vault';

View File

@@ -0,0 +1,336 @@
import { transformProxyConfig } from './proxy-util';
describe('transformProxyConfig', () => {
describe('Migration from old to new format', () => {
describe('Old Format: enabled (true | false | "global")', () => {
test('should migrate enabled: true to disabled: false, inherit: false', () => {
const oldConfig = {
enabled: true,
protocol: 'http',
hostname: 'proxy.example.com',
port: 8080,
auth: {
enabled: true,
username: 'user',
password: 'pass'
},
bypassProxy: 'localhost'
};
const result = transformProxyConfig(oldConfig);
expect(result).toEqual({
inherit: false,
config: {
protocol: 'http',
hostname: 'proxy.example.com',
port: 8080,
auth: {
username: 'user',
password: 'pass'
},
bypassProxy: 'localhost'
}
});
expect((result as any).disabled).toBeUndefined(); // disabled: false is omitted
});
test('should migrate enabled: false to disabled: true, inherit: false', () => {
const oldConfig = {
enabled: false,
protocol: 'http',
hostname: 'proxy.example.com',
port: 8080,
auth: {
enabled: false,
username: '',
password: ''
},
bypassProxy: ''
};
const result = transformProxyConfig(oldConfig);
expect((result as any).disabled).toBe(true);
expect((result as any).inherit).toBe(false);
});
test('should migrate enabled: "global" to disabled: false, inherit: true', () => {
const oldConfig = {
enabled: 'global' as const,
protocol: 'http',
hostname: '',
port: null,
auth: {
enabled: false,
username: '',
password: ''
},
bypassProxy: ''
};
const result = transformProxyConfig(oldConfig);
expect((result as any).disabled).toBeUndefined(); // disabled: false is omitted
expect((result as any).inherit).toBe(true);
});
test('should migrate auth.enabled: false to auth.disabled: true', () => {
const oldConfig = {
enabled: true,
protocol: 'http',
hostname: 'proxy.example.com',
port: 8080,
auth: {
enabled: false,
username: 'user',
password: 'pass'
},
bypassProxy: ''
};
const result = transformProxyConfig(oldConfig);
expect((result as any).config.auth.disabled).toBe(true);
expect((result as any).config.auth.username).toBe('user');
expect((result as any).config.auth.password).toBe('pass');
});
test('should omit auth.disabled when auth.enabled: true', () => {
const oldConfig = {
enabled: true,
protocol: 'http',
hostname: 'proxy.example.com',
port: 8080,
auth: {
enabled: true,
username: 'user',
password: 'pass'
},
bypassProxy: ''
};
const result = transformProxyConfig(oldConfig);
expect((result as any).config.auth.disabled).toBeUndefined();
expect((result as any).config.auth.username).toBe('user');
expect((result as any).config.auth.password).toBe('pass');
});
});
describe('New Format (no migration)', () => {
test('should not modify new format with inherit: false', () => {
const newConfig = {
inherit: false,
config: {
protocol: 'https',
hostname: 'proxy.example.com',
port: 8443,
auth: {
username: 'user',
password: 'pass'
},
bypassProxy: '*.local'
}
};
const result = transformProxyConfig(newConfig);
expect(result).toEqual(newConfig);
});
test('should not modify new format with inherit: true', () => {
const newConfig = {
inherit: true,
config: {
protocol: 'http',
hostname: '',
port: null,
auth: {
username: '',
password: ''
},
bypassProxy: ''
}
};
const result = transformProxyConfig(newConfig);
expect(result).toEqual(newConfig);
});
test('should not modify new format with disabled: true', () => {
const newConfig = {
disabled: true,
inherit: false,
config: {
protocol: 'http',
hostname: '',
port: null,
auth: {
username: '',
password: ''
},
bypassProxy: ''
}
};
const result = transformProxyConfig(newConfig);
expect(result).toEqual(newConfig);
});
test('should not modify new format with auth.disabled: true', () => {
const newConfig = {
inherit: false,
config: {
protocol: 'http',
hostname: 'proxy.example.com',
port: 8080,
auth: {
disabled: true,
username: 'user',
password: 'pass'
},
bypassProxy: ''
}
};
const result = transformProxyConfig(newConfig);
expect(result).toEqual(newConfig);
});
});
describe('Edge Cases', () => {
test('should handle missing/null/undefined proxy config', () => {
expect(transformProxyConfig(null)).toEqual({});
expect(transformProxyConfig(undefined)).toEqual({});
expect(transformProxyConfig({})).toEqual({});
});
test('should handle null port values', () => {
const oldConfig = {
enabled: true,
protocol: 'http',
hostname: 'proxy.example.com',
port: null,
auth: {
enabled: false,
username: '',
password: ''
},
bypassProxy: ''
};
const result = transformProxyConfig(oldConfig);
expect((result as any).config.port).toBeNull();
});
test('should handle SOCKS protocols', () => {
const oldConfig = {
enabled: true,
protocol: 'socks5',
hostname: 'socks.example.com',
port: 1080,
auth: {
enabled: true,
username: 'socksuser',
password: 'sockspass'
},
bypassProxy: ''
};
const result = transformProxyConfig(oldConfig);
expect((result as any).config.protocol).toBe('socks5');
expect((result as any).config.hostname).toBe('socks.example.com');
expect((result as any).config.port).toBe(1080);
});
test('should handle missing auth object', () => {
const oldConfig = {
enabled: true,
protocol: 'http',
hostname: 'proxy.example.com',
port: 8080,
bypassProxy: ''
};
const result = transformProxyConfig(oldConfig);
expect((result as any).config.auth).toEqual({
username: '',
password: ''
});
});
test('should handle missing protocol (defaults to http)', () => {
const oldConfig = {
enabled: true,
hostname: 'proxy.example.com',
port: 8080
};
const result = transformProxyConfig(oldConfig);
expect((result as any).config.protocol).toBe('http');
});
test('should handle missing hostname (defaults to empty string)', () => {
const oldConfig = {
enabled: true,
protocol: 'http',
port: 8080
};
const result = transformProxyConfig(oldConfig);
expect((result as any).config.hostname).toBe('');
});
test('should handle missing port (defaults to null)', () => {
const oldConfig = {
enabled: true,
protocol: 'http',
hostname: 'proxy.example.com'
};
const result = transformProxyConfig(oldConfig);
expect((result as any).config.port).toBeNull();
});
test('should handle missing bypassProxy (defaults to empty string)', () => {
const oldConfig = {
enabled: true,
protocol: 'http',
hostname: 'proxy.example.com',
port: 8080
};
const result = transformProxyConfig(oldConfig);
expect((result as any).config.bypassProxy).toBe('');
});
test('should handle auth with missing username/password', () => {
const oldConfig = {
enabled: true,
protocol: 'http',
hostname: 'proxy.example.com',
port: 8080,
auth: {
enabled: true
}
};
const result = transformProxyConfig(oldConfig);
expect((result as any).config.auth.username).toBe('');
expect((result as any).config.auth.password).toBe('');
});
});
});
});

View File

@@ -0,0 +1,92 @@
/**
* Transform proxy config from old format to new format.
* Old format: { enabled: true | false | 'global', protocol, hostname, port, auth: { enabled, ... }, ... }
* New format: { disabled?, inherit, config: { protocol, hostname, port, auth: { disabled?, ... }, ... } }
*/
interface OldProxyAuth {
enabled?: boolean;
username?: string;
password?: string;
}
interface OldProxyConfig {
enabled?: true | false | 'global';
protocol?: string;
hostname?: string;
port?: number | null;
auth?: OldProxyAuth;
bypassProxy?: string;
}
interface NewProxyAuth {
disabled?: boolean;
username?: string;
password?: string;
}
interface NewProxyConfig {
disabled?: boolean;
inherit: boolean;
config: {
protocol: string;
hostname: string;
port: number | null;
auth: NewProxyAuth;
bypassProxy: string;
};
}
export const transformProxyConfig = (proxy: OldProxyConfig | NewProxyConfig | null | undefined): NewProxyConfig | OldProxyConfig => {
proxy = proxy || {};
// Check if this is an old format (has 'enabled' property)
if (proxy.hasOwnProperty('enabled')) {
const oldProxy = proxy as OldProxyConfig;
const enabled = oldProxy.enabled;
const newProxy: NewProxyConfig = {
inherit: true,
config: {
protocol: oldProxy.protocol || 'http',
hostname: oldProxy.hostname || '',
port: oldProxy.port || null,
auth: {
username: oldProxy.auth?.username || '',
password: oldProxy.auth?.password || ''
},
bypassProxy: oldProxy.bypassProxy || ''
}
};
// Handle old format: enabled (true | false | 'global')
if (enabled === true) {
newProxy.disabled = false;
newProxy.inherit = false;
} else if (enabled === false) {
newProxy.disabled = true;
newProxy.inherit = false;
} else if (enabled === 'global') {
newProxy.disabled = false;
newProxy.inherit = true;
}
// Migrate auth.enabled to auth.disabled
if (oldProxy.auth?.enabled === false) {
newProxy.config.auth.disabled = true;
}
// If auth.enabled is true or undefined, omit disabled (defaults to false)
// Omit disabled: false at top level (optional field)
if (newProxy.disabled === false) {
delete newProxy.disabled;
}
// Omit auth.disabled: false (optional field)
if (newProxy.config.auth.disabled === false) {
delete newProxy.config.auth.disabled;
}
return newProxy;
}
return proxy;
};