mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-15 11:51:30 +00:00
@@ -7,6 +7,7 @@ import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections/in
|
||||
import { useDispatch } from 'react-redux';
|
||||
import OAuth2PasswordCredentials from 'components/RequestPane/Auth/OAuth2/PasswordCredentials/index';
|
||||
import OAuth2ClientCredentials from 'components/RequestPane/Auth/OAuth2/ClientCredentials/index';
|
||||
import OAuth2Implicit from 'components/RequestPane/Auth/OAuth2/Implicit/index';
|
||||
import GrantTypeSelector from 'components/RequestPane/Auth/OAuth2/GrantTypeSelector/index';
|
||||
|
||||
const GrantTypeComponentMap = ({collection }) => {
|
||||
@@ -29,6 +30,9 @@ const GrantTypeComponentMap = ({collection }) => {
|
||||
case 'client_credentials':
|
||||
return <OAuth2ClientCredentials save={save} request={request} updateAuth={updateCollectionAuth} collection={collection} />;
|
||||
break;
|
||||
case 'implicit':
|
||||
return <OAuth2Implicit save={save} request={request} updateAuth={updateCollectionAuth} collection={collection} />;
|
||||
break;
|
||||
default:
|
||||
return <div>TBD</div>;
|
||||
break;
|
||||
|
||||
@@ -7,6 +7,7 @@ import { updateFolderAuth } from 'providers/ReduxStore/slices/collections';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import OAuth2PasswordCredentials from 'components/RequestPane/Auth/OAuth2/PasswordCredentials/index';
|
||||
import OAuth2ClientCredentials from 'components/RequestPane/Auth/OAuth2/ClientCredentials/index';
|
||||
import OAuth2Implicit from 'components/RequestPane/Auth/OAuth2/Implicit/index';
|
||||
import GrantTypeSelector from 'components/RequestPane/Auth/OAuth2/GrantTypeSelector/index';
|
||||
import AuthMode from '../AuthMode';
|
||||
import BasicAuth from 'components/RequestPane/Auth/BasicAuth';
|
||||
@@ -35,6 +36,8 @@ const GrantTypeComponentMap = ({ collection, folder }) => {
|
||||
return <OAuth2AuthorizationCode save={save} item={folder} request={request} updateAuth={updateFolderAuth} collection={collection} folder={folder} />;
|
||||
case 'client_credentials':
|
||||
return <OAuth2ClientCredentials save={save} item={folder} request={request} updateAuth={updateFolderAuth} collection={collection} folder={folder} />;
|
||||
case 'implicit':
|
||||
return <OAuth2Implicit save={save} item={folder} request={request} updateAuth={updateFolderAuth} collection={collection} folder={folder} />;
|
||||
default:
|
||||
return <div>TBD</div>;
|
||||
}
|
||||
|
||||
@@ -101,6 +101,15 @@ const GrantTypeSelector = ({ item = {}, request, updateAuth, collection }) => {
|
||||
>
|
||||
Authorization Code
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onGrantTypeChange('implicit');
|
||||
}}
|
||||
>
|
||||
Implicit
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
label {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
.oauth2-input-wrapper {
|
||||
max-width: 400px;
|
||||
padding: 0.15rem 0.4rem;
|
||||
border-radius: 3px;
|
||||
border: solid 1px ${(props) => props.theme.input.border};
|
||||
background-color: ${(props) => props.theme.input.bg};
|
||||
}
|
||||
|
||||
.token-placement-selector {
|
||||
padding: 0.5rem 0px;
|
||||
border-radius: 3px;
|
||||
border: solid 1px ${(props) => props.theme.input.border};
|
||||
background-color: ${(props) => props.theme.input.bg};
|
||||
min-width: 100px;
|
||||
|
||||
.dropdown {
|
||||
width: fit-content;
|
||||
min-width: 100px;
|
||||
|
||||
div[data-tippy-root] {
|
||||
width: fit-content;
|
||||
min-width: 100px;
|
||||
}
|
||||
.tippy-box {
|
||||
width: fit-content;
|
||||
max-width: none !important;
|
||||
min-width: 100px;
|
||||
|
||||
.tippy-content {
|
||||
width: fit-content;
|
||||
max-width: none !important;
|
||||
min-width: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.token-placement-label {
|
||||
width: fit-content;
|
||||
justify-content: space-between;
|
||||
padding: 0 0.5rem;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
padding: 0.2rem 0.6rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
color: ${(props) => props.theme.colors.text.primary};
|
||||
user-select: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@@ -0,0 +1,281 @@
|
||||
import React, { useRef, forwardRef, useState, useMemo } from 'react';
|
||||
import get from 'lodash/get';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { IconCaretDown, IconLoader2, IconSettings, IconKey, IconHelp, IconAdjustmentsHorizontal } from '@tabler/icons';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import { clearOauth2Cache, fetchOauth2Credentials } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import Wrapper from './StyledWrapper';
|
||||
import { inputsConfig } from './inputsConfig';
|
||||
import toast from 'react-hot-toast';
|
||||
import Oauth2TokenViewer from '../Oauth2TokenViewer/index';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { getAllVariables } from 'utils/collections/index';
|
||||
import { interpolate } from '@usebruno/common';
|
||||
|
||||
const OAuth2Implicit = ({ save, item = {}, request, handleRun, updateAuth, collection, folder }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
const dropdownTippyRef = useRef();
|
||||
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||
const [fetchingToken, toggleFetchingToken] = useState(false);
|
||||
|
||||
const oAuth = get(request, 'auth.oauth2', {});
|
||||
const {
|
||||
callbackUrl,
|
||||
authorizationUrl,
|
||||
clientId,
|
||||
scope,
|
||||
state,
|
||||
credentialsId,
|
||||
tokenPlacement,
|
||||
tokenHeaderPrefix,
|
||||
tokenQueryKey,
|
||||
autoFetchToken
|
||||
} = oAuth;
|
||||
|
||||
const interpolatedAuthUrl = useMemo(() => {
|
||||
const variables = getAllVariables(collection, item);
|
||||
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 handleFetchOauth2Credentials = async () => {
|
||||
let requestCopy = cloneDeep(request);
|
||||
requestCopy.oauth2 = requestCopy?.auth.oauth2;
|
||||
requestCopy.headers = {};
|
||||
toggleFetchingToken(true);
|
||||
try {
|
||||
const result = await dispatch(fetchOauth2Credentials({
|
||||
itemUid: item.uid,
|
||||
request: requestCopy,
|
||||
collection,
|
||||
folderUid: folder?.uid || null,
|
||||
forceGetToken: true
|
||||
}));
|
||||
|
||||
toggleFetchingToken(false);
|
||||
|
||||
// Check if the result contains error or if access_token is missing
|
||||
if (result?.error || !result?.access_token) {
|
||||
const errorMessage = result?.error || 'No access token received from authorization server';
|
||||
toast.error(errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success('Token fetched successfully!');
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
toggleFetchingToken(false);
|
||||
toast.error(error?.message || 'An error occurred while fetching token!');
|
||||
}
|
||||
}
|
||||
|
||||
const handleSave = () => { save(); };
|
||||
|
||||
const handleChange = (key, value) => {
|
||||
dispatch(
|
||||
updateAuth({
|
||||
mode: 'oauth2',
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
grantType: 'implicit',
|
||||
callbackUrl,
|
||||
authorizationUrl,
|
||||
clientId,
|
||||
state,
|
||||
scope,
|
||||
credentialsId,
|
||||
tokenPlacement,
|
||||
tokenHeaderPrefix,
|
||||
tokenQueryKey,
|
||||
autoFetchToken,
|
||||
[key]: value,
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleAutoFetchTokenToggle = (e) => {
|
||||
handleChange('autoFetchToken', e.target.checked);
|
||||
};
|
||||
|
||||
const handleClearCache = (e) => {
|
||||
dispatch(clearOauth2Cache({ collectionUid: collection?.uid, url: interpolatedAuthUrl, credentialsId }))
|
||||
.then(() => {
|
||||
toast.success('cleared cache successfully');
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error(err.message);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Wrapper className="mt-2 flex w-full gap-4 flex-col">
|
||||
<Oauth2TokenViewer handleRun={handleRun} collection={collection} item={item} url={authorizationUrl} credentialsId={credentialsId} />
|
||||
<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">
|
||||
<IconSettings size={14} className="text-indigo-500 dark:text-indigo-400" />
|
||||
</div>
|
||||
<span className="text-sm font-medium">
|
||||
Configuration
|
||||
</span>
|
||||
</div>
|
||||
{inputsConfig.map((input) => {
|
||||
const { key, label, isSecret } = input;
|
||||
return (
|
||||
<div className="flex items-center gap-4 w-full" key={`input-${key}`}>
|
||||
<label className="block min-w-[140px]">{label}</label>
|
||||
<div className="oauth2-input-wrapper flex-1">
|
||||
<SingleLineEditor
|
||||
value={oAuth[key] || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleChange(key, val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
isSecret={isSecret}
|
||||
/>
|
||||
</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" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-gray-800 dark:text-gray-200">
|
||||
Token
|
||||
</span>
|
||||
</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">
|
||||
<SingleLineEditor
|
||||
value={oAuth['credentialsId'] || 'credentials'}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleChange('credentialsId', val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
handleChange('tokenPlacement', 'url');
|
||||
}}
|
||||
>
|
||||
URL
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{tokenPlacement == 'header' ? (
|
||||
<div className="flex items-center gap-4 w-full" key={`input-token-header-prefix`}>
|
||||
<label className="block min-w-[140px]">Header Prefix</label>
|
||||
<div className="oauth2-input-wrapper flex-1">
|
||||
<SingleLineEditor
|
||||
value={oAuth.tokenHeaderPrefix || 'Bearer'}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleChange('tokenHeaderPrefix', val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-4 w-full" key={`input-token-query-key`}>
|
||||
<label className="block min-w-[140px]">URL Query Key</label>
|
||||
<div className="oauth2-input-wrapper flex-1">
|
||||
<SingleLineEditor
|
||||
value={oAuth.tokenQueryKey || 'access_token'}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleChange('tokenQueryKey', val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</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">
|
||||
<IconAdjustmentsHorizontal size={14} className="text-indigo-500 dark:text-indigo-400" />
|
||||
</div>
|
||||
<span className="text-sm font-medium">
|
||||
Advanced Options
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 w-full">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={oAuth.autoFetchToken !== false}
|
||||
onChange={handleAutoFetchTokenToggle}
|
||||
className="cursor-pointer ml-1"
|
||||
/>
|
||||
<label className="block min-w-[140px]">Auto fetch token</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative group cursor-pointer">
|
||||
<IconHelp size={16} className="text-gray-500" />
|
||||
<span className="group-hover:opacity-100 pointer-events-none opacity-0 max-w-60 absolute left-0 bottom-full mb-1 w-max p-2 bg-gray-700 text-white text-xs rounded-md transition-opacity duration-200">
|
||||
Automatically fetch a new token when the current one expires.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row gap-4 mt-4">
|
||||
<button
|
||||
onClick={handleFetchOauth2Credentials}
|
||||
className={`submit btn btn-sm btn-secondary w-fit flex flex-row`}
|
||||
disabled={fetchingToken}
|
||||
>
|
||||
Get Access Token{fetchingToken ? <IconLoader2 className="animate-spin ml-2" size={18} strokeWidth={1.5} /> : ""}
|
||||
</button>
|
||||
<button onClick={handleClearCache} className="submit btn btn-sm btn-secondary w-fit">
|
||||
Clear Cache
|
||||
</button>
|
||||
</div>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default OAuth2Implicit;
|
||||
@@ -0,0 +1,24 @@
|
||||
const inputsConfig = [
|
||||
{
|
||||
key: 'callbackUrl',
|
||||
label: 'Callback URL'
|
||||
},
|
||||
{
|
||||
key: 'authorizationUrl',
|
||||
label: 'Authorization URL'
|
||||
},
|
||||
{
|
||||
key: 'clientId',
|
||||
label: 'Client ID'
|
||||
},
|
||||
{
|
||||
key: 'scope',
|
||||
label: 'Scope'
|
||||
},
|
||||
{
|
||||
key: 'state',
|
||||
label: 'State'
|
||||
}
|
||||
];
|
||||
|
||||
export { inputsConfig };
|
||||
@@ -28,20 +28,30 @@ const Oauth2ActionButtons = ({ item, request, collection, url: accessTokenUrl, c
|
||||
requestCopy.headers = {};
|
||||
toggleFetchingToken(true);
|
||||
try {
|
||||
const credentials = await dispatch(fetchOauth2Credentials({ itemUid: item.uid, request: requestCopy, collection }));
|
||||
const result = await dispatch(fetchOauth2Credentials({
|
||||
itemUid: item.uid,
|
||||
request: requestCopy,
|
||||
collection,
|
||||
forceGetToken: true
|
||||
}));
|
||||
|
||||
toggleFetchingToken(false);
|
||||
if (credentials?.access_token) {
|
||||
toast.success('token fetched successfully!');
|
||||
}
|
||||
else {
|
||||
toast.error('An error occurred while fetching token!');
|
||||
|
||||
// Check if the result contains error or if access_token is missing
|
||||
if (!result || !result.access_token) {
|
||||
const errorMessage = result?.error || 'No access token received from authorization server';
|
||||
console.error(errorMessage);
|
||||
toast.error(errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success('Token fetched successfully!');
|
||||
}
|
||||
catch (error) {
|
||||
console.error('could not fetch the token!');
|
||||
console.error(error);
|
||||
toggleFetchingToken(false);
|
||||
toast.error('An error occurred while fetching token!');
|
||||
toast.error(error?.message || 'An error occurred while fetching token!');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,19 +61,29 @@ const Oauth2ActionButtons = ({ item, request, collection, url: accessTokenUrl, c
|
||||
requestCopy.headers = {};
|
||||
toggleRefreshingToken(true);
|
||||
try {
|
||||
const credentials = await dispatch(refreshOauth2Credentials({ itemUid: item.uid, request: requestCopy, collection }));
|
||||
const result = await dispatch(refreshOauth2Credentials({
|
||||
itemUid: item.uid,
|
||||
request: requestCopy,
|
||||
collection,
|
||||
forceGetToken: true
|
||||
}));
|
||||
|
||||
toggleRefreshingToken(false);
|
||||
if (credentials?.access_token) {
|
||||
toast.success('token refreshed successfully!');
|
||||
}
|
||||
else {
|
||||
toast.error('An error occurred while refreshing token!');
|
||||
|
||||
// Check if the result contains error or if access_token is missing
|
||||
if (!result || !result.access_token) {
|
||||
const errorMessage = result?.error || 'No access token received from authorization server';
|
||||
console.error(errorMessage);
|
||||
toast.error(errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success('Token refreshed successfully!');
|
||||
}
|
||||
catch(error) {
|
||||
console.error(error);
|
||||
toggleRefreshingToken(false);
|
||||
toast.error('An error occurred while refreshing token!');
|
||||
toast.error(error?.message || 'An error occurred while refreshing token!');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import StyledWrapper from './StyledWrapper';
|
||||
import GrantTypeSelector from './GrantTypeSelector/index';
|
||||
import OAuth2PasswordCredentials from './PasswordCredentials/index';
|
||||
import OAuth2AuthorizationCode from './AuthorizationCode/index';
|
||||
import OAuth2Implicit from './Implicit/index';
|
||||
import OAuth2ClientCredentials from './ClientCredentials/index';
|
||||
import { updateAuth } from 'providers/ReduxStore/slices/collections';
|
||||
import { saveRequest, sendRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
@@ -31,6 +32,9 @@ const GrantTypeComponentMap = ({ item, collection }) => {
|
||||
case 'authorization_code':
|
||||
return <OAuth2AuthorizationCode item={item} save={save} request={request} handleRun={handleRun} updateAuth={updateAuth} collection={collection} />;
|
||||
break;
|
||||
case 'implicit':
|
||||
return <OAuth2Implicit item={item} save={save} request={request} handleRun={handleRun} updateAuth={updateAuth} collection={collection} />;
|
||||
break;
|
||||
case 'client_credentials':
|
||||
return <OAuth2ClientCredentials item={item} save={save} request={request} handleRun={handleRun} updateAuth={updateAuth} collection={collection} />;
|
||||
break;
|
||||
|
||||
@@ -340,6 +340,21 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {}
|
||||
autoRefreshToken: get(si.request, 'auth.oauth2.autoRefreshToken', true),
|
||||
};
|
||||
break;
|
||||
case 'implicit':
|
||||
di.request.auth.oauth2 = {
|
||||
grantType: grantType,
|
||||
callbackUrl: get(si.request, 'auth.oauth2.callbackUrl', ''),
|
||||
authorizationUrl: get(si.request, 'auth.oauth2.authorizationUrl', ''),
|
||||
clientId: get(si.request, 'auth.oauth2.clientId', ''),
|
||||
scope: get(si.request, 'auth.oauth2.scope', ''),
|
||||
state: get(si.request, 'auth.oauth2.state', ''),
|
||||
credentialsId: get(si.request, 'auth.oauth2.credentialsId', 'credentials'),
|
||||
tokenPlacement: get(si.request, 'auth.oauth2.tokenPlacement', 'header'),
|
||||
tokenHeaderPrefix: get(si.request, 'auth.oauth2.tokenHeaderPrefix', 'Bearer'),
|
||||
tokenQueryKey: get(si.request, 'auth.oauth2.tokenQueryKey', ''),
|
||||
autoFetchToken: get(si.request, 'auth.oauth2.autoFetchToken', true),
|
||||
};
|
||||
break;
|
||||
case 'client_credentials':
|
||||
di.request.auth.oauth2 = {
|
||||
grantType: grantType,
|
||||
@@ -710,23 +725,22 @@ export const humanizeRequestAPIKeyPlacement = (placement) => {
|
||||
};
|
||||
|
||||
export const humanizeGrantType = (mode) => {
|
||||
let label = 'No Auth';
|
||||
switch (mode) {
|
||||
case 'password': {
|
||||
label = 'Password Credentials';
|
||||
break;
|
||||
}
|
||||
case 'authorization_code': {
|
||||
label = 'Authorization Code';
|
||||
break;
|
||||
}
|
||||
case 'client_credentials': {
|
||||
label = 'Client Credentials';
|
||||
break;
|
||||
}
|
||||
if (!mode || typeof mode !== 'string') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return label;
|
||||
switch (mode) {
|
||||
case 'password':
|
||||
return 'Password Credentials';
|
||||
case 'authorization_code':
|
||||
return 'Authorization Code';
|
||||
case 'client_credentials':
|
||||
return 'Client Credentials';
|
||||
case 'implicit':
|
||||
return 'Implicit';
|
||||
default:
|
||||
return mode;
|
||||
}
|
||||
};
|
||||
|
||||
export const refreshUidsInItem = (item) => {
|
||||
|
||||
@@ -40,7 +40,7 @@ const UiStateSnapshotStore = require('../store/ui-state-snapshot');
|
||||
const interpolateVars = require('./network/interpolate-vars');
|
||||
const { getEnvVars, getTreePathFromCollectionToItem, mergeVars, parseBruFileMeta, hydrateRequestWithUuid, transformRequestToSaveToFilesystem } = require('../utils/collection');
|
||||
const { getProcessEnvVars } = require('../store/process-env');
|
||||
const { getOAuth2TokenUsingAuthorizationCode, getOAuth2TokenUsingClientCredentials, getOAuth2TokenUsingPasswordCredentials, refreshOauth2Token } = require('../utils/oauth2');
|
||||
const { getOAuth2TokenUsingAuthorizationCode, getOAuth2TokenUsingClientCredentials, getOAuth2TokenUsingPasswordCredentials, getOAuth2TokenUsingImplicitGrant, refreshOauth2Token } = require('../utils/oauth2');
|
||||
const { getCertsAndProxyConfig } = require('./network');
|
||||
const collectionWatcher = require('../app/collection-watcher');
|
||||
|
||||
@@ -994,22 +994,59 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
||||
collectionPath
|
||||
});
|
||||
const { oauth2: { grantType }} = requestCopy || {};
|
||||
let credentials, url, credentialsId;
|
||||
|
||||
const handleOAuth2Response = (response) => {
|
||||
if (response.error && !response.debugInfo) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
return response;
|
||||
};
|
||||
|
||||
switch (grantType) {
|
||||
case 'authorization_code':
|
||||
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars);
|
||||
({ credentials, url, credentialsId, debugInfo } = await getOAuth2TokenUsingAuthorizationCode({ request: requestCopy, collectionUid, forceFetch: true, certsAndProxyConfig }));
|
||||
break;
|
||||
return await getOAuth2TokenUsingAuthorizationCode({
|
||||
request: requestCopy,
|
||||
collectionUid,
|
||||
forceFetch: true,
|
||||
certsAndProxyConfig
|
||||
}).then(handleOAuth2Response);
|
||||
|
||||
case 'client_credentials':
|
||||
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars);
|
||||
({ credentials, url, credentialsId, debugInfo } = await getOAuth2TokenUsingClientCredentials({ request: requestCopy, collectionUid, forceFetch: true, certsAndProxyConfig }));
|
||||
break;
|
||||
return await getOAuth2TokenUsingClientCredentials({
|
||||
request: requestCopy,
|
||||
collectionUid,
|
||||
forceFetch: true,
|
||||
certsAndProxyConfig
|
||||
}).then(handleOAuth2Response);
|
||||
|
||||
case 'password':
|
||||
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars);
|
||||
({ credentials, url, credentialsId, debugInfo } = await getOAuth2TokenUsingPasswordCredentials({ request: requestCopy, collectionUid, forceFetch: true, certsAndProxyConfig }));
|
||||
break;
|
||||
return await getOAuth2TokenUsingPasswordCredentials({
|
||||
request: requestCopy,
|
||||
collectionUid,
|
||||
forceFetch: true,
|
||||
certsAndProxyConfig
|
||||
}).then(handleOAuth2Response);
|
||||
|
||||
case 'implicit':
|
||||
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars);
|
||||
return await getOAuth2TokenUsingImplicitGrant({
|
||||
request: requestCopy,
|
||||
collectionUid,
|
||||
forceFetch: true
|
||||
}).then(handleOAuth2Response);
|
||||
|
||||
default:
|
||||
return {
|
||||
error: `Unsupported grant type: ${grantType}`,
|
||||
credentials: null,
|
||||
url: null,
|
||||
collectionUid,
|
||||
credentialsId: null
|
||||
};
|
||||
}
|
||||
return { credentials, url, collectionUid, credentialsId, debugInfo };
|
||||
}
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
@@ -1082,6 +1119,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
||||
processEnvVars,
|
||||
collectionPath
|
||||
});
|
||||
|
||||
let { credentials, url, credentialsId, debugInfo } = await refreshOauth2Token({ requestCopy, collectionUid, certsAndProxyConfig });
|
||||
return { credentials, url, collectionUid, credentialsId, debugInfo };
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ const matchesCallbackUrl = (url, callbackUrl) => {
|
||||
return url ? url.href.startsWith(callbackUrl.href) : false;
|
||||
};
|
||||
|
||||
const authorizeUserInWindow = ({ authorizeUrl, callbackUrl, session }) => {
|
||||
const authorizeUserInWindow = ({ authorizeUrl, callbackUrl, session, grantType = 'authorization_code' }) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let finalUrl = null;
|
||||
let debugInfo = {
|
||||
@@ -166,9 +166,29 @@ const authorizeUserInWindow = ({ authorizeUrl, callbackUrl, session }) => {
|
||||
|
||||
if (finalUrl) {
|
||||
try {
|
||||
const callbackUrlWithCode = new URL(finalUrl);
|
||||
const authorizationCode = callbackUrlWithCode.searchParams.get('code');
|
||||
return resolve({ authorizationCode, debugInfo });
|
||||
// Handle different grant types differently
|
||||
if (grantType === 'implicit') {
|
||||
// For implicit flow, tokens are in the URL hash fragment
|
||||
const urlWithHash = new URL(finalUrl);
|
||||
const hash = urlWithHash.hash.substring(1); // Remove the leading #
|
||||
const hashParams = new URLSearchParams(hash);
|
||||
|
||||
// Extract tokens from hash fragment
|
||||
const implicitTokens = {
|
||||
access_token: hashParams.get('access_token'),
|
||||
token_type: hashParams.get('token_type'),
|
||||
expires_in: hashParams.get('expires_in'),
|
||||
state: hashParams.get('state'),
|
||||
scope: hashParams.get('scope')
|
||||
};
|
||||
|
||||
return resolve({ implicitTokens, debugInfo });
|
||||
} else {
|
||||
// Default case - authorization code flow
|
||||
const callbackUrlWithCode = new URL(finalUrl);
|
||||
const authorizationCode = callbackUrlWithCode.searchParams.get('code');
|
||||
return resolve({ authorizationCode, debugInfo });
|
||||
}
|
||||
} catch (error) {
|
||||
return reject(error);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,8 @@ const { chooseFileToSave, writeBinaryFile, writeFile } = require('../../utils/fi
|
||||
const { addCookieToJar, getDomainsWithCookies, getCookieStringForUrl } = require('../../utils/cookies');
|
||||
const { createFormData } = require('../../utils/form-data');
|
||||
const { findItemInCollectionByPathname, sortFolder, getAllRequestsInFolderRecursively, getEnvVars, getTreePathFromCollectionToItem, mergeVars } = require('../../utils/collection');
|
||||
const { getOAuth2TokenUsingAuthorizationCode, getOAuth2TokenUsingClientCredentials, getOAuth2TokenUsingPasswordCredentials } = require('../../utils/oauth2');
|
||||
const { getOAuth2TokenUsingAuthorizationCode, getOAuth2TokenUsingClientCredentials, getOAuth2TokenUsingPasswordCredentials, getOAuth2TokenUsingImplicitGrant } = require('../../utils/oauth2');
|
||||
const { setupProxyAgents } = require('../../utils/proxy-util');
|
||||
const { preferencesUtil } = require('../../store/preferences');
|
||||
const { getProcessEnvVars } = require('../../store/process-env');
|
||||
const { getBrunoConfig } = require('../../store/bruno-config');
|
||||
@@ -218,6 +219,22 @@ const configureRequest = async (
|
||||
catch(error) {}
|
||||
}
|
||||
break;
|
||||
case 'implicit':
|
||||
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars);
|
||||
({ 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) {}
|
||||
}
|
||||
break;
|
||||
case 'client_credentials':
|
||||
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars);
|
||||
({ credentials, url: oauth2Url, credentialsId, debugInfo } = await getOAuth2TokenUsingClientCredentials({ request: requestCopy, collectionUid, certsAndProxyConfig }));
|
||||
|
||||
@@ -188,6 +188,18 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
|
||||
request.oauth2.autoFetchToken = _interpolate(request.oauth2.autoFetchToken);
|
||||
request.oauth2.autoRefreshToken = _interpolate(request.oauth2.autoRefreshToken);
|
||||
break;
|
||||
case 'implicit':
|
||||
request.oauth2.callbackUrl = _interpolate(request.oauth2.callbackUrl) || '';
|
||||
request.oauth2.authorizationUrl = _interpolate(request.oauth2.authorizationUrl) || '';
|
||||
request.oauth2.clientId = _interpolate(request.oauth2.clientId) || '';
|
||||
request.oauth2.scope = _interpolate(request.oauth2.scope) || '';
|
||||
request.oauth2.state = _interpolate(request.oauth2.state) || '';
|
||||
request.oauth2.credentialsId = _interpolate(request.oauth2.credentialsId) || '';
|
||||
request.oauth2.tokenPlacement = _interpolate(request.oauth2.tokenPlacement) || '';
|
||||
request.oauth2.tokenHeaderPrefix = _interpolate(request.oauth2.tokenHeaderPrefix) || '';
|
||||
request.oauth2.tokenQueryKey = _interpolate(request.oauth2.tokenQueryKey) || '';
|
||||
request.oauth2.autoFetchToken = _interpolate(request.oauth2.autoFetchToken);
|
||||
break;
|
||||
case 'authorization_code':
|
||||
request.oauth2.callbackUrl = _interpolate(request.oauth2.callbackUrl) || '';
|
||||
request.oauth2.authorizationUrl = _interpolate(request.oauth2.authorizationUrl) || '';
|
||||
|
||||
@@ -111,6 +111,21 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
|
||||
autoRefreshToken: get(collectionAuth, 'oauth2.autoRefreshToken')
|
||||
};
|
||||
break;
|
||||
case 'implicit':
|
||||
axiosRequest.oauth2 = {
|
||||
grantType: grantType,
|
||||
callbackUrl: get(collectionAuth, 'oauth2.callbackUrl'),
|
||||
authorizationUrl: get(collectionAuth, 'oauth2.authorizationUrl'),
|
||||
clientId: get(collectionAuth, 'oauth2.clientId'),
|
||||
scope: get(collectionAuth, 'oauth2.scope'),
|
||||
state: get(collectionAuth, 'oauth2.state'),
|
||||
credentialsId: get(collectionAuth, 'oauth2.credentialsId'),
|
||||
tokenPlacement: get(collectionAuth, 'oauth2.tokenPlacement'),
|
||||
tokenHeaderPrefix: get(collectionAuth, 'oauth2.tokenHeaderPrefix'),
|
||||
tokenQueryKey: get(collectionAuth, 'oauth2.tokenQueryKey'),
|
||||
autoFetchToken: get(collectionAuth, 'oauth2.autoFetchToken')
|
||||
};
|
||||
break;
|
||||
case 'client_credentials':
|
||||
axiosRequest.oauth2 = {
|
||||
grantType: grantType,
|
||||
@@ -209,6 +224,21 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
|
||||
autoRefreshToken: get(request, 'auth.oauth2.autoRefreshToken')
|
||||
};
|
||||
break;
|
||||
case 'implicit':
|
||||
axiosRequest.oauth2 = {
|
||||
grantType: grantType,
|
||||
callbackUrl: get(request, 'auth.oauth2.callbackUrl'),
|
||||
authorizationUrl: get(request, 'auth.oauth2.authorizationUrl'),
|
||||
clientId: get(request, 'auth.oauth2.clientId'),
|
||||
scope: get(request, 'auth.oauth2.scope'),
|
||||
state: get(request, 'auth.oauth2.state'),
|
||||
credentialsId: get(request, 'auth.oauth2.credentialsId'),
|
||||
tokenPlacement: get(request, 'auth.oauth2.tokenPlacement'),
|
||||
tokenHeaderPrefix: get(request, 'auth.oauth2.tokenHeaderPrefix'),
|
||||
tokenQueryKey: get(request, 'auth.oauth2.tokenQueryKey'),
|
||||
autoFetchToken: get(request, 'auth.oauth2.autoFetchToken')
|
||||
};
|
||||
break;
|
||||
case 'client_credentials':
|
||||
axiosRequest.oauth2 = {
|
||||
grantType: grantType,
|
||||
|
||||
@@ -67,6 +67,44 @@ const getOAuth2TokenUsingAuthorizationCode = async ({ request, collectionUid, fo
|
||||
autoFetchToken,
|
||||
} = oAuth;
|
||||
const url = requestCopy?.oauth2?.accessTokenUrl;
|
||||
|
||||
// Validate required fields
|
||||
if (!authorizationUrl) {
|
||||
return {
|
||||
error: 'Authorization URL is required for OAuth2 authorization code flow',
|
||||
credentials: null,
|
||||
url,
|
||||
credentialsId
|
||||
};
|
||||
}
|
||||
|
||||
if (!url) {
|
||||
return {
|
||||
error: 'Access Token URL is required for OAuth2 authorization code flow',
|
||||
credentials: null,
|
||||
url: authorizationUrl,
|
||||
credentialsId
|
||||
};
|
||||
}
|
||||
|
||||
if (!callbackUrl) {
|
||||
return {
|
||||
error: 'Callback URL is required for OAuth2 authorization code flow',
|
||||
credentials: null,
|
||||
url,
|
||||
credentialsId
|
||||
};
|
||||
}
|
||||
|
||||
if (!clientId) {
|
||||
return {
|
||||
error: 'Client ID is required for OAuth2 authorization code flow',
|
||||
credentials: null,
|
||||
url,
|
||||
credentialsId
|
||||
};
|
||||
}
|
||||
|
||||
if (!forceFetch) {
|
||||
const storedCredentials = getStoredOauth2Credentials({ collectionUid, url, credentialsId });
|
||||
|
||||
@@ -279,6 +317,34 @@ const getOAuth2TokenUsingClientCredentials = async ({ request, collectionUid, fo
|
||||
|
||||
const url = requestCopy?.oauth2?.accessTokenUrl;
|
||||
|
||||
// Validate required fields
|
||||
if (!url) {
|
||||
return {
|
||||
error: 'Access Token URL is required for OAuth2 client credentials flow',
|
||||
credentials: null,
|
||||
url,
|
||||
credentialsId
|
||||
};
|
||||
}
|
||||
|
||||
if (!clientId) {
|
||||
return {
|
||||
error: 'Client ID is required for OAuth2 client credentials flow',
|
||||
credentials: null,
|
||||
url,
|
||||
credentialsId
|
||||
};
|
||||
}
|
||||
|
||||
if (!clientSecret) {
|
||||
return {
|
||||
error: 'Client Secret is required for OAuth2 client credentials flow',
|
||||
credentials: null,
|
||||
url,
|
||||
credentialsId
|
||||
};
|
||||
}
|
||||
|
||||
if (!forceFetch) {
|
||||
const storedCredentials = getStoredOauth2Credentials({ collectionUid, url, credentialsId });
|
||||
|
||||
@@ -447,6 +513,43 @@ const getOAuth2TokenUsingPasswordCredentials = async ({ request, collectionUid,
|
||||
} = oAuth;
|
||||
const url = requestCopy?.oauth2?.accessTokenUrl;
|
||||
|
||||
// Validate required fields
|
||||
if (!url) {
|
||||
return {
|
||||
error: 'Access Token URL is required for OAuth2 password credentials flow',
|
||||
credentials: null,
|
||||
url,
|
||||
credentialsId
|
||||
};
|
||||
}
|
||||
|
||||
if (!username) {
|
||||
return {
|
||||
error: 'Username is required for OAuth2 password credentials flow',
|
||||
credentials: null,
|
||||
url,
|
||||
credentialsId
|
||||
};
|
||||
}
|
||||
|
||||
if (!password) {
|
||||
return {
|
||||
error: 'Password is required for OAuth2 password credentials flow',
|
||||
credentials: null,
|
||||
url,
|
||||
credentialsId
|
||||
};
|
||||
}
|
||||
|
||||
if (!clientId) {
|
||||
return {
|
||||
error: 'Client ID is required for OAuth2 password credentials flow',
|
||||
credentials: null,
|
||||
url,
|
||||
credentialsId
|
||||
};
|
||||
}
|
||||
|
||||
if (!forceFetch) {
|
||||
const storedCredentials = getStoredOauth2Credentials({ collectionUid, url, credentialsId });
|
||||
|
||||
@@ -726,10 +829,167 @@ const generateCodeChallenge = (codeVerifier) => {
|
||||
return base64Hash;
|
||||
};
|
||||
|
||||
const getOAuth2TokenUsingImplicitGrant = async ({ request, collectionUid, forceFetch = false }) => {
|
||||
const { oauth2 = {} } = request;
|
||||
const {
|
||||
authorizationUrl,
|
||||
clientId,
|
||||
scope,
|
||||
state = '',
|
||||
callbackUrl,
|
||||
credentialsId = 'credentials',
|
||||
autoFetchToken = true
|
||||
} = oauth2;
|
||||
|
||||
// Validate required fields
|
||||
if (!authorizationUrl) {
|
||||
return {
|
||||
error: 'Authorization URL is required for OAuth2 implicit flow',
|
||||
credentials: null,
|
||||
url: authorizationUrl,
|
||||
credentialsId
|
||||
};
|
||||
}
|
||||
|
||||
if (!callbackUrl) {
|
||||
return {
|
||||
error: 'Callback URL is required for OAuth2 implicit flow',
|
||||
credentials: null,
|
||||
url: authorizationUrl,
|
||||
credentialsId
|
||||
};
|
||||
}
|
||||
|
||||
// Check if we already have valid credentials
|
||||
if (!forceFetch) {
|
||||
try {
|
||||
const storedCredentials = getStoredOauth2Credentials({
|
||||
collectionUid,
|
||||
url: authorizationUrl,
|
||||
credentialsId
|
||||
});
|
||||
|
||||
if (storedCredentials) {
|
||||
// Token exists
|
||||
if (!isTokenExpired(storedCredentials)) {
|
||||
// Token is valid, use it
|
||||
return {
|
||||
collectionUid,
|
||||
credentials: storedCredentials,
|
||||
url: authorizationUrl,
|
||||
credentialsId
|
||||
};
|
||||
} else {
|
||||
// Token is expired - unlike other grant types, implicit flow doesn't support refresh tokens
|
||||
if (autoFetchToken) {
|
||||
// Proceed to fetch new token
|
||||
clearOauth2Credentials({ collectionUid, url: authorizationUrl, credentialsId });
|
||||
} else {
|
||||
// Proceed with expired token
|
||||
return {
|
||||
collectionUid,
|
||||
credentials: storedCredentials,
|
||||
url: authorizationUrl,
|
||||
credentialsId
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No stored credentials
|
||||
if (!autoFetchToken) {
|
||||
// Don't fetch token if autoFetchToken is disabled
|
||||
return {
|
||||
collectionUid,
|
||||
credentials: null,
|
||||
url: authorizationUrl,
|
||||
credentialsId
|
||||
};
|
||||
}
|
||||
// Otherwise proceed to fetch new token
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error retrieving oauth2 credentials from cache', error);
|
||||
clearOauth2Credentials({ collectionUid, url: authorizationUrl, credentialsId });
|
||||
}
|
||||
}
|
||||
|
||||
const authorizationUrlWithQueryParams = new URL(authorizationUrl);
|
||||
authorizationUrlWithQueryParams.searchParams.append('response_type', 'token');
|
||||
authorizationUrlWithQueryParams.searchParams.append('client_id', clientId);
|
||||
authorizationUrlWithQueryParams.searchParams.append('redirect_uri', callbackUrl);
|
||||
if (scope) {
|
||||
authorizationUrlWithQueryParams.searchParams.append('scope', scope);
|
||||
}
|
||||
if (state) {
|
||||
authorizationUrlWithQueryParams.searchParams.append('state', state);
|
||||
}
|
||||
|
||||
const authorizeUrl = authorizationUrlWithQueryParams.toString();
|
||||
|
||||
try {
|
||||
const { implicitTokens, debugInfo } = await authorizeUserInWindow({
|
||||
authorizeUrl,
|
||||
callbackUrl,
|
||||
session: oauth2Store.getSessionIdOfCollection({ collectionUid, url: authorizationUrl }),
|
||||
grantType: 'implicit'
|
||||
});
|
||||
|
||||
if (!implicitTokens || !implicitTokens.access_token) {
|
||||
return {
|
||||
error: 'No access token received from authorization server',
|
||||
credentials: null,
|
||||
url: authorizationUrl,
|
||||
credentialsId,
|
||||
debugInfo
|
||||
};
|
||||
}
|
||||
|
||||
const credentials = {
|
||||
access_token: implicitTokens.access_token,
|
||||
token_type: implicitTokens.token_type || 'Bearer',
|
||||
state: implicitTokens.state || '',
|
||||
...(implicitTokens.expires_in ? { expires_in: parseInt(implicitTokens.expires_in) } : {}),
|
||||
created_at: Date.now()
|
||||
};
|
||||
|
||||
if (implicitTokens.scope) {
|
||||
credentials.scope = implicitTokens.scope;
|
||||
}
|
||||
|
||||
// Store the credentials
|
||||
persistOauth2Credentials({
|
||||
collectionUid,
|
||||
url: authorizationUrl,
|
||||
credentials,
|
||||
credentialsId
|
||||
});
|
||||
|
||||
return {
|
||||
collectionUid,
|
||||
credentials,
|
||||
url: authorizationUrl,
|
||||
credentialsId,
|
||||
debugInfo
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
error: error.message || 'Failed to obtain token',
|
||||
credentials: null,
|
||||
url: authorizationUrl,
|
||||
credentialsId
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
persistOauth2Credentials,
|
||||
clearOauth2Credentials,
|
||||
getStoredOauth2Credentials,
|
||||
getOAuth2TokenUsingAuthorizationCode,
|
||||
getOAuth2AuthorizationCode,
|
||||
getOAuth2TokenUsingClientCredentials,
|
||||
getOAuth2TokenUsingPasswordCredentials,
|
||||
refreshOauth2Token
|
||||
getOAuth2TokenUsingImplicitGrant,
|
||||
refreshOauth2Token,
|
||||
generateCodeVerifier,
|
||||
generateCodeChallenge
|
||||
};
|
||||
@@ -584,6 +584,20 @@ const sem = grammar.createSemantics().addAttribute('ast', {
|
||||
autoFetchToken: autoFetchTokenKey ? safeParseJson(autoFetchTokenKey?.value) ?? true : true,
|
||||
autoRefreshToken: autoRefreshTokenKey ? safeParseJson(autoRefreshTokenKey?.value) ?? false : false
|
||||
}
|
||||
: grantTypeKey?.value && grantTypeKey?.value == 'implicit'
|
||||
? {
|
||||
grantType: grantTypeKey ? grantTypeKey.value : '',
|
||||
callbackUrl: callbackUrlKey ? callbackUrlKey.value : '',
|
||||
authorizationUrl: authorizationUrlKey ? authorizationUrlKey.value : '',
|
||||
clientId: clientIdKey ? clientIdKey.value : '',
|
||||
scope: scopeKey ? scopeKey.value : '',
|
||||
state: stateKey ? stateKey.value : '',
|
||||
credentialsId: credentialsIdKey?.value ? credentialsIdKey.value : 'credentials',
|
||||
tokenPlacement: tokenPlacementKey?.value ? tokenPlacementKey.value : 'header',
|
||||
tokenHeaderPrefix: tokenHeaderPrefixKey?.value ? tokenHeaderPrefixKey.value : '',
|
||||
tokenQueryKey: tokenQueryKeyKey?.value ? tokenQueryKeyKey.value : 'access_token',
|
||||
autoFetchToken: autoFetchTokenKey ? safeParseJson(autoFetchTokenKey?.value) ?? true : true,
|
||||
}
|
||||
: {}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -328,6 +328,20 @@ const sem = grammar.createSemantics().addAttribute('ast', {
|
||||
autoFetchToken: autoFetchTokenKey ? safeParseJson(autoFetchTokenKey?.value) ?? true : true,
|
||||
autoRefreshToken: autoRefreshTokenKey ? safeParseJson(autoRefreshTokenKey?.value) ?? false : false
|
||||
}
|
||||
: grantTypeKey?.value && grantTypeKey?.value == 'implicit'
|
||||
? {
|
||||
grantType: grantTypeKey ? grantTypeKey.value : '',
|
||||
callbackUrl: callbackUrlKey ? callbackUrlKey.value : '',
|
||||
authorizationUrl: authorizationUrlKey ? authorizationUrlKey.value : '',
|
||||
clientId: clientIdKey ? clientIdKey.value : '',
|
||||
scope: scopeKey ? scopeKey.value : '',
|
||||
state: stateKey ? stateKey.value : '',
|
||||
credentialsId: credentialsIdKey?.value ? credentialsIdKey.value : 'credentials',
|
||||
tokenPlacement: tokenPlacementKey?.value ? tokenPlacementKey.value : 'header',
|
||||
tokenHeaderPrefix: tokenHeaderPrefixKey?.value ? tokenHeaderPrefixKey.value : '',
|
||||
tokenQueryKey: tokenQueryKeyKey?.value ? tokenQueryKeyKey.value : 'access_token',
|
||||
autoFetchToken: autoFetchTokenKey ? safeParseJson(autoFetchTokenKey?.value) ?? true : true,
|
||||
}
|
||||
: grantTypeKey?.value && grantTypeKey?.value == 'client_credentials'
|
||||
? {
|
||||
grantType: grantTypeKey ? grantTypeKey.value : '',
|
||||
|
||||
@@ -246,6 +246,25 @@ ${indentString(`auto_fetch_token: ${(auth?.oauth2?.autoFetchToken ?? true).toStr
|
||||
${indentString(`auto_refresh_token: ${(auth?.oauth2?.autoRefreshToken ?? false).toString()}`)}
|
||||
}
|
||||
|
||||
`;
|
||||
break;
|
||||
case 'implicit':
|
||||
bru += `auth:oauth2 {
|
||||
${indentString(`grant_type: implicit`)}
|
||||
${indentString(`callback_url: ${auth?.oauth2?.callbackUrl || ''}`)}
|
||||
${indentString(`authorization_url: ${auth?.oauth2?.authorizationUrl || ''}`)}
|
||||
${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)}
|
||||
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
|
||||
${indentString(`state: ${auth?.oauth2?.state || ''}`)}
|
||||
${indentString(`credentials_id: ${auth?.oauth2?.credentialsId || ''}`)}
|
||||
${indentString(`token_placement: ${auth?.oauth2?.tokenPlacement || ''}`)}${
|
||||
auth?.oauth2?.tokenPlacement == 'header' ? '\n' + indentString(`token_header_prefix: ${auth?.oauth2?.tokenHeaderPrefix || ''}`) : ''
|
||||
}${
|
||||
auth?.oauth2?.tokenPlacement !== 'header' ? '\n' + indentString(`token_query_key: ${auth?.oauth2?.tokenQueryKey || ''}`) : ''
|
||||
}
|
||||
${indentString(`auto_fetch_token: ${(auth?.oauth2?.autoFetchToken ?? true).toString()}`)}
|
||||
}
|
||||
|
||||
`;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -191,6 +191,25 @@ ${indentString(`auto_fetch_token: ${(auth?.oauth2?.autoFetchToken ?? true).toStr
|
||||
${indentString(`auto_refresh_token: ${(auth?.oauth2?.autoRefreshToken ?? false).toString()}`)}
|
||||
}
|
||||
|
||||
`;
|
||||
break;
|
||||
case 'implicit':
|
||||
bru += `auth:oauth2 {
|
||||
${indentString(`grant_type: implicit`)}
|
||||
${indentString(`callback_url: ${auth?.oauth2?.callbackUrl || ''}`)}
|
||||
${indentString(`authorization_url: ${auth?.oauth2?.authorizationUrl || ''}`)}
|
||||
${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)}
|
||||
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
|
||||
${indentString(`state: ${auth?.oauth2?.state || ''}`)}
|
||||
${indentString(`credentials_id: ${auth?.oauth2?.credentialsId || ''}`)}
|
||||
${indentString(`token_placement: ${auth?.oauth2?.tokenPlacement || ''}`)}${
|
||||
auth?.oauth2?.tokenPlacement == 'header' ? '\n' + indentString(`token_header_prefix: ${auth?.oauth2?.tokenHeaderPrefix || ''}`) : ''
|
||||
}${
|
||||
auth?.oauth2?.tokenPlacement !== 'header' ? '\n' + indentString(`token_query_key: ${auth?.oauth2?.tokenQueryKey || ''}`) : ''
|
||||
}
|
||||
${indentString(`auto_fetch_token: ${(auth?.oauth2?.autoFetchToken ?? true).toString()}`)}
|
||||
}
|
||||
|
||||
`;
|
||||
break;
|
||||
case 'client_credentials':
|
||||
|
||||
@@ -159,7 +159,7 @@ const authApiKeySchema = Yup.object({
|
||||
|
||||
const oauth2Schema = Yup.object({
|
||||
grantType: Yup.string()
|
||||
.oneOf(['client_credentials', 'password', 'authorization_code'])
|
||||
.oneOf(['client_credentials', 'password', 'authorization_code', 'implicit'])
|
||||
.required('grantType is required'),
|
||||
username: Yup.string().when('grantType', {
|
||||
is: (val) => ['client_credentials', 'password'].includes(val),
|
||||
@@ -172,12 +172,12 @@ const oauth2Schema = Yup.object({
|
||||
otherwise: Yup.string().nullable().strip()
|
||||
}),
|
||||
callbackUrl: Yup.string().when('grantType', {
|
||||
is: (val) => ['authorization_code'].includes(val),
|
||||
is: (val) => ['authorization_code', 'implicit'].includes(val),
|
||||
then: Yup.string().nullable(),
|
||||
otherwise: Yup.string().nullable().strip()
|
||||
}),
|
||||
authorizationUrl: Yup.string().when('grantType', {
|
||||
is: (val) => ['authorization_code'].includes(val),
|
||||
is: (val) => ['authorization_code', 'implicit'].includes(val),
|
||||
then: Yup.string().nullable(),
|
||||
otherwise: Yup.string().nullable().strip()
|
||||
}),
|
||||
@@ -187,7 +187,7 @@ const oauth2Schema = Yup.object({
|
||||
otherwise: Yup.string().nullable().strip()
|
||||
}),
|
||||
clientId: Yup.string().when('grantType', {
|
||||
is: (val) => ['client_credentials', 'password', 'authorization_code'].includes(val),
|
||||
is: (val) => ['client_credentials', 'password', 'authorization_code', 'implicit'].includes(val),
|
||||
then: Yup.string().nullable(),
|
||||
otherwise: Yup.string().nullable().strip()
|
||||
}),
|
||||
@@ -197,12 +197,12 @@ const oauth2Schema = Yup.object({
|
||||
otherwise: Yup.string().nullable().strip()
|
||||
}),
|
||||
scope: Yup.string().when('grantType', {
|
||||
is: (val) => ['client_credentials', 'password', 'authorization_code'].includes(val),
|
||||
is: (val) => ['client_credentials', 'password', 'authorization_code', 'implicit'].includes(val),
|
||||
then: Yup.string().nullable(),
|
||||
otherwise: Yup.string().nullable().strip()
|
||||
}),
|
||||
state: Yup.string().when('grantType', {
|
||||
is: (val) => ['authorization_code'].includes(val),
|
||||
is: (val) => ['authorization_code', 'implicit'].includes(val),
|
||||
then: Yup.string().nullable(),
|
||||
otherwise: Yup.string().nullable().strip()
|
||||
}),
|
||||
@@ -217,24 +217,24 @@ const oauth2Schema = Yup.object({
|
||||
otherwise: Yup.string().nullable().strip()
|
||||
}),
|
||||
credentialsId: Yup.string().when('grantType', {
|
||||
is: (val) => ['client_credentials', 'password', 'authorization_code'].includes(val),
|
||||
is: (val) => ['client_credentials', 'password', 'authorization_code', 'implicit'].includes(val),
|
||||
then: Yup.string().nullable(),
|
||||
otherwise: Yup.string().nullable().strip()
|
||||
}),
|
||||
tokenPlacement: Yup.string().when('grantType', {
|
||||
is: (val) => ['client_credentials', 'password', 'authorization_code'].includes(val),
|
||||
is: (val) => ['client_credentials', 'password', 'authorization_code', 'implicit'].includes(val),
|
||||
then: Yup.string().nullable(),
|
||||
otherwise: Yup.string().nullable().strip()
|
||||
}),
|
||||
tokenHeaderPrefix: Yup.string().when(['grantType', 'tokenPlacement'], {
|
||||
is: (grantType, tokenPlacement) =>
|
||||
['client_credentials', 'password', 'authorization_code'].includes(grantType) && tokenPlacement === 'header',
|
||||
['client_credentials', 'password', 'authorization_code', 'implicit'].includes(grantType) && tokenPlacement === 'header',
|
||||
then: Yup.string().nullable(),
|
||||
otherwise: Yup.string().nullable().strip()
|
||||
}),
|
||||
tokenQueryKey: Yup.string().when(['grantType', 'tokenPlacement'], {
|
||||
is: (grantType, tokenPlacement) =>
|
||||
['client_credentials', 'password', 'authorization_code'].includes(grantType) && tokenPlacement === 'url',
|
||||
['client_credentials', 'password', 'authorization_code', 'implicit'].includes(grantType) && tokenPlacement === 'url',
|
||||
then: Yup.string().nullable(),
|
||||
otherwise: Yup.string().nullable().strip()
|
||||
}),
|
||||
@@ -249,7 +249,7 @@ const oauth2Schema = Yup.object({
|
||||
otherwise: Yup.boolean()
|
||||
}),
|
||||
autoFetchToken: Yup.boolean().when('grantType', {
|
||||
is: (val) => ['authorization_code'].includes(val),
|
||||
is: (val) => ['authorization_code', 'implicit'].includes(val),
|
||||
then: Yup.boolean().default(true),
|
||||
otherwise: Yup.boolean()
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user