mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-09 06:55:03 +00:00
747 lines
30 KiB
JavaScript
747 lines
30 KiB
JavaScript
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 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 setAuthHeaders = (axiosRequest, request, collectionRoot) => {
|
|
const collectionAuth = get(collectionRoot, 'request.auth');
|
|
if (collectionAuth && request.auth.mode === 'inherit') {
|
|
switch (collectionAuth.mode) {
|
|
case 'awsv4':
|
|
axiosRequest.awsv4config = {
|
|
accessKeyId: get(collectionAuth, 'awsv4.accessKeyId'),
|
|
secretAccessKey: get(collectionAuth, 'awsv4.secretAccessKey'),
|
|
sessionToken: get(collectionAuth, 'awsv4.sessionToken'),
|
|
service: get(collectionAuth, 'awsv4.service'),
|
|
region: get(collectionAuth, 'awsv4.region'),
|
|
profileName: get(collectionAuth, 'awsv4.profileName')
|
|
};
|
|
break;
|
|
case 'basic':
|
|
axiosRequest.basicAuth = {
|
|
username: get(collectionAuth, 'basic.username'),
|
|
password: get(collectionAuth, 'basic.password')
|
|
};
|
|
break;
|
|
case 'bearer':
|
|
axiosRequest.headers['Authorization'] = `Bearer ${get(collectionAuth, 'bearer.token')}`;
|
|
break;
|
|
case 'digest':
|
|
axiosRequest.digestConfig = {
|
|
username: get(collectionAuth, 'digest.username'),
|
|
password: get(collectionAuth, 'digest.password')
|
|
};
|
|
break;
|
|
case 'ntlm':
|
|
axiosRequest.ntlmConfig = {
|
|
username: get(collectionAuth, 'ntlm.username'),
|
|
password: get(collectionAuth, 'ntlm.password'),
|
|
domain: get(collectionAuth, 'ntlm.domain')
|
|
};
|
|
break;
|
|
case 'wsse':
|
|
const username = get(request, 'auth.wsse.username', '');
|
|
const password = get(request, 'auth.wsse.password', '');
|
|
|
|
const ts = new Date().toISOString();
|
|
const nonce = crypto.randomBytes(16).toString('hex');
|
|
|
|
// Create the password digest using SHA-1 as required for WSSE
|
|
const hash = crypto.createHash('sha1');
|
|
hash.update(nonce + ts + password);
|
|
const digest = Buffer.from(hash.digest('hex').toString('utf8')).toString('base64');
|
|
|
|
// Construct the WSSE header
|
|
axiosRequest.headers[
|
|
'X-WSSE'
|
|
] = `UsernameToken Username="${username}", PasswordDigest="${digest}", Nonce="${nonce}", Created="${ts}"`;
|
|
break;
|
|
case 'apikey':
|
|
const apiKeyAuth = get(collectionAuth, 'apikey');
|
|
if (apiKeyAuth.placement === 'header') {
|
|
axiosRequest.headers[apiKeyAuth.key] = apiKeyAuth.value;
|
|
} else if (apiKeyAuth.placement === 'queryparams') {
|
|
// If the API key authentication is set and its placement is 'queryparams', add it to the axios request object. This will be used in the configureRequest function to append the API key to the query parameters of the request URL.
|
|
axiosRequest.apiKeyAuthValueForQueryParams = apiKeyAuth;
|
|
}
|
|
break;
|
|
case 'oauth2':
|
|
const grantType = get(collectionAuth, 'oauth2.grantType');
|
|
switch (grantType) {
|
|
case 'password':
|
|
axiosRequest.oauth2 = {
|
|
grantType: grantType,
|
|
accessTokenUrl: get(collectionAuth, 'oauth2.accessTokenUrl'),
|
|
refreshUrl: get(collectionAuth, 'oauth2.refreshUrl'),
|
|
username: get(collectionAuth, 'oauth2.username'),
|
|
password: get(collectionAuth, 'oauth2.password'),
|
|
clientId: get(collectionAuth, 'oauth2.clientId'),
|
|
clientSecret: get(collectionAuth, 'oauth2.clientSecret'),
|
|
scope: get(collectionAuth, 'oauth2.scope'),
|
|
credentialsPlacement: get(collectionAuth, 'oauth2.credentialsPlacement'),
|
|
credentialsId: get(collectionAuth, 'oauth2.credentialsId'),
|
|
tokenPlacement: get(collectionAuth, 'oauth2.tokenPlacement'),
|
|
tokenHeaderPrefix: get(collectionAuth, 'oauth2.tokenHeaderPrefix'),
|
|
tokenQueryKey: get(collectionAuth, 'oauth2.tokenQueryKey'),
|
|
autoFetchToken: get(collectionAuth, 'oauth2.autoFetchToken'),
|
|
autoRefreshToken: get(collectionAuth, 'oauth2.autoRefreshToken')
|
|
};
|
|
break;
|
|
case 'authorization_code':
|
|
axiosRequest.oauth2 = {
|
|
grantType: grantType,
|
|
callbackUrl: get(collectionAuth, 'oauth2.callbackUrl'),
|
|
authorizationUrl: get(collectionAuth, 'oauth2.authorizationUrl'),
|
|
accessTokenUrl: get(collectionAuth, 'oauth2.accessTokenUrl'),
|
|
refreshUrl: get(collectionAuth, 'oauth2.refreshUrl'),
|
|
clientId: get(collectionAuth, 'oauth2.clientId'),
|
|
clientSecret: get(collectionAuth, 'oauth2.clientSecret'),
|
|
scope: get(collectionAuth, 'oauth2.scope'),
|
|
state: get(collectionAuth, 'oauth2.state'),
|
|
pkce: get(collectionAuth, 'oauth2.pkce'),
|
|
credentialsPlacement: get(collectionAuth, 'oauth2.credentialsPlacement'),
|
|
credentialsId: get(collectionAuth, 'oauth2.credentialsId'),
|
|
tokenPlacement: get(collectionAuth, 'oauth2.tokenPlacement'),
|
|
tokenHeaderPrefix: get(collectionAuth, 'oauth2.tokenHeaderPrefix'),
|
|
tokenQueryKey: get(collectionAuth, 'oauth2.tokenQueryKey'),
|
|
autoFetchToken: get(collectionAuth, 'oauth2.autoFetchToken'),
|
|
autoRefreshToken: get(collectionAuth, 'oauth2.autoRefreshToken')
|
|
};
|
|
break;
|
|
case 'client_credentials':
|
|
axiosRequest.oauth2 = {
|
|
grantType: grantType,
|
|
accessTokenUrl: get(collectionAuth, 'oauth2.accessTokenUrl'),
|
|
refreshUrl: get(collectionAuth, 'oauth2.refreshUrl'),
|
|
clientId: get(collectionAuth, 'oauth2.clientId'),
|
|
clientSecret: get(collectionAuth, 'oauth2.clientSecret'),
|
|
scope: get(collectionAuth, 'oauth2.scope'),
|
|
credentialsPlacement: get(collectionAuth, 'oauth2.credentialsPlacement'),
|
|
credentialsId: get(collectionAuth, 'oauth2.credentialsId'),
|
|
tokenPlacement: get(collectionAuth, 'oauth2.tokenPlacement'),
|
|
tokenHeaderPrefix: get(collectionAuth, 'oauth2.tokenHeaderPrefix'),
|
|
tokenQueryKey: get(collectionAuth, 'oauth2.tokenQueryKey'),
|
|
autoFetchToken: get(collectionAuth, 'oauth2.autoFetchToken'),
|
|
autoRefreshToken: get(collectionAuth, 'oauth2.autoRefreshToken')
|
|
};
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (request.auth) {
|
|
switch (request.auth.mode) {
|
|
case 'awsv4':
|
|
axiosRequest.awsv4config = {
|
|
accessKeyId: get(request, 'auth.awsv4.accessKeyId'),
|
|
secretAccessKey: get(request, 'auth.awsv4.secretAccessKey'),
|
|
sessionToken: get(request, 'auth.awsv4.sessionToken'),
|
|
service: get(request, 'auth.awsv4.service'),
|
|
region: get(request, 'auth.awsv4.region'),
|
|
profileName: get(request, 'auth.awsv4.profileName')
|
|
};
|
|
break;
|
|
case 'basic':
|
|
axiosRequest.basicAuth = {
|
|
username: get(request, 'auth.basic.username'),
|
|
password: get(request, 'auth.basic.password')
|
|
};
|
|
break;
|
|
case 'bearer':
|
|
axiosRequest.headers['Authorization'] = `Bearer ${get(request, 'auth.bearer.token')}`;
|
|
break;
|
|
case 'digest':
|
|
axiosRequest.digestConfig = {
|
|
username: get(request, 'auth.digest.username'),
|
|
password: get(request, 'auth.digest.password')
|
|
};
|
|
break;
|
|
case 'ntlm':
|
|
axiosRequest.ntlmConfig = {
|
|
username: get(request, 'auth.ntlm.username'),
|
|
password: get(request, 'auth.ntlm.password'),
|
|
domain: get(request, 'auth.ntlm.domain')
|
|
};
|
|
case 'oauth2':
|
|
const grantType = get(request, 'auth.oauth2.grantType');
|
|
switch (grantType) {
|
|
case 'password':
|
|
axiosRequest.oauth2 = {
|
|
grantType: grantType,
|
|
accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'),
|
|
refreshUrl: get(collectionAuth, 'oauth2.refreshUrl'),
|
|
username: get(request, 'auth.oauth2.username'),
|
|
password: get(request, 'auth.oauth2.password'),
|
|
clientId: get(request, 'auth.oauth2.clientId'),
|
|
clientSecret: get(request, 'auth.oauth2.clientSecret'),
|
|
scope: get(request, 'auth.oauth2.scope'),
|
|
credentialsPlacement: get(request, 'auth.oauth2.credentialsPlacement'),
|
|
credentialsId: get(request, 'auth.oauth2.credentialsId'),
|
|
tokenPlacement: get(request, 'auth.oauth2.tokenPlacement'),
|
|
tokenHeaderPrefix: get(request, 'auth.oauth2.tokenHeaderPrefix'),
|
|
tokenQueryKey: get(request, 'auth.oauth2.tokenQueryKey'),
|
|
autoFetchToken: get(request, 'auth.oauth2.autoFetchToken'),
|
|
autoRefreshToken: get(request, 'auth.oauth2.autoRefreshToken')
|
|
};
|
|
break;
|
|
case 'authorization_code':
|
|
axiosRequest.oauth2 = {
|
|
grantType: grantType,
|
|
callbackUrl: get(request, 'auth.oauth2.callbackUrl'),
|
|
authorizationUrl: get(request, 'auth.oauth2.authorizationUrl'),
|
|
accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'),
|
|
refreshUrl: get(collectionAuth, 'oauth2.refreshUrl'),
|
|
clientId: get(request, 'auth.oauth2.clientId'),
|
|
clientSecret: get(request, 'auth.oauth2.clientSecret'),
|
|
scope: get(request, 'auth.oauth2.scope'),
|
|
state: get(request, 'auth.oauth2.state'),
|
|
pkce: get(request, 'auth.oauth2.pkce'),
|
|
credentialsPlacement: get(request, 'auth.oauth2.credentialsPlacement'),
|
|
credentialsId: get(request, 'auth.oauth2.credentialsId'),
|
|
tokenPlacement: get(request, 'auth.oauth2.tokenPlacement'),
|
|
tokenHeaderPrefix: get(request, 'auth.oauth2.tokenHeaderPrefix'),
|
|
tokenQueryKey: get(request, 'auth.oauth2.tokenQueryKey'),
|
|
autoFetchToken: get(request, 'auth.oauth2.autoFetchToken'),
|
|
autoRefreshToken: get(request, 'auth.oauth2.autoRefreshToken')
|
|
};
|
|
break;
|
|
case 'client_credentials':
|
|
axiosRequest.oauth2 = {
|
|
grantType: grantType,
|
|
accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'),
|
|
refreshUrl: get(collectionAuth, 'oauth2.refreshUrl'),
|
|
clientId: get(request, 'auth.oauth2.clientId'),
|
|
clientSecret: get(request, 'auth.oauth2.clientSecret'),
|
|
scope: get(request, 'auth.oauth2.scope'),
|
|
credentialsPlacement: get(request, 'auth.oauth2.credentialsPlacement'),
|
|
credentialsId: get(request, 'auth.oauth2.credentialsId'),
|
|
tokenPlacement: get(request, 'auth.oauth2.tokenPlacement'),
|
|
tokenHeaderPrefix: get(request, 'auth.oauth2.tokenHeaderPrefix'),
|
|
tokenQueryKey: get(request, 'auth.oauth2.tokenQueryKey'),
|
|
autoFetchToken: get(request, 'auth.oauth2.autoFetchToken'),
|
|
autoRefreshToken: get(request, 'auth.oauth2.autoRefreshToken')
|
|
};
|
|
break;
|
|
}
|
|
break;
|
|
case 'wsse':
|
|
const username = get(request, 'auth.wsse.username', '');
|
|
const password = get(request, 'auth.wsse.password', '');
|
|
|
|
const ts = new Date().toISOString();
|
|
const nonce = crypto.randomBytes(16).toString('hex');
|
|
|
|
// Create the password digest using SHA-1 as required for WSSE
|
|
const hash = crypto.createHash('sha1');
|
|
hash.update(nonce + ts + password);
|
|
const digest = Buffer.from(hash.digest('hex').toString('utf8')).toString('base64');
|
|
|
|
// Construct the WSSE header
|
|
axiosRequest.headers[
|
|
'X-WSSE'
|
|
] = `UsernameToken Username="${username}", PasswordDigest="${digest}", Nonce="${nonce}", Created="${ts}"`;
|
|
break;
|
|
case 'apikey':
|
|
const apiKeyAuth = get(request, 'auth.apikey');
|
|
if (apiKeyAuth.placement === 'header') {
|
|
axiosRequest.headers[apiKeyAuth.key] = apiKeyAuth.value;
|
|
} else if (apiKeyAuth.placement === 'queryparams') {
|
|
// If the API key authentication is set and its placement is 'queryparams', add it to the axios request object. This will be used in the configureRequest function to append the API key to the query parameters of the request URL.
|
|
axiosRequest.apiKeyAuthValueForQueryParams = apiKeyAuth;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return axiosRequest;
|
|
};
|
|
|
|
const prepareRequest = (item, collection) => {
|
|
const request = item.draft ? item.draft.request : item.request;
|
|
const collectionRoot = collection?.draft ? get(collection, 'draft', {}) : get(collection, 'root', {});
|
|
const collectionPath = collection.pathname;
|
|
const headers = {};
|
|
let contentTypeDefined = false;
|
|
let url = request.url;
|
|
|
|
each(get(collectionRoot, 'request.headers', []), (h) => {
|
|
if (h.enabled && h.name?.toLowerCase() === 'content-type') {
|
|
contentTypeDefined = true;
|
|
return false;
|
|
}
|
|
});
|
|
|
|
const scriptFlow = collection.brunoConfig?.scripts?.flow ?? 'sandwich';
|
|
const requestTreePath = getTreePathFromCollectionToItem(collection, item);
|
|
if (requestTreePath && requestTreePath.length > 0) {
|
|
mergeHeaders(collection, request, requestTreePath);
|
|
mergeScripts(collection, request, requestTreePath, scriptFlow);
|
|
mergeVars(collection, request, requestTreePath);
|
|
mergeAuth(collection, request, requestTreePath);
|
|
request.globalEnvironmentVariables = collection?.globalEnvironmentVariables;
|
|
request.oauth2CredentialVariables = getFormattedCollectionOauth2Credentials({ oauth2Credentials: collection?.oauth2Credentials });
|
|
}
|
|
|
|
|
|
each(get(request, 'headers', []), (h) => {
|
|
if (h.enabled && h.name.length > 0) {
|
|
headers[h.name] = h.value;
|
|
if (h.name.toLowerCase() === 'content-type') {
|
|
contentTypeDefined = true;
|
|
}
|
|
}
|
|
});
|
|
|
|
let axiosRequest = {
|
|
mode: request.body.mode,
|
|
method: request.method,
|
|
url,
|
|
headers,
|
|
pathParams: request?.params?.filter((param) => param.type === 'path'),
|
|
responseType: 'arraybuffer'
|
|
};
|
|
|
|
axiosRequest = setAuthHeaders(axiosRequest, request, collectionRoot);
|
|
|
|
if (request.body.mode === 'json') {
|
|
if (!contentTypeDefined) {
|
|
axiosRequest.headers['content-type'] = 'application/json';
|
|
}
|
|
try {
|
|
axiosRequest.data = decomment(request?.body?.json);
|
|
} catch (error) {
|
|
axiosRequest.data = request?.body?.json;
|
|
}
|
|
}
|
|
|
|
if (request.body.mode === 'text') {
|
|
if (!contentTypeDefined) {
|
|
axiosRequest.headers['content-type'] = 'text/plain';
|
|
}
|
|
axiosRequest.data = request.body.text;
|
|
}
|
|
|
|
if (request.body.mode === 'xml') {
|
|
if (!contentTypeDefined) {
|
|
axiosRequest.headers['content-type'] = 'application/xml';
|
|
}
|
|
axiosRequest.data = request.body.xml;
|
|
}
|
|
|
|
if (request.body.mode === 'sparql') {
|
|
if (!contentTypeDefined) {
|
|
axiosRequest.headers['content-type'] = 'application/sparql-query';
|
|
}
|
|
axiosRequest.data = request.body.sparql;
|
|
}
|
|
|
|
if (request.body.mode === 'formUrlEncoded') {
|
|
if (!contentTypeDefined) {
|
|
axiosRequest.headers['content-type'] = 'application/x-www-form-urlencoded';
|
|
}
|
|
const enabledParams = filter(request.body.formUrlEncoded, (p) => p.enabled);
|
|
axiosRequest.data = buildFormUrlEncodedPayload(enabledParams);
|
|
}
|
|
|
|
if (request.body.mode === 'multipartForm') {
|
|
if (!contentTypeDefined) {
|
|
axiosRequest.headers['content-type'] = 'multipart/form-data';
|
|
}
|
|
const enabledParams = filter(request.body.multipartForm, (p) => p.enabled);
|
|
axiosRequest.data = enabledParams;
|
|
}
|
|
|
|
if (request.body.mode === 'graphql') {
|
|
const graphqlQuery = {
|
|
query: get(request, 'body.graphql.query'),
|
|
// https://github.com/usebruno/bruno/issues/884 - we must only parse the variables after the variable interpolation
|
|
variables: decomment(get(request, 'body.graphql.variables') || '{}')
|
|
};
|
|
if (!contentTypeDefined) {
|
|
axiosRequest.headers['content-type'] = 'application/json';
|
|
}
|
|
axiosRequest.data = graphqlQuery;
|
|
}
|
|
|
|
if (request.script) {
|
|
axiosRequest.script = request.script;
|
|
}
|
|
|
|
if (request.tests) {
|
|
axiosRequest.tests = request.tests;
|
|
}
|
|
|
|
axiosRequest.vars = request.vars;
|
|
axiosRequest.collectionVariables = request.collectionVariables;
|
|
axiosRequest.folderVariables = request.folderVariables;
|
|
axiosRequest.requestVariables = request.requestVariables;
|
|
axiosRequest.globalEnvironmentVariables = request.globalEnvironmentVariables;
|
|
axiosRequest.oauth2CredentialVariables = request.oauth2CredentialVariables;
|
|
axiosRequest.assertions = request.assertions;
|
|
axiosRequest.oauth2Credentials = request.oauth2Credentials;
|
|
|
|
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
|
|
}
|