mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-08 22:45:25 +00:00
~ reverting the bruno-electron ipc-network files refactoring work to keep the diff minimal
This commit is contained in:
@@ -1,175 +0,0 @@
|
||||
const { fromIni } = require('@aws-sdk/credential-providers');
|
||||
const { aws4Interceptor } = require('aws4-axios');
|
||||
|
||||
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 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);
|
||||
}
|
||||
|
||||
function addDigestInterceptor(axiosInstance, request) {
|
||||
const { username, password } = request.digestConfig;
|
||||
console.debug('Digest Auth Interceptor Initialized');
|
||||
|
||||
if (!isStrPresent(username) || !isStrPresent(password)) {
|
||||
console.warn('Required Digest Auth fields (username/password) are not present');
|
||||
return;
|
||||
}
|
||||
|
||||
axiosInstance.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
const originalRequest = error.config;
|
||||
|
||||
// Prevent retry loops
|
||||
if (originalRequest._retry) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
originalRequest._retry = true;
|
||||
|
||||
if (
|
||||
error.response?.status === 401 &&
|
||||
containsDigestHeader(error.response) &&
|
||||
!containsAuthorizationHeader(originalRequest)
|
||||
) {
|
||||
console.debug('Processing Digest Authentication Challenge');
|
||||
console.debug(error.response.headers['www-authenticate']);
|
||||
|
||||
const authDetails = error.response.headers['www-authenticate']
|
||||
.split(',')
|
||||
.map((pair) => pair.split('=').map((item) => item.trim()).map(stripQuotes))
|
||||
.reduce((acc, [key, value]) => {
|
||||
const normalizedKey = key.toLowerCase().replace('digest ', '');
|
||||
if (normalizedKey && value !== undefined) {
|
||||
acc[normalizedKey] = value;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// Validate required auth details
|
||||
if (!authDetails.realm || !authDetails.nonce) {
|
||||
console.warn('Missing required auth details (realm or nonce)');
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
console.debug("Auth Details: \n", authDetails);
|
||||
|
||||
const nonceCount = '00000001';
|
||||
const cnonce = crypto.randomBytes(24).toString('hex');
|
||||
|
||||
if (authDetails.algorithm && authDetails.algorithm.toUpperCase() !== 'MD5') {
|
||||
console.warn(`Unsupported Digest algorithm: ${authDetails.algorithm}`);
|
||||
return Promise.reject(error);
|
||||
} else {
|
||||
authDetails.algorithm = 'MD5';
|
||||
}
|
||||
|
||||
const uri = new URL(request.url, request.baseURL || 'http://localhost').pathname; // Handle relative URLs
|
||||
const HA1 = md5(`${username}:${authDetails.realm}:${password}`);
|
||||
const HA2 = md5(`${request.method}:${uri}`);
|
||||
const response = md5(
|
||||
`${HA1}:${authDetails.nonce}:${nonceCount}:${cnonce}:auth:${HA2}`
|
||||
);
|
||||
|
||||
const headerFields = [
|
||||
`username="${username}"`,
|
||||
`realm="${authDetails.realm}"`,
|
||||
`nonce="${authDetails.nonce}"`,
|
||||
`uri="${uri}"`,
|
||||
`qop="auth"`,
|
||||
`algorithm="${authDetails.algorithm}"`,
|
||||
`response="${response}"`,
|
||||
`nc="${nonceCount}"`,
|
||||
`cnonce="${cnonce}"`,
|
||||
];
|
||||
|
||||
if (authDetails.opaque) {
|
||||
headerFields.push(`opaque="${authDetails.opaque}"`);
|
||||
}
|
||||
|
||||
const authorizationHeader = `Digest ${headerFields.join(', ')}`;
|
||||
|
||||
// Ensure headers are initialized
|
||||
originalRequest.headers = originalRequest.headers || {};
|
||||
originalRequest.headers['Authorization'] = authorizationHeader;
|
||||
|
||||
console.debug(`Authorization: ${originalRequest.headers['Authorization']}`);
|
||||
|
||||
delete originalRequest.digestConfig;
|
||||
|
||||
return axiosInstance(originalRequest);
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -1,339 +0,0 @@
|
||||
const URL = require('url');
|
||||
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 LOCAL_IPV6 = '::1';
|
||||
const LOCAL_IPV4 = '127.0.0.1';
|
||||
const LOCALHOST = 'localhost';
|
||||
const version = electronApp?.app?.getVersion()?.substring(1) ?? "";
|
||||
const redirectResponseCodes = [301, 302, 303, 307, 308];
|
||||
|
||||
const saveCookies = (url, headers) => {
|
||||
if (preferencesUtil.shouldStoreCookies()) {
|
||||
let setCookieHeaders = [];
|
||||
if (headers['set-cookie']) {
|
||||
setCookieHeaders = Array.isArray(headers['set-cookie'])
|
||||
? headers['set-cookie']
|
||||
: [headers['set-cookie']];
|
||||
for (let setCookieHeader of setCookieHeaders) {
|
||||
if (typeof setCookieHeader === 'string' && setCookieHeader.length) {
|
||||
addCookieToJar(setCookieHeader, url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getTld = (hostname) => {
|
||||
if (!hostname) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return hostname.substring(hostname.lastIndexOf('.') + 1);
|
||||
};
|
||||
|
||||
const checkConnection = (host, port) =>
|
||||
new Promise((resolve) => {
|
||||
const key = `${host}:${port}`;
|
||||
const cachedResult = connectionCache.get(key);
|
||||
|
||||
if (cachedResult !== undefined) {
|
||||
resolve(cachedResult);
|
||||
} else {
|
||||
const socket = new Socket();
|
||||
|
||||
socket.once('connect', () => {
|
||||
socket.end();
|
||||
connectionCache.set(key, true); // Cache successful connection
|
||||
resolve(true);
|
||||
});
|
||||
|
||||
socket.once('error', () => {
|
||||
connectionCache.set(key, false); // Cache failed connection
|
||||
resolve(false);
|
||||
});
|
||||
|
||||
// Try to connect to the host and port
|
||||
socket.connect(port, host);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Function that configures axios with timing interceptors
|
||||
* Important to note here that the timings are not completely accurate.
|
||||
* @see https://github.com/axios/axios/issues/695
|
||||
* @returns {axios.AxiosInstance}
|
||||
*/
|
||||
function makeAxiosInstance({
|
||||
proxyMode = 'off',
|
||||
proxyConfig = {},
|
||||
requestMaxRedirects = 5,
|
||||
httpsAgentRequestFields = {},
|
||||
interpolationOptions = {}
|
||||
} = {}) {
|
||||
/** @type {axios.AxiosInstance} */
|
||||
const instance = axios.create({
|
||||
transformRequest: function transformRequest(data, headers) {
|
||||
const contentType = headers?.['Content-Type'] || headers?.['content-type'] || '';
|
||||
const hasJSONContentType = contentType.includes('json');
|
||||
if (typeof data === 'string' && hasJSONContentType) {
|
||||
return data;
|
||||
}
|
||||
|
||||
axios.defaults.transformRequest.forEach(function (tr) {
|
||||
data = tr.call(this, data, headers);
|
||||
}, this);
|
||||
return data;
|
||||
},
|
||||
proxy: false,
|
||||
headers: {
|
||||
"User-Agent": `bruno-runtime/${version}`
|
||||
}
|
||||
});
|
||||
|
||||
instance.interceptors.request.use(async (config) => {
|
||||
const url = URL.parse(config.url);
|
||||
|
||||
config.metadata = config.metadata || {};
|
||||
config.metadata.startTime = new Date().getTime();
|
||||
config.metadata.timeline = config.metadata.timeline || [];
|
||||
|
||||
// Add initial request details to the timeline
|
||||
config.metadata.timeline.push({
|
||||
timestamp: new Date(),
|
||||
type: 'info',
|
||||
message: `Preparing request to ${config.url}`,
|
||||
});
|
||||
config.metadata.timeline.push({
|
||||
timestamp: new Date(),
|
||||
type: 'info',
|
||||
message: `Current time is ${new Date().toISOString()}`,
|
||||
});
|
||||
|
||||
// Add request method and headers
|
||||
config.metadata.timeline.push({
|
||||
timestamp: new Date(),
|
||||
type: 'request',
|
||||
message: `${config.method.toUpperCase()} ${config.url}`,
|
||||
});
|
||||
Object.entries(config.headers).forEach(([key, value]) => {
|
||||
config.metadata.timeline.push({
|
||||
timestamp: new Date(),
|
||||
type: 'requestHeader',
|
||||
message: `${key}: ${value}`,
|
||||
});
|
||||
});
|
||||
|
||||
// Add request data if available
|
||||
if (config.data) {
|
||||
let requestData;
|
||||
try {
|
||||
requestData = typeof config.data === 'string' ? config.data : JSON.stringify(config.data, null, 2);
|
||||
} catch (err) {
|
||||
requestData = config.data.toString();
|
||||
}
|
||||
config.metadata.timeline.push({
|
||||
timestamp: new Date(),
|
||||
type: 'requestData',
|
||||
message: requestData,
|
||||
});
|
||||
}
|
||||
|
||||
// Resolve all *.localhost to localhost and check if it should use IPv6 or IPv4
|
||||
// RFC: 6761 section 6.3 (https://tools.ietf.org/html/rfc6761#section-6.3)
|
||||
// @see https://github.com/usebruno/bruno/issues/124
|
||||
if (getTld(url.hostname) === LOCALHOST || url.hostname === LOCAL_IPV4 || url.hostname === LOCAL_IPV6) {
|
||||
// use custom DNS lookup for localhost
|
||||
config.lookup = (hostname, options, callback) => {
|
||||
const portNumber = Number(url.port) || (url.protocol.includes('https') ? 443 : 80);
|
||||
checkConnection(LOCAL_IPV6, portNumber).then((useIpv6) => {
|
||||
const ip = useIpv6 ? LOCAL_IPV6 : LOCAL_IPV4;
|
||||
callback(null, ip, useIpv6 ? 6 : 4);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
config.headers['request-start-time'] = Date.now();
|
||||
|
||||
const agentOptions = {
|
||||
...httpsAgentRequestFields,
|
||||
keepAlive: true,
|
||||
};
|
||||
|
||||
// Now call setupProxyAgents and pass the timeline
|
||||
setupProxyAgents({
|
||||
requestConfig: config,
|
||||
proxyMode: proxyMode, // 'on', 'off', or 'system', depending on your settings
|
||||
proxyConfig: proxyConfig,
|
||||
httpsAgentRequestFields: agentOptions,
|
||||
interpolationOptions: interpolationOptions, // Provide your interpolation options
|
||||
timeline: config.metadata.timeline,
|
||||
});
|
||||
return config;
|
||||
});
|
||||
|
||||
let redirectCount = 0
|
||||
|
||||
instance.interceptors.response.use(
|
||||
(response) => {
|
||||
const end = Date.now();
|
||||
const start = response.config.headers['request-start-time'];
|
||||
response.headers['request-duration'] = end - start;
|
||||
redirectCount = 0;
|
||||
|
||||
const config = response.config;
|
||||
const metadata = config.metadata;
|
||||
const duration = end - metadata.startTime;
|
||||
|
||||
const httpVersion = response.request?.res?.httpVersion || '1.1';
|
||||
metadata.timeline.push({
|
||||
timestamp: new Date(),
|
||||
type: 'response',
|
||||
message: `HTTP/${httpVersion} ${response.status} ${response.statusText}`,
|
||||
});
|
||||
|
||||
if (httpVersion.startsWith('2')) {
|
||||
metadata.timeline.push({
|
||||
timestamp: new Date(),
|
||||
type: 'info',
|
||||
message: `Using HTTP/2, server supports multiplexing`,
|
||||
});
|
||||
}
|
||||
|
||||
metadata.timeline.push({
|
||||
timestamp: new Date(),
|
||||
type: 'response',
|
||||
message: `HTTP/${response.httpVersion || '1.1'} ${response.status} ${response.statusText}`,
|
||||
});
|
||||
Object.entries(response.headers).forEach(([key, value]) => {
|
||||
metadata.timeline.push({
|
||||
timestamp: new Date(),
|
||||
type: 'responseHeader',
|
||||
message: `${key}: ${value}`,
|
||||
});
|
||||
});
|
||||
metadata.timeline.push({
|
||||
timestamp: new Date(),
|
||||
type: 'info',
|
||||
message: `Request completed in ${duration} ms`,
|
||||
});
|
||||
|
||||
// Attach the timeline to the response
|
||||
response.timeline = metadata.timeline;
|
||||
|
||||
return response;
|
||||
},
|
||||
(error) => {
|
||||
if (error.response) {
|
||||
const end = Date.now();
|
||||
const start = error.config.headers['request-start-time'];
|
||||
error.response.headers['request-duration'] = end - start;
|
||||
const config = error.config;
|
||||
const metadata = config.metadata;
|
||||
const duration = end - metadata.startTime;
|
||||
|
||||
if (error.response && redirectResponseCodes.includes(error.response.status)) {
|
||||
|
||||
|
||||
metadata.timeline.push({
|
||||
timestamp: new Date(),
|
||||
type: 'response',
|
||||
message: `HTTP/${error.response.httpVersion || '1.1'} ${error.response.status} ${error.response.statusText}`,
|
||||
});
|
||||
Object.entries(error.response.headers).forEach(([key, value]) => {
|
||||
metadata.timeline.push({
|
||||
timestamp: new Date(),
|
||||
type: 'responseHeader',
|
||||
message: `${key}: ${value}`,
|
||||
});
|
||||
});
|
||||
metadata.timeline.push({
|
||||
timestamp: new Date(),
|
||||
type: 'info',
|
||||
message: `Request completed in ${duration} ms`,
|
||||
});
|
||||
|
||||
// Attach the timeline to the response
|
||||
error.response.timeline = metadata.timeline;
|
||||
|
||||
if (redirectCount >= requestMaxRedirects) {
|
||||
const dataBuffer = Buffer.from(error.response.data);
|
||||
|
||||
return {
|
||||
status: error.response.status,
|
||||
statusText: error.response.statusText,
|
||||
headers: error.response.headers,
|
||||
data: error.response.data,
|
||||
dataBuffer: dataBuffer.toString('base64'),
|
||||
size: Buffer.byteLength(dataBuffer),
|
||||
duration: error.response.headers.get('request-duration') ?? 0
|
||||
};
|
||||
}
|
||||
|
||||
// Increase redirect count
|
||||
redirectCount++;
|
||||
|
||||
const locationHeader = error.response.headers.location;
|
||||
let redirectUrl = locationHeader;
|
||||
|
||||
// Handle relative URLs by resolving them against the original request URL
|
||||
if (locationHeader && !locationHeader.match(/^https?:\/\//i)) {
|
||||
// It's a relative URL, resolve it against the original URL
|
||||
redirectUrl = URL.resolve(error.config.url, locationHeader);
|
||||
|
||||
metadata.timeline.push({
|
||||
timestamp: new Date(),
|
||||
type: 'info',
|
||||
message: `Resolving relative redirect URL: ${locationHeader} → ${redirectUrl}`,
|
||||
});
|
||||
}
|
||||
|
||||
if (preferencesUtil.shouldStoreCookies()) {
|
||||
saveCookies(redirectUrl, error.response.headers);
|
||||
}
|
||||
|
||||
// Create a new request config for the redirect
|
||||
const requestConfig = {
|
||||
...error.config,
|
||||
url: redirectUrl,
|
||||
headers: {
|
||||
...error.config.headers,
|
||||
},
|
||||
};
|
||||
|
||||
if (preferencesUtil.shouldSendCookies()) {
|
||||
const cookieString = getCookieStringForUrl(redirectUrl);
|
||||
if (cookieString && typeof cookieString === 'string' && cookieString.length) {
|
||||
requestConfig.headers['cookie'] = cookieString;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
setupProxyAgents({
|
||||
requestConfig,
|
||||
proxyMode,
|
||||
proxyConfig,
|
||||
httpsAgentRequestFields,
|
||||
interpolationOptions,
|
||||
timeline: metadata.timeline
|
||||
});
|
||||
|
||||
// Make the redirected request
|
||||
return instance(requestConfig);
|
||||
}
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
makeAxiosInstance
|
||||
};
|
||||
@@ -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();
|
||||
|
||||
@@ -1,746 +0,0 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user