feat: add tokenType support for OAuth2 (#7314)

* feat: add tokenType support for OAuth2

* refactor: rename tokenType to source in OpenCollection OAuth2 mapping

* refactor: rename tokenType to source in OAuth2 configuration

* chore: bump @opencollection/types to ~0.8.0

* fix: correct OAuth2 token type label in token viewer

* refactor: replace Dropdown with MenuDropdown in OAuth2 components

Migrate all 12 dropdown instances across 5 OAuth2 auth components to use
the MenuDropdown component, removing manual tippy ref management and
forwardRef icon patterns in favor of a declarative items-based API.
This commit is contained in:
lohit
2026-02-27 15:20:23 +00:00
committed by GitHub
parent 4f4faec359
commit 4797abbeff
22 changed files with 293 additions and 315 deletions

17
package-lock.json generated
View File

@@ -30,7 +30,7 @@
"@eslint/compat": "^1.3.2",
"@faker-js/faker": "^7.6.0",
"@jest/globals": "^29.2.0",
"@opencollection/types": "~0.7.0",
"@opencollection/types": "~0.8.0",
"@playwright/test": "^1.51.1",
"@rollup/plugin-json": "^6.1.0",
"@storybook/addon-webpack5-compiler-babel": "^4.0.0",
@@ -6136,9 +6136,9 @@
}
},
"node_modules/@opencollection/types": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@opencollection/types/-/types-0.7.0.tgz",
"integrity": "sha512-CSwdaHNPa2bNNBAOy++t6W9gBTExUJZW3aPkWyhAjasusThbvjymD/0uCLR50gCXSs0ezv61jsd19m9x+2DMtQ==",
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@opencollection/types/-/types-0.8.0.tgz",
"integrity": "sha512-YnogiJdyN/BTf9lu+eTwmhAOiOwAT2cuPXv7ePvQsVT6r6gCALDR2IhD8ISergR/fQBgELWvlfj+lh/qTQ6sZw==",
"dev": true,
"license": "MIT"
},
@@ -33376,7 +33376,7 @@
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.4",
"@opencollection/types": "~0.5.0",
"@opencollection/types": "~0.8.0",
"@rollup/plugin-alias": "^5.1.0",
"@rollup/plugin-commonjs": "^23.0.2",
"@rollup/plugin-node-resolve": "^15.0.1",
@@ -33392,13 +33392,6 @@
"typescript": "^4.8.4"
}
},
"packages/bruno-converters/node_modules/@opencollection/types": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@opencollection/types/-/types-0.5.0.tgz",
"integrity": "sha512-9rpu5agMrMLcMVU2UgyV+PYV3Zf/sHBJDHMQoq8XiMEUH8lt9f7yGtlerm/9dS3SHMpGX4A8ik0OFtc0dX4r1Q==",
"dev": true,
"license": "MIT"
},
"packages/bruno-converters/node_modules/glob": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",

View File

@@ -23,7 +23,7 @@
"@eslint/compat": "^1.3.2",
"@faker-js/faker": "^7.6.0",
"@jest/globals": "^29.2.0",
"@opencollection/types": "~0.7.0",
"@opencollection/types": "~0.8.0",
"@playwright/test": "^1.51.1",
"@rollup/plugin-json": "^6.1.0",
"@storybook/addon-webpack5-compiler-babel": "^4.0.0",

View File

@@ -1,10 +1,10 @@
import React, { useRef, forwardRef } from 'react';
import React from 'react';
import { useDetectSensitiveField } from 'hooks/useDetectSensitiveField';
import get from 'lodash/get';
import { useTheme } from 'providers/Theme';
import { useDispatch, useSelector } from 'react-redux';
import { IconCaretDown, IconSettings, IconKey, IconHelp, IconAdjustmentsHorizontal } from '@tabler/icons';
import Dropdown from 'components/Dropdown';
import MenuDropdown from 'ui/MenuDropdown';
import SingleLineEditor from 'components/SingleLineEditor';
import StyledWrapper from './StyledWrapper';
import { inputsConfig } from './inputsConfig';
@@ -20,8 +20,6 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu
const preferences = useSelector((state) => state.app.preferences);
const { storedTheme } = useTheme();
const useSystemBrowser = get(preferences, 'request.oauth2.useSystemBrowser', false);
const dropdownTippyRef = useRef();
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const { isSensitive } = useDetectSensitiveField(collection);
const oAuth = get(request, 'auth.oauth2', {});
const {
@@ -41,30 +39,13 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu
refreshTokenUrl,
autoRefreshToken,
autoFetchToken,
tokenSource,
additionalParameters
} = oAuth;
const refreshTokenUrlAvailable = refreshTokenUrl?.trim() !== '';
const isAutoRefreshDisabled = !refreshTokenUrlAvailable;
const TokenPlacementIcon = forwardRef((props, ref) => {
return (
<div ref={ref} className="flex items-center justify-end token-placement-label select-none">
{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 handleSave = () => { save(); };
const handleChange = (key, value) => {
@@ -91,6 +72,7 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu
refreshTokenUrl,
autoRefreshToken,
autoFetchToken,
tokenSource,
additionalParameters,
[key]: value
}
@@ -119,6 +101,7 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu
tokenHeaderPrefix,
tokenQueryKey,
autoFetchToken,
tokenSource,
additionalParameters,
pkce: !Boolean(oAuth?.['pkce'])
}
@@ -226,26 +209,19 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu
<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
<MenuDropdown
items={[
{ id: 'body', label: 'Request Body', onClick: () => handleChange('credentialsPlacement', 'body') },
{ id: 'basic_auth_header', label: 'Basic Auth Header', onClick: () => handleChange('credentialsPlacement', 'basic_auth_header') }
]}
selectedItemId={credentialsPlacement}
placement="bottom-end"
>
<div 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>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
handleChange('credentialsPlacement', 'basic_auth_header');
}}
>
Basic Auth Header
</div>
</Dropdown>
</MenuDropdown>
</div>
</div>
<div className="flex flex-row w-full gap-4" key="pkce">
@@ -265,6 +241,24 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu
Token
</span>
</div>
<div className="flex items-center gap-4 w-full" key="input-token-type">
<label className="block min-w-[140px]">Token Source</label>
<div className="inline-flex items-center cursor-pointer token-placement-selector">
<MenuDropdown
items={[
{ id: 'access_token', label: 'Access Token', onClick: () => handleChange('tokenSource', 'access_token') },
{ id: 'id_token', label: 'ID Token', onClick: () => handleChange('tokenSource', 'id_token') }
]}
selectedItemId={tokenSource}
placement="bottom-end"
>
<div className="flex items-center justify-end token-placement-label select-none">
{tokenSource === 'id_token' ? 'ID Token' : 'Access Token'}
<IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
</div>
</MenuDropdown>
</div>
</div>
<div className="flex items-center gap-4 w-full" key="input-token-name">
<label className="block min-w-[140px]">Token ID</label>
<div className="single-line-editor-wrapper flex-1">
@@ -283,26 +277,19 @@ 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={<TokenPlacementIcon />} placement="bottom-end">
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
handleChange('tokenPlacement', 'header');
}}
>
Header
<MenuDropdown
items={[
{ id: 'header', label: 'Header', onClick: () => handleChange('tokenPlacement', 'header') },
{ id: 'url', label: 'URL', onClick: () => handleChange('tokenPlacement', 'url') }
]}
selectedItemId={tokenPlacement}
placement="bottom-end"
>
<div className="flex items-center justify-end token-placement-label select-none">
{tokenPlacement == 'url' ? 'URL' : 'Headers'}
<IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
handleChange('tokenPlacement', 'url');
}}
>
URL
</div>
</Dropdown>
</MenuDropdown>
</div>
</div>
{

View File

@@ -1,4 +1,4 @@
import React, { useRef, forwardRef } from 'react';
import React from 'react';
import { useDetectSensitiveField } from 'hooks/useDetectSensitiveField';
import get from 'lodash/get';
import { useTheme } from 'providers/Theme';
@@ -7,7 +7,7 @@ import { IconCaretDown, IconSettings, IconKey, IconAdjustmentsHorizontal, IconHe
import SingleLineEditor from 'components/SingleLineEditor';
import StyledWrapper from './StyledWrapper';
import { inputsConfig } from './inputsConfig';
import Dropdown from 'components/Dropdown';
import MenuDropdown from 'ui/MenuDropdown';
import Oauth2TokenViewer from '../Oauth2TokenViewer/index';
import Oauth2ActionButtons from '../Oauth2ActionButtons/index';
import AdditionalParams from '../AdditionalParams/index';
@@ -16,8 +16,6 @@ import SensitiveFieldWarning from 'components/SensitiveFieldWarning';
const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAuth, collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const dropdownTippyRef = useRef();
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const { isSensitive } = useDetectSensitiveField(collection);
const oAuth = get(request, 'auth.oauth2', {});
@@ -34,6 +32,7 @@ const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAu
refreshTokenUrl,
autoRefreshToken,
autoFetchToken,
tokenSource,
additionalParameters
} = oAuth;
@@ -42,24 +41,6 @@ const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAu
const handleSave = () => { save(); };
const TokenPlacementIcon = forwardRef((props, ref) => {
return (
<div ref={ref} className="flex items-center justify-end token-placement-label select-none">
{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(
updateAuth({
@@ -80,6 +61,7 @@ const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAu
refreshTokenUrl,
autoRefreshToken,
autoFetchToken,
tokenSource,
additionalParameters,
[key]: value
}
@@ -126,26 +108,19 @@ const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAu
<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
<MenuDropdown
items={[
{ id: 'body', label: 'Request Body', onClick: () => handleChange('credentialsPlacement', 'body') },
{ id: 'basic_auth_header', label: 'Basic Auth Header', onClick: () => handleChange('credentialsPlacement', 'basic_auth_header') }
]}
selectedItemId={credentialsPlacement}
placement="bottom-end"
>
<div 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>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
handleChange('credentialsPlacement', 'basic_auth_header');
}}
>
Basic Auth Header
</div>
</Dropdown>
</MenuDropdown>
</div>
</div>
<div className="flex items-center gap-2.5 mt-2">
@@ -156,6 +131,24 @@ const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAu
Token
</span>
</div>
<div className="flex items-center gap-4 w-full" key="input-token-type">
<label className="block min-w-[140px]">Token Source</label>
<div className="inline-flex items-center cursor-pointer token-placement-selector">
<MenuDropdown
items={[
{ id: 'access_token', label: 'Access Token', onClick: () => handleChange('tokenSource', 'access_token') },
{ id: 'id_token', label: 'ID Token', onClick: () => handleChange('tokenSource', 'id_token') }
]}
selectedItemId={tokenSource}
placement="bottom-end"
>
<div className="flex items-center justify-end token-placement-label select-none">
{tokenSource === 'id_token' ? 'ID Token' : 'Access Token'}
<IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
</div>
</MenuDropdown>
</div>
</div>
<div className="flex items-center gap-4 w-full" key="input-token-name">
<label className="block min-w-[140px]">Token ID</label>
<div className="single-line-editor-wrapper flex-1">
@@ -174,26 +167,19 @@ 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={<TokenPlacementIcon />} placement="bottom-end">
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
handleChange('tokenPlacement', 'header');
}}
>
Header
<MenuDropdown
items={[
{ id: 'header', label: 'Header', onClick: () => handleChange('tokenPlacement', 'header') },
{ id: 'url', label: 'URL', onClick: () => handleChange('tokenPlacement', 'url') }
]}
selectedItemId={tokenPlacement}
placement="bottom-end"
>
<div className="flex items-center justify-end token-placement-label select-none">
{tokenPlacement == 'url' ? 'URL' : 'Headers'}
<IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
handleChange('tokenPlacement', 'url');
}}
>
URL
</div>
</Dropdown>
</MenuDropdown>
</div>
</div>
{

View File

@@ -1,6 +1,6 @@
import React, { useRef, forwardRef } from 'react';
import React from 'react';
import get from 'lodash/get';
import Dropdown from 'components/Dropdown';
import MenuDropdown from 'ui/MenuDropdown';
import { useDispatch } from 'react-redux';
import StyledWrapper from './StyledWrapper';
import { IconCaretDown, IconKey } from '@tabler/icons';
@@ -10,20 +10,10 @@ import { useState } from 'react';
const GrantTypeSelector = ({ item = {}, request, updateAuth, collection }) => {
const dispatch = useDispatch();
const dropdownTippyRef = useRef();
const oAuth = get(request, 'auth.oauth2', {});
const [valuesCache, setValuesCache] = useState({
...oAuth
});
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const Icon = forwardRef((props, ref) => {
return (
<div ref={ref} className="flex items-center justify-end grant-type-label select-none">
{humanizeGrantType(oAuth?.grantType)} <IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
</div>
);
});
const onGrantTypeChange = (grantType) => {
let updatedValues = {
@@ -65,7 +55,8 @@ const GrantTypeSelector = ({ item = {}, request, updateAuth, collection }) => {
credentialsId: 'credentials',
tokenPlacement: 'header',
tokenHeaderPrefix: 'Bearer',
tokenQueryKey: 'access_token'
tokenQueryKey: 'access_token',
tokenSource: 'access_token'
}
})
);
@@ -82,44 +73,20 @@ const GrantTypeSelector = ({ item = {}, request, updateAuth, collection }) => {
</span>
</div>
<div className="inline-flex items-center cursor-pointer grant-type-mode-selector w-fit">
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onGrantTypeChange('password');
}}
>
Password Credentials
<MenuDropdown
items={[
{ id: 'password', label: 'Password Credentials', onClick: () => onGrantTypeChange('password') },
{ id: 'authorization_code', label: 'Authorization Code', onClick: () => onGrantTypeChange('authorization_code') },
{ id: 'implicit', label: 'Implicit', onClick: () => onGrantTypeChange('implicit') },
{ id: 'client_credentials', label: 'Client Credentials', onClick: () => onGrantTypeChange('client_credentials') }
]}
selectedItemId={oAuth?.grantType}
placement="bottom-end"
>
<div className="flex items-center justify-end grant-type-label select-none">
{humanizeGrantType(oAuth?.grantType)} <IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onGrantTypeChange('authorization_code');
}}
>
Authorization Code
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onGrantTypeChange('implicit');
}}
>
Implicit
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onGrantTypeChange('client_credentials');
}}
>
Client Credentials
</div>
</Dropdown>
</MenuDropdown>
</div>
</StyledWrapper>
);

View File

@@ -1,9 +1,9 @@
import React, { useRef, forwardRef, useMemo } from 'react';
import React, { useMemo } from 'react';
import get from 'lodash/get';
import { useTheme } from 'providers/Theme';
import { useDispatch, useSelector } from 'react-redux';
import { IconCaretDown, IconSettings, IconKey, IconHelp, IconAdjustmentsHorizontal } from '@tabler/icons';
import Dropdown from 'components/Dropdown';
import MenuDropdown from 'ui/MenuDropdown';
import SingleLineEditor from 'components/SingleLineEditor';
import Wrapper from './StyledWrapper';
import { inputsConfig } from './inputsConfig';
@@ -20,9 +20,6 @@ const OAuth2Implicit = ({ save, item = {}, request, handleRun, updateAuth, colle
const preferences = useSelector((state) => state.app.preferences);
const useSystemBrowser = get(preferences, 'request.oauth2.useSystemBrowser', false);
const { storedTheme } = useTheme();
const dropdownTippyRef = useRef();
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const oAuth = get(request, 'auth.oauth2', {});
const {
callbackUrl,
@@ -34,7 +31,8 @@ const OAuth2Implicit = ({ save, item = {}, request, handleRun, updateAuth, colle
tokenPlacement,
tokenHeaderPrefix,
tokenQueryKey,
autoFetchToken
autoFetchToken,
tokenSource
} = oAuth;
const interpolatedAuthUrl = useMemo(() => {
@@ -42,15 +40,6 @@ const OAuth2Implicit = ({ save, item = {}, request, handleRun, updateAuth, colle
return interpolate(authorizationUrl, variables);
}, [collection, item, authorizationUrl]);
const TokenPlacementIcon = forwardRef((props, ref) => {
return (
<div ref={ref} className="flex items-center justify-end token-placement-label select-none">
{tokenPlacement == 'url' ? 'URL' : 'Headers'}
<IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
</div>
);
});
const handleSave = () => { save(); };
const handleChange = (key, value) => {
@@ -71,6 +60,7 @@ const OAuth2Implicit = ({ save, item = {}, request, handleRun, updateAuth, colle
tokenHeaderPrefix,
tokenQueryKey,
autoFetchToken,
tokenSource,
[key]: value
}
})
@@ -184,6 +174,25 @@ const OAuth2Implicit = ({ save, item = {}, request, handleRun, updateAuth, colle
</span>
</div>
<div className="flex items-center gap-4 w-full" key="input-token-type">
<label className="block min-w-[140px]">Token Source</label>
<div className="inline-flex items-center cursor-pointer token-placement-selector">
<MenuDropdown
items={[
{ id: 'access_token', label: 'Access Token', onClick: () => handleChange('tokenSource', 'access_token') },
{ id: 'id_token', label: 'ID Token', onClick: () => handleChange('tokenSource', 'id_token') }
]}
selectedItemId={tokenSource}
placement="bottom-end"
>
<div className="flex items-center justify-end token-placement-label select-none">
{tokenSource === 'id_token' ? 'ID Token' : 'Access Token'}
<IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
</div>
</MenuDropdown>
</div>
</div>
<div className="flex items-center gap-4 w-full" key="input-token-name">
<label className="block min-w-[140px]">Token ID</label>
<div className="oauth2-input-wrapper flex-1">
@@ -203,26 +212,19 @@ const OAuth2Implicit = ({ save, item = {}, request, handleRun, updateAuth, colle
<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={<TokenPlacementIcon />} placement="bottom-end">
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
handleChange('tokenPlacement', 'header');
}}
>
Headers
<MenuDropdown
items={[
{ id: 'header', label: 'Headers', onClick: () => handleChange('tokenPlacement', 'header') },
{ id: 'url', label: 'URL', onClick: () => handleChange('tokenPlacement', 'url') }
]}
selectedItemId={tokenPlacement}
placement="bottom-end"
>
<div className="flex items-center justify-end token-placement-label select-none">
{tokenPlacement == 'url' ? 'URL' : 'Headers'}
<IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
handleChange('tokenPlacement', 'url');
}}
>
URL
</div>
</Dropdown>
</MenuDropdown>
</div>
</div>

View File

@@ -1,4 +1,4 @@
import React, { useRef, forwardRef } from 'react';
import React from 'react';
import { useDetectSensitiveField } from 'hooks/useDetectSensitiveField';
import get from 'lodash/get';
import { useTheme } from 'providers/Theme';
@@ -7,7 +7,7 @@ import { IconCaretDown, IconSettings, IconKey, IconAdjustmentsHorizontal, IconHe
import SingleLineEditor from 'components/SingleLineEditor';
import StyledWrapper from './StyledWrapper';
import { inputsConfig } from './inputsConfig';
import Dropdown from 'components/Dropdown';
import MenuDropdown from 'ui/MenuDropdown';
import Oauth2TokenViewer from '../Oauth2TokenViewer/index';
import Oauth2ActionButtons from '../Oauth2ActionButtons/index';
import AdditionalParams from '../AdditionalParams/index';
@@ -16,8 +16,6 @@ import SensitiveFieldWarning from 'components/SensitiveFieldWarning/index';
const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, updateAuth, collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const dropdownTippyRef = useRef();
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const oAuth = get(request, 'auth.oauth2', {});
const { isSensitive } = useDetectSensitiveField(collection);
@@ -36,6 +34,7 @@ const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, update
refreshTokenUrl,
autoRefreshToken,
autoFetchToken,
tokenSource,
additionalParameters
} = oAuth;
@@ -44,24 +43,6 @@ const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, update
const handleSave = () => { save(); };
const TokenPlacementIcon = forwardRef((props, ref) => {
return (
<div ref={ref} className="flex items-center justify-end token-placement-label select-none">
{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(
updateAuth({
@@ -84,6 +65,7 @@ const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, update
refreshTokenUrl,
autoRefreshToken,
autoFetchToken,
tokenSource,
additionalParameters,
[key]: value
}
@@ -130,26 +112,19 @@ const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, update
<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
<MenuDropdown
items={[
{ id: 'body', label: 'Request Body', onClick: () => handleChange('credentialsPlacement', 'body') },
{ id: 'basic_auth_header', label: 'Basic Auth Header', onClick: () => handleChange('credentialsPlacement', 'basic_auth_header') }
]}
selectedItemId={credentialsPlacement}
placement="bottom-end"
>
<div 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>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
handleChange('credentialsPlacement', 'basic_auth_header');
}}
>
Basic Auth Header
</div>
</Dropdown>
</MenuDropdown>
</div>
</div>
<div className="flex items-center gap-2.5 mt-2">
@@ -160,6 +135,24 @@ const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, update
Token
</span>
</div>
<div className="flex items-center gap-4 w-full" key="input-token-type">
<label className="block min-w-[140px]">Token Source</label>
<div className="inline-flex items-center cursor-pointer token-placement-selector">
<MenuDropdown
items={[
{ id: 'access_token', label: 'Access Token', onClick: () => handleChange('tokenSource', 'access_token') },
{ id: 'id_token', label: 'ID Token', onClick: () => handleChange('tokenSource', 'id_token') }
]}
selectedItemId={tokenSource}
placement="bottom-end"
>
<div className="flex items-center justify-end token-placement-label select-none">
{tokenSource === 'id_token' ? 'ID Token' : 'Access Token'}
<IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
</div>
</MenuDropdown>
</div>
</div>
<div className="flex items-center gap-4 w-full" key="input-token-name">
<label className="block min-w-[140px]">Token ID</label>
<div className="single-line-editor-wrapper flex-1">
@@ -178,26 +171,19 @@ 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={<TokenPlacementIcon />} placement="bottom-end">
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
handleChange('tokenPlacement', 'header');
}}
>
Header
<MenuDropdown
items={[
{ id: 'header', label: 'Header', onClick: () => handleChange('tokenPlacement', 'header') },
{ id: 'url', label: 'URL', onClick: () => handleChange('tokenPlacement', 'url') }
]}
selectedItemId={tokenPlacement}
placement="bottom-end"
>
<div className="flex items-center justify-end token-placement-label select-none">
{tokenPlacement == 'url' ? 'URL' : 'Headers'}
<IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
handleChange('tokenPlacement', 'url');
}}
>
URL
</div>
</Dropdown>
</MenuDropdown>
</div>
</div>
{

View File

@@ -28,7 +28,7 @@
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.4",
"@opencollection/types": "~0.5.0",
"@opencollection/types": "~0.8.0",
"@rollup/plugin-alias": "^5.1.0",
"@rollup/plugin-commonjs": "^23.0.2",
"@rollup/plugin-node-resolve": "^15.0.1",

View File

@@ -160,7 +160,7 @@ const configureRequest = async (
if (request.oauth2) {
let requestCopy = cloneDeep(request);
const { oauth2: { grantType, tokenPlacement, tokenHeaderPrefix, tokenQueryKey, accessTokenUrl, refreshTokenUrl } = {}, collectionVariables, folderVariables, requestVariables } = requestCopy || {};
const { oauth2: { grantType, tokenPlacement, tokenHeaderPrefix, tokenQueryKey, tokenSource, accessTokenUrl, refreshTokenUrl } = {}, collectionVariables, folderVariables, requestVariables } = requestCopy || {};
// Get cert/proxy configs for token and refresh URLs
let certsAndProxyConfigForTokenUrl = certsAndProxyConfig;
@@ -221,56 +221,68 @@ const configureRequest = async (
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars, promptVariables);
({ credentials, url: oauth2Url, credentialsId, debugInfo } = await getOAuth2TokenUsingAuthorizationCode({ request: requestCopy, collectionUid, certsAndProxyConfigForTokenUrl, certsAndProxyConfigForRefreshUrl }));
request.oauth2Credentials = { credentials, url: oauth2Url, collectionUid, credentialsId, debugInfo, folderUid: request.oauth2Credentials?.folderUid };
if (tokenPlacement == 'header' && credentials?.access_token) {
request.headers['Authorization'] = `${tokenHeaderPrefix} ${credentials.access_token}`.trim();
} else {
try {
const url = new URL(request.url);
url?.searchParams?.set(tokenQueryKey, credentials?.access_token);
request.url = url?.toString();
} catch (error) {}
{
const tokenValue = tokenSource === 'id_token' ? credentials?.id_token : credentials?.access_token;
if (tokenPlacement == 'header' && tokenValue) {
request.headers['Authorization'] = `${tokenHeaderPrefix} ${tokenValue}`.trim();
} else if (tokenValue) {
try {
const url = new URL(request.url);
url.searchParams.set(tokenQueryKey, tokenValue);
request.url = url.toString();
} catch (error) {}
}
}
break;
case 'implicit':
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars, promptVariables);
({ credentials, url: oauth2Url, credentialsId, debugInfo } = await getOAuth2TokenUsingImplicitGrant({ request: requestCopy, collectionUid }));
request.oauth2Credentials = { credentials, url: oauth2Url, collectionUid, credentialsId, debugInfo, folderUid: request.oauth2Credentials?.folderUid };
if (tokenPlacement == 'header') {
request.headers['Authorization'] = `${tokenHeaderPrefix} ${credentials?.access_token}`;
} else {
try {
const url = new URL(request.url);
url?.searchParams?.set(tokenQueryKey, credentials?.access_token);
request.url = url?.toString();
} catch (error) {}
{
const tokenValue = tokenSource === 'id_token' ? credentials?.id_token : credentials?.access_token;
if (tokenPlacement == 'header' && tokenValue) {
request.headers['Authorization'] = `${tokenHeaderPrefix} ${tokenValue}`.trim();
} else if (tokenValue) {
try {
const url = new URL(request.url);
url.searchParams.set(tokenQueryKey, tokenValue);
request.url = url.toString();
} catch (error) {}
}
}
break;
case 'client_credentials':
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars, promptVariables);
({ credentials, url: oauth2Url, credentialsId, debugInfo } = await getOAuth2TokenUsingClientCredentials({ request: requestCopy, collectionUid, certsAndProxyConfigForTokenUrl, certsAndProxyConfigForRefreshUrl }));
request.oauth2Credentials = { credentials, url: oauth2Url, collectionUid, credentialsId, debugInfo, folderUid: request.oauth2Credentials?.folderUid };
if (tokenPlacement == 'header' && credentials?.access_token) {
request.headers['Authorization'] = `${tokenHeaderPrefix} ${credentials.access_token}`.trim();
} else {
try {
const url = new URL(request.url);
url?.searchParams?.set(tokenQueryKey, credentials?.access_token);
request.url = url?.toString();
} catch (error) {}
{
const tokenValue = tokenSource === 'id_token' ? credentials?.id_token : credentials?.access_token;
if (tokenPlacement == 'header' && tokenValue) {
request.headers['Authorization'] = `${tokenHeaderPrefix} ${tokenValue}`.trim();
} else if (tokenValue) {
try {
const url = new URL(request.url);
url.searchParams.set(tokenQueryKey, tokenValue);
request.url = url.toString();
} catch (error) {}
}
}
break;
case 'password':
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars, promptVariables);
({ credentials, url: oauth2Url, credentialsId, debugInfo } = await getOAuth2TokenUsingPasswordCredentials({ request: requestCopy, collectionUid, certsAndProxyConfigForTokenUrl, certsAndProxyConfigForRefreshUrl }));
request.oauth2Credentials = { credentials, url: oauth2Url, collectionUid, credentialsId, debugInfo, folderUid: request.oauth2Credentials?.folderUid };
if (tokenPlacement == 'header' && credentials?.access_token) {
request.headers['Authorization'] = `${tokenHeaderPrefix} ${credentials.access_token}`.trim();
} else {
try {
const url = new URL(request.url);
url?.searchParams?.set(tokenQueryKey, credentials?.access_token);
request.url = url?.toString();
} catch (error) {}
{
const tokenValue = tokenSource === 'id_token' ? credentials?.id_token : credentials?.access_token;
if (tokenPlacement == 'header' && tokenValue) {
request.headers['Authorization'] = `${tokenHeaderPrefix} ${tokenValue}`.trim();
} else if (tokenValue) {
try {
const url = new URL(request.url);
url.searchParams.set(tokenQueryKey, tokenValue);
request.url = url.toString();
} catch (error) {}
}
}
break;
}

View File

@@ -89,6 +89,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
tokenPlacement: get(collectionAuth, 'oauth2.tokenPlacement'),
tokenHeaderPrefix: get(collectionAuth, 'oauth2.tokenHeaderPrefix'),
tokenQueryKey: get(collectionAuth, 'oauth2.tokenQueryKey'),
tokenSource: get(collectionAuth, 'oauth2.tokenSource'),
autoFetchToken: get(collectionAuth, 'oauth2.autoFetchToken'),
autoRefreshToken: get(collectionAuth, 'oauth2.autoRefreshToken'),
additionalParameters: get(collectionAuth, 'oauth2.additionalParameters', { authorization: [], token: [], refresh: [] })
@@ -111,6 +112,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
tokenPlacement: get(collectionAuth, 'oauth2.tokenPlacement'),
tokenHeaderPrefix: get(collectionAuth, 'oauth2.tokenHeaderPrefix'),
tokenQueryKey: get(collectionAuth, 'oauth2.tokenQueryKey'),
tokenSource: get(collectionAuth, 'oauth2.tokenSource'),
autoFetchToken: get(collectionAuth, 'oauth2.autoFetchToken'),
autoRefreshToken: get(collectionAuth, 'oauth2.autoRefreshToken'),
additionalParameters: get(collectionAuth, 'oauth2.additionalParameters', { authorization: [], token: [], refresh: [] })
@@ -128,6 +130,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
tokenPlacement: get(collectionAuth, 'oauth2.tokenPlacement'),
tokenHeaderPrefix: get(collectionAuth, 'oauth2.tokenHeaderPrefix'),
tokenQueryKey: get(collectionAuth, 'oauth2.tokenQueryKey'),
tokenSource: get(collectionAuth, 'oauth2.tokenSource'),
autoFetchToken: get(collectionAuth, 'oauth2.autoFetchToken'),
additionalParameters: get(collectionAuth, 'oauth2.additionalParameters', { authorization: [], token: [], refresh: [] })
};
@@ -145,6 +148,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
tokenPlacement: get(collectionAuth, 'oauth2.tokenPlacement'),
tokenHeaderPrefix: get(collectionAuth, 'oauth2.tokenHeaderPrefix'),
tokenQueryKey: get(collectionAuth, 'oauth2.tokenQueryKey'),
tokenSource: get(collectionAuth, 'oauth2.tokenSource'),
autoFetchToken: get(collectionAuth, 'oauth2.autoFetchToken'),
autoRefreshToken: get(collectionAuth, 'oauth2.autoRefreshToken'),
additionalParameters: get(collectionAuth, 'oauth2.additionalParameters', { authorization: [], token: [], refresh: [] })
@@ -206,6 +210,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
tokenPlacement: get(request, 'auth.oauth2.tokenPlacement'),
tokenHeaderPrefix: get(request, 'auth.oauth2.tokenHeaderPrefix'),
tokenQueryKey: get(request, 'auth.oauth2.tokenQueryKey'),
tokenSource: get(request, 'auth.oauth2.tokenSource'),
autoFetchToken: get(request, 'auth.oauth2.autoFetchToken'),
autoRefreshToken: get(request, 'auth.oauth2.autoRefreshToken'),
additionalParameters: get(request, 'auth.oauth2.additionalParameters', { authorization: [], token: [], refresh: [] })
@@ -228,6 +233,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
tokenPlacement: get(request, 'auth.oauth2.tokenPlacement'),
tokenHeaderPrefix: get(request, 'auth.oauth2.tokenHeaderPrefix'),
tokenQueryKey: get(request, 'auth.oauth2.tokenQueryKey'),
tokenSource: get(request, 'auth.oauth2.tokenSource'),
autoFetchToken: get(request, 'auth.oauth2.autoFetchToken'),
autoRefreshToken: get(request, 'auth.oauth2.autoRefreshToken'),
additionalParameters: get(request, 'auth.oauth2.additionalParameters', { authorization: [], token: [], refresh: [] })
@@ -245,6 +251,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
tokenPlacement: get(request, 'auth.oauth2.tokenPlacement'),
tokenHeaderPrefix: get(request, 'auth.oauth2.tokenHeaderPrefix'),
tokenQueryKey: get(request, 'auth.oauth2.tokenQueryKey'),
tokenSource: get(request, 'auth.oauth2.tokenSource'),
autoFetchToken: get(request, 'auth.oauth2.autoFetchToken'),
additionalParameters: get(request, 'auth.oauth2.additionalParameters', { authorization: [], token: [], refresh: [] })
};
@@ -262,6 +269,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
tokenPlacement: get(request, 'auth.oauth2.tokenPlacement'),
tokenHeaderPrefix: get(request, 'auth.oauth2.tokenHeaderPrefix'),
tokenQueryKey: get(request, 'auth.oauth2.tokenQueryKey'),
tokenSource: get(request, 'auth.oauth2.tokenSource'),
autoFetchToken: get(request, 'auth.oauth2.autoFetchToken'),
autoRefreshToken: get(request, 'auth.oauth2.autoRefreshToken'),
additionalParameters: get(request, 'auth.oauth2.additionalParameters', { authorization: [], token: [], refresh: [] })

View File

@@ -114,6 +114,8 @@ const buildTokenConfig = (oauth: BrunoOAuth2): OAuth2TokenConfig | undefined =>
};
}
tokenConfig.source = oauth.tokenSource || 'access_token';
return Object.keys(tokenConfig).length > 0 ? tokenConfig : undefined;
};
@@ -345,6 +347,7 @@ export const toBrunoOAuth2 = (oauth: AuthOAuth2 | null | undefined): BrunoOAuth2
tokenPlacement: null,
tokenHeaderPrefix: null,
tokenQueryKey: null,
tokenSource: 'access_token',
refreshTokenUrl: null,
autoRefreshToken: false, // Default to false
autoFetchToken: true, // Default to true
@@ -363,6 +366,7 @@ export const toBrunoOAuth2 = (oauth: AuthOAuth2 | null | undefined): BrunoOAuth2
// token config
if (oauth.tokenConfig?.id) brunoOAuth.credentialsId = oauth.tokenConfig.id;
if (oauth.tokenConfig?.source) brunoOAuth.tokenSource = oauth.tokenConfig.source || 'access_token';
if (oauth.tokenConfig?.placement) {
if ('header' in oauth.tokenConfig.placement) {
brunoOAuth.tokenPlacement = 'header';
@@ -408,6 +412,7 @@ export const toBrunoOAuth2 = (oauth: AuthOAuth2 | null | undefined): BrunoOAuth2
// token config
if (oauth.tokenConfig?.id) brunoOAuth.credentialsId = oauth.tokenConfig.id;
if (oauth.tokenConfig?.source) brunoOAuth.tokenSource = oauth.tokenConfig.source || 'access_token';
if (oauth.tokenConfig?.placement) {
if ('header' in oauth.tokenConfig.placement) {
brunoOAuth.tokenPlacement = 'header';
@@ -454,6 +459,7 @@ export const toBrunoOAuth2 = (oauth: AuthOAuth2 | null | undefined): BrunoOAuth2
// token config
if (oauth.tokenConfig?.id) brunoOAuth.credentialsId = oauth.tokenConfig.id;
if (oauth.tokenConfig?.source) brunoOAuth.tokenSource = oauth.tokenConfig.source || 'access_token';
if (oauth.tokenConfig?.placement) {
if ('header' in oauth.tokenConfig.placement) {
brunoOAuth.tokenPlacement = 'header';
@@ -502,6 +508,7 @@ export const toBrunoOAuth2 = (oauth: AuthOAuth2 | null | undefined): BrunoOAuth2
// token config
if (oauth.tokenConfig?.id) brunoOAuth.credentialsId = oauth.tokenConfig.id;
if (oauth.tokenConfig?.source) brunoOAuth.tokenSource = oauth.tokenConfig.source || 'access_token';
if (oauth.tokenConfig?.placement) {
if ('header' in oauth.tokenConfig.placement) {
brunoOAuth.tokenPlacement = 'header';

View File

@@ -731,6 +731,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
const tokenQueryKeyKey = _.find(auth, { name: 'token_query_key' });
const autoFetchTokenKey = _.find(auth, { name: 'auto_fetch_token' });
const autoRefreshTokenKey = _.find(auth, { name: 'auto_refresh_token' });
const tokenSourceKey = _.find(auth, { name: 'token_source' });
return {
auth: {
oauth2:
@@ -746,6 +747,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
scope: scopeKey ? scopeKey.value : '',
credentialsPlacement: credentialsPlacementKey?.value ? credentialsPlacementKey.value : 'body',
credentialsId: credentialsIdKey?.value ? credentialsIdKey.value : 'credentials',
tokenSource: tokenSourceKey?.value ? tokenSourceKey.value : 'access_token',
tokenPlacement: tokenPlacementKey?.value ? tokenPlacementKey.value : 'header',
tokenHeaderPrefix: tokenHeaderPrefixKey?.value ? tokenHeaderPrefixKey.value : '',
tokenQueryKey: tokenQueryKeyKey?.value ? tokenQueryKeyKey.value : 'access_token',
@@ -766,6 +768,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
pkce: pkceKey ? safeParseJson(pkceKey?.value) ?? false : false,
credentialsPlacement: credentialsPlacementKey?.value ? credentialsPlacementKey.value : 'body',
credentialsId: credentialsIdKey?.value ? credentialsIdKey.value : 'credentials',
tokenSource: tokenSourceKey?.value ? tokenSourceKey.value : 'access_token',
tokenPlacement: tokenPlacementKey?.value ? tokenPlacementKey.value : 'header',
tokenHeaderPrefix: tokenHeaderPrefixKey?.value ? tokenHeaderPrefixKey.value : '',
tokenQueryKey: tokenQueryKeyKey?.value ? tokenQueryKeyKey.value : 'access_token',
@@ -782,6 +785,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
scope: scopeKey ? scopeKey.value : '',
credentialsPlacement: credentialsPlacementKey?.value ? credentialsPlacementKey.value : 'body',
credentialsId: credentialsIdKey?.value ? credentialsIdKey.value : 'credentials',
tokenSource: tokenSourceKey?.value ? tokenSourceKey.value : 'access_token',
tokenPlacement: tokenPlacementKey?.value ? tokenPlacementKey.value : 'header',
tokenHeaderPrefix: tokenHeaderPrefixKey?.value ? tokenHeaderPrefixKey.value : '',
tokenQueryKey: tokenQueryKeyKey?.value ? tokenQueryKeyKey.value : 'access_token',
@@ -797,6 +801,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
scope: scopeKey ? scopeKey.value : '',
state: stateKey ? stateKey.value : '',
credentialsId: credentialsIdKey?.value ? credentialsIdKey.value : 'credentials',
tokenSource: tokenSourceKey?.value ? tokenSourceKey.value : 'access_token',
tokenPlacement: tokenPlacementKey?.value ? tokenPlacementKey.value : 'header',
tokenHeaderPrefix: tokenHeaderPrefixKey?.value ? tokenHeaderPrefixKey.value : '',
tokenQueryKey: tokenQueryKeyKey?.value ? tokenQueryKeyKey.value : 'access_token',

View File

@@ -344,6 +344,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
const tokenQueryKeyKey = _.find(auth, { name: 'token_query_key' });
const autoFetchTokenKey = _.find(auth, { name: 'auto_fetch_token' });
const autoRefreshTokenKey = _.find(auth, { name: 'auto_refresh_token' });
const tokenSourceKey = _.find(auth, { name: 'token_source' });
return {
auth: {
oauth2:
@@ -359,6 +360,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
scope: scopeKey ? scopeKey.value : '',
credentialsPlacement: credentialsPlacementKey?.value ? credentialsPlacementKey.value : 'body',
credentialsId: credentialsIdKey?.value ? credentialsIdKey.value : 'credentials',
tokenSource: tokenSourceKey?.value ? tokenSourceKey.value : 'access_token',
tokenPlacement: tokenPlacementKey?.value ? tokenPlacementKey.value : 'header',
tokenHeaderPrefix: tokenHeaderPrefixKey?.value ? tokenHeaderPrefixKey.value : '',
tokenQueryKey: tokenQueryKeyKey?.value ? tokenQueryKeyKey.value : 'access_token',
@@ -379,6 +381,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
pkce: pkceKey ? safeParseJson(pkceKey?.value) ?? false : false,
credentialsPlacement: credentialsPlacementKey?.value ? credentialsPlacementKey.value : 'body',
credentialsId: credentialsIdKey?.value ? credentialsIdKey.value : 'credentials',
tokenSource: tokenSourceKey?.value ? tokenSourceKey.value : 'access_token',
tokenPlacement: tokenPlacementKey?.value ? tokenPlacementKey.value : 'header',
tokenHeaderPrefix: tokenHeaderPrefixKey?.value ? tokenHeaderPrefixKey.value : '',
tokenQueryKey: tokenQueryKeyKey?.value ? tokenQueryKeyKey.value : 'access_token',
@@ -394,6 +397,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
scope: scopeKey ? scopeKey.value : '',
state: stateKey ? stateKey.value : '',
credentialsId: credentialsIdKey?.value ? credentialsIdKey.value : 'credentials',
tokenSource: tokenSourceKey?.value ? tokenSourceKey.value : 'access_token',
tokenPlacement: tokenPlacementKey?.value ? tokenPlacementKey.value : 'header',
tokenHeaderPrefix: tokenHeaderPrefixKey?.value ? tokenHeaderPrefixKey.value : '',
tokenQueryKey: tokenQueryKeyKey?.value ? tokenQueryKeyKey.value : 'access_token',
@@ -409,6 +413,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
scope: scopeKey ? scopeKey.value : '',
credentialsPlacement: credentialsPlacementKey?.value ? credentialsPlacementKey.value : 'body',
credentialsId: credentialsIdKey?.value ? credentialsIdKey.value : 'credentials',
tokenSource: tokenSourceKey?.value ? tokenSourceKey.value : 'access_token',
tokenPlacement: tokenPlacementKey?.value ? tokenPlacementKey.value : 'header',
tokenHeaderPrefix: tokenHeaderPrefixKey?.value ? tokenHeaderPrefixKey.value : '',
tokenQueryKey: tokenQueryKeyKey?.value ? tokenQueryKeyKey.value : 'access_token',

View File

@@ -268,6 +268,7 @@ ${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)}
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
${indentString(`credentials_placement: ${auth?.oauth2?.credentialsPlacement || ''}`)}
${indentString(`credentials_id: ${auth?.oauth2?.credentialsId || ''}`)}
${indentString(`token_source: ${auth?.oauth2?.tokenSource || 'access_token'}`)}
${indentString(`token_placement: ${auth?.oauth2?.tokenPlacement || ''}`)}${
auth?.oauth2?.tokenPlacement == 'header' ? '\n' + indentString(`token_header_prefix: ${auth?.oauth2?.tokenHeaderPrefix || ''}`) : ''
}${
@@ -293,6 +294,7 @@ ${indentString(`state: ${auth?.oauth2?.state || ''}`)}
${indentString(`pkce: ${(auth?.oauth2?.pkce || false).toString()}`)}
${indentString(`credentials_placement: ${auth?.oauth2?.credentialsPlacement || ''}`)}
${indentString(`credentials_id: ${auth?.oauth2?.credentialsId || ''}`)}
${indentString(`token_source: ${auth?.oauth2?.tokenSource || 'access_token'}`)}
${indentString(`token_placement: ${auth?.oauth2?.tokenPlacement || ''}`)}${
auth?.oauth2?.tokenPlacement == 'header' ? '\n' + indentString(`token_header_prefix: ${auth?.oauth2?.tokenHeaderPrefix || ''}`) : ''
}${
@@ -314,6 +316,7 @@ ${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)}
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
${indentString(`credentials_placement: ${auth?.oauth2?.credentialsPlacement || ''}`)}
${indentString(`credentials_id: ${auth?.oauth2?.credentialsId || ''}`)}
${indentString(`token_source: ${auth?.oauth2?.tokenSource || 'access_token'}`)}
${indentString(`token_placement: ${auth?.oauth2?.tokenPlacement || ''}`)}${
auth?.oauth2?.tokenPlacement == 'header' ? '\n' + indentString(`token_header_prefix: ${auth?.oauth2?.tokenHeaderPrefix || ''}`) : ''
}${
@@ -334,6 +337,7 @@ ${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)}
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
${indentString(`state: ${auth?.oauth2?.state || ''}`)}
${indentString(`credentials_id: ${auth?.oauth2?.credentialsId || ''}`)}
${indentString(`token_source: ${auth?.oauth2?.tokenSource || 'access_token'}`)}
${indentString(`token_placement: ${auth?.oauth2?.tokenPlacement || ''}`)}${
auth?.oauth2?.tokenPlacement == 'header' ? '\n' + indentString(`token_header_prefix: ${auth?.oauth2?.tokenHeaderPrefix || ''}`) : ''
}${

View File

@@ -157,6 +157,7 @@ ${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)}
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
${indentString(`credentials_placement: ${auth?.oauth2?.credentialsPlacement || ''}`)}
${indentString(`credentials_id: ${auth?.oauth2?.credentialsId || ''}`)}
${indentString(`token_source: ${auth?.oauth2?.tokenSource || 'access_token'}`)}
${indentString(`token_placement: ${auth?.oauth2?.tokenPlacement || ''}`)}${
auth?.oauth2?.tokenPlacement == 'header' ? '\n' + indentString(`token_header_prefix: ${auth?.oauth2?.tokenHeaderPrefix || ''}`) : ''
}${
@@ -182,6 +183,7 @@ ${indentString(`state: ${auth?.oauth2?.state || ''}`)}
${indentString(`pkce: ${(auth?.oauth2?.pkce || false).toString()}`)}
${indentString(`credentials_placement: ${auth?.oauth2?.credentialsPlacement || ''}`)}
${indentString(`credentials_id: ${auth?.oauth2?.credentialsId || ''}`)}
${indentString(`token_source: ${auth?.oauth2?.tokenSource || 'access_token'}`)}
${indentString(`token_placement: ${auth?.oauth2?.tokenPlacement || ''}`)}${
auth?.oauth2?.tokenPlacement == 'header' ? '\n' + indentString(`token_header_prefix: ${auth?.oauth2?.tokenHeaderPrefix || ''}`) : ''
}${
@@ -202,6 +204,7 @@ ${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)}
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
${indentString(`state: ${auth?.oauth2?.state || ''}`)}
${indentString(`credentials_id: ${auth?.oauth2?.credentialsId || ''}`)}
${indentString(`token_source: ${auth?.oauth2?.tokenSource || 'access_token'}`)}
${indentString(`token_placement: ${auth?.oauth2?.tokenPlacement || ''}`)}${
auth?.oauth2?.tokenPlacement == 'header' ? '\n' + indentString(`token_header_prefix: ${auth?.oauth2?.tokenHeaderPrefix || ''}`) : ''
}${
@@ -222,6 +225,7 @@ ${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)}
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
${indentString(`credentials_placement: ${auth?.oauth2?.credentialsPlacement || ''}`)}
${indentString(`credentials_id: ${auth?.oauth2?.credentialsId || ''}`)}
${indentString(`token_source: ${auth?.oauth2?.tokenSource || 'access_token'}`)}
${indentString(`token_placement: ${auth?.oauth2?.tokenPlacement || ''}`)}${
auth?.oauth2?.tokenPlacement == 'header' ? '\n' + indentString(`token_header_prefix: ${auth?.oauth2?.tokenHeaderPrefix || ''}`) : ''
}${

View File

@@ -27,6 +27,7 @@ auth:oauth2 {
pkce: true
credentials_placement: header
credentials_id: authorization
token_source: access_token
token_placement: header
token_header_prefix: Bearer
auto_fetch_token: true

View File

@@ -31,6 +31,7 @@
"pkce": true,
"credentialsPlacement": "header",
"credentialsId": "authorization",
"tokenSource": "access_token",
"tokenPlacement": "header",
"tokenHeaderPrefix": "Bearer",
"tokenQueryKey": "access_token",

View File

@@ -81,6 +81,7 @@ auth:oauth2 {
pkce: false
credentials_placement: body
credentials_id: credentials
token_source: access_token
token_placement: header
token_header_prefix: Bearer
auto_fetch_token: true

View File

@@ -141,6 +141,7 @@
"clientId": "client_id_1",
"clientSecret": "client_secret_1",
"credentialsId": "credentials",
"tokenSource": "access_token",
"credentialsPlacement": "body",
"grantType": "authorization_code",
"pkce": false,

View File

@@ -27,6 +27,7 @@ export interface OAuth2Config {
credentialsId?: string;
autoRefreshToken?: boolean;
autoFetchToken?: boolean;
tokenSource?: 'access_token' | 'id_token';
additionalParameters?: {
token?: AdditionalParameter[];
};
@@ -320,7 +321,8 @@ export const getOAuth2Token = async (oauth2Config: OAuth2Config, tokenStore: Tok
grantType,
accessTokenUrl,
credentialsId = 'default',
autoFetchToken = true
autoFetchToken = true,
tokenSource = 'access_token'
} = oauth2Config;
if (verbose) {
@@ -346,7 +348,7 @@ export const getOAuth2Token = async (oauth2Config: OAuth2Config, tokenStore: Tok
// Check if token is expired
if (!isTokenExpired(existingToken)) {
// Token is valid, use it
return existingToken.access_token;
return tokenSource === 'id_token' ? existingToken.id_token : existingToken.access_token;
} else {
// Token is expired
if (autoFetchToken) {
@@ -354,7 +356,7 @@ export const getOAuth2Token = async (oauth2Config: OAuth2Config, tokenStore: Tok
await tokenStore.deleteCredential({ url: accessTokenUrl, credentialsId });
} else {
// Return expired token if autoFetchToken is disabled
return existingToken.access_token;
return tokenSource === 'id_token' ? existingToken.id_token : existingToken.access_token;
}
}
} else {
@@ -393,5 +395,5 @@ export const getOAuth2Token = async (oauth2Config: OAuth2Config, tokenStore: Tok
console.warn('OAuth2: Failed to save token to store, but proceeding with token');
}
return tokenResponse.access_token;
return tokenSource === 'id_token' ? tokenResponse.id_token : tokenResponse.access_token;
};

View File

@@ -77,6 +77,7 @@ export interface OAuth2 {
refreshTokenUrl?: string | null;
autoRefreshToken?: boolean | null;
autoFetchToken?: boolean | null;
tokenSource?: 'access_token' | 'id_token';
additionalParameters?: OAuthAdditionalParameters | null;
}

View File

@@ -285,6 +285,11 @@ const oauth2Schema = Yup.object({
then: Yup.string().nullable(),
otherwise: Yup.string().nullable().strip()
}),
tokenSource: Yup.string().when('grantType', {
is: (val) => ['client_credentials', 'password', 'authorization_code', 'implicit'].includes(val),
then: Yup.string().oneOf(['access_token', 'id_token']).optional(),
otherwise: Yup.string().optional().strip()
}),
tokenPlacement: Yup.string().when('grantType', {
is: (val) => ['client_credentials', 'password', 'authorization_code', 'implicit'].includes(val),
then: Yup.string().nullable(),