mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-09 06:55:03 +00:00
Merge remote-tracking branch 'pietrygamat/feature/inherit-oauth' into pietrygamat/inherit-oauth
This commit is contained in:
@@ -12,7 +12,6 @@ const { ipcMain } = require('electron');
|
||||
const { isUndefined, isNull, each, get, compact, cloneDeep, forOwn, extend } = require('lodash');
|
||||
const { VarsRuntime, AssertRuntime, ScriptRuntime, TestRuntime } = require('@usebruno/js');
|
||||
const prepareRequest = require('./prepare-request');
|
||||
const prepareCollectionRequest = require('./prepare-collection-request');
|
||||
const prepareGqlIntrospectionRequest = require('./prepare-gql-introspection-request');
|
||||
const { cancelTokens, saveCancelToken, deleteCancelToken } = require('../../utils/cancel-token');
|
||||
const { uuid } = require('../../utils/common');
|
||||
@@ -31,9 +30,9 @@ const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../../utils/proxy-ut
|
||||
const { chooseFileToSave, writeBinaryFile, writeFile } = require('../../utils/filesystem');
|
||||
const { getCookieStringForUrl, addCookieToJar, getDomainsWithCookies } = require('../../utils/cookies');
|
||||
const {
|
||||
resolveOAuth2AuthorizationCodeAccessToken,
|
||||
transformClientCredentialsRequest,
|
||||
transformPasswordCredentialsRequest
|
||||
oauth2AuthorizeWithAuthorizationCode,
|
||||
oauth2AuthorizeWithClientCredentials,
|
||||
oauth2AuthorizeWithPasswordCredentials
|
||||
} = require('./oauth2-helper');
|
||||
const Oauth2Store = require('../../store/oauth2');
|
||||
const iconv = require('iconv-lite');
|
||||
@@ -276,35 +275,30 @@ const configureRequest = async (
|
||||
|
||||
if (request.oauth2) {
|
||||
let requestCopy = cloneDeep(request);
|
||||
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars);
|
||||
let credentials, response;
|
||||
switch (request?.oauth2?.grantType) {
|
||||
case 'authorization_code':
|
||||
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars);
|
||||
const { data: authorizationCodeData, url: authorizationCodeAccessTokenUrl } =
|
||||
await resolveOAuth2AuthorizationCodeAccessToken(requestCopy, collectionUid);
|
||||
request.method = 'POST';
|
||||
request.headers['content-type'] = 'application/x-www-form-urlencoded';
|
||||
request.data = authorizationCodeData;
|
||||
request.url = authorizationCodeAccessTokenUrl;
|
||||
case 'authorization_code': {
|
||||
({ credentials, response } = await oauth2AuthorizeWithAuthorizationCode(requestCopy, collectionUid));
|
||||
break;
|
||||
case 'client_credentials':
|
||||
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars);
|
||||
const { data: clientCredentialsData, url: clientCredentialsAccessTokenUrl } =
|
||||
await transformClientCredentialsRequest(requestCopy);
|
||||
request.method = 'POST';
|
||||
request.headers['content-type'] = 'application/x-www-form-urlencoded';
|
||||
request.data = clientCredentialsData;
|
||||
request.url = clientCredentialsAccessTokenUrl;
|
||||
}
|
||||
case 'client_credentials': {
|
||||
({ credentials, response } = await oauth2AuthorizeWithClientCredentials(requestCopy, collectionUid));
|
||||
break;
|
||||
case 'password':
|
||||
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars);
|
||||
const { data: passwordData, url: passwordAccessTokenUrl } = await transformPasswordCredentialsRequest(
|
||||
requestCopy
|
||||
);
|
||||
request.method = 'POST';
|
||||
request.headers['content-type'] = 'application/x-www-form-urlencoded';
|
||||
request.data = passwordData;
|
||||
request.url = passwordAccessTokenUrl;
|
||||
}
|
||||
case 'password': {
|
||||
({ credentials, response } = await oauth2AuthorizeWithPasswordCredentials(requestCopy, collectionUid));
|
||||
break;
|
||||
}
|
||||
}
|
||||
request.credentials = credentials;
|
||||
request.authRequestResponse = response;
|
||||
|
||||
// Bruno can handle bearer token type automatically.
|
||||
// Other - more exotic token types are not touched
|
||||
// Users are free to use pre-request script and operate on req.credentials.access_token variable
|
||||
if (credentials?.token_type?.toLowerCase() === 'bearer') {
|
||||
request.headers['Authorization'] = `Bearer ${credentials.access_token}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -781,7 +775,7 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
);
|
||||
|
||||
interpolateVars(request, envVars, collection.runtimeVariables, processEnvVars);
|
||||
const axiosInstance = await configureRequest(
|
||||
await configureRequest(
|
||||
collection.uid,
|
||||
request,
|
||||
envVars,
|
||||
@@ -790,19 +784,13 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
collectionPath
|
||||
);
|
||||
|
||||
try {
|
||||
response = await axiosInstance(request);
|
||||
} catch (error) {
|
||||
if (error?.response) {
|
||||
response = error.response;
|
||||
} else {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
const response = request.authRequestResponse;
|
||||
// When credentials are loaded from cache, authRequestResponse has no data
|
||||
if (response.data) {
|
||||
const { data } = parseDataFromResponse(response, request.__brunoDisableParsingResponseJson);
|
||||
response.data = data;
|
||||
}
|
||||
|
||||
const { data } = parseDataFromResponse(response, request.__brunoDisableParsingResponseJson);
|
||||
response.data = data;
|
||||
|
||||
await runPostResponse(
|
||||
request,
|
||||
response,
|
||||
@@ -820,7 +808,8 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: response.headers,
|
||||
data: response.data
|
||||
data: response.data,
|
||||
credentials: request.credentials
|
||||
};
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
@@ -839,6 +828,17 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.handle('read-oauth2-cached-credentials', async (event, uid) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const oauth2Store = new Oauth2Store();
|
||||
return resolve(oauth2Store.getOauth2DataOfCollection(uid).credentials ?? {});
|
||||
} catch (err) {
|
||||
reject(new Error('Could not read cached oauth2 credentials'));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.handle('cancel-http-request', async (event, cancelTokenUid) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (cancelTokenUid && cancelTokens[cancelTokenUid]) {
|
||||
|
||||
@@ -170,14 +170,6 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
|
||||
request.oauth2.clientId = clientId;
|
||||
request.oauth2.clientSecret = clientSecret;
|
||||
request.oauth2.scope = scope;
|
||||
request.data = {
|
||||
grant_type: 'password',
|
||||
username,
|
||||
password,
|
||||
client_id: clientId,
|
||||
client_secret: clientSecret,
|
||||
scope
|
||||
};
|
||||
break;
|
||||
case 'authorization_code':
|
||||
request.oauth2.callbackUrl = _interpolate(request.oauth2.callbackUrl) || '';
|
||||
@@ -197,12 +189,6 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
|
||||
request.oauth2.clientId = clientId;
|
||||
request.oauth2.clientSecret = clientSecret;
|
||||
request.oauth2.scope = scope;
|
||||
request.data = {
|
||||
grant_type: 'client_credentials',
|
||||
client_id: clientId,
|
||||
client_secret: clientSecret,
|
||||
scope
|
||||
};
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -2,6 +2,9 @@ const { get, cloneDeep } = require('lodash');
|
||||
const crypto = require('crypto');
|
||||
const { authorizeUserInWindow } = require('./authorize-user-in-window');
|
||||
const Oauth2Store = require('../../store/oauth2');
|
||||
const { makeAxiosInstance } = require('./axios-instance');
|
||||
|
||||
const oauth2Store = new Oauth2Store();
|
||||
|
||||
const generateCodeVerifier = () => {
|
||||
return crypto.randomBytes(22).toString('hex');
|
||||
@@ -14,16 +17,34 @@ const generateCodeChallenge = (codeVerifier) => {
|
||||
return base64Hash.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
||||
};
|
||||
|
||||
const getPersistedOauth2Credentials = (collectionUid) => {
|
||||
const collectionOauthStore = oauth2Store.getOauth2DataOfCollection(collectionUid);
|
||||
const cachedCredentials = collectionOauthStore.credentials;
|
||||
return { cachedCredentials };
|
||||
};
|
||||
|
||||
const persistOauth2Credentials = (credentials, collectionUid) => {
|
||||
const collectionOauthStore = oauth2Store.getOauth2DataOfCollection(collectionUid);
|
||||
collectionOauthStore.credentials = credentials;
|
||||
oauth2Store.updateOauth2DataOfCollection(collectionUid, collectionOauthStore);
|
||||
};
|
||||
|
||||
// AUTHORIZATION CODE
|
||||
|
||||
const resolveOAuth2AuthorizationCodeAccessToken = async (request, collectionUid) => {
|
||||
const oauth2AuthorizeWithAuthorizationCode = async (request, collectionUid) => {
|
||||
const { cachedCredentials } = getPersistedOauth2Credentials(collectionUid);
|
||||
if (cachedCredentials?.access_token) {
|
||||
console.log('Reusing Stored access token');
|
||||
return { credentials: cachedCredentials, response: {} };
|
||||
}
|
||||
|
||||
let codeVerifier = generateCodeVerifier();
|
||||
let codeChallenge = generateCodeChallenge(codeVerifier);
|
||||
|
||||
let requestCopy = cloneDeep(request);
|
||||
const { authorizationCode } = await getOAuth2AuthorizationCode(requestCopy, codeChallenge, collectionUid);
|
||||
const oAuth = get(requestCopy, 'oauth2', {});
|
||||
const { clientId, clientSecret, callbackUrl, scope, pkce } = oAuth;
|
||||
const { clientId, clientSecret, callbackUrl, pkce } = oAuth;
|
||||
const data = {
|
||||
grant_type: 'authorization_code',
|
||||
code: authorizationCode,
|
||||
@@ -35,11 +56,16 @@ const resolveOAuth2AuthorizationCodeAccessToken = async (request, collectionUid)
|
||||
data['code_verifier'] = codeVerifier;
|
||||
}
|
||||
|
||||
const url = requestCopy?.oauth2?.accessTokenUrl;
|
||||
return {
|
||||
data,
|
||||
url
|
||||
};
|
||||
request.method = 'POST';
|
||||
request.headers['content-type'] = 'application/x-www-form-urlencoded';
|
||||
request.data = data;
|
||||
request.url = request?.oauth2?.accessTokenUrl;
|
||||
|
||||
const axiosInstance = makeAxiosInstance();
|
||||
const response = await axiosInstance(request);
|
||||
const credentials = JSON.parse(response.data);
|
||||
persistOauth2Credentials(credentials, collectionUid);
|
||||
return { credentials, response };
|
||||
};
|
||||
|
||||
const getOAuth2AuthorizationCode = (request, codeChallenge, collectionUid) => {
|
||||
@@ -64,7 +90,6 @@ const getOAuth2AuthorizationCode = (request, codeChallenge, collectionUid) => {
|
||||
authorizationUrlWithQueryParams.searchParams.append('state', state);
|
||||
}
|
||||
try {
|
||||
const oauth2Store = new Oauth2Store();
|
||||
const { authorizationCode } = await authorizeUserInWindow({
|
||||
authorizeUrl: authorizationUrlWithQueryParams.toString(),
|
||||
callbackUrl,
|
||||
@@ -79,7 +104,13 @@ const getOAuth2AuthorizationCode = (request, codeChallenge, collectionUid) => {
|
||||
|
||||
// CLIENT CREDENTIALS
|
||||
|
||||
const transformClientCredentialsRequest = async (request) => {
|
||||
const oauth2AuthorizeWithClientCredentials = async (request, collectionUid) => {
|
||||
const { cachedCredentials } = getPersistedOauth2Credentials(collectionUid);
|
||||
if (cachedCredentials?.access_token) {
|
||||
console.log('Reusing Stored access token');
|
||||
return { credentials: cachedCredentials, response: {} };
|
||||
}
|
||||
|
||||
let requestCopy = cloneDeep(request);
|
||||
const oAuth = get(requestCopy, 'oauth2', {});
|
||||
const { clientId, clientSecret, scope } = oAuth;
|
||||
@@ -91,18 +122,29 @@ const transformClientCredentialsRequest = async (request) => {
|
||||
if (scope) {
|
||||
data.scope = scope;
|
||||
}
|
||||
const url = requestCopy?.oauth2?.accessTokenUrl;
|
||||
return {
|
||||
data,
|
||||
url
|
||||
};
|
||||
|
||||
request.method = 'POST';
|
||||
request.headers['content-type'] = 'application/x-www-form-urlencoded';
|
||||
request.data = data;
|
||||
request.url = request?.oauth2?.accessTokenUrl;
|
||||
|
||||
const axiosInstance = makeAxiosInstance();
|
||||
let response = await axiosInstance(request);
|
||||
let credentials = JSON.parse(response.data);
|
||||
persistOauth2Credentials(credentials, collectionUid);
|
||||
return { credentials, response };
|
||||
};
|
||||
|
||||
// PASSWORD CREDENTIALS
|
||||
|
||||
const transformPasswordCredentialsRequest = async (request) => {
|
||||
let requestCopy = cloneDeep(request);
|
||||
const oAuth = get(requestCopy, 'oauth2', {});
|
||||
const oauth2AuthorizeWithPasswordCredentials = async (request, collectionUid) => {
|
||||
const { cachedCredentials } = getPersistedOauth2Credentials(collectionUid);
|
||||
if (cachedCredentials?.access_token) {
|
||||
console.log('Reusing Stored access token');
|
||||
return { credentials: cachedCredentials, response: {} };
|
||||
}
|
||||
|
||||
const oAuth = get(request, 'oauth2', {});
|
||||
const { username, password, clientId, clientSecret, scope } = oAuth;
|
||||
const data = {
|
||||
grant_type: 'password',
|
||||
@@ -114,16 +156,20 @@ const transformPasswordCredentialsRequest = async (request) => {
|
||||
if (scope) {
|
||||
data.scope = scope;
|
||||
}
|
||||
const url = requestCopy?.oauth2?.accessTokenUrl;
|
||||
return {
|
||||
data,
|
||||
url
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
resolveOAuth2AuthorizationCodeAccessToken,
|
||||
getOAuth2AuthorizationCode,
|
||||
transformClientCredentialsRequest,
|
||||
transformPasswordCredentialsRequest
|
||||
request.method = 'POST';
|
||||
request.headers['content-type'] = 'application/x-www-form-urlencoded';
|
||||
request.data = data;
|
||||
request.url = request?.oauth2?.accessTokenUrl;
|
||||
|
||||
const axiosInstance = makeAxiosInstance();
|
||||
let response = await axiosInstance(request);
|
||||
let credentials = JSON.parse(response.data);
|
||||
persistOauth2Credentials(credentials, collectionUid);
|
||||
return { credentials, response };
|
||||
};
|
||||
module.exports = {
|
||||
oauth2AuthorizeWithAuthorizationCode,
|
||||
oauth2AuthorizeWithClientCredentials,
|
||||
oauth2AuthorizeWithPasswordCredentials
|
||||
};
|
||||
|
||||
@@ -59,6 +59,9 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
|
||||
axiosRequest.apiKeyAuthValueForQueryParams = apiKeyAuth;
|
||||
}
|
||||
break;
|
||||
case 'oauth2':
|
||||
request.auth = collectionAuth;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +197,7 @@ const prepareRequest = (item, collection) => {
|
||||
});
|
||||
|
||||
let axiosRequest = {
|
||||
mode: request.body.mode,
|
||||
mode: request?.body?.mode,
|
||||
method: request.method,
|
||||
url,
|
||||
headers,
|
||||
@@ -204,7 +207,7 @@ const prepareRequest = (item, collection) => {
|
||||
|
||||
axiosRequest = setAuthHeaders(axiosRequest, request, collectionRoot);
|
||||
|
||||
if (request.body.mode === 'json') {
|
||||
if (request.body?.mode === 'json') {
|
||||
if (!contentTypeDefined) {
|
||||
axiosRequest.headers['content-type'] = 'application/json';
|
||||
}
|
||||
@@ -215,28 +218,28 @@ const prepareRequest = (item, collection) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (request.body.mode === 'text') {
|
||||
if (request.body?.mode === 'text') {
|
||||
if (!contentTypeDefined) {
|
||||
axiosRequest.headers['content-type'] = 'text/plain';
|
||||
}
|
||||
axiosRequest.data = request.body.text;
|
||||
}
|
||||
|
||||
if (request.body.mode === 'xml') {
|
||||
if (request.body?.mode === 'xml') {
|
||||
if (!contentTypeDefined) {
|
||||
axiosRequest.headers['content-type'] = 'application/xml';
|
||||
}
|
||||
axiosRequest.data = request.body.xml;
|
||||
}
|
||||
|
||||
if (request.body.mode === 'sparql') {
|
||||
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 (request.body?.mode === 'formUrlEncoded') {
|
||||
if (!contentTypeDefined) {
|
||||
axiosRequest.headers['content-type'] = 'application/x-www-form-urlencoded';
|
||||
}
|
||||
@@ -252,7 +255,7 @@ const prepareRequest = (item, collection) => {
|
||||
axiosRequest.data = enabledParams;
|
||||
}
|
||||
|
||||
if (request.body.mode === 'graphql') {
|
||||
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
|
||||
|
||||
@@ -85,6 +85,7 @@ class Oauth2Store {
|
||||
|
||||
let oauth2DataForCollection = this.getOauth2DataOfCollection(collectionUid);
|
||||
delete oauth2DataForCollection.sessionId;
|
||||
delete oauth2DataForCollection.credentials;
|
||||
|
||||
let updatedOauth2Data = oauth2Data.filter((d) => d.collectionUid !== collectionUid);
|
||||
updatedOauth2Data.push({ ...oauth2DataForCollection });
|
||||
|
||||
Reference in New Issue
Block a user