diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/index.js
index 474e44717..e8d20f25c 100644
--- a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/index.js
+++ b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/index.js
@@ -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 ;
break;
+ case 'implicit':
+ return ;
+ break;
default:
return
{
+ dropdownTippyRef.current.hide();
+ onGrantTypeChange('implicit');
+ }}
+ >
+ Implicit
+
{
diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Implicit/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Implicit/StyledWrapper.js
new file mode 100644
index 000000000..273806001
--- /dev/null
+++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Implicit/StyledWrapper.js
@@ -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;
\ No newline at end of file
diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Implicit/index.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Implicit/index.js
new file mode 100644
index 000000000..275e6b040
--- /dev/null
+++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Implicit/index.js
@@ -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 (
+
+ {tokenPlacement == 'url' ? 'URL' : 'Headers'}
+
+
+ );
+ });
+
+ 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 (
+
+
+
+
+
+
+
+ Configuration
+
+
+ {inputsConfig.map((input) => {
+ const { key, label, isSecret } = input;
+ return (
+
+
+
+ handleChange(key, val)}
+ onRun={handleRun}
+ collection={collection}
+ item={item}
+ isSecret={isSecret}
+ />
+
+
+ );
+ })}
+
+
+
+
+
+
+ handleChange('credentialsId', val)}
+ onRun={handleRun}
+ collection={collection}
+ item={item}
+ />
+
+
+
+
+
+
+
} placement="bottom-end">
+
{
+ dropdownTippyRef.current.hide();
+ handleChange('tokenPlacement', 'header');
+ }}
+ >
+ Headers
+
+
{
+ dropdownTippyRef.current.hide();
+ handleChange('tokenPlacement', 'url');
+ }}
+ >
+ URL
+
+
+
+
+
+ {tokenPlacement == 'header' ? (
+
+
+
+ handleChange('tokenHeaderPrefix', val)}
+ onRun={handleRun}
+ collection={collection}
+ item={item}
+ />
+
+
+ ) : (
+
+
+
+ handleChange('tokenQueryKey', val)}
+ onRun={handleRun}
+ collection={collection}
+ item={item}
+ />
+
+
+ )}
+
+
+
+
+
+
+ Advanced Options
+
+
+
+
+
+
+
+
+
+
+ Automatically fetch a new token when the current one expires.
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default OAuth2Implicit;
\ No newline at end of file
diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Implicit/inputsConfig.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Implicit/inputsConfig.js
new file mode 100644
index 000000000..86040b838
--- /dev/null
+++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Implicit/inputsConfig.js
@@ -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 };
\ No newline at end of file
diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Oauth2ActionButtons/index.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Oauth2ActionButtons/index.js
index 7b45f03ea..ee38fa1f2 100644
--- a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Oauth2ActionButtons/index.js
+++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Oauth2ActionButtons/index.js
@@ -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!');
}
};
diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/index.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/index.js
index bbee9d4f5..98b435f1d 100644
--- a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/index.js
+++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/index.js
@@ -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
;
break;
+ case 'implicit':
+ return
;
+ break;
case 'client_credentials':
return
;
break;
diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js
index acb867ebe..3f391c6e3 100644
--- a/packages/bruno-app/src/utils/collections/index.js
+++ b/packages/bruno-app/src/utils/collections/index.js
@@ -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) => {
diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js
index f76590768..96322e4b4 100644
--- a/packages/bruno-electron/src/ipc/collection.js
+++ b/packages/bruno-electron/src/ipc/collection.js
@@ -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 };
}
diff --git a/packages/bruno-electron/src/ipc/network/authorize-user-in-window.js b/packages/bruno-electron/src/ipc/network/authorize-user-in-window.js
index 7d2e23abc..0cf45d70c 100644
--- a/packages/bruno-electron/src/ipc/network/authorize-user-in-window.js
+++ b/packages/bruno-electron/src/ipc/network/authorize-user-in-window.js
@@ -5,7 +5,7 @@ const matchesCallbackUrl = (url, callbackUrl) => {
return url ? url.href.startsWith(callbackUrl.href) : false;
};
-const authorizeUserInWindow = ({ authorizeUrl, callbackUrl, session }) => {
+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);
}
diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js
index 21ae36d2b..4f17c06cf 100644
--- a/packages/bruno-electron/src/ipc/network/index.js
+++ b/packages/bruno-electron/src/ipc/network/index.js
@@ -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 }));
diff --git a/packages/bruno-electron/src/ipc/network/interpolate-vars.js b/packages/bruno-electron/src/ipc/network/interpolate-vars.js
index a7206ec22..5d80cf567 100644
--- a/packages/bruno-electron/src/ipc/network/interpolate-vars.js
+++ b/packages/bruno-electron/src/ipc/network/interpolate-vars.js
@@ -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) || '';
diff --git a/packages/bruno-electron/src/ipc/network/prepare-request.js b/packages/bruno-electron/src/ipc/network/prepare-request.js
index 8f2e89f0d..e51192f3b 100644
--- a/packages/bruno-electron/src/ipc/network/prepare-request.js
+++ b/packages/bruno-electron/src/ipc/network/prepare-request.js
@@ -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,
diff --git a/packages/bruno-electron/src/utils/oauth2.js b/packages/bruno-electron/src/utils/oauth2.js
index dd8594728..d288f8a45 100644
--- a/packages/bruno-electron/src/utils/oauth2.js
+++ b/packages/bruno-electron/src/utils/oauth2.js
@@ -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
};
\ No newline at end of file
diff --git a/packages/bruno-lang/v2/src/bruToJson.js b/packages/bruno-lang/v2/src/bruToJson.js
index e99b690c2..844e21e79 100644
--- a/packages/bruno-lang/v2/src/bruToJson.js
+++ b/packages/bruno-lang/v2/src/bruToJson.js
@@ -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,
+ }
: {}
}
};
diff --git a/packages/bruno-lang/v2/src/collectionBruToJson.js b/packages/bruno-lang/v2/src/collectionBruToJson.js
index 73f5af1a8..d50926014 100644
--- a/packages/bruno-lang/v2/src/collectionBruToJson.js
+++ b/packages/bruno-lang/v2/src/collectionBruToJson.js
@@ -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 : '',
diff --git a/packages/bruno-lang/v2/src/jsonToBru.js b/packages/bruno-lang/v2/src/jsonToBru.js
index c7395e2ff..9013599b7 100644
--- a/packages/bruno-lang/v2/src/jsonToBru.js
+++ b/packages/bruno-lang/v2/src/jsonToBru.js
@@ -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;
}
diff --git a/packages/bruno-lang/v2/src/jsonToCollectionBru.js b/packages/bruno-lang/v2/src/jsonToCollectionBru.js
index 2812798a5..adcdf6b06 100644
--- a/packages/bruno-lang/v2/src/jsonToCollectionBru.js
+++ b/packages/bruno-lang/v2/src/jsonToCollectionBru.js
@@ -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':
diff --git a/packages/bruno-schema/src/collections/index.js b/packages/bruno-schema/src/collections/index.js
index af4b13434..7a03141d5 100644
--- a/packages/bruno-schema/src/collections/index.js
+++ b/packages/bruno-schema/src/collections/index.js
@@ -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()
})