oauth2 workflow improvements (#3874)

~ basic auth credentials should be assigned to `request.basicAuth` instead `request.auth` object
~ added credentials_placement option, fixed headers issue client credentials flow
~ cache input field values when grant type select box value changes
~ updated logic for - cache input field values when grant type select box value changes
~ updated token expiry timer component logic
This commit is contained in:
lohit
2025-01-24 18:44:02 +05:30
committed by GitHub
parent f38c7ae03a
commit 1d1e701ccb
27 changed files with 497 additions and 255 deletions

View File

@@ -22,9 +22,9 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu
const oAuth = get(request, 'auth.oauth2', {});
const { callbackUrl, authorizationUrl, accessTokenUrl, clientId, clientSecret, scope, state, pkce, credentialsId, tokenPlacement, tokenPrefix, tokenQueryParamKey, reuseToken } = oAuth;
const { callbackUrl, authorizationUrl, accessTokenUrl, clientId, clientSecret, scope, credentialsPlacement, state, pkce, credentialsId, tokenPlacement, tokenPrefix, tokenQueryKey, reuseToken } = oAuth;
const Icon = forwardRef((props, ref) => {
const TokenPlacementIcon = forwardRef((props, ref) => {
return (
<div ref={ref} className="flex items-center justify-end token-placement-label select-none">
{tokenPlacement == 'url' ? 'URL' : 'Headers'}
@@ -33,6 +33,16 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu
);
});
const CredentialsPlacementIcon = forwardRef((props, ref) => {
return (
<div ref={ref} className="flex items-center justify-end token-placement-label select-none">
{credentialsPlacement == 'body' ? 'Request Body' : 'Basic Auth Header'}
<IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
</div>
);
});
const handleFetchOauth2Credentials = async () => {
let requestCopy = cloneDeep(request);
requestCopy.oauth2 = requestCopy?.auth.oauth2;
@@ -41,11 +51,12 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu
try {
await dispatch(fetchOauth2Credentials({ request: requestCopy, collection }));
toggleFetchingToken(false);
toast.success('token fetched successfully!');
}
catch(error) {
console.error('could not fetch the token!');
console.error(error);
toggleFetchingToken(false);
toast.error('An error occured while fetching token!');
}
}
@@ -67,10 +78,11 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu
state,
scope,
pkce,
credentialsPlacement,
credentialsId,
tokenPlacement,
tokenPrefix,
tokenQueryParamKey,
tokenQueryKey,
reuseToken,
[key]: value
}
@@ -93,10 +105,11 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu
clientSecret,
state,
scope,
credentialsPlacement,
credentialsId,
tokenPlacement,
tokenPrefix,
tokenQueryParamKey,
tokenQueryKey,
reuseToken,
pkce: !Boolean(oAuth?.['pkce'])
}
@@ -146,6 +159,31 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu
</div>
);
})}
<div className="flex items-center gap-4 w-full" key={`input-credentials-placement`}>
<label className="block min-w-[140px]">Add Credentials to</label>
<div className="inline-flex items-center cursor-pointer token-placement-selector">
<Dropdown onCreate={onDropdownCreate} icon={<CredentialsPlacementIcon />} placement="bottom-end">
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
handleChange('credentialsPlacement', 'body');
}}
>
Request Body
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
handleChange('credentialsPlacement', 'basic_auth_header');
}}
>
Basic Auth Header
</div>
</Dropdown>
</div>
</div>
<div className="flex flex-row w-full gap-4" key="pkce">
<label className="block">Use PKCE</label>
<input
@@ -180,7 +218,7 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu
<div className="flex items-center gap-4 w-full" key={`input-token-placement`}>
<label className="block min-w-[140px]">Add token to</label>
<div className="inline-flex items-center cursor-pointer token-placement-selector">
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
<Dropdown onCreate={onDropdownCreate} icon={<TokenPlacementIcon />} placement="bottom-end">
<div
className="dropdown-item"
onClick={() => {
@@ -222,10 +260,10 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu
<label className="block font-medium min-w-[140px]">Query Param Key</label>
<div className="single-line-editor-wrapper flex-1">
<SingleLineEditor
value={oAuth['tokenQueryParamKey'] || ''}
value={oAuth['tokenQueryKey'] || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleChange('tokenQueryParamKey', val)}
onChange={(val) => handleChange('tokenQueryKey', val)}
onRun={handleRun}
collection={collection}
/>

View File

@@ -22,7 +22,7 @@ const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAu
const oAuth = get(request, 'auth.oauth2', {});
const { accessTokenUrl, clientId, clientSecret, scope, credentialsId, tokenPlacement, tokenPrefix, tokenQueryParamKey, reuseToken } = oAuth;
const { accessTokenUrl, clientId, clientSecret, scope, credentialsPlacement, credentialsId, tokenPlacement, tokenPrefix, tokenQueryKey, reuseToken } = oAuth;
const handleFetchOauth2Credentials = async () => {
let requestCopy = cloneDeep(request);
@@ -32,24 +32,34 @@ const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAu
try {
await dispatch(fetchOauth2Credentials({ request: requestCopy, collection }));
toggleFetchingToken(false);
toast.success('Token fetched successfully!');
}
catch (error) {
console.error('could not fetch the token!');
console.error(error);
toggleFetchingToken(false);
toast.error('An error occured while fetching token!');
}
}
const handleSave = () => { save(); };
const Icon = forwardRef((props, ref) => {
const TokenPlacementIcon = forwardRef((props, ref) => {
return (
<div ref={ref} className="flex items-center justify-end token-placement-label select-none">
{tokenPlacement == 'url' ? 'URL' : 'Headers'}
{tokenPlacement == 'url' ? 'URL' : 'Headers'}
<IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
</div>
);
});
const CredentialsPlacementIcon = forwardRef((props, ref) => {
return (
<div ref={ref} className="flex items-center justify-end token-placement-label select-none">
{credentialsPlacement == 'body' ? 'Request Body' : 'Basic Auth Header'}
<IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
</div>
);
});
const handleChange = (key, value) => {
dispatch(
@@ -63,10 +73,11 @@ const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAu
clientId,
clientSecret,
scope,
credentialsPlacement,
credentialsId,
tokenPlacement,
tokenPrefix,
tokenQueryParamKey,
tokenQueryKey,
reuseToken,
[key]: value
}
@@ -116,6 +127,31 @@ const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAu
</div>
);
})}
<div className="flex items-center gap-4 w-full" key={`input-credentials-placement`}>
<label className="block min-w-[140px]">Add Credentials to</label>
<div className="inline-flex items-center cursor-pointer token-placement-selector">
<Dropdown onCreate={onDropdownCreate} icon={<CredentialsPlacementIcon />} placement="bottom-end">
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
handleChange('credentialsPlacement', 'body');
}}
>
Request Body
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
handleChange('credentialsPlacement', 'basic_auth_header');
}}
>
Basic Auth Header
</div>
</Dropdown>
</div>
</div>
<div className="flex items-center gap-2.5 mt-2">
<div className="flex items-center px-2.5 py-1.5 bg-indigo-50/50 dark:bg-indigo-500/10 rounded-md">
<IconKey size={14} className="text-indigo-500 dark:text-indigo-400" />
@@ -141,7 +177,7 @@ const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAu
<div className="flex items-center gap-4 w-full" key={`input-token-placement`}>
<label className="block min-w-[140px]">Add token to</label>
<div className="inline-flex items-center cursor-pointer token-placement-selector w-fit">
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
<Dropdown onCreate={onDropdownCreate} icon={<TokenPlacementIcon />} placement="bottom-end">
<div
className="dropdown-item"
onClick={() => {
@@ -183,10 +219,10 @@ const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAu
<label className="block font-medium min-w-[140px]">Query Param Key</label>
<div className="single-line-editor-wrapper flex-1">
<SingleLineEditor
value={oAuth['tokenQueryParamKey'] || ''}
value={oAuth['tokenQueryKey'] || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleChange('tokenQueryParamKey', val)}
onChange={(val) => handleChange('tokenQueryKey', val)}
onRun={handleRun}
collection={collection}
/>

View File

@@ -6,13 +6,16 @@ import StyledWrapper from './StyledWrapper';
import { IconCaretDown, IconKey } from '@tabler/icons';
import { humanizeGrantType } from 'utils/collections';
import { useEffect } from 'react';
import { useState } from 'react';
const GrantTypeSelector = ({ item = {}, request, updateAuth, collection }) => {
const dispatch = useDispatch();
const dropdownTippyRef = useRef();
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const oAuth = get(request, 'auth.oauth2', {});
const [valuesCache, setValuesCache] = useState({
...oAuth
});
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const Icon = forwardRef((props, ref) => {
return (
@@ -23,13 +26,19 @@ const GrantTypeSelector = ({ item = {}, request, updateAuth, collection }) => {
});
const onGrantTypeChange = (grantType) => {
let updatedValues = {
...valuesCache,
...oAuth,
grantType
};
setValuesCache(updatedValues);
dispatch(
updateAuth({
mode: 'oauth2',
collectionUid: collection.uid,
itemUid: item.uid,
content: {
...(defaultValues?.[grantType] || {})
...updatedValues
}
})
);
@@ -52,10 +61,11 @@ const GrantTypeSelector = ({ item = {}, request, updateAuth, collection }) => {
clientId: '',
clientSecret: '',
scope: '',
credentialsPlacement: 'body',
credentialsId: 'credentials',
tokenPlacement: 'header',
tokenPrefix: 'Bearer',
tokenQueryParamKey: 'access_token',
tokenQueryKey: 'access_token',
reuseToken: false
}
})
@@ -106,47 +116,4 @@ const GrantTypeSelector = ({ item = {}, request, updateAuth, collection }) => {
</StyledWrapper>
);
};
export default GrantTypeSelector;
const defaultValues = {
'authorization_code': {
grantType: 'authorization_code',
accessTokenUrl: '',
username: '',
password: '',
clientId: '',
clientSecret: '',
scope: '',
credentialsId: 'credentials',
tokenPlacement: 'header',
tokenPrefix: 'Bearer',
tokenQueryParamKey: 'access_token',
reuseToken: false
},
'client_credentials': {
grantType: 'client_credentials',
accessTokenUrl: '',
clientId: '',
clientSecret: '',
scope: '',
credentialsId: 'credentials',
tokenPlacement: 'header',
tokenPrefix: 'Bearer',
tokenQueryParamKey: 'access_token',
reuseToken: false
},
'password': {
grantType: 'password',
accessTokenUrl: '',
username: '',
password: '',
clientId: '',
clientSecret: '',
scope: '',
credentialsId: 'credentials',
tokenPlacement: 'header',
tokenPrefix: 'Bearer',
tokenQueryParamKey: 'access_token',
reuseToken: false
}
}
export default GrantTypeSelector;

View File

@@ -37,12 +37,15 @@ const TokenSection = ({ title, token }) => {
className="flex items-center justify-between px-3 py-2 bg-gray-50 dark:bg-gray-800 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-750 transition-colors"
onClick={() => setIsExpanded(!isExpanded)}
>
<div className="flex items-center space-x-2">
<div className="flex items-center space-x-2 w-full">
{isExpanded ?
<IconChevronDown size={18} className="text-gray-500" /> :
<IconChevronRight size={18} className="text-gray-500" />
}
<div className="flex flex-row justify-between w-full">
<h3 className="text-sm font-medium">{title}</h3>
{decodedToken?.exp && <ExpiryTimer expiresIn={decodedToken?.exp}/>}
</div>
</div>
</div>
{isExpanded && (
@@ -86,62 +89,48 @@ const TokenSection = ({ title, token }) => {
};
const formatExpiryTime = (seconds) => {
if (seconds < 60) {
return `${seconds}s`;
} else if (seconds < 3600) {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins}m ${secs}s`;
} else {
const hours = Math.floor(seconds / 3600);
const mins = Math.floor((seconds % 3600) / 60);
return `${hours}h ${mins}m`;
}
if (seconds < 60) return `${seconds}s`;
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
return `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}m`;
};
const ExpiryTimer = ({ initialExpiresIn }) => {
const [timeLeft, setTimeLeft] = useState(initialExpiresIn);
const ExpiryTimer = ({ expiresIn }) => {
if (!expiresIn) return null;
const [timeLeft, setTimeLeft] = useState(() => Math.max(0, Math.floor((expiresIn - Date.now() / 1000))));
useEffect(() => {
const timer = setInterval(() => {
setTimeLeft(prev => {
if (prev <= 0) {
clearInterval(timer);
return 0;
}
return prev - 1;
});
setTimeLeft((prev) => (prev > 0 ? prev - 1 : 0));
}, 1000);
return () => clearInterval(timer);
}, []);
return (
<div className={`text-xs px-2 py-1 rounded-full ${
timeLeft <= 30
? 'bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-400'
: 'bg-blue-50 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400'
}`}>
Expires in {formatExpiryTime(timeLeft)}
<div
className={`text-xs px-2 py-1 rounded-full ${
timeLeft <= 30
? "bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-400"
: "bg-blue-50 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400"
}`}
>
{timeLeft > 0 ? `Expires in ${formatExpiryTime(timeLeft)}` : `Expired` }
</div>
);
};
const Oauth2TokenViewer = ({ collection, item, url, credentialsId, handleRun }) => {
const { uid: collectionUid } = collection;
const interpolatedUrl = interpolateStringUsingCollectionAndItem({ collection, item, string: url });
const credentialsData = find(collection?.oauth2Credentials, creds => creds?.url == interpolatedUrl && creds?.collectionUid == collectionUid && creds?.credentialsId == credentialsId);
const creds = credentialsData?.credentials;
const creds = credentialsData?.credentials || {};
return (
<StyledWrapper className="relative w-auto h-fit mt-2">
{creds ? (
{Object.keys(creds)?.length ? (
<div className="border border-gray-200 dark:border-gray-700 rounded-lg p-4 shadow-sm">
<div className="flex items-center justify-between mb-3">
<h2 className="text-sm font-medium">Token</h2>
{creds?.expires_in && <ExpiryTimer initialExpiresIn={creds.expires_in} />}
</div>
<TokenSection title="Access Token" token={creds.access_token} />
<TokenSection title="Refresh Token" token={creds.refresh_token} />
<TokenSection title="ID Token" token={creds.id_token} />

View File

@@ -22,7 +22,7 @@ const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, update
const oAuth = get(request, 'auth.oauth2', {});
const { accessTokenUrl, username, password, clientId, clientSecret, scope, credentialsId, tokenPlacement, tokenPrefix, tokenQueryParamKey, reuseToken } = oAuth;
const { accessTokenUrl, username, password, clientId, clientSecret, scope, credentialsPlacement, credentialsId, tokenPlacement, tokenPrefix, tokenQueryKey, reuseToken } = oAuth;
const handleFetchOauth2Credentials = async () => {
let requestCopy = cloneDeep(request);
@@ -32,20 +32,30 @@ const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, update
try {
await dispatch(fetchOauth2Credentials({ request: requestCopy, collection }));
toggleFetchingToken(false);
toast.success('Token fetched successfully!');
}
catch (error) {
console.error('could not fetch the token!');
console.error(error);
toggleFetchingToken(false);
toast.error('An error occured while fetching token!');
}
}
const handleSave = () => { save(); }
const Icon = forwardRef((props, ref) => {
const TokenPlacementIcon = forwardRef((props, ref) => {
return (
<div ref={ref} className="flex items-center justify-end token-placement-label select-none">
{tokenPlacement == 'url' ? 'URL' : 'Headers'}
{tokenPlacement == 'url' ? 'URL' : 'Headers'}
<IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
</div>
);
});
const CredentialsPlacementIcon = forwardRef((props, ref) => {
return (
<div ref={ref} className="flex items-center justify-end token-placement-label select-none">
{credentialsPlacement == 'body' ? 'Request Body' : 'Basic Auth Header'}
<IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
</div>
);
@@ -65,10 +75,11 @@ const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, update
clientId,
clientSecret,
scope,
credentialsPlacement,
credentialsId,
tokenPlacement,
tokenPrefix,
tokenQueryParamKey,
tokenQueryKey,
reuseToken,
[key]: value
}
@@ -118,6 +129,31 @@ const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, update
</div>
);
})}
<div className="flex items-center gap-4 w-full" key={`input-credentials-placement`}>
<label className="block min-w-[140px]">Add Credentials to</label>
<div className="inline-flex items-center cursor-pointer token-placement-selector">
<Dropdown onCreate={onDropdownCreate} icon={<CredentialsPlacementIcon />} placement="bottom-end">
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
handleChange('credentialsPlacement', 'body');
}}
>
Request Body
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
handleChange('credentialsPlacement', 'basic_auth_header');
}}
>
Basic Auth Header
</div>
</Dropdown>
</div>
</div>
<div className="flex items-center gap-2.5 mt-2">
<div className="flex items-center px-2.5 py-1.5 bg-indigo-50/50 dark:bg-indigo-500/10 rounded-md">
<IconKey size={14} className="text-indigo-500 dark:text-indigo-400" />
@@ -143,7 +179,7 @@ const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, update
<div className="flex items-center gap-4 w-full" key={`input-token-placement`}>
<label className="block min-w-[140px]">Add token to</label>
<div className="inline-flex items-center cursor-pointer token-placement-selector">
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
<Dropdown onCreate={onDropdownCreate} icon={<TokenPlacementIcon />} placement="bottom-end">
<div
className="dropdown-item"
onClick={() => {
@@ -185,10 +221,10 @@ const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, update
<label className="block font-medium min-w-[140px]">Query Param Key</label>
<div className="single-line-editor-wrapper flex-1">
<SingleLineEditor
value={oAuth['tokenQueryParamKey'] || ''}
value={oAuth['tokenQueryKey'] || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleChange('tokenQueryParamKey', val)}
onChange={(val) => handleChange('tokenQueryKey', val)}
onRun={handleRun}
collection={collection}
/>

View File

@@ -1225,7 +1225,7 @@ export const clearOauth2Cache = (payload) => async (dispatch, getState) => {
const { collectionUid, url, credentialsId } = payload;
return new Promise((resolve, reject) => {
const { ipcRenderer } = window;
ipcRenderer.invoke('clear-oauth2-cache', collectionUid, url, credentialsId).then(resolve => {
ipcRenderer.invoke('clear-oauth2-cache', collectionUid, url, credentialsId).then(() => {
dispatch(collectionClearOauth2CredentialsByUrl({ collectionUid, url, credentialsId }));
resolve();
}).catch(reject);

View File

@@ -361,10 +361,11 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {}
clientId: get(si.request, 'auth.oauth2.clientId', ''),
clientSecret: get(si.request, 'auth.oauth2.clientSecret', ''),
scope: get(si.request, 'auth.oauth2.scope', ''),
credentialsPlacement: get(si.request, 'auth.oauth2.credentialsPlacement', 'body'),
credentialsId: get(si.request, 'auth.oauth2.credentialsId', 'credentials'),
tokenPlacement: get(si.request, 'auth.oauth2.tokenPlacement', 'header'),
tokenPrefix: get(si.request, 'auth.oauth2.tokenPrefix', 'Bearer'),
tokenQueryParamKey: get(si.request, 'auth.oauth2.tokenQueryParamKey', ''),
tokenQueryKey: get(si.request, 'auth.oauth2.tokenQueryKey', ''),
reuseToken: get(si.request, 'auth.oauth2.reuseToken', false)
};
break;
@@ -377,11 +378,12 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {}
clientId: get(si.request, 'auth.oauth2.clientId', ''),
clientSecret: get(si.request, 'auth.oauth2.clientSecret', ''),
scope: get(si.request, 'auth.oauth2.scope', ''),
credentialsPlacement: get(si.request, 'auth.oauth2.credentialsPlacement', 'body'),
pkce: get(si.request, 'auth.oauth2.pkce', false),
credentialsId: get(si.request, 'auth.oauth2.credentialsId', 'credentials'),
tokenPlacement: get(si.request, 'auth.oauth2.tokenPlacement', 'header'),
tokenPrefix: get(si.request, 'auth.oauth2.tokenPrefix', 'Bearer'),
tokenQueryParamKey: get(si.request, 'auth.oauth2.tokenQueryParamKey', ''),
tokenQueryKey: get(si.request, 'auth.oauth2.tokenQueryKey', ''),
reuseToken: get(si.request, 'auth.oauth2.reuseToken', false)
};
break;
@@ -392,10 +394,11 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {}
clientId: get(si.request, 'auth.oauth2.clientId', ''),
clientSecret: get(si.request, 'auth.oauth2.clientSecret', ''),
scope: get(si.request, 'auth.oauth2.scope', ''),
credentialsPlacement: get(si.request, 'auth.oauth2.credentialsPlacement', 'body'),
credentialsId: get(si.request, 'auth.oauth2.credentialsId', 'credentials'),
tokenPlacement: get(si.request, 'auth.oauth2.tokenPlacement', 'header'),
tokenPrefix: get(si.request, 'auth.oauth2.tokenPrefix', 'Bearer'),
tokenQueryParamKey: get(si.request, 'auth.oauth2.tokenQueryParamKey', ''),
tokenQueryKey: get(si.request, 'auth.oauth2.tokenQueryKey', ''),
reuseToken: get(si.request, 'auth.oauth2.reuseToken', false)
};
break;
@@ -1045,7 +1048,7 @@ export const getFormattedCollectionOauth2Credentials = ({ oauth2Credentials = []
let credentialsVariables = {};
oauth2Credentials.forEach(({ credentialsId, credentials }) => {
Object.entries(credentials).forEach(([key, value]) => {
credentialsVariables[`$auth.${credentialsId}.${key}`] = value;
credentialsVariables[`$oauth2.${credentialsId}.${key}`] = value;
});
});
return credentialsVariables;

View File

@@ -147,13 +147,13 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
// todo: we have things happening in two places w.r.t basic auth
// need to refactor this in the future
// the request.auth (basic auth) object gets set inside the prepare-request.js file
if (request.auth) {
const username = _interpolate(request.auth.username) || '';
const password = _interpolate(request.auth.password) || '';
if (request.basicAuth) {
const username = _interpolate(request.basicAuth.username) || '';
const password = _interpolate(request.basicAuth.password) || '';
// use auth header based approach and delete the request.auth object
request.headers['Authorization'] = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
delete request.auth;
delete request.basicAuth;
}
if (request.awsv4config) {
@@ -165,12 +165,14 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
request.awsv4config.profileName = _interpolate(request.awsv4config.profileName) || '';
}
// interpolate vars for ntlmConfig auth
if (request.ntlmConfig) {
request.ntlmConfig.username = _interpolate(request.ntlmConfig.username) || '';
request.ntlmConfig.password = _interpolate(request.ntlmConfig.password) || '';
request.ntlmConfig.domain = _interpolate(request.ntlmConfig.domain) || '';
}
// interpolate vars for ntlmConfig auth
if (request.ntlmConfig) {
request.ntlmConfig.username = _interpolate(request.ntlmConfig.username) || '';
request.ntlmConfig.password = _interpolate(request.ntlmConfig.password) || '';
request.ntlmConfig.domain = _interpolate(request.ntlmConfig.domain) || '';
}
if(request?.auth) delete request.auth;
if (request) return request;
};

View File

@@ -38,7 +38,7 @@ const prepareRequest = (item = {}, collection = {}) => {
const collectionAuth = get(collection, 'root.request.auth');
if (collectionAuth && request.auth?.mode === 'inherit') {
if (collectionAuth.mode === 'basic') {
axiosRequest.auth = {
axiosRequest.basicAuth = {
username: get(collectionAuth, 'basic.username'),
password: get(collectionAuth, 'basic.password')
};
@@ -69,7 +69,7 @@ const prepareRequest = (item = {}, collection = {}) => {
if (request.auth && request.auth.mode !== 'inherit') {
if (request.auth.mode === 'basic') {
axiosRequest.auth = {
axiosRequest.basicAuth = {
username: get(request, 'auth.basic.username'),
password: get(request, 'auth.basic.password')
};

View File

@@ -149,12 +149,12 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
// todo: we have things happening in two places w.r.t basic auth
// need to refactor this in the future
// the request.auth (basic auth) object gets set inside the prepare-request.js file
if (request.auth) {
const username = _interpolate(request.auth.username) || '';
const password = _interpolate(request.auth.password) || '';
if (request.basicAuth) {
const username = _interpolate(request.basicAuth.username) || '';
const password = _interpolate(request.basicAuth.password) || '';
// use auth header based approach and delete the request.auth object
request.headers['Authorization'] = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
delete request.auth;
delete request.basicAuth;
}
if (request?.oauth2?.grantType) {
@@ -167,10 +167,11 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
request.oauth2.clientId = _interpolate(request.oauth2.clientId) || '';
request.oauth2.clientSecret = _interpolate(request.oauth2.clientSecret) || '';
request.oauth2.scope = _interpolate(request.oauth2.scope) || '';
request.oauth2.credentialsPlacement = _interpolate(request.oauth2.credentialsPlacement) || '';
request.oauth2.credentialsId = _interpolate(request.oauth2.credentialsId) || '';
request.oauth2.tokenPlacement = _interpolate(request.oauth2.tokenPlacement) || '';
request.oauth2.tokenPrefix = _interpolate(request.oauth2.tokenPrefix) || '';
request.oauth2.tokenQueryParamKey = _interpolate(request.oauth2.tokenQueryParamKey) || '';
request.oauth2.tokenQueryKey = _interpolate(request.oauth2.tokenQueryKey) || '';
request.oauth2.reuseToken = _interpolate(request.oauth2.reuseToken) || false;
break;
case 'authorization_code':
@@ -182,10 +183,11 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
request.oauth2.scope = _interpolate(request.oauth2.scope) || '';
request.oauth2.state = _interpolate(request.oauth2.state) || '';
request.oauth2.pkce = _interpolate(request.oauth2.pkce) || false;
request.oauth2.credentialsPlacement = _interpolate(request.oauth2.credentialsPlacement) || '';
request.oauth2.credentialsId = _interpolate(request.oauth2.credentialsId) || '';
request.oauth2.tokenPlacement = _interpolate(request.oauth2.tokenPlacement) || '';
request.oauth2.tokenPrefix = _interpolate(request.oauth2.tokenPrefix) || '';
request.oauth2.tokenQueryParamKey = _interpolate(request.oauth2.tokenQueryParamKey) || '';
request.oauth2.tokenQueryKey = _interpolate(request.oauth2.tokenQueryKey) || '';
request.oauth2.reuseToken = _interpolate(request.oauth2.reuseToken) || false;
break;
case 'client_credentials':
@@ -193,10 +195,11 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
request.oauth2.clientId = _interpolate(request.oauth2.clientId) || '';
request.oauth2.clientSecret = _interpolate(request.oauth2.clientSecret) || '';
request.oauth2.scope = _interpolate(request.oauth2.scope) || '';
request.oauth2.credentialsPlacement = _interpolate(request.oauth2.credentialsPlacement) || '';
request.oauth2.credentialsId = _interpolate(request.oauth2.credentialsId) || '';
request.oauth2.tokenPlacement = _interpolate(request.oauth2.tokenPlacement) || '';
request.oauth2.tokenPrefix = _interpolate(request.oauth2.tokenPrefix) || '';
request.oauth2.tokenQueryParamKey = _interpolate(request.oauth2.tokenQueryParamKey) || '';
request.oauth2.tokenQueryKey = _interpolate(request.oauth2.tokenQueryKey) || '';
request.oauth2.reuseToken = _interpolate(request.oauth2.reuseToken) || false;
break;
default:
@@ -234,6 +237,8 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
request.ntlmConfig.domain = _interpolate(request.ntlmConfig.domain) || '';
}
if(request?.auth) delete request.auth;
return request;
};

View File

@@ -294,7 +294,7 @@ const getFormattedCollectionOauth2Credentials = ({ oauth2Credentials = [] }) =>
let credentialsVariables = {};
oauth2Credentials.forEach(({ credentialsId, credentials }) => {
Object.entries(credentials).forEach(([key, value]) => {
credentialsVariables[`$auth.${credentialsId}.${key}`] = value;
credentialsVariables[`$oauth2.${credentialsId}.${key}`] = value;
});
});
return credentialsVariables;

View File

@@ -3,7 +3,7 @@ const crypto = require('crypto');
const { authorizeUserInWindow } = require('../ipc/network/authorize-user-in-window');
const Oauth2Store = require('../store/oauth2');
const { makeAxiosInstance } = require('./axios-instance');
const { safeParseJSON } = require('./common');
const { safeParseJSON, safeStringifyJSON } = require('./common');
const oauth2Store = new Oauth2Store();
@@ -31,38 +31,46 @@ const getOAuth2TokenUsingAuthorizationCode = async ({ request, collectionUid, fo
let requestCopy = cloneDeep(request);
const oAuth = get(requestCopy, 'oauth2', {});
const { clientId, clientSecret, callbackUrl, scope, pkce, authorizationUrl, credentialsId, reuseToken } = oAuth;
const { clientId, clientSecret, callbackUrl, scope, pkce, credentialsPlacement, authorizationUrl, credentialsId, reuseToken } = oAuth;
const url = requestCopy?.oauth2?.accessTokenUrl;
if ((reuseToken || ALWAYS_REUSE_ACCESS_TOKEN____UNLESS_FETCHED_MANUALLY) && !forceFetch) {
const credentials = getStoredOauth2Credentials({ collectionUid, url, credentialsId }) || {};
return { collectionUid, url, credentials, credentialsId };
}
const { authorizationCode } = await getOAuth2AuthorizationCode(requestCopy, codeChallenge, collectionUid);
const data = {
grant_type: 'authorization_code',
code: authorizationCode,
redirect_uri: callbackUrl,
client_id: clientId,
client_secret: clientSecret
};
if (pkce) {
data['code_verifier'] = codeVerifier;
}
requestCopy.method = 'POST';
requestCopy.headers['content-type'] = 'application/x-www-form-urlencoded';
requestCopy.headers['Accept'] = 'application/json';
requestCopy.data = data;
if (credentialsPlacement == "basic_auth_header") {
requestCopy.headers['Authorization'] = `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`;
}
else {
const data = {
grant_type: 'authorization_code',
code: authorizationCode,
redirect_uri: callbackUrl,
client_id: clientId,
client_secret: clientSecret
};
if (pkce) {
data['code_verifier'] = codeVerifier;
}
requestCopy.data = data;
}
requestCopy.url = url;
const axiosInstance = makeAxiosInstance();
const response = await axiosInstance(requestCopy);
const responseData = Buffer.isBuffer(response.data) ? response.data?.toString() : response.data;
const parsedResponseData = safeParseJSON(responseData);
persistOauth2Credentials({ collectionUid, url, credentials: parsedResponseData, credentialsId });
return { collectionUid, url, credentials: parsedResponseData, credentialsId };
try {
const axiosInstance = makeAxiosInstance();
const response = await axiosInstance(requestCopy);
const responseData = Buffer.isBuffer(response.data) ? response.data?.toString() : response.data;
const parsedResponseData = safeParseJSON(responseData);
persistOauth2Credentials({ collectionUid, url, credentials: parsedResponseData, credentialsId });
return { collectionUid, url, credentials: parsedResponseData, credentialsId };
}
catch (error) {
return Promise.reject(safeStringifyJSON(error?.response?.data));
}
};
const getOAuth2AuthorizationCode = (request, codeChallenge, collectionUid) => {
@@ -105,15 +113,7 @@ const getOAuth2AuthorizationCode = (request, codeChallenge, collectionUid) => {
const getOAuth2TokenUsingClientCredentials = async ({ request, collectionUid, forceFetch = false }) => {
let requestCopy = cloneDeep(request);
const oAuth = get(requestCopy, 'oauth2', {});
const { clientId, clientSecret, scope, credentialsId, reuseToken } = oAuth;
const data = {
grant_type: 'client_credentials',
client_id: clientId,
client_secret: clientSecret
};
if (scope) {
data.scope = scope;
}
const { clientId, clientSecret, scope, credentialsPlacement, credentialsId, reuseToken } = oAuth;
const url = requestCopy?.oauth2?.accessTokenUrl;
@@ -125,16 +125,34 @@ const getOAuth2TokenUsingClientCredentials = async ({ request, collectionUid, fo
requestCopy.method = 'POST';
requestCopy.headers['content-type'] = 'application/x-www-form-urlencoded';
requestCopy.headers['Accept'] = 'application/json';
requestCopy.data = data;
if (credentialsPlacement == "basic_auth_header") {
requestCopy.headers['Authorization'] = `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`;
}
else {
const data = {
grant_type: 'client_credentials',
client_id: clientId,
client_secret: clientSecret
};
if (scope) {
data.scope = scope;
}
requestCopy.data = data;
}
requestCopy.url = url;
const axiosInstance = makeAxiosInstance();
const response = await axiosInstance(requestCopy);
const responseData = Buffer.isBuffer(response.data) ? response.data?.toString() : response.data;
const parsedResponseData = safeParseJSON(responseData);
persistOauth2Credentials({ collectionUid, url, credentials: parsedResponseData, credentialsId });
return { collectionUid, url, credentials: parsedResponseData, credentialsId };
try {
const response = await axiosInstance(requestCopy);
const responseData = Buffer.isBuffer(response.data) ? response.data?.toString() : response.data;
const parsedResponseData = safeParseJSON(responseData);
persistOauth2Credentials({ collectionUid, url, credentials: parsedResponseData, credentialsId });
return { collectionUid, url, credentials: parsedResponseData, credentialsId };
}
catch (error) {
return Promise.reject(safeStringifyJSON(error?.response?.data));
}
};
// PASSWORD CREDENTIALS
@@ -142,17 +160,7 @@ const getOAuth2TokenUsingClientCredentials = async ({ request, collectionUid, fo
const getOAuth2TokenUsingPasswordCredentials = async ({ request, collectionUid, forceFetch = false }) => {
let requestCopy = cloneDeep(request);
const oAuth = get(requestCopy, 'oauth2', {});
const { username, password, clientId, clientSecret, scope, credentialsId, reuseToken } = oAuth;
const data = {
grant_type: 'password',
username,
password,
client_id: clientId,
client_secret: clientSecret
};
if (scope) {
data.scope = scope;
}
const { username, password, clientId, clientSecret, scope, credentialsPlacement, credentialsId, reuseToken } = oAuth;
const url = requestCopy?.oauth2?.accessTokenUrl;
if ((reuseToken || ALWAYS_REUSE_ACCESS_TOKEN____UNLESS_FETCHED_MANUALLY) && !forceFetch) {
@@ -163,15 +171,35 @@ const getOAuth2TokenUsingPasswordCredentials = async ({ request, collectionUid,
requestCopy.method = 'POST';
requestCopy.headers['content-type'] = 'application/x-www-form-urlencoded';
requestCopy.headers['Accept'] = 'application/json';
requestCopy.data = data;
if (credentialsPlacement == "basic_auth_header") {
requestCopy.headers['Authorization'] = `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`;
}
else {
const data = {
grant_type: 'password',
username,
password,
client_id: clientId,
client_secret: clientSecret
};
if (scope) {
data.scope = scope;
}
requestCopy.data = data;
}
requestCopy.url = url;
const axiosInstance = makeAxiosInstance();
const response = await axiosInstance(requestCopy);
const responseData = Buffer.isBuffer(response.data) ? response.data?.toString() : response.data;
const parsedResponseData = safeParseJSON(responseData);
persistOauth2Credentials({ collectionUid, url, credentials: parsedResponseData, credentialsId });
return { collectionUid, url, credentials: parsedResponseData, credentialsId };
try {
const axiosInstance = makeAxiosInstance();
const response = await axiosInstance(requestCopy);
const responseData = Buffer.isBuffer(response.data) ? response.data?.toString() : response.data;
const parsedResponseData = safeParseJSON(responseData);
persistOauth2Credentials({ collectionUid, url, credentials: parsedResponseData, credentialsId });
return { collectionUid, url, credentials: parsedResponseData, credentialsId };
}
catch (error) {
return Promise.reject(safeStringifyJSON(error?.response?.data));
}
};
const refreshOauth2Token = async (request, collectionUid) => {
@@ -207,9 +235,9 @@ const refreshOauth2Token = async (request, collectionUid) => {
persistOauth2Credentials({ collectionUid, url, credentials: parsedResponseData, credentialsId });
return { collectionUid, url, credentials: parsedResponseData, credentialsId };
}
catch(error) {
catch (error) {
clearOauth2Credentials({ collectionUid, url, credentialsId });
return Promise.reject(error);
return Promise.reject(safeStringifyJSON(error?.response?.data));
}
}
}

View File

@@ -38,7 +38,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
};
break;
case 'basic':
axiosRequest.auth = {
axiosRequest.basicAuth = {
username: get(collectionAuth, 'basic.username'),
password: get(collectionAuth, 'basic.password')
};
@@ -97,10 +97,11 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
clientId: get(collectionAuth, 'oauth2.clientId'),
clientSecret: get(collectionAuth, 'oauth2.clientSecret'),
scope: get(collectionAuth, 'oauth2.scope'),
credentialsPlacement: get(collectionAuth, 'oauth2.credentialsPlacement'),
credentialsId: get(collectionAuth, 'oauth2.credentialsId'),
tokenPlacement: get(collectionAuth, 'oauth2.tokenPlacement'),
tokenPrefix: get(collectionAuth, 'oauth2.tokenPrefix'),
tokenQueryParamKey: get(collectionAuth, 'oauth2.tokenQueryParamKey'),
tokenQueryKey: get(collectionAuth, 'oauth2.tokenQueryKey'),
reuseToken: get(collectionAuth, 'oauth2.reuseToken')
};
break;
@@ -115,10 +116,11 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
scope: get(collectionAuth, 'oauth2.scope'),
state: get(collectionAuth, 'oauth2.state'),
pkce: get(collectionAuth, 'oauth2.pkce'),
credentialsPlacement: get(collectionAuth, 'oauth2.credentialsPlacement'),
credentialsId: get(collectionAuth, 'oauth2.credentialsId'),
tokenPlacement: get(collectionAuth, 'oauth2.tokenPlacement'),
tokenPrefix: get(collectionAuth, 'oauth2.tokenPrefix'),
tokenQueryParamKey: get(collectionAuth, 'oauth2.tokenQueryParamKey'),
tokenQueryKey: get(collectionAuth, 'oauth2.tokenQueryKey'),
reuseToken: get(collectionAuth, 'oauth2.reuseToken')
};
break;
@@ -129,10 +131,11 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
clientId: get(collectionAuth, 'oauth2.clientId'),
clientSecret: get(collectionAuth, 'oauth2.clientSecret'),
scope: get(collectionAuth, 'oauth2.scope'),
credentialsPlacement: get(collectionAuth, 'oauth2.credentialsPlacement'),
credentialsId: get(collectionAuth, 'oauth2.credentialsId'),
tokenPlacement: get(collectionAuth, 'oauth2.tokenPlacement'),
tokenPrefix: get(collectionAuth, 'oauth2.tokenPrefix'),
tokenQueryParamKey: get(collectionAuth, 'oauth2.tokenQueryParamKey'),
tokenQueryKey: get(collectionAuth, 'oauth2.tokenQueryKey'),
reuseToken: get(collectionAuth, 'oauth2.reuseToken')
};
break;
@@ -154,7 +157,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
};
break;
case 'basic':
axiosRequest.auth = {
axiosRequest.basicAuth = {
username: get(request, 'auth.basic.username'),
password: get(request, 'auth.basic.password')
};
@@ -186,10 +189,11 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
clientId: get(request, 'auth.oauth2.clientId'),
clientSecret: get(request, 'auth.oauth2.clientSecret'),
scope: get(request, 'auth.oauth2.scope'),
credentialsPlacement: get(request, 'auth.oauth2.credentialsPlacement'),
credentialsId: get(request, 'auth.oauth2.credentialsId'),
tokenPlacement: get(request, 'auth.oauth2.tokenPlacement'),
tokenPrefix: get(request, 'auth.oauth2.tokenPrefix'),
tokenQueryParamKey: get(request, 'auth.oauth2.tokenQueryParamKey'),
tokenQueryKey: get(request, 'auth.oauth2.tokenQueryKey'),
reuseToken: get(request, 'auth.oauth2.reuseToken')
};
break;
@@ -204,10 +208,11 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
scope: get(request, 'auth.oauth2.scope'),
state: get(request, 'auth.oauth2.state'),
pkce: get(request, 'auth.oauth2.pkce'),
credentialsPlacement: get(request, 'auth.oauth2.credentialsPlacement'),
credentialsId: get(request, 'auth.oauth2.credentialsId'),
tokenPlacement: get(request, 'auth.oauth2.tokenPlacement'),
tokenPrefix: get(request, 'auth.oauth2.tokenPrefix'),
tokenQueryParamKey: get(request, 'auth.oauth2.tokenQueryParamKey'),
tokenQueryKey: get(request, 'auth.oauth2.tokenQueryKey'),
reuseToken: get(request, 'auth.oauth2.reuseToken')
};
break;
@@ -218,10 +223,11 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
clientId: get(request, 'auth.oauth2.clientId'),
clientSecret: get(request, 'auth.oauth2.clientSecret'),
scope: get(request, 'auth.oauth2.scope'),
credentialsPlacement: get(request, 'auth.oauth2.credentialsPlacement'),
credentialsId: get(request, 'auth.oauth2.credentialsId'),
tokenPlacement: get(request, 'auth.oauth2.tokenPlacement'),
tokenPrefix: get(request, 'auth.oauth2.tokenPrefix'),
tokenQueryParamKey: get(request, 'auth.oauth2.tokenQueryParamKey'),
tokenQueryKey: get(request, 'auth.oauth2.tokenQueryKey'),
reuseToken: get(request, 'auth.oauth2.reuseToken')
};
break;
@@ -607,7 +613,7 @@ const configureRequest = async (
if (request.oauth2) {
let requestCopy = cloneDeep(request);
const { oauth2: { grantType, tokenPlacement, tokenPrefix, tokenQueryParamKey } = {} } = requestCopy || {};
const { oauth2: { grantType, tokenPlacement, tokenPrefix, tokenQueryKey } = {} } = requestCopy || {};
let credentials, credentialsId;
switch (grantType) {
case 'authorization_code':
@@ -620,7 +626,7 @@ const configureRequest = async (
else {
try {
const url = new URL(request.url);
url?.searchParams?.set(tokenQueryParamKey, credentials?.access_token);
url?.searchParams?.set(tokenQueryKey, credentials?.access_token);
request.url = url?.toString();
}
catch(error) {}
@@ -636,7 +642,7 @@ const configureRequest = async (
else {
try {
const url = new URL(request.url);
url?.searchParams?.set(tokenQueryParamKey, credentials?.access_token);
url?.searchParams?.set(tokenQueryKey, credentials?.access_token);
request.url = url?.toString();
}
catch(error) {}
@@ -652,7 +658,7 @@ const configureRequest = async (
else {
try {
const url = new URL(request.url);
url?.searchParams?.set(tokenQueryParamKey, credentials?.access_token);
url?.searchParams?.set(tokenQueryKey, credentials?.access_token);
request.url = url?.toString();
}
catch(error) {}

View File

@@ -484,11 +484,12 @@ const sem = grammar.createSemantics().addAttribute('ast', {
const scopeKey = _.find(auth, { name: 'scope' });
const stateKey = _.find(auth, { name: 'state' });
const pkceKey = _.find(auth, { name: 'pkce' });
const credentialsIdKey = _.find(auth, { name: 'credentialsId' });
const tokenPlacementKey = _.find(auth, { name: 'tokenPlacement' });
const tokenPrefixKey = _.find(auth, { name: 'tokenPrefix' });
const tokenQueryParamKeyKey = _.find(auth, { name: 'tokenQueryParamKey' });
const reuseTokenKey = _.find(auth, { name: 'reuseToken' });
const credentialsPlacementKey = _.find(auth, { name: 'credentials_placement' });
const credentialsIdKey = _.find(auth, { name: 'credentials_id' });
const tokenPlacementKey = _.find(auth, { name: 'token_placement' });
const tokenPrefixKey = _.find(auth, { name: 'token_prefix' });
const tokenQueryKeyKey = _.find(auth, { name: 'token_query_key' });
const reuseTokenKey = _.find(auth, { name: 'reuse_token' });
return {
auth: {
oauth2:
@@ -501,10 +502,11 @@ const sem = grammar.createSemantics().addAttribute('ast', {
clientId: clientIdKey ? clientIdKey.value : '',
clientSecret: clientSecretKey ? clientSecretKey.value : '',
scope: scopeKey ? scopeKey.value : '',
credentialsPlacement: credentialsPlacementKey?.value ? credentialsPlacementKey.value : 'body',
credentialsId: credentialsIdKey?.value ? credentialsIdKey.value : 'credentials',
tokenPlacement: tokenPlacementKey?.value ? tokenPlacementKey.value : 'header',
tokenPrefix: tokenPrefixKey?.value ? tokenPrefixKey.value : 'Bearer',
tokenQueryParamKey: tokenQueryParamKeyKey?.value ? tokenQueryParamKeyKey.value : 'access_token',
tokenQueryKey: tokenQueryKeyKey?.value ? tokenQueryKeyKey.value : 'access_token',
reuseToken: reuseTokenKey?.value ? JSON.parse(reuseTokenKey?.value || false) : false
}
: grantTypeKey?.value && grantTypeKey?.value == 'authorization_code'
@@ -518,10 +520,11 @@ const sem = grammar.createSemantics().addAttribute('ast', {
scope: scopeKey ? scopeKey.value : '',
state: stateKey ? stateKey.value : '',
pkce: pkceKey ? JSON.parse(pkceKey?.value || false) : false,
credentialsPlacement: credentialsPlacementKey?.value ? credentialsPlacementKey.value : 'body',
credentialsId: credentialsIdKey?.value ? credentialsIdKey.value : 'credentials',
tokenPlacement: tokenPlacementKey?.value ? tokenPlacementKey.value : 'header',
tokenPrefix: tokenPrefixKey?.value ? tokenPrefixKey.value : 'Bearer',
tokenQueryParamKey: tokenQueryParamKeyKey?.value ? tokenQueryParamKeyKey.value : 'access_token',
tokenQueryKey: tokenQueryKeyKey?.value ? tokenQueryKeyKey.value : 'access_token',
reuseToken: reuseTokenKey?.value ? JSON.parse(reuseTokenKey?.value || false) : false
}
: grantTypeKey?.value && grantTypeKey?.value == 'client_credentials'
@@ -531,10 +534,11 @@ const sem = grammar.createSemantics().addAttribute('ast', {
clientId: clientIdKey ? clientIdKey.value : '',
clientSecret: clientSecretKey ? clientSecretKey.value : '',
scope: scopeKey ? scopeKey.value : '',
credentialsPlacement: credentialsPlacementKey?.value ? credentialsPlacementKey.value : 'body',
credentialsId: credentialsIdKey?.value ? credentialsIdKey.value : 'credentials',
tokenPlacement: tokenPlacementKey?.value ? tokenPlacementKey.value : 'header',
tokenPrefix: tokenPrefixKey?.value ? tokenPrefixKey.value : 'Bearer',
tokenQueryParamKey: tokenQueryParamKeyKey?.value ? tokenQueryParamKeyKey.value : 'access_token',
tokenQueryKey: tokenQueryKeyKey?.value ? tokenQueryKeyKey.value : 'access_token',
reuseToken: reuseTokenKey?.value ? JSON.parse(reuseTokenKey?.value || false) : false
}
: {}

View File

@@ -279,10 +279,11 @@ const sem = grammar.createSemantics().addAttribute('ast', {
const scopeKey = _.find(auth, { name: 'scope' });
const stateKey = _.find(auth, { name: 'state' });
const pkceKey = _.find(auth, { name: 'pkce' });
const credentialsIdKey = _.find(auth, { name: 'credentialsId' });
const tokenPlacementKey = _.find(auth, { name: 'tokenPlacement' });
const tokenPrefixKey = _.find(auth, { name: 'tokenPrefix' });
const tokenQueryParamKeyKey = _.find(auth, { name: 'tokenQueryParamKey' });
const credentialsPlacementKey = _.find(auth, { name: 'credentials_placement' });
const credentialsIdKey = _.find(auth, { name: 'credentials_id' });
const tokenPlacementKey = _.find(auth, { name: 'token_placement' });
const tokenPrefixKey = _.find(auth, { name: 'token_prefix' });
const tokenQueryKeyKey = _.find(auth, { name: 'token_query_key' });
const reuseTokenKey = _.find(auth, { name: 'reuseToken' });
return {
auth: {
@@ -296,10 +297,11 @@ const sem = grammar.createSemantics().addAttribute('ast', {
clientId: clientIdKey ? clientIdKey.value : '',
clientSecret: clientSecretKey ? clientSecretKey.value : '',
scope: scopeKey ? scopeKey.value : '',
credentialsPlacement: credentialsPlacementKey?.value ? credentialsPlacementKey.value : 'body',
credentialsId: credentialsIdKey?.value ? credentialsIdKey.value : 'credentials',
tokenPlacement: tokenPlacementKey?.value ? tokenPlacementKey.value : 'header',
tokenPrefix: tokenPrefixKey?.value ? tokenPrefixKey.value : 'Bearer',
tokenQueryParamKey: tokenQueryParamKeyKey?.value ? tokenQueryParamKeyKey.value : 'access_token',
tokenQueryKey: tokenQueryKeyKey?.value ? tokenQueryKeyKey.value : 'access_token',
reuseToken: reuseTokenKey?.value ? JSON.parse(reuseTokenKey?.value || false) : false
}
: grantTypeKey?.value && grantTypeKey?.value == 'authorization_code'
@@ -313,10 +315,11 @@ const sem = grammar.createSemantics().addAttribute('ast', {
scope: scopeKey ? scopeKey.value : '',
state: stateKey ? stateKey.value : '',
pkce: pkceKey ? JSON.parse(pkceKey?.value || false) : false,
credentialsPlacement: credentialsPlacementKey?.value ? credentialsPlacementKey.value : 'body',
credentialsId: credentialsIdKey?.value ? credentialsIdKey.value : 'credentials',
tokenPlacement: tokenPlacementKey?.value ? tokenPlacementKey.value : 'header',
tokenPrefix: tokenPrefixKey?.value ? tokenPrefixKey.value : 'Bearer',
tokenQueryParamKey: tokenQueryParamKeyKey?.value ? tokenQueryParamKeyKey.value : 'access_token',
tokenQueryKey: tokenQueryKeyKey?.value ? tokenQueryKeyKey.value : 'access_token',
reuseToken: reuseTokenKey?.value ? JSON.parse(reuseTokenKey?.value || false) : false
}
: grantTypeKey?.value && grantTypeKey?.value == 'client_credentials'
@@ -326,10 +329,11 @@ const sem = grammar.createSemantics().addAttribute('ast', {
clientId: clientIdKey ? clientIdKey.value : '',
clientSecret: clientSecretKey ? clientSecretKey.value : '',
scope: scopeKey ? scopeKey.value : '',
credentialsPlacement: credentialsPlacementKey?.value ? credentialsPlacementKey.value : 'body',
credentialsId: credentialsIdKey?.value ? credentialsIdKey.value : 'credentials',
tokenPlacement: tokenPlacementKey?.value ? tokenPlacementKey.value : 'header',
tokenPrefix: tokenPrefixKey?.value ? tokenPrefixKey.value : 'Bearer',
tokenQueryParamKey: tokenQueryParamKeyKey?.value ? tokenQueryParamKeyKey.value : 'access_token',
tokenQueryKey: tokenQueryKeyKey?.value ? tokenQueryKeyKey.value : 'access_token',
reuseToken: reuseTokenKey?.value ? JSON.parse(reuseTokenKey?.value || false) : false
}
: {}

View File

@@ -188,11 +188,14 @@ ${indentString(`password: ${auth?.oauth2?.password || ''}`)}
${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)}
${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)}
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
${indentString(`credentialsId: ${auth?.oauth2?.credentialsId || ''}`)}
${indentString(`tokenPlacement: ${auth?.oauth2?.tokenPlacement || ''}`)}
${indentString(`tokenPrefix: ${auth?.oauth2?.tokenPrefix || ''}`)}
${indentString(`tokenQueryParamKey: ${auth?.oauth2?.tokenQueryParamKey || ''}`)}
${indentString(`reuseToken: ${auth?.oauth2?.reuseToken || ''}`)}
${indentString(`credentials_placement: ${auth?.oauth2?.credentialsPlacement || ''}`)}
${indentString(`credentials_id: ${auth?.oauth2?.credentialsId || ''}`)}
${indentString(`token_placement: ${auth?.oauth2?.tokenPlacement || ''}`)}${
auth?.oauth2?.tokenPlacement == 'header' ? '\n' + indentString(`token_prefix: ${auth?.oauth2?.tokenPrefix || ''}`) : ''
}${
auth?.oauth2?.tokenPlacement !== 'header' ? '\n' + indentString(`token_query_key: ${auth?.oauth2?.tokenQueryKey || ''}`) : ''
}
${indentString(`reuse_token: ${auth?.oauth2?.reuseToken || ''}`)}
}
`;
@@ -208,11 +211,14 @@ ${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)}
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
${indentString(`state: ${auth?.oauth2?.state || ''}`)}
${indentString(`pkce: ${(auth?.oauth2?.pkce || false).toString()}`)}
${indentString(`credentialsId: ${auth?.oauth2?.credentialsId || ''}`)}
${indentString(`tokenPlacement: ${auth?.oauth2?.tokenPlacement || ''}`)}
${indentString(`tokenPrefix: ${auth?.oauth2?.tokenPrefix || ''}`)}
${indentString(`tokenQueryParamKey: ${auth?.oauth2?.tokenQueryParamKey || ''}`)}
${indentString(`reuseToken: ${auth?.oauth2?.reuseToken || ''}`)}
${indentString(`credentials_placement: ${auth?.oauth2?.credentialsPlacement || ''}`)}
${indentString(`credentials_id: ${auth?.oauth2?.credentialsId || ''}`)}
${indentString(`token_placement: ${auth?.oauth2?.tokenPlacement || ''}`)}${
auth?.oauth2?.tokenPlacement == 'header' ? '\n' + indentString(`token_prefix: ${auth?.oauth2?.tokenPrefix || ''}`) : ''
}${
auth?.oauth2?.tokenPlacement !== 'header' ? '\n' + indentString(`token_query_key: ${auth?.oauth2?.tokenQueryKey || ''}`) : ''
}
${indentString(`reuse_token: ${auth?.oauth2?.reuseToken || ''}`)}
}
`;
@@ -224,11 +230,14 @@ ${indentString(`access_token_url: ${auth?.oauth2?.accessTokenUrl || ''}`)}
${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)}
${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)}
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
${indentString(`credentialsId: ${auth?.oauth2?.credentialsId || ''}`)}
${indentString(`tokenPlacement: ${auth?.oauth2?.tokenPlacement || ''}`)}
${indentString(`tokenPrefix: ${auth?.oauth2?.tokenPrefix || ''}`)}
${indentString(`tokenQueryParamKey: ${auth?.oauth2?.tokenQueryParamKey || ''}`)}
${indentString(`reuseToken: ${auth?.oauth2?.reuseToken || ''}`)}
${indentString(`credentials_placement: ${auth?.oauth2?.credentialsPlacement || ''}`)}
${indentString(`credentials_id: ${auth?.oauth2?.credentialsId || ''}`)}
${indentString(`token_placement: ${auth?.oauth2?.tokenPlacement || ''}`)}${
auth?.oauth2?.tokenPlacement == 'header' ? '\n' + indentString(`token_prefix: ${auth?.oauth2?.tokenPrefix || ''}`) : ''
}${
auth?.oauth2?.tokenPlacement !== 'header' ? '\n' + indentString(`token_query_key: ${auth?.oauth2?.tokenQueryKey || ''}`) : ''
}
${indentString(`reuse_token: ${auth?.oauth2?.reuseToken || ''}`)}
}
`;

View File

@@ -154,11 +154,14 @@ ${indentString(`password: ${auth?.oauth2?.password || ''}`)}
${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)}
${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)}
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
${indentString(`credentialsId: ${auth?.oauth2?.credentialsId || ''}`)}
${indentString(`tokenPlacement: ${auth?.oauth2?.tokenPlacement || ''}`)}
${indentString(`tokenPrefix: ${auth?.oauth2?.tokenPrefix || ''}`)}
${indentString(`tokenQueryParamKey: ${auth?.oauth2?.tokenQueryParamKey || ''}`)}
${indentString(`reuseToken: ${auth?.oauth2?.reuseToken || ''}`)}
${indentString(`credentials_placement: ${auth?.oauth2?.credentialsPlacement || ''}`)}
${indentString(`credentials_id: ${auth?.oauth2?.credentialsId || ''}`)}
${indentString(`token_placement: ${auth?.oauth2?.tokenPlacement || ''}`)}${
auth?.oauth2?.tokenPlacement == 'header' ? '\n' + indentString(`token_prefix: ${auth?.oauth2?.tokenPrefix || ''}`) : ''
}${
auth?.oauth2?.tokenPlacement !== 'header' ? '\n' + indentString(`token_query_key: ${auth?.oauth2?.tokenQueryKey || ''}`) : ''
}
${indentString(`reuse_token: ${auth?.oauth2?.reuseToken || ''}`)}
}
`;
@@ -174,11 +177,14 @@ ${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)}
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
${indentString(`state: ${auth?.oauth2?.state || ''}`)}
${indentString(`pkce: ${(auth?.oauth2?.pkce || false).toString()}`)}
${indentString(`credentialsId: ${auth?.oauth2?.credentialsId || ''}`)}
${indentString(`tokenPlacement: ${auth?.oauth2?.tokenPlacement || ''}`)}
${indentString(`tokenPrefix: ${auth?.oauth2?.tokenPrefix || ''}`)}
${indentString(`tokenQueryParamKey: ${auth?.oauth2?.tokenQueryParamKey || ''}`)}
${indentString(`reuseToken: ${auth?.oauth2?.reuseToken || ''}`)}
${indentString(`credentials_placement: ${auth?.oauth2?.credentialsPlacement || ''}`)}
${indentString(`credentials_id: ${auth?.oauth2?.credentialsId || ''}`)}
${indentString(`token_placement: ${auth?.oauth2?.tokenPlacement || ''}`)}${
auth?.oauth2?.tokenPlacement == 'header' ? '\n' + indentString(`token_prefix: ${auth?.oauth2?.tokenPrefix || ''}`) : ''
}${
auth?.oauth2?.tokenPlacement !== 'header' ? '\n' + indentString(`token_query_key: ${auth?.oauth2?.tokenQueryKey || ''}`) : ''
}
${indentString(`reuse_token: ${auth?.oauth2?.reuseToken || ''}`)}
}
`;
@@ -190,11 +196,14 @@ ${indentString(`access_token_url: ${auth?.oauth2?.accessTokenUrl || ''}`)}
${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)}
${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)}
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
${indentString(`credentialsId: ${auth?.oauth2?.credentialsId || ''}`)}
${indentString(`tokenPlacement: ${auth?.oauth2?.tokenPlacement || ''}`)}
${indentString(`tokenPrefix: ${auth?.oauth2?.tokenPrefix || ''}`)}
${indentString(`tokenQueryParamKey: ${auth?.oauth2?.tokenQueryParamKey || ''}`)}
${indentString(`reuseToken: ${auth?.oauth2?.reuseToken || ''}`)}
${indentString(`credentials_placement: ${auth?.oauth2?.credentialsPlacement || ''}`)}
${indentString(`credentials_id: ${auth?.oauth2?.credentialsId || ''}`)}
${indentString(`token_placement: ${auth?.oauth2?.tokenPlacement || ''}`)}${
auth?.oauth2?.tokenPlacement == 'header' ? '\n' + indentString(`token_prefix: ${auth?.oauth2?.tokenPrefix || ''}`) : ''
}${
auth?.oauth2?.tokenPlacement !== 'header' ? '\n' + indentString(`token_query_key: ${auth?.oauth2?.tokenQueryKey || ''}`) : ''
}
${indentString(`reuse_token: ${auth?.oauth2?.reuseToken || ''}`)}
}
`;

View File

@@ -200,6 +200,11 @@ const oauth2Schema = Yup.object({
then: Yup.boolean().default(false),
otherwise: Yup.boolean()
}),
credentialsPlacement: Yup.string().when('grantType', {
is: (val) => ['client_credentials', 'password', 'authorization_code'].includes(val),
then: Yup.string().nullable(),
otherwise: Yup.string().nullable().strip()
}),
credentialsId: Yup.string().when('grantType', {
is: (val) => ['client_credentials', 'password', 'authorization_code'].includes(val),
then: Yup.string().nullable(),
@@ -216,7 +221,7 @@ const oauth2Schema = Yup.object({
then: Yup.string().nullable(),
otherwise: Yup.string().nullable().strip()
}),
tokenQueryParamKey: Yup.string().when(['grantType', 'tokenPlacement'], {
tokenQueryKey: Yup.string().when(['grantType', 'tokenPlacement'], {
is: (grantType, tokenPlacement) =>
['client_credentials', 'password', 'authorization_code'].includes(grantType) && tokenPlacement === 'url',
then: Yup.string().nullable(),

View File

@@ -12,9 +12,9 @@ auth:oauth2 {
scope: openid
state:
pkce: true
credentialsId: keycloak
tokenPlacement: header
tokenPrefix: Bearer
tokenQueryParamKey: access_token
reuseToken:
credentials_placement: body
credentials_id: credentials
token_placement: header
token_prefix: Bearer
reuse_token:
}

View File

@@ -11,5 +11,5 @@ get {
}
auth:bearer {
token: {{$auth.keycloak.access_token}}
token: {{$oauth2.keycloak.access_token}}
}

View File

@@ -20,9 +20,9 @@ auth:oauth2 {
scope: openid
state:
pkce: true
credentialsId: keycloak_request
tokenPlacement: header
tokenPrefix: Bearer
tokenQueryParamKey: token
reuseToken:
credentials_placement: body
credentials_id: credentials
token_placement: header
token_prefix: Bearer
reuse_token:
}

View File

@@ -0,0 +1,9 @@
{
"version": "1",
"name": "keycloak-client-credentials",
"type": "collection",
"ignore": [
"node_modules",
".git"
]
}

View File

@@ -0,0 +1,20 @@
auth {
mode: oauth2
}
auth:oauth2 {
grant_type: authorization_code
callback_url: {{key-host}}/realms/bruno/account
authorization_url: {{key-host}}/realms/bruno/protocol/openid-connect/auth
access_token_url: {{key-host}}/realms/bruno/protocol/openid-connect/token
client_id: account
client_secret: Lh3NkRikMZpO12rwSBwVimde9v89B5Rw
scope: openid
state:
pkce: true
tokenId: keycloak
tokenPlacement: header
tokenPrefix: Bearer
tokenQueryKey: access_token
reuseToken:
}

View File

@@ -0,0 +1,22 @@
vars {
host: http://localhost:8080
bearer_auth_token: your_secret_token
basic_auth_password: della
client_id: client_id_1
client_secret: client_secret_1
password_credentials_access_token_url: http://localhost:8081/api/auth/oauth2/password_credentials/token
password_credentials_username: foo
password_credentials_password: bar
password_credentials_scope:
authorization_code_authorize_url: http://localhost:8081/api/auth/oauth2/authorization_code/authorize
authorization_code_callback_url: http://localhost:8081/api/auth/oauth2/authorization_code/callback
authorization_code_access_token_url: http://localhost:8081/api/auth/oauth2/authorization_code/token
authorization_code_access_token: null
client_credentials_access_token_url: http://localhost:8081/api/auth/oauth2/client_credentials/token
client_credentials_client_id: client_id_1
client_credentials_client_secret: client_secret_1
client_credentials_scope: admin
client_credentials_access_token: 870132a2ed28a3c94d34f868e6514720
key-host: http://localhost:8080
key-host-1: http://localhost:8082
}

View File

@@ -0,0 +1,11 @@
meta {
name: user_info_coll-auth
type: http
seq: 1
}
get {
url: {{key-host}}/realms/bruno/protocol/openid-connect/userinfo
body: none
auth: inherit
}

View File

@@ -0,0 +1,15 @@
meta {
name: user_info_custom
type: http
seq: 2
}
get {
url: {{key-host}}/realms/bruno/protocol/openid-connect/userinfo
body: none
auth: bearer
}
auth:bearer {
token: {{$oauth2.keycloak.access_token}}
}

View File

@@ -0,0 +1,24 @@
meta {
name: user_info_request-auth
type: http
seq: 3
}
get {
url: {{key-host}}/realms/bruno/protocol/openid-connect/userinfo
body: none
auth: oauth2
}
auth:oauth2 {
grant_type: client_credentials
access_token_url: {{key-host}}/realms/bruno/protocol/openid-connect/token
client_id: account
client_secret: Lh3NkRikMZpO12rwSBwVimde9v89B5Rw
scope: openid
credentials_placement: body
credentials_id: credentials
token_placement: header
token_prefix: Bearer
reuse_token:
}