diff --git a/package-lock.json b/package-lock.json index 98b220863..9773e05c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,7 +50,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -787,7 +786,6 @@ "version": "7.26.0", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", - "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", @@ -818,7 +816,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -836,7 +833,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/@babel/generator": { @@ -1116,7 +1112,6 @@ "version": "7.26.0", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.25.9", @@ -7083,7 +7078,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", - "dev": true, "license": "MIT" }, "node_modules/@types/lodash": { @@ -7096,7 +7090,6 @@ "version": "12.2.3", "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/linkify-it": "*", @@ -7107,7 +7100,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", - "dev": true, "license": "MIT" }, "node_modules/@types/ms": { @@ -10115,7 +10107,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, "license": "MIT" }, "node_modules/cookie": { @@ -11718,7 +11709,6 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -12739,7 +12729,6 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -23213,7 +23202,7 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AuthorizationCode/index.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AuthorizationCode/index.js index 3ffad9067..62d841d20 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AuthorizationCode/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AuthorizationCode/index.js @@ -1,16 +1,16 @@ -import React, { useRef, forwardRef, useState } from 'react'; +import React, { useRef, forwardRef, useState, useEffect } from 'react'; import get from 'lodash/get'; import { useTheme } from 'providers/Theme'; import { useDispatch } from 'react-redux'; -import { IconCaretDown, IconLoader2, IconSettings, IconKey } from '@tabler/icons'; +import { IconCaretDown, IconLoader2, IconSettings, IconKey, IconAdjustmentsHorizontal } from '@tabler/icons'; import Dropdown from 'components/Dropdown'; import SingleLineEditor from 'components/SingleLineEditor'; -import { clearOauth2Cache, fetchOauth2Credentials } from 'providers/ReduxStore/slices/collections/actions'; +import { clearOauth2Cache, fetchOauth2Credentials, refreshOauth2Credentials } from 'providers/ReduxStore/slices/collections/actions'; import StyledWrapper from './StyledWrapper'; import { inputsConfig } from './inputsConfig'; import toast from 'react-hot-toast'; import Oauth2TokenViewer from '../Oauth2TokenViewer/index'; -import { cloneDeep } from 'lodash'; +import { cloneDeep, find } from 'lodash'; import { interpolateStringUsingCollectionAndItem } from 'utils/collections/index'; const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAuth, collection }) => { @@ -19,10 +19,28 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu const dropdownTippyRef = useRef(); const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref); const [fetchingToken, toggleFetchingToken] = useState(false); + const [refreshingToken, toggleRefreshingToken] = useState(false); + const [showRefreshButton, setShowRefreshButton] = useState(false); const oAuth = get(request, 'auth.oauth2', {}); - - const { callbackUrl, authorizationUrl, accessTokenUrl, clientId, clientSecret, scope, credentialsPlacement, state, pkce, credentialsId, tokenPlacement, tokenHeaderPrefix, tokenQueryKey, reuseToken } = oAuth; + const { + callbackUrl, + authorizationUrl, + accessTokenUrl, + clientId, + clientSecret, + scope, + credentialsPlacement, + state, + pkce, + credentialsId, + tokenPlacement, + tokenHeaderPrefix, + tokenQueryKey, + reuseToken, + refreshUrl, + autoRefresh + } = oAuth; const TokenPlacementIcon = forwardRef((props, ref) => { return ( @@ -60,6 +78,23 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu } } + const handleRefreshAccessToken = async () => { + let requestCopy = cloneDeep(request); + requestCopy.oauth2 = requestCopy?.auth.oauth2; + requestCopy.headers = {}; + toggleRefreshingToken(true); + try { + await dispatch(refreshOauth2Credentials({ request: requestCopy, collection })); + toggleRefreshingToken(false); + toast.success('token refreshed successfully!'); + } + catch(error) { + console.error(error); + toggleRefreshingToken(false); + toast.error('An error occured while refreshing token!'); + } + } + const handleSave = () => {save();}; const handleChange = (key, value) => { @@ -84,6 +119,8 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu tokenHeaderPrefix, tokenQueryKey, reuseToken, + refreshUrl, + autoRefresh, [key]: value } }) @@ -128,6 +165,16 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu }); }; + const { uid: collectionUid } = collection; + const interpolatedUrl = interpolateStringUsingCollectionAndItem({ collection, item, string: accessTokenUrl }); + const credentialsData = find(collection?.oauth2Credentials, creds => creds?.url == interpolatedUrl && creds?.collectionUid == collectionUid && creds?.credentialsId == credentialsId); + const creds = credentialsData?.credentials || {}; + + useEffect(() => { + // Update visibility whenever credentials change + setShowRefreshButton(Boolean(creds?.refresh_token && creds?.access_token)); + }, [creds?.refresh_token, creds?.access_token, credentialsData]); + return ( @@ -270,10 +317,49 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu } +
+
+ +
+ + Advanced Settings + +
+ +
+ +
+ handleChange("refreshUrl", val)} + collection={collection} + item={item} + /> +
+
+ +
+ + handleChange('autoRefresh', e.target.checked)} + /> + Automatically refresh the token when it expires +
+
+ {showRefreshButton && ( + + )} diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/ClientCredentials/index.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/ClientCredentials/index.js index 518a21efb..1b769b8f4 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/ClientCredentials/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/ClientCredentials/index.js @@ -2,9 +2,9 @@ import React, { useRef, forwardRef, useState } from 'react'; import get from 'lodash/get'; import { useTheme } from 'providers/Theme'; import { useDispatch } from 'react-redux'; -import { IconCaretDown, IconLoader2, IconSettings, IconKey } from '@tabler/icons'; +import { IconCaretDown, IconLoader2, IconSettings, IconKey, IconAdjustmentsHorizontal } from '@tabler/icons'; import SingleLineEditor from 'components/SingleLineEditor'; -import { fetchOauth2Credentials, clearOauth2Cache } from 'providers/ReduxStore/slices/collections/actions'; +import { fetchOauth2Credentials, clearOauth2Cache, refreshOauth2Credentials } from 'providers/ReduxStore/slices/collections/actions'; import StyledWrapper from './StyledWrapper'; import { inputsConfig } from './inputsConfig'; import Dropdown from 'components/Dropdown'; @@ -19,10 +19,11 @@ const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAu const dropdownTippyRef = useRef(); const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref); const [fetchingToken, toggleFetchingToken] = useState(false); + const [refreshingToken, toggleRefreshingToken] = useState(false); const oAuth = get(request, 'auth.oauth2', {}); - const { accessTokenUrl, clientId, clientSecret, scope, credentialsPlacement, credentialsId, tokenPlacement, tokenHeaderPrefix, tokenQueryKey, reuseToken } = oAuth; + const { accessTokenUrl, clientId, clientSecret, scope, credentialsPlacement, credentialsId, tokenPlacement, tokenHeaderPrefix, tokenQueryKey, reuseToken, refreshUrl, autoRefresh } = oAuth; const handleFetchOauth2Credentials = async () => { let requestCopy = cloneDeep(request); @@ -41,6 +42,24 @@ const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAu toast.error('An error occured while fetching token!'); } } + + const handleRefreshAccessToken = async () => { + let requestCopy = cloneDeep(request); + requestCopy.oauth2 = requestCopy?.auth.oauth2; + requestCopy.headers = {}; + toggleRefreshingToken(true); + try { + await dispatch(refreshOauth2Credentials({ request: requestCopy, collection })); + toggleRefreshingToken(false); + toast.success('token refreshed successfully!'); + } + catch(error) { + console.error(error); + toggleRefreshingToken(false); + toast.error('An error occured while refreshing token!'); + } + }; + const handleSave = () => { save(); }; const TokenPlacementIcon = forwardRef((props, ref) => { @@ -79,6 +98,8 @@ const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAu tokenHeaderPrefix, tokenQueryKey, reuseToken, + refreshUrl, + autoRefresh, [key]: value } }) @@ -229,10 +250,47 @@ const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAu
} +
+
+ +
+ + Advanced Settings + +
+ +
+ +
+ handleChange("refreshUrl", val)} + collection={collection} + item={item} + /> +
+
+ +
+ + handleChange('autoRefresh', e.target.checked)} + /> + Automatically refresh the token when it expires +
+
+ diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/PasswordCredentials/index.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/PasswordCredentials/index.js index 54c5e0142..543f7332e 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/PasswordCredentials/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/PasswordCredentials/index.js @@ -2,9 +2,9 @@ import React, { useRef, forwardRef, useState } from 'react'; import get from 'lodash/get'; import { useTheme } from 'providers/Theme'; import { useDispatch } from 'react-redux'; -import { IconCaretDown, IconLoader2, IconSettings, IconKey } from '@tabler/icons'; +import { IconCaretDown, IconLoader2, IconSettings, IconKey, IconAdjustmentsHorizontal } from '@tabler/icons'; import SingleLineEditor from 'components/SingleLineEditor'; -import { fetchOauth2Credentials, clearOauth2Cache } from 'providers/ReduxStore/slices/collections/actions'; +import { fetchOauth2Credentials, clearOauth2Cache, refreshOauth2Credentials } from 'providers/ReduxStore/slices/collections/actions'; import StyledWrapper from './StyledWrapper'; import { inputsConfig } from './inputsConfig'; import Dropdown from 'components/Dropdown'; @@ -19,10 +19,26 @@ const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, update const dropdownTippyRef = useRef(); const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref); const [fetchingToken, toggleFetchingToken] = useState(false); + const [refreshingToken, toggleRefreshingToken] = useState(false); const oAuth = get(request, 'auth.oauth2', {}); - const { accessTokenUrl, username, password, clientId, clientSecret, scope, credentialsPlacement, credentialsId, tokenPlacement, tokenHeaderPrefix, tokenQueryKey, reuseToken } = oAuth; + const { + accessTokenUrl, + username, + password, + clientId, + clientSecret, + scope, + credentialsPlacement, + credentialsId, + tokenPlacement, + tokenHeaderPrefix, + tokenQueryKey, + reuseToken, + refreshUrl, + autoRefresh + } = oAuth; const handleFetchOauth2Credentials = async () => { let requestCopy = cloneDeep(request); @@ -41,6 +57,23 @@ const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, update } } + const handleRefreshAccessToken = async () => { + let requestCopy = cloneDeep(request); + requestCopy.oauth2 = requestCopy?.auth.oauth2; + requestCopy.headers = {}; + toggleRefreshingToken(true); + try { + await dispatch(refreshOauth2Credentials({ request: requestCopy, collection })); + toggleRefreshingToken(false); + toast.success('token refreshed successfully!'); + } + catch(error) { + console.error(error); + toggleRefreshingToken(false); + toast.error('An error occured while refreshing token!'); + } + }; + const handleSave = () => { save(); } const TokenPlacementIcon = forwardRef((props, ref) => { @@ -81,6 +114,8 @@ const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, update tokenHeaderPrefix, tokenQueryKey, reuseToken, + refreshUrl, + autoRefresh, [key]: value } }) @@ -231,10 +266,47 @@ const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, update
} +
+
+ +
+ + Advanced Settings + +
+ +
+ +
+ handleChange("refreshUrl", val)} + collection={collection} + item={item} + /> +
+
+ +
+ + handleChange('autoRefresh', e.target.checked)} + /> + Automatically refresh the token when it expires +
+
+ diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js index 0a57b78ac..f6d366dee 100644 --- a/packages/bruno-app/src/utils/collections/index.js +++ b/packages/bruno-app/src/utils/collections/index.js @@ -356,6 +356,7 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {} di.request.auth.oauth2 = { grantType: grantType, accessTokenUrl: get(si.request, 'auth.oauth2.accessTokenUrl', ''), + refreshUrl: get(si.request, 'auth.oauth2.refreshUrl', ''), username: get(si.request, 'auth.oauth2.username', ''), password: get(si.request, 'auth.oauth2.password', ''), clientId: get(si.request, 'auth.oauth2.clientId', ''), @@ -375,6 +376,7 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {} callbackUrl: get(si.request, 'auth.oauth2.callbackUrl', ''), authorizationUrl: get(si.request, 'auth.oauth2.authorizationUrl', ''), accessTokenUrl: get(si.request, 'auth.oauth2.accessTokenUrl', ''), + refreshUrl: get(si.request, 'auth.oauth2.refreshUrl', ''), clientId: get(si.request, 'auth.oauth2.clientId', ''), clientSecret: get(si.request, 'auth.oauth2.clientSecret', ''), scope: get(si.request, 'auth.oauth2.scope', ''), @@ -391,6 +393,7 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {} di.request.auth.oauth2 = { grantType: grantType, accessTokenUrl: get(si.request, 'auth.oauth2.accessTokenUrl', ''), + refreshUrl: get(si.request, 'auth.oauth2.refreshUrl', ''), clientId: get(si.request, 'auth.oauth2.clientId', ''), clientSecret: get(si.request, 'auth.oauth2.clientSecret', ''), scope: get(si.request, 'auth.oauth2.scope', ''), diff --git a/packages/bruno-app/src/utils/importers/postman-collection.js b/packages/bruno-app/src/utils/importers/postman-collection.js index f7529eced..e0b0a080f 100644 --- a/packages/bruno-app/src/utils/importers/postman-collection.js +++ b/packages/bruno-app/src/utils/importers/postman-collection.js @@ -445,6 +445,7 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) = authorizationUrl: findValueUsingKey('authUrl'), callbackUrl: findValueUsingKey('redirect_uri'), accessTokenUrl: findValueUsingKey('accessTokenUrl'), + refreshUrl: findValueUsingKey('refreshTokenUrl'), clientId: findValueUsingKey('clientId'), clientSecret: findValueUsingKey('clientSecret'), scope: findValueUsingKey('scope'), @@ -458,6 +459,7 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) = brunoRequestItem.request.auth.oauth2 = { grantType: 'password', accessTokenUrl: findValueUsingKey('accessTokenUrl'), + refreshUrl: findValueUsingKey('refreshTokenUrl'), username: findValueUsingKey('username'), password: findValueUsingKey('password'), clientId: findValueUsingKey('clientId'), @@ -472,6 +474,7 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) = brunoRequestItem.request.auth.oauth2 = { grantType: 'client_credentials', accessTokenUrl: findValueUsingKey('accessTokenUrl'), + refreshUrl: findValueUsingKey('refreshTokenUrl'), clientId: findValueUsingKey('clientId'), clientSecret: findValueUsingKey('clientSecret'), scope: findValueUsingKey('scope'), diff --git a/packages/bruno-electron/src/ipc/network/interpolate-vars.js b/packages/bruno-electron/src/ipc/network/interpolate-vars.js index e27b6549f..2f7e69665 100644 --- a/packages/bruno-electron/src/ipc/network/interpolate-vars.js +++ b/packages/bruno-electron/src/ipc/network/interpolate-vars.js @@ -162,6 +162,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc switch (request.oauth2.grantType) { case 'password': request.oauth2.accessTokenUrl = _interpolate(request.oauth2.accessTokenUrl) || ''; + request.oauth2.refreshUrl = _interpolate(request.oauth2.refreshUrl) || ''; request.oauth2.username = _interpolate(request.oauth2.username) || ''; request.oauth2.password = _interpolate(request.oauth2.password) || ''; request.oauth2.clientId = _interpolate(request.oauth2.clientId) || ''; @@ -178,6 +179,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc request.oauth2.callbackUrl = _interpolate(request.oauth2.callbackUrl) || ''; request.oauth2.authorizationUrl = _interpolate(request.oauth2.authorizationUrl) || ''; request.oauth2.accessTokenUrl = _interpolate(request.oauth2.accessTokenUrl) || ''; + request.oauth2.refreshUrl = _interpolate(request.oauth2.refreshUrl) || ''; request.oauth2.clientId = _interpolate(request.oauth2.clientId) || ''; request.oauth2.clientSecret = _interpolate(request.oauth2.clientSecret) || ''; request.oauth2.scope = _interpolate(request.oauth2.scope) || ''; @@ -192,6 +194,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc break; case 'client_credentials': request.oauth2.accessTokenUrl = _interpolate(request.oauth2.accessTokenUrl) || ''; + request.oauth2.refreshUrl = _interpolate(request.oauth2.refreshUrl) || ''; request.oauth2.clientId = _interpolate(request.oauth2.clientId) || ''; request.oauth2.clientSecret = _interpolate(request.oauth2.clientSecret) || ''; request.oauth2.scope = _interpolate(request.oauth2.scope) || ''; diff --git a/packages/bruno-electron/src/utils/oauth2.js b/packages/bruno-electron/src/utils/oauth2.js index 29991cf60..83ced9e49 100644 --- a/packages/bruno-electron/src/utils/oauth2.js +++ b/packages/bruno-electron/src/utils/oauth2.js @@ -200,7 +200,7 @@ const refreshOauth2Token = async (request, collectionUid) => { let requestCopy = cloneDeep(request); const oAuth = get(requestCopy, 'oauth2', {}); const { clientId, clientSecret, credentialsId } = oAuth; - const url = requestCopy?.oauth2?.accessTokenUrl; + const url = requestCopy?.oauth2?.refreshUrl ? requestCopy?.oauth2?.refreshUrl : requestCopy?.oauth2?.accessTokenUrl; const credentials = getStoredOauth2Credentials({ collectionUid, url, credentialsId }); if (!credentials?.refresh_token) { diff --git a/packages/bruno-electron/src/utils/request.js b/packages/bruno-electron/src/utils/request.js index cf085eae0..1fe5b7c70 100644 --- a/packages/bruno-electron/src/utils/request.js +++ b/packages/bruno-electron/src/utils/request.js @@ -92,6 +92,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => { axiosRequest.oauth2 = { grantType: grantType, accessTokenUrl: get(collectionAuth, 'oauth2.accessTokenUrl'), + refreshUrl: get(collectionAuth, 'oauth2.refreshUrl'), username: get(collectionAuth, 'oauth2.username'), password: get(collectionAuth, 'oauth2.password'), clientId: get(collectionAuth, 'oauth2.clientId'), @@ -111,6 +112,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => { callbackUrl: get(collectionAuth, 'oauth2.callbackUrl'), authorizationUrl: get(collectionAuth, 'oauth2.authorizationUrl'), accessTokenUrl: get(collectionAuth, 'oauth2.accessTokenUrl'), + refreshUrl: get(collectionAuth, 'oauth2.refreshUrl'), clientId: get(collectionAuth, 'oauth2.clientId'), clientSecret: get(collectionAuth, 'oauth2.clientSecret'), scope: get(collectionAuth, 'oauth2.scope'), @@ -128,6 +130,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => { axiosRequest.oauth2 = { grantType: grantType, accessTokenUrl: get(collectionAuth, 'oauth2.accessTokenUrl'), + refreshUrl: get(collectionAuth, 'oauth2.refreshUrl'), clientId: get(collectionAuth, 'oauth2.clientId'), clientSecret: get(collectionAuth, 'oauth2.clientSecret'), scope: get(collectionAuth, 'oauth2.scope'), @@ -184,6 +187,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => { axiosRequest.oauth2 = { grantType: grantType, accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'), + refreshUrl: get(collectionAuth, 'oauth2.refreshUrl'), username: get(request, 'auth.oauth2.username'), password: get(request, 'auth.oauth2.password'), clientId: get(request, 'auth.oauth2.clientId'), @@ -203,6 +207,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => { callbackUrl: get(request, 'auth.oauth2.callbackUrl'), authorizationUrl: get(request, 'auth.oauth2.authorizationUrl'), accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'), + refreshUrl: get(collectionAuth, 'oauth2.refreshUrl'), clientId: get(request, 'auth.oauth2.clientId'), clientSecret: get(request, 'auth.oauth2.clientSecret'), scope: get(request, 'auth.oauth2.scope'), @@ -220,6 +225,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => { axiosRequest.oauth2 = { grantType: grantType, accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'), + refreshUrl: get(collectionAuth, 'oauth2.refreshUrl'), clientId: get(request, 'auth.oauth2.clientId'), clientSecret: get(request, 'auth.oauth2.clientSecret'), scope: get(request, 'auth.oauth2.scope'), diff --git a/packages/bruno-lang/v2/src/bruToJson.js b/packages/bruno-lang/v2/src/bruToJson.js index 267f1ea6e..7f1f059f5 100644 --- a/packages/bruno-lang/v2/src/bruToJson.js +++ b/packages/bruno-lang/v2/src/bruToJson.js @@ -479,6 +479,7 @@ const sem = grammar.createSemantics().addAttribute('ast', { const callbackUrlKey = _.find(auth, { name: 'callback_url' }); const authorizationUrlKey = _.find(auth, { name: 'authorization_url' }); const accessTokenUrlKey = _.find(auth, { name: 'access_token_url' }); + const refreshUrlKey = _.find(auth, { name: 'refresh_url' }); const clientIdKey = _.find(auth, { name: 'client_id' }); const clientSecretKey = _.find(auth, { name: 'client_secret' }); const scopeKey = _.find(auth, { name: 'scope' }); @@ -497,6 +498,7 @@ const sem = grammar.createSemantics().addAttribute('ast', { ? { grantType: grantTypeKey ? grantTypeKey.value : '', accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '', + refreshUrl: refreshUrlKey ? refreshUrlKey.value : '', username: usernameKey ? usernameKey.value : '', password: passwordKey ? passwordKey.value : '', clientId: clientIdKey ? clientIdKey.value : '', @@ -515,6 +517,7 @@ const sem = grammar.createSemantics().addAttribute('ast', { callbackUrl: callbackUrlKey ? callbackUrlKey.value : '', authorizationUrl: authorizationUrlKey ? authorizationUrlKey.value : '', accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '', + refreshUrl: refreshUrlKey ? refreshUrlKey.value : '', clientId: clientIdKey ? clientIdKey.value : '', clientSecret: clientSecretKey ? clientSecretKey.value : '', scope: scopeKey ? scopeKey.value : '', @@ -531,6 +534,7 @@ const sem = grammar.createSemantics().addAttribute('ast', { ? { grantType: grantTypeKey ? grantTypeKey.value : '', accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '', + refreshUrl: refreshUrlKey ? refreshUrlKey.value : '', clientId: clientIdKey ? clientIdKey.value : '', clientSecret: clientSecretKey ? clientSecretKey.value : '', scope: scopeKey ? scopeKey.value : '', diff --git a/packages/bruno-lang/v2/src/collectionBruToJson.js b/packages/bruno-lang/v2/src/collectionBruToJson.js index 68c1c98e4..b6ebdd5af 100644 --- a/packages/bruno-lang/v2/src/collectionBruToJson.js +++ b/packages/bruno-lang/v2/src/collectionBruToJson.js @@ -274,6 +274,7 @@ const sem = grammar.createSemantics().addAttribute('ast', { const callbackUrlKey = _.find(auth, { name: 'callback_url' }); const authorizationUrlKey = _.find(auth, { name: 'authorization_url' }); const accessTokenUrlKey = _.find(auth, { name: 'access_token_url' }); + const refreshUrlKey = _.find(auth, { name: 'refresh_url' }); const clientIdKey = _.find(auth, { name: 'client_id' }); const clientSecretKey = _.find(auth, { name: 'client_secret' }); const scopeKey = _.find(auth, { name: 'scope' }); @@ -292,6 +293,7 @@ const sem = grammar.createSemantics().addAttribute('ast', { ? { grantType: grantTypeKey ? grantTypeKey.value : '', accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '', + refreshUrl: refreshUrlKey ? refreshUrlKey.value : '', username: usernameKey ? usernameKey.value : '', password: passwordKey ? passwordKey.value : '', clientId: clientIdKey ? clientIdKey.value : '', @@ -310,6 +312,7 @@ const sem = grammar.createSemantics().addAttribute('ast', { callbackUrl: callbackUrlKey ? callbackUrlKey.value : '', authorizationUrl: authorizationUrlKey ? authorizationUrlKey.value : '', accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '', + refreshUrl: refreshUrlKey ? refreshUrlKey.value : '', clientId: clientIdKey ? clientIdKey.value : '', clientSecret: clientSecretKey ? clientSecretKey.value : '', scope: scopeKey ? scopeKey.value : '', @@ -326,6 +329,7 @@ const sem = grammar.createSemantics().addAttribute('ast', { ? { grantType: grantTypeKey ? grantTypeKey.value : '', accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '', + refreshUrl: refreshUrlKey ? refreshUrlKey.value : '', clientId: clientIdKey ? clientIdKey.value : '', clientSecret: clientSecretKey ? clientSecretKey.value : '', scope: scopeKey ? scopeKey.value : '', diff --git a/packages/bruno-lang/v2/src/jsonToBru.js b/packages/bruno-lang/v2/src/jsonToBru.js index d05f6bca5..9675e5e35 100644 --- a/packages/bruno-lang/v2/src/jsonToBru.js +++ b/packages/bruno-lang/v2/src/jsonToBru.js @@ -183,6 +183,7 @@ ${indentString(`domain: ${auth?.ntlm?.domain || ''}`)} bru += `auth:oauth2 { ${indentString(`grant_type: password`)} ${indentString(`access_token_url: ${auth?.oauth2?.accessTokenUrl || ''}`)} +${indentString(`refresh_url: ${auth?.oauth2?.refreshUrl || ''}`)} ${indentString(`username: ${auth?.oauth2?.username || ''}`)} ${indentString(`password: ${auth?.oauth2?.password || ''}`)} ${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)} @@ -206,6 +207,7 @@ ${indentString(`grant_type: authorization_code`)} ${indentString(`callback_url: ${auth?.oauth2?.callbackUrl || ''}`)} ${indentString(`authorization_url: ${auth?.oauth2?.authorizationUrl || ''}`)} ${indentString(`access_token_url: ${auth?.oauth2?.accessTokenUrl || ''}`)} +${indentString(`refresh_url: ${auth?.oauth2?.refreshUrl || ''}`)} ${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)} ${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)} ${indentString(`scope: ${auth?.oauth2?.scope || ''}`)} @@ -227,6 +229,7 @@ ${indentString(`reuse_token: ${auth?.oauth2?.reuseToken || ''}`)} bru += `auth:oauth2 { ${indentString(`grant_type: client_credentials`)} ${indentString(`access_token_url: ${auth?.oauth2?.accessTokenUrl || ''}`)} +${indentString(`refresh_url: ${auth?.oauth2?.refreshUrl || ''}`)} ${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)} ${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)} ${indentString(`scope: ${auth?.oauth2?.scope || ''}`)} diff --git a/packages/bruno-lang/v2/src/jsonToCollectionBru.js b/packages/bruno-lang/v2/src/jsonToCollectionBru.js index a5d879ac6..0d8ac5eec 100644 --- a/packages/bruno-lang/v2/src/jsonToCollectionBru.js +++ b/packages/bruno-lang/v2/src/jsonToCollectionBru.js @@ -149,6 +149,7 @@ ${indentString(`placement: ${auth?.apikey?.placement || ''}`)} bru += `auth:oauth2 { ${indentString(`grant_type: password`)} ${indentString(`access_token_url: ${auth?.oauth2?.accessTokenUrl || ''}`)} +${indentString(`refresh_url: ${auth?.oauth2?.refreshUrl || ''}`)} ${indentString(`username: ${auth?.oauth2?.username || ''}`)} ${indentString(`password: ${auth?.oauth2?.password || ''}`)} ${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)} @@ -172,6 +173,7 @@ ${indentString(`grant_type: authorization_code`)} ${indentString(`callback_url: ${auth?.oauth2?.callbackUrl || ''}`)} ${indentString(`authorization_url: ${auth?.oauth2?.authorizationUrl || ''}`)} ${indentString(`access_token_url: ${auth?.oauth2?.accessTokenUrl || ''}`)} +${indentString(`refresh_url: ${auth?.oauth2?.refreshUrl || ''}`)} ${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)} ${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)} ${indentString(`scope: ${auth?.oauth2?.scope || ''}`)} @@ -193,6 +195,7 @@ ${indentString(`reuse_token: ${auth?.oauth2?.reuseToken || ''}`)} bru += `auth:oauth2 { ${indentString(`grant_type: client_credentials`)} ${indentString(`access_token_url: ${auth?.oauth2?.accessTokenUrl || ''}`)} +${indentString(`refresh_url: ${auth?.oauth2?.refreshUrl || ''}`)} ${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)} ${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)} ${indentString(`scope: ${auth?.oauth2?.scope || ''}`)} diff --git a/packages/bruno-schema/src/collections/index.js b/packages/bruno-schema/src/collections/index.js index 9dbc0c4fc..d5645a690 100644 --- a/packages/bruno-schema/src/collections/index.js +++ b/packages/bruno-schema/src/collections/index.js @@ -231,6 +231,16 @@ const oauth2Schema = Yup.object({ is: (val) => ['client_credentials', 'password', 'authorization_code'].includes(val), then: Yup.boolean().default(false), otherwise: Yup.boolean() + }), + refreshUrl: Yup.string().when('grantType', { + is: (val) => ['client_credentials', 'password', 'authorization_code'].includes(val), + then: Yup.string().nullable(), + otherwise: Yup.string().nullable().strip() + }), + autoRefresh: Yup.boolean().when('grantType', { + is: (val) => ['client_credentials', 'password', 'authorization_code'].includes(val), + then: Yup.boolean().default(false), + otherwise: Yup.boolean() }) }) .noUnknown(true)