diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AdditionalParams/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AdditionalParams/StyledWrapper.js
new file mode 100644
index 000000000..712367fd7
--- /dev/null
+++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AdditionalParams/StyledWrapper.js
@@ -0,0 +1,66 @@
+import styled from 'styled-components';
+
+const StyledWrapper = styled.div`
+ .tabs {
+ .tab {
+ cursor: pointer;
+ padding: 4px 8px !important;
+ font-size: 12px;
+ border-radius: 4px;
+
+ &:hover {
+ background-color: ${(props) => props.theme.mode === 'dark' ? 'rgba(99, 102, 241, 0.1)' : 'rgba(99, 102, 241, 0.1)'};
+ }
+
+ &.active {
+ background-color: ${(props) => props.theme.mode === 'dark' ? 'rgba(99, 102, 241, 0.2)' : 'rgba(99, 102, 241, 0.1)'};
+ color: ${(props) => props.theme.mode === 'dark' ? '#6366f1' : '#4f46e5'};
+ font-weight: 500;
+ }
+ }
+ }
+
+ table {
+ width: 100%;
+ border-collapse: collapse;
+ font-weight: 600;
+ table-layout: fixed;
+
+ thead,
+ td {
+ border: 1px solid ${(props) => props.theme.table.border};
+ }
+
+ thead {
+ color: ${(props) => props.theme.table.thead.color};
+ font-size: 0.8125rem;
+ user-select: none;
+ }
+ td {
+ padding: 6px 10px;
+ }
+ }
+
+ .additional-parameter-sends-in-selector {
+ select {
+ height: 32px;
+ width: 100%;
+ border: 1px solid ${(props) => props.theme.input.border};
+ border-radius: 4px;
+ padding: 0 8px;
+
+ &:focus {
+ outline: none;
+ border-color: ${(props) => props.theme.mode === 'dark' ? '#6366f1' : '#4f46e5'};
+ }
+ }
+ }
+
+ .add-additional-param-actions {
+ &:hover {
+ color: ${(props) => props.theme.mode === 'dark' ? '#6366f1' : '#4f46e5'};
+ }
+ }
+`
+
+export default StyledWrapper;
\ No newline at end of file
diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AdditionalParams/index.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AdditionalParams/index.js
new file mode 100644
index 000000000..1d2f81bee
--- /dev/null
+++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AdditionalParams/index.js
@@ -0,0 +1,306 @@
+import { useDispatch } from "react-redux";
+import React, { useState } from 'react';
+import get from 'lodash/get';
+import { useTheme } from 'providers/Theme';
+import { IconPlus, IconTrash, IconAdjustmentsHorizontal } from '@tabler/icons';
+import { cloneDeep } from "lodash";
+import SingleLineEditor from "components/SingleLineEditor/index";
+import StyledWrapper from "./StyledWrapper";
+import Table from "components/Table/index";
+
+const AdditionalParams = ({ item = {}, request, updateAuth, collection, handleSave }) => {
+ const dispatch = useDispatch();
+ const { storedTheme } = useTheme();
+
+ const oAuth = get(request, 'auth.oauth2', {});
+ const {
+ grantType,
+ additionalParameters = {}
+ } = oAuth;
+
+ const [activeTab, setActiveTab] = useState(
+ (grantType == 'authorization_code' || grantType == 'implicit') ? 'authorization' : 'token'
+ );
+
+ const isEmptyParam = (param) => {
+ return !param.name.trim() && !param.value.trim();
+ };
+
+ const hasEmptyRow = () => {
+ const tabParams = additionalParameters[activeTab] || [];
+ return tabParams.some(isEmptyParam);
+ };
+
+ const updateAdditionalParameters = ({ updatedAdditionalParameters }) => {
+ const filteredParams = cloneDeep(updatedAdditionalParameters);
+
+ Object.keys(filteredParams).forEach(paramType => {
+ if (filteredParams[paramType]?.length) {
+ filteredParams[paramType] = filteredParams[paramType].filter(param =>
+ param.name.trim() || param.value.trim()
+ );
+
+ if (filteredParams[paramType].length === 0) {
+ delete filteredParams[paramType];
+ }
+ } else if (Array.isArray(filteredParams[paramType]) && filteredParams[paramType].length === 0) {
+ // Remove empty arrays
+ delete filteredParams[paramType];
+ }
+ });
+
+ dispatch(
+ updateAuth({
+ mode: 'oauth2',
+ collectionUid: collection.uid,
+ itemUid: item.uid,
+ content: {
+ ...oAuth,
+ additionalParameters: Object.keys(filteredParams).length > 0 ? filteredParams : undefined
+ }
+ })
+ );
+ }
+
+ const handleUpdateAdditionalParam = ({ paramType, key, paramIndex, value }) => {
+ const updatedAdditionalParameters = cloneDeep(additionalParameters);
+
+ if (!updatedAdditionalParameters[paramType]) {
+ updatedAdditionalParameters[paramType] = [];
+ }
+
+ if (!updatedAdditionalParameters[paramType][paramIndex]) {
+ updatedAdditionalParameters[paramType][paramIndex] = {
+ name: '',
+ value: '',
+ sendIn: 'headers',
+ enabled: true
+ };
+ }
+
+ updatedAdditionalParameters[paramType][paramIndex][key] = value;
+
+ // Only filter when updating a parameter
+ updateAdditionalParameters({ updatedAdditionalParameters });
+ }
+
+ const handleDeleteAdditionalParam = ({ paramType, paramIndex }) => {
+ const updatedAdditionalParameters = cloneDeep(additionalParameters);
+
+ if (updatedAdditionalParameters[paramType]?.length) {
+ updatedAdditionalParameters[paramType] = updatedAdditionalParameters[paramType].filter((_, index) => index !== paramIndex);
+
+ // If the array is now empty, ensure we're not sending empty arrays
+ if (updatedAdditionalParameters[paramType].length === 0) {
+ delete updatedAdditionalParameters[paramType];
+ }
+ }
+
+ updateAdditionalParameters({ updatedAdditionalParameters });
+ }
+
+ const handleAddNewAdditionalParam = () => {
+ // Prevent adding multiple empty rows
+ if (hasEmptyRow()) {
+ return;
+ }
+
+ const paramType = activeTab;
+ const localAdditionalParameters = cloneDeep(additionalParameters);
+
+ if (!localAdditionalParameters[paramType]) {
+ localAdditionalParameters[paramType] = [];
+ }
+
+ localAdditionalParameters[paramType] = [
+ ...localAdditionalParameters[paramType],
+ {
+ name: '',
+ value: '',
+ sendIn: 'headers',
+ enabled: true
+ }
+ ];
+
+ // Don't filter here to allow the empty row to display in UI
+ // But don't permanently store it in state until it has values
+ dispatch(
+ updateAuth({
+ mode: 'oauth2',
+ collectionUid: collection.uid,
+ itemUid: item.uid,
+ content: {
+ ...oAuth,
+ additionalParameters: localAdditionalParameters,
+ }
+ })
+ );
+ }
+
+ // Add a class to the Add Parameter button if it's disabled
+ const addButtonDisabled = hasEmptyRow();
+
+ // Define available tabs for each grant type
+ const getAvailableTabs = (grantType) => {
+ const tabConfig = {
+ 'authorization_code': ['authorization', 'token', 'refresh'],
+ 'implicit': ['authorization'],
+ 'password': ['token', 'refresh'],
+ 'client_credentials': ['token', 'refresh']
+ };
+ return tabConfig[grantType] || ['token', 'refresh'];
+ };
+
+ const availableTabs = getAvailableTabs(grantType);
+
+ const renderTab = (tabKey, tabLabel) => (
+
{/* Method and URL */}
{/* Headers */}
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
index a9a5af093..75b5ecc55 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
@@ -1589,7 +1589,7 @@ export const refreshOauth2Credentials = (payload) => async (dispatch, getState)
request.globalEnvironmentVariables = globalEnvironmentVariables;
return new Promise((resolve, reject) => {
window.ipcRenderer
- .invoke('renderer:refresh-oauth2-credentials', { request, collection })
+ .invoke('renderer:refresh-oauth2-credentials', { itemUid, request, collection })
.then(({ credentials, url, collectionUid, debugInfo, credentialsId }) => {
dispatch(
collectionAddOauth2CredentialsByUrl({
diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js
index c3b1c1048..30cf11111 100644
--- a/packages/bruno-electron/src/ipc/collection.js
+++ b/packages/bruno-electron/src/ipc/collection.js
@@ -973,9 +973,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
const processEnvVars = getProcessEnvVars(collectionUid);
const partialItem = { uid: itemUid };
const requestTreePath = getTreePathFromCollectionToItem(collection, partialItem);
- if (requestTreePath && requestTreePath.length > 0) {
- mergeVars(collection, requestCopy, requestTreePath);
- }
+ mergeVars(collection, requestCopy, requestTreePath);
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars);
const certsAndProxyConfig = await getCertsAndProxyConfig({
@@ -1096,7 +1094,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
}
});
- ipcMain.handle('renderer:refresh-oauth2-credentials', async (event, { request, collection }) => {
+ ipcMain.handle('renderer:refresh-oauth2-credentials', async (event, { itemUid, request, collection }) => {
try {
if (request.oauth2) {
let requestCopy = _.cloneDeep(request);
@@ -1104,7 +1102,11 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
const environment = _.find(environments, (e) => e.uid === activeEnvironmentUid);
const envVars = getEnvVars(environment);
const processEnvVars = getProcessEnvVars(collectionUid);
+ const partialItem = { uid: itemUid };
+ const requestTreePath = getTreePathFromCollectionToItem(collection, partialItem);
+ mergeVars(collection, requestCopy, requestTreePath);
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars);
+
const certsAndProxyConfig = await getCertsAndProxyConfig({
collectionUid,
request: requestCopy,
diff --git a/packages/bruno-electron/src/ipc/network/authorize-user-in-window.js b/packages/bruno-electron/src/ipc/network/authorize-user-in-window.js
index 0cf45d70c..4c671fb62 100644
--- a/packages/bruno-electron/src/ipc/network/authorize-user-in-window.js
+++ b/packages/bruno-electron/src/ipc/network/authorize-user-in-window.js
@@ -5,7 +5,7 @@ const matchesCallbackUrl = (url, callbackUrl) => {
return url ? url.href.startsWith(callbackUrl.href) : false;
};
-const authorizeUserInWindow = ({ authorizeUrl, callbackUrl, session, grantType = 'authorization_code' }) => {
+const authorizeUserInWindow = ({ authorizeUrl, callbackUrl, session, additionalHeaders = {}, grantType = 'authorization_code' }) => {
return new Promise(async (resolve, reject) => {
let finalUrl = null;
let debugInfo = {
@@ -75,6 +75,14 @@ const authorizeUserInWindow = ({ authorizeUrl, callbackUrl, session, grantType =
webSession.webRequest.onBeforeSendHeaders((details, callback) => {
const { id: requestId, requestHeaders, method, url } = details;
+
+ if (details.resourceType === 'mainFrame' && Object.keys(additionalHeaders).length > 0) {
+ // Add our custom headers
+ for (const [name, value] of Object.entries(additionalHeaders)) {
+ requestHeaders[name] = value;
+ }
+ }
+
if (currentMainRequest?.requestId === requestId) {
currentMainRequest.request = {
url,
@@ -211,4 +219,4 @@ const authorizeUserInWindow = ({ authorizeUrl, callbackUrl, session, grantType =
});
};
-module.exports = { authorizeUserInWindow, matchesCallbackUrl };
\ No newline at end of file
+module.exports = { authorizeUserInWindow, matchesCallbackUrl };
diff --git a/packages/bruno-electron/src/ipc/network/interpolate-vars.js b/packages/bruno-electron/src/ipc/network/interpolate-vars.js
index db7e56b8e..4aef1e73f 100644
--- a/packages/bruno-electron/src/ipc/network/interpolate-vars.js
+++ b/packages/bruno-electron/src/ipc/network/interpolate-vars.js
@@ -243,6 +243,39 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
default:
break;
}
+
+ // Interpolate additional parameters for all OAuth2 grant types
+ if (request.oauth2.additionalParameters) {
+ // Interpolate authorization parameters
+ if (Array.isArray(request.oauth2.additionalParameters.authorization)) {
+ request.oauth2.additionalParameters.authorization.forEach(param => {
+ if (param && param.enabled !== false) {
+ param.name = _interpolate(param.name) || '';
+ param.value = _interpolate(param.value) || '';
+ }
+ });
+ }
+
+ // Interpolate token parameters
+ if (Array.isArray(request.oauth2.additionalParameters.token)) {
+ request.oauth2.additionalParameters.token.forEach(param => {
+ if (param && param.enabled !== false) {
+ param.name = _interpolate(param.name) || '';
+ param.value = _interpolate(param.value) || '';
+ }
+ });
+ }
+
+ // Interpolate refresh parameters
+ if (Array.isArray(request.oauth2.additionalParameters.refresh)) {
+ request.oauth2.additionalParameters.refresh.forEach(param => {
+ if (param && param.enabled !== false) {
+ param.name = _interpolate(param.name) || '';
+ param.value = _interpolate(param.value) || '';
+ }
+ });
+ }
+ }
}
// interpolate vars for aws sigv4 auth
diff --git a/packages/bruno-electron/src/ipc/network/prepare-request.js b/packages/bruno-electron/src/ipc/network/prepare-request.js
index 0a95657c7..375422164 100644
--- a/packages/bruno-electron/src/ipc/network/prepare-request.js
+++ b/packages/bruno-electron/src/ipc/network/prepare-request.js
@@ -88,7 +88,8 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
tokenHeaderPrefix: get(collectionAuth, 'oauth2.tokenHeaderPrefix'),
tokenQueryKey: get(collectionAuth, 'oauth2.tokenQueryKey'),
autoFetchToken: get(collectionAuth, 'oauth2.autoFetchToken'),
- autoRefreshToken: get(collectionAuth, 'oauth2.autoRefreshToken')
+ autoRefreshToken: get(collectionAuth, 'oauth2.autoRefreshToken'),
+ additionalParameters: get(collectionAuth, 'oauth2.additionalParameters', { authorization: [], token: [], refresh: [] })
};
break;
case 'authorization_code':
@@ -109,7 +110,8 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
tokenHeaderPrefix: get(collectionAuth, 'oauth2.tokenHeaderPrefix'),
tokenQueryKey: get(collectionAuth, 'oauth2.tokenQueryKey'),
autoFetchToken: get(collectionAuth, 'oauth2.autoFetchToken'),
- autoRefreshToken: get(collectionAuth, 'oauth2.autoRefreshToken')
+ autoRefreshToken: get(collectionAuth, 'oauth2.autoRefreshToken'),
+ additionalParameters: get(collectionAuth, 'oauth2.additionalParameters', { authorization: [], token: [], refresh: [] })
};
break;
case 'implicit':
@@ -124,7 +126,8 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
tokenPlacement: get(collectionAuth, 'oauth2.tokenPlacement'),
tokenHeaderPrefix: get(collectionAuth, 'oauth2.tokenHeaderPrefix'),
tokenQueryKey: get(collectionAuth, 'oauth2.tokenQueryKey'),
- autoFetchToken: get(collectionAuth, 'oauth2.autoFetchToken')
+ autoFetchToken: get(collectionAuth, 'oauth2.autoFetchToken'),
+ additionalParameters: get(collectionAuth, 'oauth2.additionalParameters', { authorization: [], token: [], refresh: [] })
};
break;
case 'client_credentials':
@@ -141,7 +144,8 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
tokenHeaderPrefix: get(collectionAuth, 'oauth2.tokenHeaderPrefix'),
tokenQueryKey: get(collectionAuth, 'oauth2.tokenQueryKey'),
autoFetchToken: get(collectionAuth, 'oauth2.autoFetchToken'),
- autoRefreshToken: get(collectionAuth, 'oauth2.autoRefreshToken')
+ autoRefreshToken: get(collectionAuth, 'oauth2.autoRefreshToken'),
+ additionalParameters: get(collectionAuth, 'oauth2.additionalParameters', { authorization: [], token: [], refresh: [] })
};
break;
}
@@ -201,7 +205,8 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
tokenHeaderPrefix: get(request, 'auth.oauth2.tokenHeaderPrefix'),
tokenQueryKey: get(request, 'auth.oauth2.tokenQueryKey'),
autoFetchToken: get(request, 'auth.oauth2.autoFetchToken'),
- autoRefreshToken: get(request, 'auth.oauth2.autoRefreshToken')
+ autoRefreshToken: get(request, 'auth.oauth2.autoRefreshToken'),
+ additionalParameters: get(request, 'auth.oauth2.additionalParameters', { authorization: [], token: [], refresh: [] })
};
break;
case 'authorization_code':
@@ -222,7 +227,8 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
tokenHeaderPrefix: get(request, 'auth.oauth2.tokenHeaderPrefix'),
tokenQueryKey: get(request, 'auth.oauth2.tokenQueryKey'),
autoFetchToken: get(request, 'auth.oauth2.autoFetchToken'),
- autoRefreshToken: get(request, 'auth.oauth2.autoRefreshToken')
+ autoRefreshToken: get(request, 'auth.oauth2.autoRefreshToken'),
+ additionalParameters: get(request, 'auth.oauth2.additionalParameters', { authorization: [], token: [], refresh: [] })
};
break;
case 'implicit':
@@ -237,7 +243,8 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
tokenPlacement: get(request, 'auth.oauth2.tokenPlacement'),
tokenHeaderPrefix: get(request, 'auth.oauth2.tokenHeaderPrefix'),
tokenQueryKey: get(request, 'auth.oauth2.tokenQueryKey'),
- autoFetchToken: get(request, 'auth.oauth2.autoFetchToken')
+ autoFetchToken: get(request, 'auth.oauth2.autoFetchToken'),
+ additionalParameters: get(request, 'auth.oauth2.additionalParameters', { authorization: [], token: [], refresh: [] })
};
break;
case 'client_credentials':
@@ -254,7 +261,8 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
tokenHeaderPrefix: get(request, 'auth.oauth2.tokenHeaderPrefix'),
tokenQueryKey: get(request, 'auth.oauth2.tokenQueryKey'),
autoFetchToken: get(request, 'auth.oauth2.autoFetchToken'),
- autoRefreshToken: get(request, 'auth.oauth2.autoRefreshToken')
+ autoRefreshToken: get(request, 'auth.oauth2.autoRefreshToken'),
+ additionalParameters: get(request, 'auth.oauth2.additionalParameters', { authorization: [], token: [], refresh: [] })
};
break;
}
diff --git a/packages/bruno-electron/src/utils/collection.js b/packages/bruno-electron/src/utils/collection.js
index 2d5bae473..1ab774211 100644
--- a/packages/bruno-electron/src/utils/collection.js
+++ b/packages/bruno-electron/src/utils/collection.js
@@ -47,7 +47,7 @@ const mergeHeaders = (collection, request, requestTreePath) => {
request.headers = Array.from(headers, ([name, value]) => ({ name, value, enabled: true }));
};
-const mergeVars = (collection, request, requestTreePath) => {
+const mergeVars = (collection, request, requestTreePath = []) => {
let reqVars = new Map();
let collectionRequestVars = get(collection, 'root.request.vars.req', []);
let collectionVariables = {};
diff --git a/packages/bruno-electron/src/utils/oauth2.js b/packages/bruno-electron/src/utils/oauth2.js
index c4e01e364..61d3d80c3 100644
--- a/packages/bruno-electron/src/utils/oauth2.js
+++ b/packages/bruno-electron/src/utils/oauth2.js
@@ -148,6 +148,7 @@ const getOAuth2TokenUsingAuthorizationCode = async ({ request, collectionUid, fo
credentialsId,
autoRefreshToken,
autoFetchToken,
+ additionalParameters,
} = oAuth;
const url = requestCopy?.oauth2?.accessTokenUrl;
@@ -242,7 +243,7 @@ const getOAuth2TokenUsingAuthorizationCode = async ({ request, collectionUid, fo
}
// Fetch new token process
- const { authorizationCode, debugInfo } = await getOAuth2AuthorizationCode(requestCopy, codeChallenge, collectionUid);
+ let { authorizationCode, debugInfo } = await getOAuth2AuthorizationCode(requestCopy, codeChallenge, collectionUid);
let axiosRequestConfig = {};
axiosRequestConfig.method = 'POST';
@@ -268,9 +269,13 @@ const getOAuth2TokenUsingAuthorizationCode = async ({ request, collectionUid, fo
data['code_verifier'] = codeVerifier;
}
- axiosRequestConfig.data = qs.stringify(data);
axiosRequestConfig.url = url;
axiosRequestConfig.responseType = 'arraybuffer';
+ // Apply additional parameters to token request
+ if (additionalParameters?.token?.length) {
+ applyAdditionalParameters(axiosRequestConfig, data, additionalParameters.token);
+ }
+ axiosRequestConfig.data = qs.stringify(data);
try {
const { credentials, requestDetails } = await getCredentialsFromTokenUrl({ requestConfig: axiosRequestConfig, certsAndProxyConfig });
@@ -292,7 +297,7 @@ const getOAuth2TokenUsingAuthorizationCode = async ({ request, collectionUid, fo
const getOAuth2AuthorizationCode = (request, codeChallenge, collectionUid) => {
return new Promise(async (resolve, reject) => {
const { oauth2 } = request;
- const { callbackUrl, clientId, authorizationUrl, scope, state, pkce, accessTokenUrl } = oauth2;
+ const { callbackUrl, clientId, authorizationUrl, scope, state, pkce, accessTokenUrl, additionalParameters } = oauth2;
const authorizationUrlWithQueryParams = new URL(authorizationUrl);
authorizationUrlWithQueryParams.searchParams.append('response_type', 'code');
@@ -310,12 +315,23 @@ const getOAuth2AuthorizationCode = (request, codeChallenge, collectionUid) => {
if (state) {
authorizationUrlWithQueryParams.searchParams.append('state', state);
}
+ if (additionalParameters?.authorization?.length) {
+ additionalParameters.authorization.forEach(param => {
+ if (param.enabled && param.name) {
+ if (param.sendIn === 'queryparams') {
+ authorizationUrlWithQueryParams.searchParams.append(param.name, param.value || '');
+ }
+ }
+ });
+ }
+
try {
const authorizeUrl = authorizationUrlWithQueryParams.toString();
const { authorizationCode, debugInfo } = await authorizeUserInWindow({
authorizeUrl,
callbackUrl,
- session: oauth2Store.getSessionIdOfCollection({ collectionUid, url: accessTokenUrl })
+ session: oauth2Store.getSessionIdOfCollection({ collectionUid, url: accessTokenUrl }),
+ additionalHeaders: getAdditionalHeaders(additionalParameters?.authorization)
});
resolve({ authorizationCode, debugInfo });
} catch (err) {
@@ -324,6 +340,21 @@ const getOAuth2AuthorizationCode = (request, codeChallenge, collectionUid) => {
});
};
+const getAdditionalHeaders = (params) => {
+ if (!params || !params.length) {
+ return {};
+ }
+
+ const headers = {};
+ params.forEach(param => {
+ if (param.enabled && param.name && param.sendIn === 'headers') {
+ headers[param.name] = param.value || '';
+ }
+ });
+
+ return headers;
+};
+
// CLIENT CREDENTIALS
const getOAuth2TokenUsingClientCredentials = async ({ request, collectionUid, forceFetch = false, certsAndProxyConfig }) => {
@@ -337,6 +368,7 @@ const getOAuth2TokenUsingClientCredentials = async ({ request, collectionUid, fo
credentialsId,
autoRefreshToken,
autoFetchToken,
+ additionalParameters,
} = oAuth;
const url = requestCopy?.oauth2?.accessTokenUrl;
@@ -433,9 +465,12 @@ const getOAuth2TokenUsingClientCredentials = async ({ request, collectionUid, fo
if (scope && scope.trim() !== '') {
data.scope = scope;
}
- axiosRequestConfig.data = qs.stringify(data);
axiosRequestConfig.url = url;
axiosRequestConfig.responseType = 'arraybuffer';
+ if (additionalParameters?.token?.length) {
+ applyAdditionalParameters(axiosRequestConfig, data, additionalParameters.token);
+ }
+ axiosRequestConfig.data = qs.stringify(data);
let debugInfo = { data: [] };
try {
const { credentials, requestDetails } = await getCredentialsFromTokenUrl({ requestConfig: axiosRequestConfig, certsAndProxyConfig });
@@ -462,6 +497,7 @@ const getOAuth2TokenUsingPasswordCredentials = async ({ request, collectionUid,
credentialsId,
autoRefreshToken,
autoFetchToken,
+ additionalParameters,
} = oAuth;
const url = requestCopy?.oauth2?.accessTokenUrl;
@@ -578,9 +614,12 @@ const getOAuth2TokenUsingPasswordCredentials = async ({ request, collectionUid,
if (scope && scope.trim() !== '') {
data.scope = scope;
}
- axiosRequestConfig.data = qs.stringify(data);
axiosRequestConfig.url = url;
axiosRequestConfig.responseType = 'arraybuffer';
+ if (additionalParameters?.token?.length) {
+ applyAdditionalParameters(axiosRequestConfig, data, additionalParameters.token);
+ }
+ axiosRequestConfig.data = qs.stringify(data);
let debugInfo = { data: [] };
try {
const { credentials, requestDetails } = await getCredentialsFromTokenUrl({ requestConfig: axiosRequestConfig, certsAndProxyConfig });
@@ -594,7 +633,7 @@ const getOAuth2TokenUsingPasswordCredentials = async ({ request, collectionUid,
const refreshOauth2Token = async ({ requestCopy, collectionUid, certsAndProxyConfig }) => {
const oAuth = get(requestCopy, 'oauth2', {});
- const { clientId, clientSecret, credentialsId, credentialsPlacement } = oAuth;
+ const { clientId, clientSecret, credentialsId, credentialsPlacement, additionalParameters } = oAuth;
const url = oAuth.refreshTokenUrl ? oAuth.refreshTokenUrl : oAuth.accessTokenUrl;
const credentials = getStoredOauth2Credentials({ collectionUid, url, credentialsId });
@@ -622,9 +661,12 @@ const refreshOauth2Token = async ({ requestCopy, collectionUid, certsAndProxyCon
if (credentialsPlacement === "basic_auth_header") {
axiosRequestConfig.headers['Authorization'] = `Basic ${Buffer.from(`${encodeURIComponent(clientId)}:${encodeURIComponent(clientSecret)}`).toString('base64')}`;
}
- axiosRequestConfig.data = qs.stringify(data);
axiosRequestConfig.url = url;
axiosRequestConfig.responseType = 'arraybuffer';
+ if (additionalParameters?.refresh?.length) {
+ applyAdditionalParameters(axiosRequestConfig, data, additionalParameters.refresh);
+ }
+ axiosRequestConfig.data = qs.stringify(data);
let debugInfo = { data: [] };
try {
const { credentials, requestDetails } = await getCredentialsFromTokenUrl({ requestConfig: axiosRequestConfig, certsAndProxyConfig });
@@ -659,6 +701,36 @@ const generateCodeChallenge = (codeVerifier) => {
return base64Hash;
};
+// Apply additional parameters to a request
+const applyAdditionalParameters = (requestCopy, data, params = []) => {
+ params.forEach(param => {
+ if (!param.enabled || !param.name) {
+ return;
+ }
+
+ switch (param.sendIn) {
+ case 'headers':
+ requestCopy.headers[param.name] = param.value || '';
+ break;
+ case 'queryparams':
+ // For query params, add to URL
+ try {
+ let url = new URL(requestCopy.url);
+ url.searchParams.append(param.name, param.value || '');
+ requestCopy.url = url.href;
+ }
+ catch (error) {
+ console.error('invalid token/refresh url', requestCopy.url);
+ }
+ break;
+ case 'body':
+ // For body, add to data object
+ data[param.name] = param.value || '';
+ break;
+ }
+ });
+}
+
const getOAuth2TokenUsingImplicitGrant = async ({ request, collectionUid, forceFetch = false }) => {
const { oauth2 = {} } = request;
const {
@@ -668,7 +740,8 @@ const getOAuth2TokenUsingImplicitGrant = async ({ request, collectionUid, forceF
state = '',
callbackUrl,
credentialsId = 'credentials',
- autoFetchToken = true
+ autoFetchToken = true,
+ additionalParameters
} = oauth2;
// Validate required fields
@@ -753,6 +826,15 @@ const getOAuth2TokenUsingImplicitGrant = async ({ request, collectionUid, forceF
if (state) {
authorizationUrlWithQueryParams.searchParams.append('state', state);
}
+ if (additionalParameters?.authorization?.length) {
+ additionalParameters.authorization.forEach(param => {
+ if (param.enabled && param.name) {
+ if (param.sendIn === 'queryparams') {
+ authorizationUrlWithQueryParams.searchParams.append(param.name, param.value || '');
+ }
+ }
+ });
+ }
const authorizeUrl = authorizationUrlWithQueryParams.toString();
@@ -761,7 +843,8 @@ const getOAuth2TokenUsingImplicitGrant = async ({ request, collectionUid, forceF
authorizeUrl,
callbackUrl,
session: oauth2Store.getSessionIdOfCollection({ collectionUid, url: authorizationUrl }),
- grantType: 'implicit'
+ grantType: 'implicit',
+ additionalHeaders: getAdditionalHeaders(additionalParameters?.authorization)
});
if (!implicitTokens || !implicitTokens.access_token) {
diff --git a/packages/bruno-filestore/src/formats/bru/index.ts b/packages/bruno-filestore/src/formats/bru/index.ts
index c5358d23f..79095e945 100644
--- a/packages/bruno-filestore/src/formats/bru/index.ts
+++ b/packages/bruno-filestore/src/formats/bru/index.ts
@@ -7,6 +7,7 @@ import {
collectionBruToJson as _collectionBruToJson,
jsonToCollectionBru as _jsonToCollectionBru
} from '@usebruno/lang';
+import { getOauth2AdditionalParameters } from './utils/oauth2-additional-params';
export const bruRequestToJson = (data: string | any, parsed: boolean = false): any => {
try {
@@ -72,6 +73,16 @@ export const bruRequestToJson = (data: string | any, parsed: boolean = false): a
transformedJson.request.body.mode = _.get(json, 'http.body', 'none');
}
+ // add oauth2 additional parameters if they exist
+ const hasOauth2GrantType = json?.auth?.oauth2?.grantType;
+ if (hasOauth2GrantType) {
+ const additionalParameters = getOauth2AdditionalParameters(json);
+ const hasAdditionalParameters = Object.keys(additionalParameters || {}).length > 0;
+ if (hasAdditionalParameters) {
+ transformedJson.request.auth.oauth2.additionalParameters = additionalParameters;
+ }
+ }
+
return transformedJson;
} catch (error) {
throw error;
@@ -200,6 +211,16 @@ export const bruCollectionToJson = (data: string | any, parsed: boolean = false)
}
}
+ // add oauth2 additional parameters if they exist
+ const hasOauth2GrantType = json?.auth?.oauth2?.grantType;
+ if (hasOauth2GrantType) {
+ const additionalParameters = getOauth2AdditionalParameters(json);
+ const hasAdditionalParameters = Object.keys(additionalParameters).length > 0;
+ if (hasAdditionalParameters) {
+ transformedJson.request.auth.oauth2.additionalParameters = additionalParameters;
+ }
+ }
+
return transformedJson;
} catch (error) {
return Promise.reject(error);
diff --git a/packages/bruno-filestore/src/formats/bru/tests/fixtures/oauth2-additional-params.js b/packages/bruno-filestore/src/formats/bru/tests/fixtures/oauth2-additional-params.js
new file mode 100644
index 000000000..c3498340c
--- /dev/null
+++ b/packages/bruno-filestore/src/formats/bru/tests/fixtures/oauth2-additional-params.js
@@ -0,0 +1,116 @@
+const getBruJsonWithAdditionalParams = (grantType) => ({
+ "meta": {
+ "name": "OAuth2 Additional Params Test",
+ "type": "http",
+ "seq": 1
+ },
+ "http": {
+ "method": "get",
+ "url": "https://api.usebruno.com/protected"
+ },
+ "auth": {
+ "oauth2": {
+ "grantType": grantType,
+ },
+ },
+ "oauth2_additional_parameters_auth_req_headers": [
+ {
+ "name": "auth-header",
+ "value": "auth-header-value",
+ "enabled": true
+ },
+ {
+ "name": "disabled-auth-header",
+ "value": "disabled-auth-header-value",
+ "enabled": false
+ }
+ ],
+ "oauth2_additional_parameters_auth_req_queryparams": [
+ {
+ "name": "auth-query-param",
+ "value": "auth-query-param-value",
+ "enabled": true
+ },
+ {
+ "name": "disabled-auth-query-param",
+ "value": "disabled-auth-query-param-value",
+ "enabled": false
+ }
+ ],
+ "oauth2_additional_parameters_access_token_req_headers": [
+ {
+ "name": "token-header",
+ "value": "token-header-value",
+ "enabled": true
+ },
+ {
+ "name": "disabled-token-header",
+ "value": "disabled-token-header-value",
+ "enabled": false
+ }
+ ],
+ "oauth2_additional_parameters_access_token_req_queryparams": [
+ {
+ "name": "token-query-param",
+ "value": "token-query-param-value",
+ "enabled": true
+ },
+ {
+ "name": "disabled-token-query-param",
+ "value": "disabled-token-query-param-value",
+ "enabled": false
+ }
+ ],
+ "oauth2_additional_parameters_access_token_req_bodyvalues": [
+ {
+ "name": "token-body",
+ "value": "token-body-value",
+ "enabled": true
+ },
+ {
+ "name": "disabled-token-body",
+ "value": "disabled-token-body-value",
+ "enabled": false
+ }
+ ],
+ "oauth2_additional_parameters_refresh_token_req_headers": [
+ {
+ "name": "refresh-header",
+ "value": "refresh-header-value",
+ "enabled": true
+ },
+ {
+ "name": "disabled-refresh-header",
+ "value": "disabled-refresh-header-value",
+ "enabled": false
+ }
+ ],
+ "oauth2_additional_parameters_refresh_token_req_queryparams": [
+ {
+ "name": "refresh-query-param",
+ "value": "refresh-query-param-value",
+ "enabled": true
+ },
+ {
+ "name": "disabled-refresh-query-param",
+ "value": "disabled-refresh-query-param-value",
+ "enabled": false
+ }
+ ],
+ "oauth2_additional_parameters_refresh_token_req_bodyvalues": [
+ {
+ "name": "refresh-body",
+ "value": "refresh-body-value",
+ "enabled": true
+ },
+ {
+ "name": "disabled-refresh-body",
+ "value": "disabled-refresh-body-value",
+ "enabled": false
+ }
+ ]
+})
+
+export {
+ getBruJsonWithAdditionalParams
+};
diff --git a/packages/bruno-filestore/src/formats/bru/tests/oauth2-additional-params.spec.js b/packages/bruno-filestore/src/formats/bru/tests/oauth2-additional-params.spec.js
new file mode 100644
index 000000000..707c6127c
--- /dev/null
+++ b/packages/bruno-filestore/src/formats/bru/tests/oauth2-additional-params.spec.js
@@ -0,0 +1,45 @@
+const { getOauth2AdditionalParameters } = require('../utils/oauth2-additional-params');
+const { bruRequestToJson, bruCollectionToJson } = require('../index');
+const { getBruJsonWithAdditionalParams } = require('./fixtures/oauth2-additional-params');
+
+describe('getOauth2AdditionalParameters', () => {
+ it('authorization_code', () => {
+ const additionalParameters = getOauth2AdditionalParameters(getBruJsonWithAdditionalParams('authorization_code'));
+ expect(additionalParameters.authorization).toHaveLength(4);
+ expect(additionalParameters.token).toHaveLength(6);
+ expect(additionalParameters.refresh).toHaveLength(6);
+
+ expect(additionalParameters.authorization.map(p => p.sendIn).sort()).toEqual(['headers', 'headers', 'queryparams', 'queryparams']);
+ expect(additionalParameters.token.map(p => p.sendIn).sort()).toEqual(['body', 'body', 'headers', 'headers', 'queryparams', 'queryparams']);
+ expect(additionalParameters.refresh.map(p => p.sendIn).sort()).toEqual(['body', 'body', 'headers', 'headers', 'queryparams', 'queryparams']);
+ });
+
+ it('client_credentials', () => {
+ const additionalParameters = getOauth2AdditionalParameters(getBruJsonWithAdditionalParams('client_credentials'));
+ expect(additionalParameters.authorization).toBeUndefined();
+ expect(additionalParameters.token).toHaveLength(6);
+ expect(additionalParameters.refresh).toHaveLength(6);
+
+ expect(additionalParameters.token.map(p => p.sendIn).sort()).toEqual(['body', 'body', 'headers', 'headers', 'queryparams', 'queryparams']);
+ expect(additionalParameters.refresh.map(p => p.sendIn).sort()).toEqual(['body', 'body', 'headers', 'headers', 'queryparams', 'queryparams']);
+ });
+
+ it('password', () => {
+ const additionalParameters = getOauth2AdditionalParameters(getBruJsonWithAdditionalParams('password'));
+ expect(additionalParameters.authorization).toBeUndefined();
+ expect(additionalParameters.token).toHaveLength(6);
+ expect(additionalParameters.refresh).toHaveLength(6);
+
+ expect(additionalParameters.token.map(p => p.sendIn).sort()).toEqual(['body', 'body', 'headers', 'headers', 'queryparams', 'queryparams']);
+ expect(additionalParameters.refresh.map(p => p.sendIn).sort()).toEqual(['body', 'body', 'headers', 'headers', 'queryparams', 'queryparams']);
+ });
+
+ it('implicit', () => {
+ const additionalParameters = getOauth2AdditionalParameters(getBruJsonWithAdditionalParams('implicit'));
+ expect(additionalParameters.authorization).toHaveLength(4);
+ expect(additionalParameters.token).toBeUndefined();
+ expect(additionalParameters.refresh).toBeUndefined();
+
+ expect(additionalParameters.authorization.map(p => p.sendIn).sort()).toEqual(['headers', 'headers', 'queryparams', 'queryparams']);
+ });
+});
\ No newline at end of file
diff --git a/packages/bruno-filestore/src/formats/bru/utils/oauth2-additional-params.ts b/packages/bruno-filestore/src/formats/bru/utils/oauth2-additional-params.ts
new file mode 100644
index 000000000..54a613807
--- /dev/null
+++ b/packages/bruno-filestore/src/formats/bru/utils/oauth2-additional-params.ts
@@ -0,0 +1,141 @@
+type T_Oauth2ParameterType = 'authorization' | 'token' | 'refresh';
+type T_Oauth2ParameterSendInType = 'headers' | 'queryparams' | 'body';
+
+export interface T_OAuth2AdditionalParam {
+ name: string;
+ value: string;
+ enabled: boolean;
+ sendIn: T_Oauth2ParameterSendInType
+}
+
+export interface T_OAuth2AdditionalParameters {
+ authorization?: T_OAuth2AdditionalParam[];
+ token?: T_OAuth2AdditionalParam[];
+ refresh?: T_OAuth2AdditionalParam[];
+}
+
+export interface T_Oauth2Auth {
+ grantType: string;
+ additionalParameters?: T_OAuth2AdditionalParameters;
+}
+
+export interface T_BruJson {
+ auth: {
+ oauth2: T_Oauth2Auth;
+ };
+ oauth2_additional_parameters_auth_req_headers?: any[];
+ oauth2_additional_parameters_auth_req_queryparams?: any[];
+ oauth2_additional_parameters_access_token_req_headers?: any[];
+ oauth2_additional_parameters_access_token_req_queryparams?: any[];
+ oauth2_additional_parameters_access_token_req_bodyvalues?: any[];
+ oauth2_additional_parameters_refresh_token_req_headers?: any[];
+ oauth2_additional_parameters_refresh_token_req_queryparams?: any[];
+ oauth2_additional_parameters_refresh_token_req_bodyvalues?: any[];
+}
+
+interface T_Oauth2ParameterMapping {
+ type: T_Oauth2ParameterType;
+ sendIn: T_Oauth2ParameterSendInType;
+ source: keyof T_BruJson;
+}
+
+const PARAMETER_MAPPINGS: T_Oauth2ParameterMapping[] = [
+ // Authorization parameters (only for authorization_code grant type)
+ { type: 'authorization', sendIn: 'headers', source: 'oauth2_additional_parameters_auth_req_headers' },
+ { type: 'authorization', sendIn: 'queryparams', source: 'oauth2_additional_parameters_auth_req_queryparams' },
+
+ // Token parameters (for all grant types)
+ { type: 'token', sendIn: 'headers', source: 'oauth2_additional_parameters_access_token_req_headers' },
+ { type: 'token', sendIn: 'queryparams', source: 'oauth2_additional_parameters_access_token_req_queryparams' },
+ { type: 'token', sendIn: 'body', source: 'oauth2_additional_parameters_access_token_req_bodyvalues' },
+
+ // Refresh parameters (for grant types that support refresh)
+ { type: 'refresh', sendIn: 'headers', source: 'oauth2_additional_parameters_refresh_token_req_headers' },
+ { type: 'refresh', sendIn: 'queryparams', source: 'oauth2_additional_parameters_refresh_token_req_queryparams' },
+ { type: 'refresh', sendIn: 'body', source: 'oauth2_additional_parameters_refresh_token_req_bodyvalues' },
+];
+
+/**
+ * Maps source parameters to T_OAuth2AdditionalParam format
+ */
+const mapParametersFromSource = (sourceParams: any[], sendIn: T_Oauth2ParameterSendInType): T_OAuth2AdditionalParam[] => {
+ if (!sourceParams?.length) {
+ return [];
+ }
+
+ return sourceParams.map(param => ({
+ ...param,
+ sendIn
+ }));
+};
+
+/**
+ * Checks if a parameter type should be included based on grant type
+ */
+const shouldIncludeParameterType = (type: T_Oauth2ParameterType, grantType: string): boolean => {
+ // Authorization parameters are only valid for authorization_code grant type
+ if (type === 'authorization') {
+ return grantType === 'authorization_code' || grantType === 'implicit';
+ }
+
+ if (type === 'token' || type === 'refresh') {
+ return grantType !== 'implicit';
+ }
+
+ // Token and refresh parameters are valid for all grant types
+ return true;
+};
+
+/**
+ * Collects all parameters for a specific type (authorization, token, or refresh)
+ */
+const collectParametersForType = (
+ json: T_BruJson,
+ type: T_Oauth2ParameterType,
+ grantType: string
+): T_OAuth2AdditionalParam[] => {
+ if (!shouldIncludeParameterType(type, grantType)) {
+ return [];
+ }
+
+ const relevantMappings = PARAMETER_MAPPINGS.filter(mapping => mapping.type === type);
+ const allParams: T_OAuth2AdditionalParam[] = [];
+
+ for (const mapping of relevantMappings) {
+ const sourceParams = json[mapping.source] as any[];
+ const mappedParams = mapParametersFromSource(sourceParams, mapping.sendIn);
+ allParams.push(...mappedParams);
+ }
+
+ return allParams;
+};
+
+/**
+ * This function extracts OAuth2 additional parameters from various sources in the bru json data and organizes
+ * them into a structured format based on their usage context (authorization, token, refresh).
+ *
+ * @param json - json object containing OAuth2 configuration and additional parameters
+ * @returns OAuth2 additional parameters
+ */
+export const getOauth2AdditionalParameters = (json: T_BruJson): T_OAuth2AdditionalParameters => {
+ const grantType = json.auth.oauth2.grantType;
+ const additionalParameters: T_OAuth2AdditionalParameters = {};
+
+ try {
+ // Collect parameters for each type
+ const parameterTypes: T_Oauth2ParameterType[] = ['authorization', 'token', 'refresh'];
+
+ for (const type of parameterTypes) {
+ const params = collectParametersForType(json, type, grantType);
+ if (params.length > 0) {
+ additionalParameters[type] = params;
+ }
+ }
+ }
+ catch(error) {
+ console.error(error);
+ console.error("Error while getting the oauth2 additional parameters!");
+ }
+
+ return additionalParameters;
+};
\ No newline at end of file
diff --git a/packages/bruno-lang/v2/src/bruToJson.js b/packages/bruno-lang/v2/src/bruToJson.js
index 68c2a6916..baa5b4ead 100644
--- a/packages/bruno-lang/v2/src/bruToJson.js
+++ b/packages/bruno-lang/v2/src/bruToJson.js
@@ -30,11 +30,17 @@ const { safeParseJson, outdentString } = require('./utils');
*/
const grammar = ohm.grammar(`Bru {
BruFile = (meta | http | grpc | query | params | headers | metadata | auths | bodies | varsandassert | script | tests | settings | docs)*
- auths = authawsv4 | authbasic | authbearer | authdigest | authNTLM | authOAuth2 | authwsse | authapikey
+ auths = authawsv4 | authbasic | authbearer | authdigest | authNTLM | authOAuth2 | authwsse | authapikey | authOauth2Configs
bodies = bodyjson | bodytext | bodyxml | bodysparql | bodygraphql | bodygraphqlvars | bodyforms | body | bodygrpc
bodyforms = bodyformurlencoded | bodymultipart | bodyfile
params = paramspath | paramsquery
-
+
+ // Oauth2 additional parameters
+ authOauth2Configs = oauth2AuthReqConfig | oauth2AccessTokenReqConfig | oauth2RefreshTokenReqConfig
+ oauth2AuthReqConfig = oauth2AuthReqHeaders | oauth2AuthReqQueryParams
+ oauth2AccessTokenReqConfig = oauth2AccessTokenReqHeaders | oauth2AccessTokenReqQueryParams | oauth2AccessTokenReqBody
+ oauth2RefreshTokenReqConfig = oauth2RefreshTokenReqHeaders | oauth2RefreshTokenReqQueryParams | oauth2RefreshTokenReqBody
+
nl = "\\r"? "\\n"
st = " " | "\\t"
stnl = st | nl
@@ -109,6 +115,15 @@ const grammar = ohm.grammar(`Bru {
authwsse = "auth:wsse" dictionary
authapikey = "auth:apikey" dictionary
+ oauth2AuthReqHeaders = "auth:oauth2:additional_params:auth_req:headers" dictionary
+ oauth2AuthReqQueryParams = "auth:oauth2:additional_params:auth_req:queryparams" dictionary
+ oauth2AccessTokenReqHeaders = "auth:oauth2:additional_params:access_token_req:headers" dictionary
+ oauth2AccessTokenReqQueryParams = "auth:oauth2:additional_params:access_token_req:queryparams" dictionary
+ oauth2AccessTokenReqBody = "auth:oauth2:additional_params:access_token_req:body" dictionary
+ oauth2RefreshTokenReqHeaders = "auth:oauth2:additional_params:refresh_token_req:headers" dictionary
+ oauth2RefreshTokenReqQueryParams = "auth:oauth2:additional_params:refresh_token_req:queryparams" dictionary
+ oauth2RefreshTokenReqBody = "auth:oauth2:additional_params:refresh_token_req:body" dictionary
+
body = "body" st* "{" nl* textblock tagend
bodyjson = "body:json" st* "{" nl* textblock tagend
bodytext = "body:text" st* "{" nl* textblock tagend
@@ -662,6 +677,46 @@ const sem = grammar.createSemantics().addAttribute('ast', {
}
};
},
+ oauth2AuthReqHeaders(_1, dictionary) {
+ return {
+ oauth2_additional_parameters_auth_req_headers: mapPairListToKeyValPairs(dictionary.ast)
+ };
+ },
+ oauth2AuthReqQueryParams(_1, dictionary) {
+ return {
+ oauth2_additional_parameters_auth_req_queryparams: mapPairListToKeyValPairs(dictionary.ast)
+ };
+ },
+ oauth2AccessTokenReqHeaders(_1, dictionary) {
+ return {
+ oauth2_additional_parameters_access_token_req_headers: mapPairListToKeyValPairs(dictionary.ast)
+ };
+ },
+ oauth2AccessTokenReqQueryParams(_1, dictionary) {
+ return {
+ oauth2_additional_parameters_access_token_req_queryparams: mapPairListToKeyValPairs(dictionary.ast)
+ };
+ },
+ oauth2AccessTokenReqBody(_1, dictionary) {
+ return {
+ oauth2_additional_parameters_access_token_req_bodyvalues: mapPairListToKeyValPairs(dictionary.ast)
+ };
+ },
+ oauth2RefreshTokenReqHeaders(_1, dictionary) {
+ return {
+ oauth2_additional_parameters_refresh_token_req_headers: mapPairListToKeyValPairs(dictionary.ast)
+ };
+ },
+ oauth2RefreshTokenReqQueryParams(_1, dictionary) {
+ return {
+ oauth2_additional_parameters_refresh_token_req_queryparams: mapPairListToKeyValPairs(dictionary.ast)
+ };
+ },
+ oauth2RefreshTokenReqBody(_1, dictionary) {
+ return {
+ oauth2_additional_parameters_refresh_token_req_bodyvalues: mapPairListToKeyValPairs(dictionary.ast)
+ };
+ },
authwsse(_1, dictionary) {
const auth = mapPairListToKeyValPairs(dictionary.ast, false);
@@ -883,11 +938,12 @@ const parser = (input) => {
const match = grammar.match(input);
if (match.succeeded()) {
- return sem(match).ast;
+ let ast = sem(match).ast
+
+ return ast;
} else {
throw new Error(match.message);
}
};
-module.exports = parser;
-
\ No newline at end of file
+module.exports = parser;
\ No newline at end of file
diff --git a/packages/bruno-lang/v2/src/collectionBruToJson.js b/packages/bruno-lang/v2/src/collectionBruToJson.js
index d50926014..f3925ad62 100644
--- a/packages/bruno-lang/v2/src/collectionBruToJson.js
+++ b/packages/bruno-lang/v2/src/collectionBruToJson.js
@@ -4,7 +4,13 @@ const { safeParseJson, outdentString } = require('./utils');
const grammar = ohm.grammar(`Bru {
BruFile = (meta | query | headers | auth | auths | vars | script | tests | docs)*
- auths = authawsv4 | authbasic | authbearer | authdigest | authNTLM |authOAuth2 | authwsse | authapikey
+ auths = authawsv4 | authbasic | authbearer | authdigest | authNTLM |authOAuth2 | authwsse | authapikey | authOauth2Configs
+
+ // Oauth2 additional parameters
+ authOauth2Configs = oauth2AuthReqConfig | oauth2AccessTokenReqConfig | oauth2RefreshTokenReqConfig
+ oauth2AuthReqConfig = oauth2AuthReqHeaders | oauth2AuthReqQueryParams
+ oauth2AccessTokenReqConfig = oauth2AccessTokenReqHeaders | oauth2AccessTokenReqQueryParams | oauth2AccessTokenReqBody
+ oauth2RefreshTokenReqConfig = oauth2RefreshTokenReqHeaders | oauth2RefreshTokenReqQueryParams | oauth2RefreshTokenReqBody
nl = "\\r"? "\\n"
st = " " | "\\t"
@@ -30,6 +36,15 @@ const grammar = ohm.grammar(`Bru {
auth = "auth" dictionary
+ oauth2AuthReqHeaders = "auth:oauth2:additional_params:auth_req:headers" dictionary
+ oauth2AuthReqQueryParams = "auth:oauth2:additional_params:auth_req:queryparams" dictionary
+ oauth2AccessTokenReqHeaders = "auth:oauth2:additional_params:access_token_req:headers" dictionary
+ oauth2AccessTokenReqQueryParams = "auth:oauth2:additional_params:access_token_req:queryparams" dictionary
+ oauth2AccessTokenReqBody = "auth:oauth2:additional_params:access_token_req:body" dictionary
+ oauth2RefreshTokenReqHeaders = "auth:oauth2:additional_params:refresh_token_req:headers" dictionary
+ oauth2RefreshTokenReqQueryParams = "auth:oauth2:additional_params:refresh_token_req:queryparams" dictionary
+ oauth2RefreshTokenReqBody = "auth:oauth2:additional_params:refresh_token_req:body" dictionary
+
headers = "headers" dictionary
query = "query" dictionary
@@ -362,6 +377,46 @@ const sem = grammar.createSemantics().addAttribute('ast', {
}
};
},
+ oauth2AuthReqHeaders(_1, dictionary) {
+ return {
+ oauth2_additional_parameters_auth_req_headers: mapPairListToKeyValPairs(dictionary.ast)
+ };
+ },
+ oauth2AuthReqQueryParams(_1, dictionary) {
+ return {
+ oauth2_additional_parameters_auth_req_queryparams: mapPairListToKeyValPairs(dictionary.ast)
+ };
+ },
+ oauth2AccessTokenReqHeaders(_1, dictionary) {
+ return {
+ oauth2_additional_parameters_access_token_req_headers: mapPairListToKeyValPairs(dictionary.ast)
+ };
+ },
+ oauth2AccessTokenReqQueryParams(_1, dictionary) {
+ return {
+ oauth2_additional_parameters_access_token_req_queryparams: mapPairListToKeyValPairs(dictionary.ast)
+ };
+ },
+ oauth2AccessTokenReqBody(_1, dictionary) {
+ return {
+ oauth2_additional_parameters_access_token_req_bodyvalues: mapPairListToKeyValPairs(dictionary.ast)
+ };
+ },
+ oauth2RefreshTokenReqHeaders(_1, dictionary) {
+ return {
+ oauth2_additional_parameters_refresh_token_req_headers: mapPairListToKeyValPairs(dictionary.ast)
+ };
+ },
+ oauth2RefreshTokenReqQueryParams(_1, dictionary) {
+ return {
+ oauth2_additional_parameters_refresh_token_req_queryparams: mapPairListToKeyValPairs(dictionary.ast)
+ };
+ },
+ oauth2RefreshTokenReqBody(_1, dictionary) {
+ return {
+ oauth2_additional_parameters_refresh_token_req_bodyvalues: mapPairListToKeyValPairs(dictionary.ast)
+ };
+ },
authwsse(_1, dictionary) {
const auth = mapPairListToKeyValPairs(dictionary.ast, false);
const userKey = _.find(auth, { name: 'username' });
@@ -465,7 +520,9 @@ const parser = (input) => {
const match = grammar.match(input);
if (match.succeeded()) {
- return sem(match).ast;
+ let ast = sem(match).ast;
+
+ return ast;
} else {
throw new Error(match.message);
}
diff --git a/packages/bruno-lang/v2/src/jsonToBru.js b/packages/bruno-lang/v2/src/jsonToBru.js
index 1e8f56241..26de19b51 100644
--- a/packages/bruno-lang/v2/src/jsonToBru.js
+++ b/packages/bruno-lang/v2/src/jsonToBru.js
@@ -339,6 +339,114 @@ ${indentString(`auto_fetch_token: ${(auth?.oauth2?.autoFetchToken ?? true).toStr
`;
break;
}
+
+ if (auth?.oauth2?.additionalParameters) {
+ const { authorization: authorizationParams, token: tokenParams, refresh: refreshParams } = auth?.oauth2?.additionalParameters;
+ const authorizationHeaders = authorizationParams?.filter(p => p?.sendIn == 'headers');
+ if (authorizationHeaders?.length) {
+ bru += `auth:oauth2:additional_params:auth_req:headers {
+${indentString(
+ authorizationHeaders
+ .filter(item => item?.name?.length)
+ .map((item) => `${item.enabled ? '' : '~'}${item.name}: ${item.value}`)
+ .join('\n')
+ )}
+}
+
+`;
+ }
+ const authorizationQueryParams = authorizationParams?.filter(p => p?.sendIn == 'queryparams');
+ if (authorizationQueryParams?.length) {
+ bru += `auth:oauth2:additional_params:auth_req:queryparams {
+${indentString(
+ authorizationQueryParams
+ .filter(item => item?.name?.length)
+ .map((item) => `${item.enabled ? '' : '~'}${item.name}: ${item.value}`)
+ .join('\n')
+ )}
+}
+
+`;
+ }
+ const tokenHeaders = tokenParams?.filter(p => p?.sendIn == 'headers');
+ if (tokenHeaders?.length) {
+ bru += `auth:oauth2:additional_params:access_token_req:headers {
+${indentString(
+ tokenHeaders
+ .filter(item => item?.name?.length)
+ .map((item) => `${item.enabled ? '' : '~'}${item.name}: ${item.value}`)
+ .join('\n')
+ )}
+}
+
+`;
+ }
+ const tokenQueryParams = tokenParams?.filter(p => p?.sendIn == 'queryparams');
+ if (tokenQueryParams?.length) {
+ bru += `auth:oauth2:additional_params:access_token_req:queryparams {
+${indentString(
+ tokenQueryParams
+ .filter(item => item?.name?.length)
+ .map((item) => `${item.enabled ? '' : '~'}${item.name}: ${item.value}`)
+ .join('\n')
+ )}
+}
+
+`;
+ }
+ const tokenBodyValues = tokenParams?.filter(p => p?.sendIn == 'body');
+ if (tokenBodyValues?.length) {
+ bru += `auth:oauth2:additional_params:access_token_req:body {
+${indentString(
+ tokenBodyValues
+ .filter(item => item?.name?.length)
+ .map((item) => `${item.enabled ? '' : '~'}${item.name}: ${item.value}`)
+ .join('\n')
+ )}
+}
+
+`;
+ }
+ const refreshHeaders = refreshParams?.filter(p => p?.sendIn == 'headers');
+ if (refreshHeaders?.length) {
+ bru += `auth:oauth2:additional_params:refresh_token_req:headers {
+${indentString(
+ refreshHeaders
+ .filter(item => item?.name?.length)
+ .map((item) => `${item.enabled ? '' : '~'}${item.name}: ${item.value}`)
+ .join('\n')
+ )}
+}
+
+`;
+ }
+ const refreshQueryParams = refreshParams?.filter(p => p?.sendIn == 'queryparams');
+ if (refreshQueryParams?.length) {
+ bru += `auth:oauth2:additional_params:refresh_token_req:queryparams {
+${indentString(
+ refreshQueryParams
+ .filter(item => item?.name?.length)
+ .map((item) => `${item.enabled ? '' : '~'}${item.name}: ${item.value}`)
+ .join('\n')
+ )}
+}
+
+`;
+ }
+ const refreshBodyValues = refreshParams?.filter(p => p?.sendIn == 'body');
+ if (refreshBodyValues?.length) {
+ bru += `auth:oauth2:additional_params:refresh_token_req:body {
+${indentString(
+ refreshBodyValues
+ .filter(item => item?.name?.length)
+ .map((item) => `${item.enabled ? '' : '~'}${item.name}: ${item.value}`)
+ .join('\n')
+ )}
+}
+
+`;
+ }
+ }
}
if (auth && auth.apikey) {
diff --git a/packages/bruno-lang/v2/src/jsonToCollectionBru.js b/packages/bruno-lang/v2/src/jsonToCollectionBru.js
index adcdf6b06..d5aa1c1e0 100644
--- a/packages/bruno-lang/v2/src/jsonToCollectionBru.js
+++ b/packages/bruno-lang/v2/src/jsonToCollectionBru.js
@@ -234,6 +234,114 @@ ${indentString(`auto_refresh_token: ${(auth?.oauth2?.autoRefreshToken ?? false).
`;
break;
}
+
+ if (auth?.oauth2?.additionalParameters) {
+ const { authorization: authorizationParams, token: tokenParams, refresh: refreshParams } = auth?.oauth2?.additionalParameters;
+ const authorizationHeaders = authorizationParams?.filter(p => p?.sendIn == 'headers');
+ if (authorizationHeaders?.length) {
+ bru += `auth:oauth2:additional_params:auth_req:headers {
+${indentString(
+ authorizationHeaders
+ .filter(item => item?.name?.length)
+ .map((item) => `${item.enabled ? '' : '~'}${item.name}: ${item.value}`)
+ .join('\n')
+ )}
+}
+
+`;
+ }
+ const authorizationQueryParams = authorizationParams?.filter(p => p?.sendIn == 'queryparams');
+ if (authorizationQueryParams?.length) {
+ bru += `auth:oauth2:additional_params:auth_req:queryparams {
+${indentString(
+ authorizationQueryParams
+ .filter(item => item?.name?.length)
+ .map((item) => `${item.enabled ? '' : '~'}${item.name}: ${item.value}`)
+ .join('\n')
+ )}
+}
+
+`;
+ }
+ const tokenHeaders = tokenParams?.filter(p => p?.sendIn == 'headers');
+ if (tokenHeaders?.length) {
+ bru += `auth:oauth2:additional_params:access_token_req:headers {
+${indentString(
+ tokenHeaders
+ .filter(item => item?.name?.length)
+ .map((item) => `${item.enabled ? '' : '~'}${item.name}: ${item.value}`)
+ .join('\n')
+ )}
+}
+
+`;
+ }
+ const tokenQueryParams = tokenParams?.filter(p => p?.sendIn == 'queryparams');
+ if (tokenQueryParams?.length) {
+ bru += `auth:oauth2:additional_params:access_token_req:queryparams {
+${indentString(
+ tokenQueryParams
+ .filter(item => item?.name?.length)
+ .map((item) => `${item.enabled ? '' : '~'}${item.name}: ${item.value}`)
+ .join('\n')
+ )}
+}
+
+`;
+ }
+ const tokenBodyValues = tokenParams?.filter(p => p?.sendIn == 'body');
+ if (tokenBodyValues?.length) {
+ bru += `auth:oauth2:additional_params:access_token_req:body {
+${indentString(
+ tokenBodyValues
+ .filter(item => item?.name?.length)
+ .map((item) => `${item.enabled ? '' : '~'}${item.name}: ${item.value}`)
+ .join('\n')
+ )}
+}
+
+`;
+ }
+ const refreshHeaders = refreshParams?.filter(p => p?.sendIn == 'headers');
+ if (refreshHeaders?.length) {
+ bru += `auth:oauth2:additional_params:refresh_token_req:headers {
+${indentString(
+ refreshHeaders
+ .filter(item => item?.name?.length)
+ .map((item) => `${item.enabled ? '' : '~'}${item.name}: ${item.value}`)
+ .join('\n')
+ )}
+}
+
+`;
+ }
+ const refreshQueryParams = refreshParams?.filter(p => p?.sendIn == 'queryparams');
+ if (refreshQueryParams?.length) {
+ bru += `auth:oauth2:additional_params:refresh_token_req:queryparams {
+${indentString(
+ refreshQueryParams
+ .filter(item => item?.name?.length)
+ .map((item) => `${item.enabled ? '' : '~'}${item.name}: ${item.value}`)
+ .join('\n')
+ )}
+}
+
+`;
+ }
+ const refreshBodyValues = refreshParams?.filter(p => p?.sendIn == 'body');
+ if (refreshBodyValues?.length) {
+ bru += `auth:oauth2:additional_params:refresh_token_req:body {
+${indentString(
+ refreshBodyValues
+ .filter(item => item?.name?.length)
+ .map((item) => `${item.enabled ? '' : '~'}${item.name}: ${item.value}`)
+ .join('\n')
+ )}
+}
+
+`;
+ }
+ }
}
let reqvars = _.get(vars, 'req');
diff --git a/packages/bruno-lang/v2/tests/oauth2-additional-params.spec.js b/packages/bruno-lang/v2/tests/oauth2-additional-params.spec.js
new file mode 100644
index 000000000..c38ea7ef0
--- /dev/null
+++ b/packages/bruno-lang/v2/tests/oauth2-additional-params.spec.js
@@ -0,0 +1,329 @@
+const bruToJson = require('../src/bruToJson');
+const collectionBruToJson = require('../src/collectionBruToJson');
+
+describe('OAuth2 Additional Parameters - request level', () => {
+ it('should parse all oauth2 additional parameters config types together', () => {
+ const input = `
+meta {
+ name: OAuth2 Additional Params Test
+ type: http
+}
+
+get {
+ url: https://api.usebruno.com/protected
+}
+
+auth:oauth2 {
+ grant_type: authorization_code
+ client_id: bruno-client-id
+ client_secret: bruno-client-secret
+ authorization_url: https://auth.usebruno.com/oauth/authorize
+ access_token_url: https://auth.usebruno.com/oauth/token
+}
+
+auth:oauth2:additional_params:auth_req:headers {
+ auth-header: auth-header-value
+ ~disabled-auth-header: disabled-auth-header-value
+}
+
+auth:oauth2:additional_params:auth_req:queryparams {
+ auth-query-param: auth-query-param-value
+ ~disabled-auth-query-param: disabled-auth-query-param-value
+}
+
+auth:oauth2:additional_params:access_token_req:headers {
+ token-header: token-header-value
+ ~disabled-token-header: disabled-token-header-value
+}
+
+auth:oauth2:additional_params:access_token_req:queryparams {
+ token-query-param: token-query-param-value
+ ~disabled-token-query-param: disabled-token-query-param-value
+}
+
+auth:oauth2:additional_params:access_token_req:body {
+ token-body: token-body-value
+ ~disabled-token-body: disabled-token-body-value
+}
+
+auth:oauth2:additional_params:refresh_token_req:headers {
+ refresh-header: refresh-header-value
+ ~disabled-refresh-header: disabled-refresh-header-value
+}
+
+auth:oauth2:additional_params:refresh_token_req:queryparams {
+ refresh-query-param: refresh-query-param-value
+ ~disabled-refresh-query-param: disabled-refresh-query-param-value
+}
+
+auth:oauth2:additional_params:refresh_token_req:body {
+ refresh-body: refresh-body-value
+ ~disabled-refresh-body: disabled-refresh-body-value
+}
+ `.trim();
+
+ const result = bruToJson(input);
+
+ // Verify all config types are present
+ expect(result).toHaveProperty('oauth2_additional_parameters_auth_req_headers');
+ expect(result).toHaveProperty('oauth2_additional_parameters_auth_req_queryparams');
+ expect(result).toHaveProperty('oauth2_additional_parameters_access_token_req_headers');
+ expect(result).toHaveProperty('oauth2_additional_parameters_access_token_req_queryparams');
+ expect(result).toHaveProperty('oauth2_additional_parameters_access_token_req_bodyvalues');
+ expect(result).toHaveProperty('oauth2_additional_parameters_refresh_token_req_headers');
+ expect(result).toHaveProperty('oauth2_additional_parameters_refresh_token_req_queryparams');
+ expect(result).toHaveProperty('oauth2_additional_parameters_refresh_token_req_bodyvalues');
+
+ // Verify each has exactly one parameter
+ expect(result.oauth2_additional_parameters_auth_req_headers).toHaveLength(2);
+ expect(result.oauth2_additional_parameters_auth_req_queryparams).toHaveLength(2);
+ expect(result.oauth2_additional_parameters_access_token_req_headers).toHaveLength(2);
+ expect(result.oauth2_additional_parameters_access_token_req_queryparams).toHaveLength(2);
+ expect(result.oauth2_additional_parameters_access_token_req_bodyvalues).toHaveLength(2);
+ expect(result.oauth2_additional_parameters_refresh_token_req_headers).toHaveLength(2);
+ expect(result.oauth2_additional_parameters_refresh_token_req_queryparams).toHaveLength(2);
+ expect(result.oauth2_additional_parameters_refresh_token_req_bodyvalues).toHaveLength(2);
+
+ // Verify parameter values
+ expect(result.oauth2_additional_parameters_auth_req_headers).toEqual([{
+ name: 'auth-header',
+ value: 'auth-header-value',
+ enabled: true
+ }, {
+ name: 'disabled-auth-header',
+ value: 'disabled-auth-header-value',
+ enabled: false
+ }]);
+
+ expect(result.oauth2_additional_parameters_auth_req_queryparams).toEqual([{
+ name: 'auth-query-param',
+ value: 'auth-query-param-value',
+ enabled: true
+ }, {
+ name: 'disabled-auth-query-param',
+ value: 'disabled-auth-query-param-value',
+ enabled: false
+ }]);
+
+ expect(result.oauth2_additional_parameters_access_token_req_headers).toEqual([{
+ name: 'token-header',
+ value: 'token-header-value',
+ enabled: true
+ }, {
+ name: 'disabled-token-header',
+ value: 'disabled-token-header-value',
+ enabled: false
+ }]);
+
+ expect(result.oauth2_additional_parameters_access_token_req_queryparams).toEqual([{
+ name: 'token-query-param',
+ value: 'token-query-param-value',
+ enabled: true
+ }, {
+ name: 'disabled-token-query-param',
+ value: 'disabled-token-query-param-value',
+ enabled: false
+ }]);
+
+ expect(result.oauth2_additional_parameters_access_token_req_bodyvalues).toEqual([{
+ name: 'token-body',
+ value: 'token-body-value',
+ enabled: true
+ }, {
+ name: 'disabled-token-body',
+ value: 'disabled-token-body-value',
+ enabled: false
+ }]);
+
+ expect(result.oauth2_additional_parameters_refresh_token_req_headers).toEqual([{
+ name: 'refresh-header',
+ value: 'refresh-header-value',
+ enabled: true
+ }, {
+ name: 'disabled-refresh-header',
+ value: 'disabled-refresh-header-value',
+ enabled: false
+ }]);
+
+ expect(result.oauth2_additional_parameters_refresh_token_req_queryparams).toEqual([{
+ name: 'refresh-query-param',
+ value: 'refresh-query-param-value',
+ enabled: true
+ }, {
+ name: 'disabled-refresh-query-param',
+ value: 'disabled-refresh-query-param-value',
+ enabled: false
+ }]);
+
+ expect(result.oauth2_additional_parameters_refresh_token_req_bodyvalues).toEqual([{
+ name: 'refresh-body',
+ value: 'refresh-body-value',
+ enabled: true
+ }, {
+ name: 'disabled-refresh-body',
+ value: 'disabled-refresh-body-value',
+ enabled: false
+ }]);
+ });
+});
+
+describe('OAuth2 Additional Parameters - collection/folder level', () => {
+ it('should parse all oauth2 additional parameters config types together', () => {
+ const input = `
+auth {
+ mode: oauth2
+}
+
+auth:oauth2 {
+ grant_type: authorization_code
+ client_id: bruno-client-id
+ client_secret: bruno-client-secret
+ authorization_url: https://auth.usebruno.com/oauth/authorize
+ access_token_url: https://auth.usebruno.com/oauth/token
+}
+
+auth:oauth2:additional_params:auth_req:headers {
+ auth-header: auth-header-value
+ ~disabled-auth-header: disabled-auth-header-value
+}
+
+auth:oauth2:additional_params:auth_req:queryparams {
+ auth-query-param: auth-query-param-value
+ ~disabled-auth-query-param: disabled-auth-query-param-value
+}
+
+auth:oauth2:additional_params:access_token_req:headers {
+ token-header: token-header-value
+ ~disabled-token-header: disabled-token-header-value
+}
+
+auth:oauth2:additional_params:access_token_req:queryparams {
+ token-query-param: token-query-param-value
+ ~disabled-token-query-param: disabled-token-query-param-value
+}
+
+auth:oauth2:additional_params:access_token_req:body {
+ token-body: token-body-value
+ ~disabled-token-body: disabled-token-body-value
+}
+
+auth:oauth2:additional_params:refresh_token_req:headers {
+ refresh-header: refresh-header-value
+ ~disabled-refresh-header: disabled-refresh-header-value
+}
+
+auth:oauth2:additional_params:refresh_token_req:queryparams {
+ refresh-query-param: refresh-query-param-value
+ ~disabled-refresh-query-param: disabled-refresh-query-param-value
+}
+
+auth:oauth2:additional_params:refresh_token_req:body {
+ refresh-body: refresh-body-value
+ ~disabled-refresh-body: disabled-refresh-body-value
+}
+ `.trim();
+
+ const result = collectionBruToJson(input);
+
+ // Verify all config types are present
+ expect(result).toHaveProperty('oauth2_additional_parameters_auth_req_headers');
+ expect(result).toHaveProperty('oauth2_additional_parameters_auth_req_queryparams');
+ expect(result).toHaveProperty('oauth2_additional_parameters_access_token_req_headers');
+ expect(result).toHaveProperty('oauth2_additional_parameters_access_token_req_queryparams');
+ expect(result).toHaveProperty('oauth2_additional_parameters_access_token_req_bodyvalues');
+ expect(result).toHaveProperty('oauth2_additional_parameters_refresh_token_req_headers');
+ expect(result).toHaveProperty('oauth2_additional_parameters_refresh_token_req_queryparams');
+ expect(result).toHaveProperty('oauth2_additional_parameters_refresh_token_req_bodyvalues');
+
+ // Verify each has exactly one parameter
+ expect(result.oauth2_additional_parameters_auth_req_headers).toHaveLength(2);
+ expect(result.oauth2_additional_parameters_auth_req_queryparams).toHaveLength(2);
+ expect(result.oauth2_additional_parameters_access_token_req_headers).toHaveLength(2);
+ expect(result.oauth2_additional_parameters_access_token_req_queryparams).toHaveLength(2);
+ expect(result.oauth2_additional_parameters_access_token_req_bodyvalues).toHaveLength(2);
+ expect(result.oauth2_additional_parameters_refresh_token_req_headers).toHaveLength(2);
+ expect(result.oauth2_additional_parameters_refresh_token_req_queryparams).toHaveLength(2);
+ expect(result.oauth2_additional_parameters_refresh_token_req_bodyvalues).toHaveLength(2);
+
+ // Verify parameter values
+ expect(result.oauth2_additional_parameters_auth_req_headers).toEqual([{
+ name: 'auth-header',
+ value: 'auth-header-value',
+ enabled: true
+ }, {
+ name: 'disabled-auth-header',
+ value: 'disabled-auth-header-value',
+ enabled: false
+ }]);
+
+ expect(result.oauth2_additional_parameters_auth_req_queryparams).toEqual([{
+ name: 'auth-query-param',
+ value: 'auth-query-param-value',
+ enabled: true
+ }, {
+ name: 'disabled-auth-query-param',
+ value: 'disabled-auth-query-param-value',
+ enabled: false
+ }]);
+
+ expect(result.oauth2_additional_parameters_access_token_req_headers).toEqual([{
+ name: 'token-header',
+ value: 'token-header-value',
+ enabled: true
+ }, {
+ name: 'disabled-token-header',
+ value: 'disabled-token-header-value',
+ enabled: false
+ }]);
+
+ expect(result.oauth2_additional_parameters_access_token_req_queryparams).toEqual([{
+ name: 'token-query-param',
+ value: 'token-query-param-value',
+ enabled: true
+ }, {
+ name: 'disabled-token-query-param',
+ value: 'disabled-token-query-param-value',
+ enabled: false
+ }]);
+
+ expect(result.oauth2_additional_parameters_access_token_req_bodyvalues).toEqual([{
+ name: 'token-body',
+ value: 'token-body-value',
+ enabled: true
+ }, {
+ name: 'disabled-token-body',
+ value: 'disabled-token-body-value',
+ enabled: false
+ }]);
+
+ expect(result.oauth2_additional_parameters_refresh_token_req_headers).toEqual([{
+ name: 'refresh-header',
+ value: 'refresh-header-value',
+ enabled: true
+ }, {
+ name: 'disabled-refresh-header',
+ value: 'disabled-refresh-header-value',
+ enabled: false
+ }]);
+
+ expect(result.oauth2_additional_parameters_refresh_token_req_queryparams).toEqual([{
+ name: 'refresh-query-param',
+ value: 'refresh-query-param-value',
+ enabled: true
+ }, {
+ name: 'disabled-refresh-query-param',
+ value: 'disabled-refresh-query-param-value',
+ enabled: false
+ }]);
+
+ expect(result.oauth2_additional_parameters_refresh_token_req_bodyvalues).toEqual([{
+ name: 'refresh-body',
+ value: 'refresh-body-value',
+ enabled: true
+ }, {
+ name: 'disabled-refresh-body',
+ value: 'disabled-refresh-body-value',
+ enabled: false
+ }]);
+ });
+});
\ No newline at end of file
diff --git a/packages/bruno-schema/src/collections/index.js b/packages/bruno-schema/src/collections/index.js
index 6db0174b0..ff830ca99 100644
--- a/packages/bruno-schema/src/collections/index.js
+++ b/packages/bruno-schema/src/collections/index.js
@@ -157,6 +157,28 @@ const authApiKeySchema = Yup.object({
.noUnknown(true)
.strict();
+const oauth2AuthorizationAdditionalParametersSchema = Yup.object({
+ name: Yup.string().nullable(),
+ value: Yup.string().nullable(),
+ sendIn: Yup.string()
+ .oneOf(['headers', 'queryparams'])
+ .required('send in property is required'),
+ enabled: Yup.boolean()
+})
+ .noUnknown(true)
+ .strict();
+
+const oauth2AdditionalParametersSchema = Yup.object({
+ name: Yup.string().nullable(),
+ value: Yup.string().nullable(),
+ sendIn: Yup.string()
+ .oneOf(['headers', 'queryparams', 'body'])
+ .required('send in property is required'),
+ enabled: Yup.boolean()
+ })
+ .noUnknown(true)
+ .strict();
+
const oauth2Schema = Yup.object({
grantType: Yup.string()
.oneOf(['client_credentials', 'password', 'authorization_code', 'implicit'])
@@ -252,6 +274,15 @@ const oauth2Schema = Yup.object({
is: (val) => ['authorization_code', 'implicit'].includes(val),
then: Yup.boolean().default(true),
otherwise: Yup.boolean()
+ }),
+ additionalParameters: Yup.object({
+ authorization: Yup.mixed().when('grantType', {
+ is: 'authorization_code',
+ then: Yup.array().of(oauth2AuthorizationAdditionalParametersSchema).required(),
+ otherwise: Yup.mixed().nullable().optional()
+ }),
+ token: Yup.array().of(oauth2AdditionalParametersSchema).optional(),
+ refresh: Yup.array().of(oauth2AdditionalParametersSchema).optional()
})
})
.noUnknown(true)