mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-08 14:35:03 +00:00
feat: add support for oauth2 in cli (#4578)
Co-authored-by: Pooja Belaramani <109731557+poojabela@users.noreply.github.com>
This commit is contained in:
@@ -156,6 +156,37 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
|
||||
delete request.basicAuth;
|
||||
}
|
||||
|
||||
if (request?.oauth2?.grantType) {
|
||||
switch (request.oauth2.grantType) {
|
||||
case 'password':
|
||||
request.oauth2.accessTokenUrl = _interpolate(request.oauth2.accessTokenUrl) || '';
|
||||
request.oauth2.refreshTokenUrl = _interpolate(request.oauth2.refreshTokenUrl) || '';
|
||||
request.oauth2.username = _interpolate(request.oauth2.username) || '';
|
||||
request.oauth2.password = _interpolate(request.oauth2.password) || '';
|
||||
request.oauth2.clientId = _interpolate(request.oauth2.clientId) || '';
|
||||
request.oauth2.clientSecret = _interpolate(request.oauth2.clientSecret) || '';
|
||||
request.oauth2.scope = _interpolate(request.oauth2.scope) || '';
|
||||
request.oauth2.credentialsPlacement = _interpolate(request.oauth2.credentialsPlacement) || '';
|
||||
request.oauth2.tokenPlacement = _interpolate(request.oauth2.tokenPlacement) || '';
|
||||
request.oauth2.tokenHeaderPrefix = _interpolate(request.oauth2.tokenHeaderPrefix) || '';
|
||||
request.oauth2.tokenQueryKey = _interpolate(request.oauth2.tokenQueryKey) || '';
|
||||
break;
|
||||
case 'client_credentials':
|
||||
request.oauth2.accessTokenUrl = _interpolate(request.oauth2.accessTokenUrl) || '';
|
||||
request.oauth2.refreshTokenUrl = _interpolate(request.oauth2.refreshTokenUrl) || '';
|
||||
request.oauth2.clientId = _interpolate(request.oauth2.clientId) || '';
|
||||
request.oauth2.clientSecret = _interpolate(request.oauth2.clientSecret) || '';
|
||||
request.oauth2.scope = _interpolate(request.oauth2.scope) || '';
|
||||
request.oauth2.credentialsPlacement = _interpolate(request.oauth2.credentialsPlacement) || '';
|
||||
request.oauth2.tokenPlacement = _interpolate(request.oauth2.tokenPlacement) || '';
|
||||
request.oauth2.tokenHeaderPrefix = _interpolate(request.oauth2.tokenHeaderPrefix) || '';
|
||||
request.oauth2.tokenQueryKey = _interpolate(request.oauth2.tokenQueryKey) || '';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (request.awsv4config) {
|
||||
request.awsv4config.accessKeyId = _interpolate(request.awsv4config.accessKeyId) || '';
|
||||
request.awsv4config.secretAccessKey = _interpolate(request.awsv4config.secretAccessKey) || '';
|
||||
|
||||
6
packages/bruno-cli/src/runner/oauth2.js
Normal file
6
packages/bruno-cli/src/runner/oauth2.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const { getOAuth2Token } = require('@usebruno/requests');
|
||||
const tokenStore = require('./tokenStore');
|
||||
|
||||
module.exports = {
|
||||
getOAuth2Token: (oauth2Config) => getOAuth2Token(oauth2Config, tokenStore)
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
const { get, each, filter } = require('lodash');
|
||||
const decomment = require('decomment');
|
||||
const crypto = require('node:crypto');
|
||||
const { mergeHeaders, mergeScripts, mergeVars, getTreePathFromCollectionToItem } = require('../utils/collection');
|
||||
const { mergeHeaders, mergeScripts, mergeVars, mergeAuth, getTreePathFromCollectionToItem } = require('../utils/collection');
|
||||
const { createFormData } = require('../utils/form-data');
|
||||
|
||||
const prepareRequest = (item = {}, collection = {}) => {
|
||||
@@ -16,6 +16,7 @@ const prepareRequest = (item = {}, collection = {}) => {
|
||||
mergeHeaders(collection, request, requestTreePath);
|
||||
mergeScripts(collection, request, requestTreePath, scriptFlow);
|
||||
mergeVars(collection, request, requestTreePath);
|
||||
mergeAuth(collection, request, requestTreePath);
|
||||
}
|
||||
|
||||
each(get(request, 'headers', []), (h) => {
|
||||
@@ -73,6 +74,37 @@ const prepareRequest = (item = {}, collection = {}) => {
|
||||
};
|
||||
}
|
||||
|
||||
if (collectionAuth.mode === 'oauth2') {
|
||||
const grantType = get(collectionAuth, 'oauth2.grantType');
|
||||
|
||||
if (grantType === 'client_credentials') {
|
||||
axiosRequest.oauth2 = {
|
||||
grantType,
|
||||
accessTokenUrl: get(collectionAuth, 'oauth2.accessTokenUrl'),
|
||||
clientId: get(collectionAuth, 'oauth2.clientId'),
|
||||
clientSecret: get(collectionAuth, 'oauth2.clientSecret'),
|
||||
scope: get(collectionAuth, 'oauth2.scope'),
|
||||
credentialsPlacement: get(collectionAuth, 'oauth2.credentialsPlacement'),
|
||||
tokenPlacement: get(collectionAuth, 'oauth2.tokenPlacement'),
|
||||
tokenHeaderPrefix: get(collectionAuth, 'oauth2.tokenHeaderPrefix'),
|
||||
tokenQueryKey: get(collectionAuth, 'oauth2.tokenQueryKey')
|
||||
};
|
||||
} else if (grantType === 'password') {
|
||||
axiosRequest.oauth2 = {
|
||||
grantType,
|
||||
accessTokenUrl: get(collectionAuth, 'oauth2.accessTokenUrl'),
|
||||
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'),
|
||||
tokenPlacement: get(collectionAuth, 'oauth2.tokenPlacement'),
|
||||
tokenHeaderPrefix: get(collectionAuth, 'oauth2.tokenHeaderPrefix'),
|
||||
tokenQueryKey: get(collectionAuth, 'oauth2.tokenQueryKey')
|
||||
};
|
||||
}
|
||||
}
|
||||
if (collectionAuth.mode === 'awsv4') {
|
||||
axiosRequest.awsv4config = {
|
||||
accessKeyId: get(collectionAuth, 'awsv4.accessKeyId'),
|
||||
@@ -169,6 +201,38 @@ const prepareRequest = (item = {}, collection = {}) => {
|
||||
};
|
||||
}
|
||||
|
||||
if (request.auth.mode === 'oauth2') {
|
||||
const grantType = get(request, 'auth.oauth2.grantType');
|
||||
|
||||
if (grantType === 'client_credentials') {
|
||||
axiosRequest.oauth2 = {
|
||||
grantType,
|
||||
clientId: get(request, 'auth.oauth2.clientId'),
|
||||
clientSecret: get(request, 'auth.oauth2.clientSecret'),
|
||||
scope: get(request, 'auth.oauth2.scope'),
|
||||
accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'),
|
||||
tokenPlacement: get(request, 'auth.oauth2.tokenPlacement'),
|
||||
credentialsPlacement: get(request, 'auth.oauth2.credentialsPlacement'),
|
||||
tokenHeaderPrefix: get(request, 'auth.oauth2.tokenHeaderPrefix'),
|
||||
tokenQueryKey: get(request, 'auth.oauth2.tokenQueryKey')
|
||||
};
|
||||
} else if (grantType === 'password') {
|
||||
axiosRequest.oauth2 = {
|
||||
grantType,
|
||||
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'),
|
||||
accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'),
|
||||
tokenPlacement: get(request, 'auth.oauth2.tokenPlacement'),
|
||||
credentialsPlacement: get(request, 'auth.oauth2.credentialsPlacement'),
|
||||
tokenHeaderPrefix: get(request, 'auth.oauth2.tokenHeaderPrefix'),
|
||||
tokenQueryKey: get(request, 'auth.oauth2.tokenQueryKey')
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (request.auth.mode === 'apikey') {
|
||||
if (request.auth.apikey?.placement === 'header') {
|
||||
axiosRequest.headers[request.auth.apikey?.key] = request.auth.apikey?.value;
|
||||
|
||||
@@ -22,6 +22,7 @@ const path = require('path');
|
||||
const { parseDataFromResponse } = require('../utils/common');
|
||||
const { getCookieStringForUrl, saveCookies, shouldUseCookies } = require('../utils/cookies');
|
||||
const { createFormData } = require('../utils/form-data');
|
||||
const { getOAuth2Token } = require('./oauth2');
|
||||
const protocolRegex = /^([-+\w]{1,25})(:?\/\/|:)/;
|
||||
const { NtlmClient } = require('axios-ntlm');
|
||||
const { addDigestInterceptor } = require('@usebruno/requests');
|
||||
@@ -303,6 +304,33 @@ const runSingleRequest = async function (
|
||||
}
|
||||
}
|
||||
|
||||
// Handle OAuth2 authentication
|
||||
if (request.oauth2) {
|
||||
try {
|
||||
const token = await getOAuth2Token(request.oauth2);
|
||||
if (token) {
|
||||
const { tokenPlacement = 'header', tokenHeaderPrefix = 'Bearer', tokenQueryKey = 'access_token' } = request.oauth2;
|
||||
|
||||
if (tokenPlacement === 'header') {
|
||||
request.headers['Authorization'] = `${tokenHeaderPrefix} ${token}`;
|
||||
} else if (tokenPlacement === 'url') {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
url.searchParams.set(tokenQueryKey, token);
|
||||
request.url = url.toString();
|
||||
} catch (error) {
|
||||
console.error('Error applying OAuth2 token to URL:', error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('OAuth2 token fetch error:', error.message);
|
||||
}
|
||||
|
||||
// Remove oauth2 config from request to prevent it from being sent
|
||||
delete request.oauth2;
|
||||
}
|
||||
|
||||
let response, responseTime;
|
||||
try {
|
||||
|
||||
|
||||
22
packages/bruno-cli/src/runner/tokenStore.js
Normal file
22
packages/bruno-cli/src/runner/tokenStore.js
Normal file
@@ -0,0 +1,22 @@
|
||||
// In-memory token store implementation for OAuth2 tokens
|
||||
const tokenStore = {
|
||||
tokens: new Map(),
|
||||
|
||||
// Save a token with optional expiry information
|
||||
async saveToken(serviceId, account, token) {
|
||||
this.tokens.set(`${serviceId}:${account}`, token);
|
||||
return true;
|
||||
},
|
||||
|
||||
// Get a token
|
||||
async getToken(serviceId, account) {
|
||||
return this.tokens.get(`${serviceId}:${account}`);
|
||||
},
|
||||
|
||||
// Delete a token
|
||||
async deleteToken(serviceId, account) {
|
||||
return this.tokens.delete(`${serviceId}:${account}`);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = tokenStore;
|
||||
@@ -310,6 +310,24 @@ const getTreePathFromCollectionToItem = (collection, _item) => {
|
||||
return path;
|
||||
};
|
||||
|
||||
const mergeAuth = (collection, request, requestTreePath) => {
|
||||
let collectionAuth = collection?.root?.request?.auth || { mode: 'none' };
|
||||
let effectiveAuth = collectionAuth;
|
||||
|
||||
for (let i of requestTreePath) {
|
||||
if (i.type === 'folder') {
|
||||
const folderAuth = i?.root?.request?.auth;
|
||||
if (folderAuth && folderAuth.mode && folderAuth.mode !== 'none' && folderAuth.mode !== 'inherit') {
|
||||
effectiveAuth = folderAuth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (request.auth && request.auth.mode === 'inherit') {
|
||||
request.auth = effectiveAuth;
|
||||
}
|
||||
}
|
||||
|
||||
const getAllRequestsInFolder = (folderItems = [], recursive = true) => {
|
||||
let requests = [];
|
||||
|
||||
@@ -461,8 +479,6 @@ const processCollectionItems = async (items = [], currentPath) => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
createCollectionJsonFromPathname,
|
||||
mergeHeaders,
|
||||
@@ -470,7 +486,8 @@ module.exports = {
|
||||
mergeScripts,
|
||||
findItemInCollection,
|
||||
getTreePathFromCollectionToItem,
|
||||
createCollectionFromBrunoObject,
|
||||
mergeAuth,
|
||||
getAllRequestsInFolder,
|
||||
getAllRequestsAtFolderRoot,
|
||||
createCollectionFromBrunoObject
|
||||
getAllRequestsAtFolderRoot
|
||||
}
|
||||
Reference in New Issue
Block a user