mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-25 05:35:41 +00:00
Merge pull request #4447 from usebruno/oauth2_additional_params
This commit is contained in:
@@ -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;
|
||||
@@ -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) => (
|
||||
<div
|
||||
key={tabKey}
|
||||
className={`tab ${activeTab === tabKey ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab(tabKey)}
|
||||
>
|
||||
{tabLabel}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledWrapper className="mt-4">
|
||||
<div className="flex items-center gap-2.5 mb-3">
|
||||
<div className="flex items-center px-2.5 py-1.5 bg-indigo-50/50 dark:bg-indigo-500/10 rounded-md">
|
||||
<IconAdjustmentsHorizontal size={14} className="text-indigo-500 dark:text-indigo-400" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-gray-800 dark:text-gray-200">
|
||||
Additional Parameters
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="tabs flex w-full gap-2 my-2">
|
||||
{availableTabs.includes('authorization') && renderTab('authorization', 'Authorization')}
|
||||
{availableTabs.includes('token') && renderTab('token', 'Token')}
|
||||
{availableTabs.includes('refresh') && renderTab('refresh', 'Refresh')}
|
||||
</div>
|
||||
<Table
|
||||
headers={[
|
||||
{ name: 'Key', accessor: 'name', width: '30%' },
|
||||
{ name: 'Value', accessor: 'value', width: '30%' },
|
||||
{ name: 'Send In', accessor: 'sendIn', width: '150px' },
|
||||
{ name: '', accessor: '', width: '15%' }
|
||||
]}
|
||||
>
|
||||
<tbody>
|
||||
{(additionalParameters?.[activeTab] || []).map((param, index) =>
|
||||
<tr key={index}>
|
||||
<td className='flex relative'>
|
||||
<SingleLineEditor
|
||||
value={param?.name || ''}
|
||||
theme={storedTheme}
|
||||
onChange={(value) => handleUpdateAdditionalParam({
|
||||
paramType: activeTab,
|
||||
key: 'name',
|
||||
paramIndex: index,
|
||||
value
|
||||
})}
|
||||
collection={collection}
|
||||
onSave={handleSave}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<SingleLineEditor
|
||||
value={param?.value || ''}
|
||||
theme={storedTheme}
|
||||
onChange={(value) => handleUpdateAdditionalParam({
|
||||
paramType: activeTab,
|
||||
key: 'value',
|
||||
paramIndex: index,
|
||||
value
|
||||
})}
|
||||
collection={collection}
|
||||
onSave={handleSave}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div className="w-full additional-parameter-sends-in-selector">
|
||||
<select
|
||||
value={param?.sendIn || 'headers'}
|
||||
onChange={e => {
|
||||
handleUpdateAdditionalParam({
|
||||
paramType: activeTab,
|
||||
key: 'sendIn',
|
||||
paramIndex: index,
|
||||
value: e.target.value
|
||||
})
|
||||
}}
|
||||
className="mousetrap bg-transparent"
|
||||
>
|
||||
{sendInOptionsMap[grantType || 'authorization_code'][activeTab].map((optionValue) => (
|
||||
<option key={optionValue} value={optionValue}>
|
||||
{optionValue}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={param?.enabled ?? true}
|
||||
tabIndex="-1"
|
||||
className="mr-3 mousetrap"
|
||||
onChange={(e) => {
|
||||
handleUpdateAdditionalParam({
|
||||
paramType: activeTab,
|
||||
key: 'enabled',
|
||||
paramIndex: index,
|
||||
value: e.target.checked
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
tabIndex="-1"
|
||||
onClick={() => {
|
||||
handleDeleteAdditionalParam({
|
||||
paramType: activeTab,
|
||||
paramIndex: index
|
||||
})
|
||||
}}
|
||||
>
|
||||
<IconTrash strokeWidth={1.5} size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</Table>
|
||||
<div
|
||||
className={`add-additional-param-actions w-fit flex items-center mt-2 ${addButtonDisabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}`}
|
||||
onClick={addButtonDisabled ? null : handleAddNewAdditionalParam}
|
||||
>
|
||||
<IconPlus size={16} strokeWidth={1.5} style={{ marginLeft: '2px' }} />
|
||||
<span className="ml-1 text-sm text-gray-500">Add Parameter</span>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default AdditionalParams;
|
||||
|
||||
const sendInOptionsMap = {
|
||||
'authorization_code': {
|
||||
'authorization': ['headers', 'queryparams'],
|
||||
'token': ['headers', 'queryparams', 'body'],
|
||||
'refresh': ['headers', 'queryparams', 'body']
|
||||
},
|
||||
'password': {
|
||||
'token': ['headers', 'queryparams', 'body'],
|
||||
'refresh': ['headers', 'queryparams', 'body']
|
||||
},
|
||||
'client_credentials': {
|
||||
'token': ['headers', 'queryparams', 'body'],
|
||||
'refresh': ['headers', 'queryparams', 'body']
|
||||
},
|
||||
'implicit': {
|
||||
'authorization': ['headers', 'queryparams']
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import StyledWrapper from './StyledWrapper';
|
||||
import { inputsConfig } from './inputsConfig';
|
||||
import Oauth2TokenViewer from '../Oauth2TokenViewer/index';
|
||||
import Oauth2ActionButtons from '../Oauth2ActionButtons/index';
|
||||
import AdditionalParams from '../AdditionalParams/index';
|
||||
import SensitiveFieldWarning from 'components/SensitiveFieldWarning';
|
||||
|
||||
const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAuth, collection, folder }) => {
|
||||
@@ -35,7 +36,8 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu
|
||||
tokenQueryKey,
|
||||
refreshTokenUrl,
|
||||
autoRefreshToken,
|
||||
autoFetchToken
|
||||
autoFetchToken,
|
||||
additionalParameters
|
||||
} = oAuth;
|
||||
|
||||
const refreshTokenUrlAvailable = refreshTokenUrl?.trim() !== '';
|
||||
@@ -85,6 +87,7 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu
|
||||
refreshTokenUrl,
|
||||
autoRefreshToken,
|
||||
autoFetchToken,
|
||||
additionalParameters,
|
||||
[key]: value,
|
||||
}
|
||||
})
|
||||
@@ -112,6 +115,7 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu
|
||||
tokenHeaderPrefix,
|
||||
tokenQueryKey,
|
||||
autoFetchToken,
|
||||
additionalParameters,
|
||||
pkce: !Boolean(oAuth?.['pkce'])
|
||||
}
|
||||
})
|
||||
@@ -332,6 +336,13 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AdditionalParams
|
||||
item={item}
|
||||
request={request}
|
||||
collection={collection}
|
||||
updateAuth={updateAuth}
|
||||
handleSave={handleSave}
|
||||
/>
|
||||
<Oauth2ActionButtons item={item} request={request} collection={collection} url={accessTokenUrl} credentialsId={credentialsId} />
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
||||
@@ -10,6 +10,7 @@ import { inputsConfig } from './inputsConfig';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import Oauth2TokenViewer from '../Oauth2TokenViewer/index';
|
||||
import Oauth2ActionButtons from '../Oauth2ActionButtons/index';
|
||||
import AdditionalParams from '../AdditionalParams/index';
|
||||
import SensitiveFieldWarning from 'components/SensitiveFieldWarning';
|
||||
|
||||
const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAuth, collection }) => {
|
||||
@@ -32,7 +33,8 @@ const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAu
|
||||
tokenQueryKey,
|
||||
refreshTokenUrl,
|
||||
autoRefreshToken,
|
||||
autoFetchToken
|
||||
autoFetchToken,
|
||||
additionalParameters
|
||||
} = oAuth;
|
||||
|
||||
const refreshTokenUrlAvailable = refreshTokenUrl?.trim() !== '';
|
||||
@@ -79,6 +81,7 @@ const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAu
|
||||
refreshTokenUrl,
|
||||
autoRefreshToken,
|
||||
autoFetchToken,
|
||||
additionalParameters,
|
||||
[key]: value
|
||||
}
|
||||
})
|
||||
@@ -290,7 +293,13 @@ const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAu
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AdditionalParams
|
||||
item={item}
|
||||
request={request}
|
||||
collection={collection}
|
||||
updateAuth={updateAuth}
|
||||
handleSave={handleSave}
|
||||
/>
|
||||
<Oauth2ActionButtons item={item} request={request} collection={collection} url={accessTokenUrl} credentialsId={credentialsId} />
|
||||
|
||||
</StyledWrapper>
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import React, { useRef, forwardRef, useState, useMemo } from 'react';
|
||||
import React, { useRef, forwardRef, useMemo } from 'react';
|
||||
import get from 'lodash/get';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { IconCaretDown, IconLoader2, IconSettings, IconKey, IconHelp, IconAdjustmentsHorizontal } from '@tabler/icons';
|
||||
import { IconCaretDown, IconSettings, IconKey, IconHelp, IconAdjustmentsHorizontal } from '@tabler/icons';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import { clearOauth2Cache, fetchOauth2Credentials } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import Wrapper from './StyledWrapper';
|
||||
import { inputsConfig } from './inputsConfig';
|
||||
import toast from 'react-hot-toast';
|
||||
import Oauth2TokenViewer from '../Oauth2TokenViewer/index';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import Oauth2ActionButtons from '../Oauth2ActionButtons/index';
|
||||
import AdditionalParams from '../AdditionalParams/index';
|
||||
import { getAllVariables } from 'utils/collections/index';
|
||||
import { interpolate } from '@usebruno/common';
|
||||
|
||||
@@ -19,7 +18,6 @@ const OAuth2Implicit = ({ save, item = {}, request, handleRun, updateAuth, colle
|
||||
const { storedTheme } = useTheme();
|
||||
const dropdownTippyRef = useRef();
|
||||
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||
const [fetchingToken, toggleFetchingToken] = useState(false);
|
||||
|
||||
const oAuth = get(request, 'auth.oauth2', {});
|
||||
const {
|
||||
@@ -49,38 +47,6 @@ const OAuth2Implicit = ({ save, item = {}, request, handleRun, updateAuth, colle
|
||||
);
|
||||
});
|
||||
|
||||
const handleFetchOauth2Credentials = async () => {
|
||||
let requestCopy = cloneDeep(request);
|
||||
requestCopy.oauth2 = requestCopy?.auth.oauth2;
|
||||
requestCopy.headers = {};
|
||||
toggleFetchingToken(true);
|
||||
try {
|
||||
const result = await dispatch(fetchOauth2Credentials({
|
||||
itemUid: item.uid,
|
||||
request: requestCopy,
|
||||
collection,
|
||||
folderUid: folder?.uid || null,
|
||||
forceGetToken: true
|
||||
}));
|
||||
|
||||
toggleFetchingToken(false);
|
||||
|
||||
// Check if the result contains error or if access_token is missing
|
||||
if (result?.error || !result?.access_token) {
|
||||
const errorMessage = result?.error || 'No access token received from authorization server';
|
||||
toast.error(errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success('Token fetched successfully!');
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
toggleFetchingToken(false);
|
||||
toast.error(error?.message || 'An error occurred while fetching token!');
|
||||
}
|
||||
}
|
||||
|
||||
const handleSave = () => { save(); };
|
||||
|
||||
const handleChange = (key, value) => {
|
||||
@@ -111,16 +77,6 @@ const OAuth2Implicit = ({ save, item = {}, request, handleRun, updateAuth, colle
|
||||
handleChange('autoFetchToken', e.target.checked);
|
||||
};
|
||||
|
||||
const handleClearCache = (e) => {
|
||||
dispatch(clearOauth2Cache({ collectionUid: collection?.uid, url: interpolatedAuthUrl, credentialsId }))
|
||||
.then(() => {
|
||||
toast.success('Cleared cache successfully');
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error(err.message);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Wrapper className="mt-2 flex w-full gap-4 flex-col">
|
||||
<Oauth2TokenViewer handleRun={handleRun} collection={collection} item={item} url={authorizationUrl} credentialsId={credentialsId} />
|
||||
@@ -262,18 +218,14 @@ const OAuth2Implicit = ({ save, item = {}, request, handleRun, updateAuth, colle
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row gap-4 mt-4">
|
||||
<button
|
||||
onClick={handleFetchOauth2Credentials}
|
||||
className={`submit btn btn-sm btn-secondary w-fit flex flex-row`}
|
||||
disabled={fetchingToken}
|
||||
>
|
||||
Get Access Token{fetchingToken ? <IconLoader2 className="animate-spin ml-2" size={18} strokeWidth={1.5} /> : ""}
|
||||
</button>
|
||||
<button onClick={handleClearCache} className="submit btn btn-sm btn-secondary w-fit">
|
||||
Clear Cache
|
||||
</button>
|
||||
</div>
|
||||
<AdditionalParams
|
||||
item={item}
|
||||
request={request}
|
||||
collection={collection}
|
||||
updateAuth={updateAuth}
|
||||
handleSave={handleSave}
|
||||
/>
|
||||
<Oauth2ActionButtons item={item} request={request} collection={collection} url={interpolatedAuthUrl} credentialsId={credentialsId} />
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -99,12 +99,22 @@ const Oauth2ActionButtons = ({ item, request, collection, url: accessTokenUrl, c
|
||||
|
||||
return (
|
||||
<div className="flex flex-row gap-4 mt-4">
|
||||
<button onClick={handleFetchOauth2Credentials} className={`submit btn btn-sm btn-secondary w-fit flex flex-row`}>
|
||||
<button
|
||||
onClick={handleFetchOauth2Credentials}
|
||||
className={`submit btn btn-sm btn-secondary w-fit flex flex-row`}
|
||||
disabled={fetchingToken || refreshingToken}
|
||||
>
|
||||
Get Access Token{fetchingToken? <IconLoader2 className="animate-spin ml-2" size={18} strokeWidth={1.5} /> : ""}
|
||||
</button>
|
||||
{creds?.refresh_token ? <button onClick={handleRefreshAccessToken} className={`submit btn btn-sm btn-secondary w-fit flex flex-row`}>
|
||||
Refresh Token{refreshingToken? <IconLoader2 className="animate-spin ml-2" size={18} strokeWidth={1.5} /> : ""}
|
||||
</button> : null}
|
||||
{creds?.refresh_token ?
|
||||
<button
|
||||
onClick={handleRefreshAccessToken}
|
||||
className={`submit btn btn-sm btn-secondary w-fit flex flex-row`}
|
||||
disabled={fetchingToken || refreshingToken}
|
||||
>
|
||||
Refresh Token{refreshingToken? <IconLoader2 className="animate-spin ml-2" size={18} strokeWidth={1.5} /> : ""}
|
||||
</button>
|
||||
: null}
|
||||
<button onClick={handleClearCache} className="submit btn btn-sm btn-secondary w-fit">
|
||||
Clear Cache
|
||||
</button>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { inputsConfig } from './inputsConfig';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import Oauth2TokenViewer from '../Oauth2TokenViewer/index';
|
||||
import Oauth2ActionButtons from '../Oauth2ActionButtons/index';
|
||||
import AdditionalParams from '../AdditionalParams/index';
|
||||
import SensitiveFieldWarning from 'components/SensitiveFieldWarning/index';
|
||||
|
||||
const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, updateAuth, collection }) => {
|
||||
@@ -34,7 +35,8 @@ const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, update
|
||||
tokenQueryKey,
|
||||
refreshTokenUrl,
|
||||
autoRefreshToken,
|
||||
autoFetchToken
|
||||
autoFetchToken,
|
||||
additionalParameters
|
||||
} = oAuth;
|
||||
|
||||
const refreshTokenUrlAvailable = refreshTokenUrl?.trim() !== '';
|
||||
@@ -82,6 +84,7 @@ const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, update
|
||||
refreshTokenUrl,
|
||||
autoRefreshToken,
|
||||
autoFetchToken,
|
||||
additionalParameters,
|
||||
[key]: value
|
||||
}
|
||||
})
|
||||
@@ -293,6 +296,13 @@ const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, update
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AdditionalParams
|
||||
item={item}
|
||||
request={request}
|
||||
collection={collection}
|
||||
updateAuth={updateAuth}
|
||||
handleSave={handleSave}
|
||||
/>
|
||||
<Oauth2ActionButtons item={item} request={request} collection={collection} url={accessTokenUrl} credentialsId={credentialsId} />
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
||||
@@ -26,7 +26,7 @@ const Request = ({ collection, request, item }) => {
|
||||
<div>
|
||||
{/* Method and URL */}
|
||||
<div className="mb-1 flex gap-2">
|
||||
<pre className="whitespace-pre-wrap">{url}</pre>
|
||||
<pre className="whitespace-pre-wrap" title={url}>{url}</pre>
|
||||
</div>
|
||||
|
||||
{/* Headers */}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 };
|
||||
module.exports = { authorizeUserInWindow, matchesCallbackUrl };
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
116
packages/bruno-filestore/src/formats/bru/tests/fixtures/oauth2-additional-params.js
vendored
Normal file
116
packages/bruno-filestore/src/formats/bru/tests/fixtures/oauth2-additional-params.js
vendored
Normal file
@@ -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
|
||||
};
|
||||
@@ -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']);
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
module.exports = parser;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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');
|
||||
|
||||
329
packages/bruno-lang/v2/tests/oauth2-additional-params.spec.js
Normal file
329
packages/bruno-lang/v2/tests/oauth2-additional-params.spec.js
Normal file
@@ -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
|
||||
}]);
|
||||
});
|
||||
});
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user