~ reverting the bruno-electron ipc-network files refactoring work to keep the diff minimal

This commit is contained in:
lohxt1
2025-03-15 21:01:31 +05:30
parent 8cda05c431
commit f99e8770f0
9 changed files with 442 additions and 442 deletions

View File

@@ -37,7 +37,7 @@ const interpolateVars = require('./network/interpolate-vars');
const { getEnvVars, getTreePathFromCollectionToItem, mergeVars } = require('../utils/collection');
const { getProcessEnvVars } = require('../store/process-env');
const { getOAuth2TokenUsingAuthorizationCode, getOAuth2TokenUsingClientCredentials, getOAuth2TokenUsingPasswordCredentials, refreshOauth2Token } = require('../utils/oauth2');
const { configureRequestWithCertsAndProxy } = require('../utils/request');
const { configureRequestWithCertsAndProxy } = require('./network');
const environmentSecretsStore = new EnvironmentSecretsStore();
const collectionSecurityStore = new CollectionSecurityStore();

View File

@@ -0,0 +1,57 @@
const { fromIni } = require('@aws-sdk/credential-providers');
const { aws4Interceptor } = require('aws4-axios');
function isStrPresent(str) {
return str && str !== '' && str !== 'undefined';
}
async function resolveAwsV4Credentials(request) {
const awsv4 = request.awsv4config;
if (isStrPresent(awsv4.profileName)) {
try {
credentialsProvider = fromIni({
profile: awsv4.profileName,
ignoreCache: true
});
credentials = await credentialsProvider();
awsv4.accessKeyId = credentials.accessKeyId;
awsv4.secretAccessKey = credentials.secretAccessKey;
awsv4.sessionToken = credentials.sessionToken;
} catch {
console.error('Failed to fetch credentials from AWS profile.');
}
}
return awsv4;
}
function addAwsV4Interceptor(axiosInstance, request) {
if (!request.awsv4config) {
console.warn('No Auth Config found!');
return;
}
const awsv4 = request.awsv4config;
if (!isStrPresent(awsv4.accessKeyId) || !isStrPresent(awsv4.secretAccessKey)) {
console.warn('Required Auth Fields are not present');
return;
}
const interceptor = aws4Interceptor({
options: {
region: awsv4.region,
service: awsv4.service
},
credentials: {
accessKeyId: awsv4.accessKeyId,
secretAccessKey: awsv4.secretAccessKey,
sessionToken: awsv4.sessionToken
}
});
axiosInstance.interceptors.request.use(interceptor);
}
module.exports = {
addAwsV4Interceptor,
resolveAwsV4Credentials
};

View File

@@ -3,9 +3,9 @@ const Socket = require('net').Socket;
const axios = require('axios');
const connectionCache = new Map(); // Cache to store checkConnection() results
const electronApp = require("electron");
const { setupProxyAgents } = require('./proxy-util');
const { addCookieToJar, getCookieStringForUrl } = require('./cookies');
const { preferencesUtil } = require('../store/preferences');
const { setupProxyAgents } = require('../../utils/proxy-util');
const { addCookieToJar, getCookieStringForUrl } = require('../../utils/cookies');
const { preferencesUtil } = require('../../store/preferences');
const LOCAL_IPV6 = '::1';
const LOCAL_IPV4 = '127.0.0.1';

View File

@@ -1,49 +1,28 @@
const { fromIni } = require('@aws-sdk/credential-providers');
const { aws4Interceptor } = require('aws4-axios');
const crypto = require('crypto');
const { URL } = require('url');
async function resolveAwsV4Credentials(request) {
const awsv4 = request.awsv4config;
if (isStrPresent(awsv4.profileName)) {
try {
credentialsProvider = fromIni({
profile: awsv4.profileName
});
credentials = await credentialsProvider();
awsv4.accessKeyId = credentials.accessKeyId;
awsv4.secretAccessKey = credentials.secretAccessKey;
awsv4.sessionToken = credentials.sessionToken;
} catch {
console.error('Failed to fetch credentials from AWS profile.');
}
}
return awsv4;
function isStrPresent(str) {
return str && str.trim() !== '' && str.trim() !== 'undefined';
}
function addAwsV4Interceptor(axiosInstance, request) {
if (!request.awsv4config) {
console.warn('No Auth Config found!');
return;
}
function stripQuotes(str) {
return str.replace(/"/g, '');
}
const awsv4 = request.awsv4config;
if (!isStrPresent(awsv4.accessKeyId) || !isStrPresent(awsv4.secretAccessKey)) {
console.warn('Required Auth Fields are not present');
return;
}
function containsDigestHeader(response) {
const authHeader = response?.headers?.['www-authenticate'];
return authHeader ? authHeader.trim().toLowerCase().startsWith('digest') : false;
}
const interceptor = aws4Interceptor({
options: {
region: awsv4.region,
service: awsv4.service
},
credentials: {
accessKeyId: awsv4.accessKeyId,
secretAccessKey: awsv4.secretAccessKey,
sessionToken: awsv4.sessionToken
}
});
function containsAuthorizationHeader(originalRequest) {
return Boolean(
originalRequest.headers['Authorization'] ||
originalRequest.headers['authorization']
);
}
axiosInstance.interceptors.request.use(interceptor);
function md5(input) {
return crypto.createHash('md5').update(input).digest('hex');
}
function addDigestInterceptor(axiosInstance, request) {
@@ -144,32 +123,4 @@ function addDigestInterceptor(axiosInstance, request) {
);
}
function containsDigestHeader(response) {
const authHeader = response?.headers?.['www-authenticate'];
return authHeader ? authHeader.trim().toLowerCase().startsWith('digest') : false;
}
function containsAuthorizationHeader(originalRequest) {
return Boolean(
originalRequest.headers['Authorization'] ||
originalRequest.headers['authorization']
);
}
function md5(input) {
return crypto.createHash('md5').update(input).digest('hex');
}
function isStrPresent(str) {
return str && str !== '' && str !== 'undefined';
}
function stripQuotes(str) {
return str.replace(/"/g, '');
}
module.exports = {
addAwsV4Interceptor,
resolveAwsV4Credentials,
addDigestInterceptor
}
module.exports = { addDigestInterceptor };

View File

@@ -3,24 +3,35 @@ const https = require('https');
const axios = require('axios');
const path = require('path');
const decomment = require('decomment');
const iconv = require('iconv-lite');
const fs = require('fs');
const tls = require('tls');
const contentDispositionParser = require('content-disposition');
const mime = require('mime-types');
const FormData = require('form-data');
const { ipcMain } = require('electron');
const { each, get, extend, cloneDeep } = require('lodash');
const { NtlmClient } = require('axios-ntlm');
const { VarsRuntime, AssertRuntime, ScriptRuntime, TestRuntime } = require('@usebruno/js');
const { prepareRequest, prepareGqlIntrospectionRequest, getJsSandboxRuntime, configureRequest, parseDataFromResponse } = require('../../utils/request');
const { interpolateString } = require('./interpolate-string');
const { resolveAwsV4Credentials, addAwsV4Interceptor } = require('./awsv4auth-helper');
const { addDigestInterceptor } = require('./digestauth-helper');
const prepareGqlIntrospectionRequest = require('./prepare-gql-introspection-request');
const { prepareRequest } = require('./prepare-request');
const interpolateVars = require('./interpolate-vars');
const { makeAxiosInstance } = require('./axios-instance');
const { cancelTokens, saveCancelToken, deleteCancelToken } = require('../../utils/cancel-token');
const { uuid, safeStringifyJSON, safeParseJSON } = require('../../utils/common');
const interpolateVars = require('./interpolate-vars');
const { chooseFileToSave, writeBinaryFile, writeFile } = require('../../utils/filesystem');
const { addCookieToJar, getDomainsWithCookies, getCookieStringForUrl } = require('../../utils/cookies');
const { createFormData } = require('../../utils/form-data');
const { findItemInCollectionByPathname, sortFolder, getAllRequestsInFolderRecursively, getEnvVars } = require('../../utils/collection');
const { getOAuth2TokenUsingAuthorizationCode, getOAuth2TokenUsingClientCredentials, getOAuth2TokenUsingPasswordCredentials } = require('../../utils/oauth2');
const { setupProxyAgents } = require('../../utils/proxy-util');
const { preferencesUtil } = require('../../store/preferences');
const { getProcessEnvVars } = require('../../store/process-env');
const { getBrunoConfig } = require('../../store/bruno-config');
const { chooseFileToSave, writeBinaryFile, writeFile } = require('../../utils/filesystem');
const { addCookieToJar, getDomainsWithCookies, getCookieStringForUrl } = require('../../utils/cookies');
const Oauth2Store = require('../../store/oauth2');
const FormData = require('form-data');
const { createFormData } = require('../../utils/form-data');
const { findItemInCollectionByPathname, sortFolder, getAllRequestsInFolderRecursively, getEnvVars } = require('../../utils/collection');
const saveCookies = (url, headers) => {
if (preferencesUtil.shouldStoreCookies()) {
@@ -38,6 +49,293 @@ const saveCookies = (url, headers) => {
}
}
const getJsSandboxRuntime = (collection) => {
const securityConfig = get(collection, 'securityConfig', {});
return securityConfig.jsSandboxMode === 'safe' ? 'quickjs' : 'vm2';
};
const parseDataFromResponse = (response, disableParsingResponseJson = false) => {
// Parse the charset from content type: https://stackoverflow.com/a/33192813
const charsetMatch = /charset=([^()<>@,;:"/[\]?.=\s]*)/i.exec(response.headers['content-type'] || '');
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec#using_exec_with_regexp_literals
const charsetValue = charsetMatch?.[1];
const dataBuffer = Buffer.from(response.data);
// Overwrite the original data for backwards compatibility
let data;
if (iconv.encodingExists(charsetValue)) {
data = iconv.decode(dataBuffer, charsetValue);
} else {
data = iconv.decode(dataBuffer, 'utf-8');
}
// Try to parse response to JSON, this can quietly fail
try {
// Filter out ZWNBSP character
// https://gist.github.com/antic183/619f42b559b78028d1fe9e7ae8a1352d
data = data.replace(/^\uFEFF/, '');
// If the response is a string and starts and ends with double quotes, it's a stringified JSON and should not be parsed
if ( !disableParsingResponseJson && ! (typeof data === 'string' && data.startsWith("\"") && data.endsWith("\""))) {
data = Buffer?.isBuffer(data)? JSON.parse(data?.toString()) : JSON.parse(data);
}
} catch(error) {
console.error(error);
console.log('Failed to parse response data as JSON');
}
return { data, dataBuffer };
};
const configureRequestWithCertsAndProxy = async ({
collectionUid,
request,
envVars,
runtimeVariables,
processEnvVars,
collectionPath
}) => {
/**
* @see https://github.com/usebruno/bruno/issues/211 set keepAlive to true, this should fix socket hang up errors
* @see https://github.com/nodejs/node/pull/43522 keepAlive was changed to true globally on Node v19+
*/
const httpsAgentRequestFields = { keepAlive: true };
if (!preferencesUtil.shouldVerifyTls()) {
httpsAgentRequestFields['rejectUnauthorized'] = false;
}
if (preferencesUtil.shouldUseCustomCaCertificate()) {
const caCertFilePath = preferencesUtil.getCustomCaCertificateFilePath();
if (caCertFilePath) {
let caCertBuffer = fs.readFileSync(caCertFilePath);
if (preferencesUtil.shouldKeepDefaultCaCertificates()) {
caCertBuffer += '\n' + tls.rootCertificates.join('\n'); // Augment default truststore with custom CA certificates
}
httpsAgentRequestFields['ca'] = caCertBuffer;
}
}
const brunoConfig = getBrunoConfig(collectionUid);
const interpolationOptions = {
envVars,
runtimeVariables,
processEnvVars
};
// client certificate config
const clientCertConfig = get(brunoConfig, 'clientCertificates.certs', []);
for (let clientCert of clientCertConfig) {
const domain = interpolateString(clientCert?.domain, interpolationOptions);
const type = clientCert?.type || 'cert';
if (domain) {
const hostRegex = '^https:\\/\\/' + domain.replaceAll('.', '\\.').replaceAll('*', '.*');
if (request.url.match(hostRegex)) {
if (type === 'cert') {
try {
let certFilePath = interpolateString(clientCert?.certFilePath, interpolationOptions);
certFilePath = path.isAbsolute(certFilePath) ? certFilePath : path.join(collectionPath, certFilePath);
let keyFilePath = interpolateString(clientCert?.keyFilePath, interpolationOptions);
keyFilePath = path.isAbsolute(keyFilePath) ? keyFilePath : path.join(collectionPath, keyFilePath);
httpsAgentRequestFields['cert'] = fs.readFileSync(certFilePath);
httpsAgentRequestFields['key'] = fs.readFileSync(keyFilePath);
} catch (err) {
console.error('Error reading cert/key file', err);
throw new Error('Error reading cert/key file' + err);
}
} else if (type === 'pfx') {
try {
let pfxFilePath = interpolateString(clientCert?.pfxFilePath, interpolationOptions);
pfxFilePath = path.isAbsolute(pfxFilePath) ? pfxFilePath : path.join(collectionPath, pfxFilePath);
httpsAgentRequestFields['pfx'] = fs.readFileSync(pfxFilePath);
} catch (err) {
console.error('Error reading pfx file', err);
throw new Error('Error reading pfx file' + err);
}
}
httpsAgentRequestFields['passphrase'] = interpolateString(clientCert.passphrase, interpolationOptions);
break;
}
}
}
/**
* Proxy configuration
*
* Preferences proxyMode has three possible values: on, off, system
* Collection proxyMode has three possible values: true, false, global
*
* When collection proxyMode is true, it overrides the app-level proxy settings
* When collection proxyMode is false, it ignores the app-level proxy settings
* When collection proxyMode is global, it uses the app-level proxy settings
*
* Below logic calculates the proxyMode and proxyConfig to be used for the request
*/
let proxyMode = 'off';
let proxyConfig = {};
const collectionProxyConfig = get(brunoConfig, 'proxy', {});
const collectionProxyEnabled = get(collectionProxyConfig, 'enabled', 'global');
if (collectionProxyEnabled === true) {
proxyConfig = collectionProxyConfig;
proxyMode = 'on';
} else if (collectionProxyEnabled === 'global') {
proxyConfig = preferencesUtil.getGlobalProxyConfig();
proxyMode = get(proxyConfig, 'mode', 'off');
}
setupProxyAgents({
requestConfig: request,
proxyMode,
proxyConfig,
httpsAgentRequestFields,
interpolationOptions
});
return {proxyMode, newRequest: request, proxyConfig, httpsAgentRequestFields, interpolationOptions};
}
const configureRequest = async (
collectionUid,
request,
envVars,
runtimeVariables,
processEnvVars,
collectionPath
) => {
const protocolRegex = /^([-+\w]{1,25})(:?\/\/|:)/;
if (!protocolRegex.test(request.url)) {
request.url = `http://${request.url}`;
}
const {proxyMode, newRequest, proxyConfig, httpsAgentRequestFields, interpolationOptions} = await configureRequestWithCertsAndProxy({
collectionUid,
request,
envVars,
runtimeVariables,
processEnvVars,
collectionPath
});
request = newRequest
let requestMaxRedirects = request.maxRedirects
// Don't override maxRedirects here, let it be controlled by the request object
// request.maxRedirects = 0
// Set default value for requestMaxRedirects if not explicitly set
if (requestMaxRedirects === undefined) {
requestMaxRedirects = 5; // Default to 5 redirects
}
let axiosInstance = makeAxiosInstance({
proxyMode,
proxyConfig,
requestMaxRedirects,
httpsAgentRequestFields,
interpolationOptions
});
if (request.ntlmConfig) {
axiosInstance=NtlmClient(request.ntlmConfig,axiosInstance.defaults)
delete request.ntlmConfig;
}
if (request.oauth2) {
let requestCopy = cloneDeep(request);
const { oauth2: { grantType, tokenPlacement, tokenHeaderPrefix, tokenQueryKey } = {} } = requestCopy || {};
let credentials, credentialsId;
switch (grantType) {
case 'authorization_code':
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars);
({ credentials, url: oauth2Url, credentialsId, debugInfo } = await getOAuth2TokenUsingAuthorizationCode({ request: requestCopy, collectionUid }));
request.oauth2Credentials = { credentials, url: oauth2Url, collectionUid, credentialsId, debugInfo, folderUid: request.oauth2Credentials?.folderUid };
if (tokenPlacement == 'header') {
request.headers['Authorization'] = `${tokenHeaderPrefix} ${credentials?.access_token}`;
}
else {
try {
const url = new URL(request.url);
url?.searchParams?.set(tokenQueryKey, credentials?.access_token);
request.url = url?.toString();
}
catch(error) {}
}
break;
case 'client_credentials':
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars);
({ credentials, url: oauth2Url, credentialsId, debugInfo } = await getOAuth2TokenUsingClientCredentials({ request: requestCopy, collectionUid }));
request.oauth2Credentials = { credentials, url: oauth2Url, collectionUid, credentialsId, debugInfo, folderUid: request.oauth2Credentials?.folderUid };
if (tokenPlacement == 'header') {
request.headers['Authorization'] = `${tokenHeaderPrefix} ${credentials?.access_token}`;
}
else {
try {
const url = new URL(request.url);
url?.searchParams?.set(tokenQueryKey, credentials?.access_token);
request.url = url?.toString();
}
catch(error) {}
}
break;
case 'password':
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars);
({ credentials, url: oauth2Url, credentialsId, debugInfo } = await getOAuth2TokenUsingPasswordCredentials({ request: requestCopy, collectionUid }));
request.oauth2Credentials = { credentials, url: oauth2Url, collectionUid, credentialsId, debugInfo, folderUid: request.oauth2Credentials?.folderUid };
if (tokenPlacement == 'header') {
request.headers['Authorization'] = `${tokenHeaderPrefix} ${credentials?.access_token}`;
}
else {
try {
const url = new URL(request.url);
url?.searchParams?.set(tokenQueryKey, credentials?.access_token);
request.url = url?.toString();
}
catch(error) {}
}
break;
}
}
if (request.awsv4config) {
request.awsv4config = await resolveAwsV4Credentials(request);
addAwsV4Interceptor(axiosInstance, request);
delete request.awsv4config;
}
if (request.digestConfig) {
addDigestInterceptor(axiosInstance, request);
}
request.timeout = preferencesUtil.getRequestTimeout();
// add cookies to request
if (preferencesUtil.shouldSendCookies()) {
const cookieString = getCookieStringForUrl(request.url);
if (cookieString && typeof cookieString === 'string' && cookieString.length) {
request.headers['cookie'] = cookieString;
}
}
// Add API key to the URL
if (request.apiKeyAuthValueForQueryParams && request.apiKeyAuthValueForQueryParams.placement === 'queryparams') {
const urlObj = new URL(request.url);
// Interpolate key and value as they can be variables before adding to the URL.
const key = interpolateString(request.apiKeyAuthValueForQueryParams.key, interpolationOptions);
const value = interpolateString(request.apiKeyAuthValueForQueryParams.value, interpolationOptions);
urlObj.searchParams.set(key, value);
request.url = urlObj.toString();
}
// Remove pathParams, already in URL (Issue #2439)
delete request.pathParams;
// Remove apiKeyAuthValueForQueryParams, already interpolated and added to URL
delete request.apiKeyAuthValueForQueryParams;
return axiosInstance;
};
const registerNetworkIpc = (mainWindow) => {
const onConsoleLog = (type, args) => {
console[type](...args);
@@ -986,3 +1284,4 @@ const registerNetworkIpc = (mainWindow) => {
module.exports = registerNetworkIpc;
module.exports.configureRequest = configureRequest;
module.exports.configureRequestWithCertsAndProxy = configureRequestWithCertsAndProxy;

View File

@@ -0,0 +1,48 @@
const { get, each } = require('lodash');
const { interpolate } = require('@usebruno/common');
const { getIntrospectionQuery } = require('graphql');
const { setAuthHeaders } = require('./prepare-request');
const prepareGqlIntrospectionRequest = (endpoint, envVars, request, collectionRoot) => {
if (endpoint && endpoint.length) {
endpoint = interpolate(endpoint, envVars);
}
const queryParams = {
query: getIntrospectionQuery()
};
let axiosRequest = {
method: 'POST',
url: endpoint,
headers: {
...mapHeaders(request.headers, get(collectionRoot, 'request.headers', [])),
Accept: 'application/json',
'Content-Type': 'application/json'
},
data: JSON.stringify(queryParams)
};
return setAuthHeaders(axiosRequest, request, collectionRoot);
};
const mapHeaders = (requestHeaders, collectionHeaders) => {
const headers = {};
each(requestHeaders, (h) => {
if (h.enabled) {
headers[h.name] = h.value;
}
});
// collection headers
each(collectionHeaders, (h) => {
if (h.enabled) {
headers[h.name] = h.value;
}
});
return headers;
};
module.exports = prepareGqlIntrospectionRequest;

View File

@@ -1,27 +1,8 @@
const https = require('https');
const fs = require('fs');
const path = require('path');
const tls = require('tls');
const { isUndefined, isNull, each, get, cloneDeep, filter } = require('lodash');
const { each, get, filter } = require('lodash');
const decomment = require('decomment');
const crypto = require('node:crypto');
const { getIntrospectionQuery } = require('graphql');
const { HttpProxyAgent } = require('http-proxy-agent');
const { SocksProxyAgent } = require('socks-proxy-agent');
const iconv = require('iconv-lite');
const { interpolate } = require('@usebruno/common');
const { getTreePathFromCollectionToItem, mergeHeaders, mergeScripts, mergeVars, getFormattedCollectionOauth2Credentials, mergeAuth } = require('./collection');
const { buildFormUrlEncodedPayload } = require('./form-data');
const { setupProxyAgents } = require('./proxy-util');
const { makeAxiosInstance } = require('./axios-instance');
const { getOAuth2TokenUsingAuthorizationCode, getOAuth2TokenUsingClientCredentials, getOAuth2TokenUsingPasswordCredentials } = require('./oauth2');
const { resolveAwsV4Credentials, addAwsV4Interceptor, addDigestInterceptor } = require('./auth');
const { getCookieStringForUrl } = require('./cookies');
const { preferencesUtil } = require('../store/preferences');
const { getBrunoConfig } = require('../store/bruno-config');
const { interpolateString } = require('../ipc/network/interpolate-string');
const interpolateVars = require('../ipc/network/interpolate-vars');
const { NtlmClient } = require('axios-ntlm');
const { getTreePathFromCollectionToItem, mergeHeaders, mergeScripts, mergeVars, getFormattedCollectionOauth2Credentials, mergeAuth } = require('../../utils/collection');
const { buildFormUrlEncodedPayload, createFormData } = require('../../utils/form-data');
const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
const collectionAuth = get(collectionRoot, 'request.auth');
@@ -404,343 +385,7 @@ const prepareRequest = (item, collection) => {
return axiosRequest;
};
const prepareGqlIntrospectionRequest = (endpoint, envVars, request, collectionRoot) => {
if (endpoint && endpoint.length) {
endpoint = interpolate(endpoint, envVars);
}
const queryParams = {
query: getIntrospectionQuery()
};
let axiosRequest = {
method: 'POST',
url: endpoint,
headers: {
...mapHeaders(request.headers, get(collectionRoot, 'request.headers', [])),
Accept: 'application/json',
'Content-Type': 'application/json'
},
data: JSON.stringify(queryParams)
};
return setAuthHeaders(axiosRequest, request, collectionRoot);
};
const mapHeaders = (requestHeaders, collectionHeaders) => {
const headers = {};
each(requestHeaders, (h) => {
if (h.enabled) {
headers[h.name] = h.value;
}
});
// collection headers
each(collectionHeaders, (h) => {
if (h.enabled) {
headers[h.name] = h.value;
}
});
return headers;
};
const configureRequestWithCertsAndProxy = async ({
collectionUid,
request,
envVars,
runtimeVariables,
processEnvVars,
collectionPath
}) => {
/**
* @see https://github.com/usebruno/bruno/issues/211 set keepAlive to true, this should fix socket hang up errors
* @see https://github.com/nodejs/node/pull/43522 keepAlive was changed to true globally on Node v19+
*/
const httpsAgentRequestFields = { keepAlive: true };
if (!preferencesUtil.shouldVerifyTls()) {
httpsAgentRequestFields['rejectUnauthorized'] = false;
}
if (preferencesUtil.shouldUseCustomCaCertificate()) {
const caCertFilePath = preferencesUtil.getCustomCaCertificateFilePath();
if (caCertFilePath) {
let caCertBuffer = fs.readFileSync(caCertFilePath);
if (preferencesUtil.shouldKeepDefaultCaCertificates()) {
caCertBuffer += '\n' + tls.rootCertificates.join('\n'); // Augment default truststore with custom CA certificates
}
httpsAgentRequestFields['ca'] = caCertBuffer;
}
}
const brunoConfig = getBrunoConfig(collectionUid);
const interpolationOptions = {
envVars,
runtimeVariables,
processEnvVars
};
// client certificate config
const clientCertConfig = get(brunoConfig, 'clientCertificates.certs', []);
for (let clientCert of clientCertConfig) {
const domain = interpolateString(clientCert?.domain, interpolationOptions);
const type = clientCert?.type || 'cert';
if (domain) {
const hostRegex = '^https:\\/\\/' + domain.replaceAll('.', '\\.').replaceAll('*', '.*');
if (request.url.match(hostRegex)) {
if (type === 'cert') {
try {
let certFilePath = interpolateString(clientCert?.certFilePath, interpolationOptions);
certFilePath = path.isAbsolute(certFilePath) ? certFilePath : path.join(collectionPath, certFilePath);
let keyFilePath = interpolateString(clientCert?.keyFilePath, interpolationOptions);
keyFilePath = path.isAbsolute(keyFilePath) ? keyFilePath : path.join(collectionPath, keyFilePath);
httpsAgentRequestFields['cert'] = fs.readFileSync(certFilePath);
httpsAgentRequestFields['key'] = fs.readFileSync(keyFilePath);
} catch (err) {
console.error('Error reading cert/key file', err);
throw new Error('Error reading cert/key file' + err);
}
} else if (type === 'pfx') {
try {
let pfxFilePath = interpolateString(clientCert?.pfxFilePath, interpolationOptions);
pfxFilePath = path.isAbsolute(pfxFilePath) ? pfxFilePath : path.join(collectionPath, pfxFilePath);
httpsAgentRequestFields['pfx'] = fs.readFileSync(pfxFilePath);
} catch (err) {
console.error('Error reading pfx file', err);
throw new Error('Error reading pfx file' + err);
}
}
httpsAgentRequestFields['passphrase'] = interpolateString(clientCert.passphrase, interpolationOptions);
break;
}
}
}
/**
* Proxy configuration
*
* Preferences proxyMode has three possible values: on, off, system
* Collection proxyMode has three possible values: true, false, global
*
* When collection proxyMode is true, it overrides the app-level proxy settings
* When collection proxyMode is false, it ignores the app-level proxy settings
* When collection proxyMode is global, it uses the app-level proxy settings
*
* Below logic calculates the proxyMode and proxyConfig to be used for the request
*/
let proxyMode = 'off';
let proxyConfig = {};
const collectionProxyConfig = get(brunoConfig, 'proxy', {});
const collectionProxyEnabled = get(collectionProxyConfig, 'enabled', 'global');
if (collectionProxyEnabled === true) {
proxyConfig = collectionProxyConfig;
proxyMode = 'on';
} else if (collectionProxyEnabled === 'global') {
proxyConfig = preferencesUtil.getGlobalProxyConfig();
proxyMode = get(proxyConfig, 'mode', 'off');
}
setupProxyAgents({
requestConfig: request,
proxyMode,
proxyConfig,
httpsAgentRequestFields,
interpolationOptions
});
return {proxyMode, newRequest: request, proxyConfig, httpsAgentRequestFields, interpolationOptions};
}
const configureRequest = async (
collectionUid,
request,
envVars,
runtimeVariables,
processEnvVars,
collectionPath
) => {
const protocolRegex = /^([-+\w]{1,25})(:?\/\/|:)/;
if (!protocolRegex.test(request.url)) {
request.url = `http://${request.url}`;
}
const {proxyMode, newRequest, proxyConfig, httpsAgentRequestFields, interpolationOptions} = await configureRequestWithCertsAndProxy({
collectionUid,
request,
envVars,
runtimeVariables,
processEnvVars,
collectionPath
});
request = newRequest
let requestMaxRedirects = request.maxRedirects
// Don't override maxRedirects here, let it be controlled by the request object
// request.maxRedirects = 0
// Set default value for requestMaxRedirects if not explicitly set
if (requestMaxRedirects === undefined) {
requestMaxRedirects = 5; // Default to 5 redirects
}
let axiosInstance = makeAxiosInstance({
proxyMode,
proxyConfig,
requestMaxRedirects,
httpsAgentRequestFields,
interpolationOptions
});
if (request.ntlmConfig) {
axiosInstance=NtlmClient(request.ntlmConfig,axiosInstance.defaults)
delete request.ntlmConfig;
}
if (request.oauth2) {
let requestCopy = cloneDeep(request);
const { oauth2: { grantType, tokenPlacement, tokenHeaderPrefix, tokenQueryKey } = {} } = requestCopy || {};
let credentials, credentialsId;
switch (grantType) {
case 'authorization_code':
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars);
({ credentials, url: oauth2Url, credentialsId, debugInfo } = await getOAuth2TokenUsingAuthorizationCode({ request: requestCopy, collectionUid }));
request.oauth2Credentials = { credentials, url: oauth2Url, collectionUid, credentialsId, debugInfo, folderUid: request.oauth2Credentials?.folderUid };
if (tokenPlacement == 'header') {
request.headers['Authorization'] = `${tokenHeaderPrefix} ${credentials?.access_token}`;
}
else {
try {
const url = new URL(request.url);
url?.searchParams?.set(tokenQueryKey, credentials?.access_token);
request.url = url?.toString();
}
catch(error) {}
}
break;
case 'client_credentials':
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars);
({ credentials, url: oauth2Url, credentialsId, debugInfo } = await getOAuth2TokenUsingClientCredentials({ request: requestCopy, collectionUid }));
request.oauth2Credentials = { credentials, url: oauth2Url, collectionUid, credentialsId, debugInfo, folderUid: request.oauth2Credentials?.folderUid };
if (tokenPlacement == 'header') {
request.headers['Authorization'] = `${tokenHeaderPrefix} ${credentials?.access_token}`;
}
else {
try {
const url = new URL(request.url);
url?.searchParams?.set(tokenQueryKey, credentials?.access_token);
request.url = url?.toString();
}
catch(error) {}
}
break;
case 'password':
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars);
({ credentials, url: oauth2Url, credentialsId, debugInfo } = await getOAuth2TokenUsingPasswordCredentials({ request: requestCopy, collectionUid }));
request.oauth2Credentials = { credentials, url: oauth2Url, collectionUid, credentialsId, debugInfo, folderUid: request.oauth2Credentials?.folderUid };
if (tokenPlacement == 'header') {
request.headers['Authorization'] = `${tokenHeaderPrefix} ${credentials?.access_token}`;
}
else {
try {
const url = new URL(request.url);
url?.searchParams?.set(tokenQueryKey, credentials?.access_token);
request.url = url?.toString();
}
catch(error) {}
}
break;
}
}
if (request.awsv4config) {
request.awsv4config = await resolveAwsV4Credentials(request);
addAwsV4Interceptor(axiosInstance, request);
delete request.awsv4config;
}
if (request.digestConfig) {
addDigestInterceptor(axiosInstance, request);
}
request.timeout = preferencesUtil.getRequestTimeout();
// add cookies to request
if (preferencesUtil.shouldSendCookies()) {
const cookieString = getCookieStringForUrl(request.url);
if (cookieString && typeof cookieString === 'string' && cookieString.length) {
request.headers['cookie'] = cookieString;
}
}
// Add API key to the URL
if (request.apiKeyAuthValueForQueryParams && request.apiKeyAuthValueForQueryParams.placement === 'queryparams') {
const urlObj = new URL(request.url);
// Interpolate key and value as they can be variables before adding to the URL.
const key = interpolateString(request.apiKeyAuthValueForQueryParams.key, interpolationOptions);
const value = interpolateString(request.apiKeyAuthValueForQueryParams.value, interpolationOptions);
urlObj.searchParams.set(key, value);
request.url = urlObj.toString();
}
// Remove pathParams, already in URL (Issue #2439)
delete request.pathParams;
// Remove apiKeyAuthValueForQueryParams, already interpolated and added to URL
delete request.apiKeyAuthValueForQueryParams;
return axiosInstance;
};
const getJsSandboxRuntime = (collection) => {
const securityConfig = get(collection, 'securityConfig', {});
return securityConfig.jsSandboxMode === 'safe' ? 'quickjs' : 'vm2';
};
const parseDataFromResponse = (response, disableParsingResponseJson = false) => {
// Parse the charset from content type: https://stackoverflow.com/a/33192813
const charsetMatch = /charset=([^()<>@,;:"/[\]?.=\s]*)/i.exec(response.headers['content-type'] || '');
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec#using_exec_with_regexp_literals
const charsetValue = charsetMatch?.[1];
const dataBuffer = Buffer.from(response.data);
// Overwrite the original data for backwards compatibility
let data;
if (iconv.encodingExists(charsetValue)) {
data = iconv.decode(dataBuffer, charsetValue);
} else {
data = iconv.decode(dataBuffer, 'utf-8');
}
// Try to parse response to JSON, this can quietly fail
try {
// Filter out ZWNBSP character
// https://gist.github.com/antic183/619f42b559b78028d1fe9e7ae8a1352d
data = data.replace(/^\uFEFF/, '');
// If the response is a string and starts and ends with double quotes, it's a stringified JSON and should not be parsed
if ( !disableParsingResponseJson && ! (typeof data === 'string' && data.startsWith("\"") && data.endsWith("\""))) {
data = Buffer?.isBuffer(data)? JSON.parse(data?.toString()) : JSON.parse(data);
}
} catch(error) {
console.error(error);
console.log('Failed to parse response data as JSON');
}
return { data, dataBuffer };
};
module.exports = {
prepareRequest,
prepareGqlIntrospectionRequest,
setAuthHeaders,
getJsSandboxRuntime,
configureRequestWithCertsAndProxy,
configureRequest,
parseDataFromResponse
}
setAuthHeaders
}

View File

@@ -2,7 +2,7 @@ const { get, cloneDeep } = require('lodash');
const crypto = require('crypto');
const { authorizeUserInWindow } = require('../ipc/network/authorize-user-in-window');
const Oauth2Store = require('../store/oauth2');
const { makeAxiosInstance } = require('./axios-instance');
const { makeAxiosInstance } = require('../ipc/network/axios-instance');
const { safeParseJSON, safeStringifyJSON } = require('./common');
const oauth2Store = new Oauth2Store();

View File

@@ -11,5 +11,5 @@ get {
}
auth:bearer {
token: {{$oauth2.keycloak.access_token}}
token: {{$oauth2.credentials.access_token}}
}