diff --git a/packages/bruno-app/src/components/FolderSettings/Auth/StyledWrapper.js b/packages/bruno-app/src/components/FolderSettings/Auth/StyledWrapper.js index ecb0976dff..ba243d42b9 100644 --- a/packages/bruno-app/src/components/FolderSettings/Auth/StyledWrapper.js +++ b/packages/bruno-app/src/components/FolderSettings/Auth/StyledWrapper.js @@ -11,6 +11,12 @@ const Wrapper = styled.div` border: solid 1px ${(props) => props.theme.input.border}; background-color: ${(props) => props.theme.input.bg}; } + .inherit-mode-text { + color: ${(props) => props.theme.colors.text.yellow}; + } + .auth-mode-label { + color: ${(props) => props.theme.colors.text.yellow}; + } `; export default Wrapper; \ No newline at end of file diff --git a/packages/bruno-app/src/components/FolderSettings/Auth/index.js b/packages/bruno-app/src/components/FolderSettings/Auth/index.js index 360d5c64ff..89b109f82b 100644 --- a/packages/bruno-app/src/components/FolderSettings/Auth/index.js +++ b/packages/bruno-app/src/components/FolderSettings/Auth/index.js @@ -9,6 +9,14 @@ import OAuth2PasswordCredentials from 'components/RequestPane/Auth/OAuth2/Passwo import OAuth2ClientCredentials from 'components/RequestPane/Auth/OAuth2/ClientCredentials/index'; import GrantTypeSelector from 'components/RequestPane/Auth/OAuth2/GrantTypeSelector/index'; import AuthMode from '../AuthMode'; +import BasicAuth from 'components/RequestPane/Auth/BasicAuth'; +import BearerAuth from 'components/RequestPane/Auth/BearerAuth'; +import DigestAuth from 'components/RequestPane/Auth/DigestAuth'; +import NTLMAuth from 'components/RequestPane/Auth/NTLMAuth'; +import WsseAuth from 'components/RequestPane/Auth/WsseAuth'; +import ApiKeyAuth from 'components/RequestPane/Auth/ApiKeyAuth'; +import AwsV4Auth from 'components/RequestPane/Auth/AwsV4Auth'; +import { findItemInCollection, findParentItemInCollection, humanizeRequestAuthMode } from 'utils/collections/index'; const GrantTypeComponentMap = ({ collection, folder }) => { const dispatch = useDispatch(); @@ -37,12 +45,132 @@ const Auth = ({ collection, folder }) => { let request = get(folder, 'root.request', {}); const authMode = get(folder, 'root.request.auth.mode'); + const getTreePathFromCollectionToFolder = (collection, _folder) => { + let path = []; + let item = findItemInCollection(collection, _folder?.uid); + while (item) { + path.unshift(item); + item = findParentItemInCollection(collection, item?.uid); + } + return path; + }; + + const getEffectiveAuthSource = () => { + if (authMode !== 'inherit') return null; + + const collectionAuth = get(collection, 'root.request.auth'); + let effectiveSource = { + type: 'collection', + name: 'Collection', + auth: collectionAuth + }; + + // Get path from collection to current folder + const folderTreePath = getTreePathFromCollectionToFolder(collection, folder); + + // Check parent folders to find closest auth configuration + // Skip the last item which is the current folder + for (let i = 0; i < folderTreePath.length - 1; i++) { + const parentFolder = folderTreePath[i]; + if (parentFolder.type === 'folder') { + const folderAuth = get(parentFolder, 'root.request.auth'); + if (folderAuth && folderAuth.mode && folderAuth.mode !== 'none' && folderAuth.mode !== 'inherit') { + effectiveSource = { + type: 'folder', + name: parentFolder.name, + auth: folderAuth + }; + break; + } + } + } + + return effectiveSource; + }; + const handleSave = () => { dispatch(saveFolderRoot(collection.uid, folder.uid)); }; const getAuthView = () => { switch (authMode) { + case 'basic': { + return ( + handleSave()} + /> + ); + } + case 'bearer': { + return ( + handleSave()} + /> + ); + } + case 'digest': { + return ( + handleSave()} + /> + ); + } + case 'ntlm': { + return ( + handleSave()} + /> + ); + } + case 'wsse': { + return ( + handleSave()} + /> + ); + } + case 'apikey': { + return ( + handleSave()} + /> + ); + } + case 'awsv4': { + return ( + handleSave()} + /> + ); + } case 'oauth2': { return ( <> @@ -56,6 +184,17 @@ const Auth = ({ collection, folder }) => { ); } + case 'inherit': { + const source = getEffectiveAuthSource(); + return ( + <> +
+
Auth inherited from {source.name}:
+
{humanizeRequestAuthMode(source.auth?.mode)}
+
+ + ); + } case 'none': { return null; } @@ -64,6 +203,7 @@ const Auth = ({ collection, folder }) => { } }; + return (
diff --git a/packages/bruno-app/src/components/FolderSettings/AuthMode/index.js b/packages/bruno-app/src/components/FolderSettings/AuthMode/index.js index e6e48f110b..36377973a8 100644 --- a/packages/bruno-app/src/components/FolderSettings/AuthMode/index.js +++ b/packages/bruno-app/src/components/FolderSettings/AuthMode/index.js @@ -35,6 +35,51 @@ const AuthMode = ({ collection, folder }) => {
} placement="bottom-end"> +
{ + dropdownTippyRef.current.hide(); + onModeChange('awsv4'); + }} + > + AWS Sig v4 +
+
{ + dropdownTippyRef.current.hide(); + onModeChange('basic'); + }} + > + Basic Auth +
+
{ + dropdownTippyRef.current.hide(); + onModeChange('bearer'); + }} + > + Bearer Token +
+
{ + dropdownTippyRef.current.hide(); + onModeChange('digest'); + }} + > + Digest Auth +
+
{ + dropdownTippyRef.current.hide(); + onModeChange('ntlm'); + }} + > + NTLM Auth +
{ @@ -44,6 +89,33 @@ const AuthMode = ({ collection, folder }) => { > OAuth 2.0
+
{ + dropdownTippyRef.current.hide(); + onModeChange('wsse'); + }} + > + WSSE Auth +
+
{ + dropdownTippyRef.current.hide(); + onModeChange('apikey'); + }} + > + API Key +
+
{ + dropdownTippyRef.current.hide(); + onModeChange('inherit'); + }} + > + Inherit +
{ diff --git a/packages/bruno-app/src/components/RequestPane/Auth/ApiKeyAuth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/ApiKeyAuth/index.js index 22a16563e3..513c29500a 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/ApiKeyAuth/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/ApiKeyAuth/index.js @@ -5,21 +5,23 @@ import { IconCaretDown } from '@tabler/icons'; import Dropdown from 'components/Dropdown'; import { useTheme } from 'providers/Theme'; import SingleLineEditor from 'components/SingleLineEditor'; -import { updateAuth } from 'providers/ReduxStore/slices/collections'; -import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; +import { sendRequest } from 'providers/ReduxStore/slices/collections/actions'; import StyledWrapper from './StyledWrapper'; import { humanizeRequestAPIKeyPlacement } from 'utils/collections'; -const ApiKeyAuth = ({ item, collection }) => { +const ApiKeyAuth = ({ item, collection, updateAuth, request, save }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); const dropdownTippyRef = useRef(); const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref); - const apikeyAuth = item.draft ? get(item, 'draft.request.auth.apikey', {}) : get(item, 'request.auth.apikey', {}); + const apikeyAuth = get(request, 'auth.apikey', {}); const handleRun = () => dispatch(sendRequest(item, collection.uid)); - const handleSave = () => dispatch(saveRequest(item.uid, collection.uid)); + + const handleSave = () => { + save(); + }; const Icon = forwardRef((props, ref) => { return ( @@ -90,7 +92,7 @@ const ApiKeyAuth = ({ item, collection }) => {
{ - dropdownTippyRef.current.hide(); + dropdownTippyRef?.current?.hide(); handleAuthChange('placement', 'header'); }} > @@ -99,11 +101,11 @@ const ApiKeyAuth = ({ item, collection }) => {
{ - dropdownTippyRef.current.hide(); + dropdownTippyRef?.current?.hide(); handleAuthChange('placement', 'queryparams'); }} > - Query Params + Query Param
diff --git a/packages/bruno-app/src/components/RequestPane/Auth/AwsV4Auth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/AwsV4Auth/index.js index a44cecc1b9..75469d7845 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/AwsV4Auth/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/AwsV4Auth/index.js @@ -8,14 +8,17 @@ import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collection import StyledWrapper from './StyledWrapper'; import { update } from 'lodash'; -const AwsV4Auth = ({ onTokenChange, item, collection }) => { +const AwsV4Auth = ({ item, collection, updateAuth, request, save }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); - const awsv4Auth = item.draft ? get(item, 'draft.request.auth.awsv4', {}) : get(item, 'request.auth.awsv4', {}); + const awsv4Auth = get(request, 'auth.awsv4', {}); const handleRun = () => dispatch(sendRequest(item, collection.uid)); - const handleSave = () => dispatch(saveRequest(item.uid, collection.uid)); + + const handleSave = () => { + save(); + }; const handleAccessKeyIdChange = (accessKeyId) => { dispatch( diff --git a/packages/bruno-app/src/components/RequestPane/Auth/BasicAuth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/BasicAuth/index.js index 8582a53cdc..ef714f528c 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/BasicAuth/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/BasicAuth/index.js @@ -7,14 +7,17 @@ import { updateAuth } from 'providers/ReduxStore/slices/collections'; import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; import StyledWrapper from './StyledWrapper'; -const BasicAuth = ({ item, collection }) => { +const BasicAuth = ({ item, collection, updateAuth, request, save }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); - const basicAuth = item.draft ? get(item, 'draft.request.auth.basic', {}) : get(item, 'request.auth.basic', {}); + const basicAuth = get(request, 'auth.basic', {}); const handleRun = () => dispatch(sendRequest(item, collection.uid)); - const handleSave = () => dispatch(saveRequest(item.uid, collection.uid)); + + const handleSave = () => { + save(); + }; const handleUsernameChange = (username) => { dispatch( diff --git a/packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/index.js index bef4d062a8..c8ba9d1c68 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/BearerAuth/index.js @@ -7,16 +7,18 @@ import { updateAuth } from 'providers/ReduxStore/slices/collections'; import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; import StyledWrapper from './StyledWrapper'; -const BearerAuth = ({ item, collection }) => { +const BearerAuth = ({ item, collection, updateAuth, request, save }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); - const bearerToken = item.draft - ? get(item, 'draft.request.auth.bearer.token', '') - : get(item, 'request.auth.bearer.token', ''); + // Use the request prop directly like OAuth2ClientCredentials does + const bearerToken = get(request, 'auth.bearer.token', ''); const handleRun = () => dispatch(sendRequest(item, collection.uid)); - const handleSave = () => dispatch(saveRequest(item.uid, collection.uid)); + + const handleSave = () => { + save(); + }; const handleTokenChange = (token) => { dispatch( diff --git a/packages/bruno-app/src/components/RequestPane/Auth/DigestAuth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/DigestAuth/index.js index e91ed8d1f2..50b92f6695 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/DigestAuth/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/DigestAuth/index.js @@ -3,18 +3,20 @@ import get from 'lodash/get'; import { useTheme } from 'providers/Theme'; import { useDispatch } from 'react-redux'; import SingleLineEditor from 'components/SingleLineEditor'; -import { updateAuth } from 'providers/ReduxStore/slices/collections'; import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; import StyledWrapper from './StyledWrapper'; -const DigestAuth = ({ item, collection }) => { +const DigestAuth = ({ item, collection, updateAuth, request, save }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); - const digestAuth = item.draft ? get(item, 'draft.request.auth.digest', {}) : get(item, 'request.auth.digest', {}); + const digestAuth = get(request, 'auth.digest', {}); const handleRun = () => dispatch(sendRequest(item, collection.uid)); - const handleSave = () => dispatch(saveRequest(item.uid, collection.uid)); + + const handleSave = () => { + save(); + }; const handleUsernameChange = (username) => { dispatch( diff --git a/packages/bruno-app/src/components/RequestPane/Auth/NTLMAuth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/NTLMAuth/index.js index 65e7560418..1164fb9039 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/NTLMAuth/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/NTLMAuth/index.js @@ -7,14 +7,17 @@ import { updateAuth } from 'providers/ReduxStore/slices/collections'; import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; import StyledWrapper from './StyledWrapper'; -const NTLMAuth = ({ item, collection }) => { +const NTLMAuth = ({ item, collection, request, save, updateAuth }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); - const ntlmAuth = item.draft ? get(item, 'draft.request.auth.ntlm', {}) : get(item, 'request.auth.ntlm', {}); + const ntlmAuth = get(request, 'auth.ntlm', {}); const handleRun = () => dispatch(sendRequest(item, collection.uid)); - const handleSave = () => dispatch(saveRequest(item.uid, collection.uid)); + + const handleSave = () => { + save(); + }; const handleUsernameChange = (username) => { dispatch( @@ -26,7 +29,6 @@ const NTLMAuth = ({ item, collection }) => { username: username, password: ntlmAuth.password, domain: ntlmAuth.domain - } }) ); diff --git a/packages/bruno-app/src/components/RequestPane/Auth/WsseAuth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/WsseAuth/index.js index 76a20e6f6b..ae201370e5 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/WsseAuth/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/WsseAuth/index.js @@ -7,14 +7,17 @@ import { updateAuth } from 'providers/ReduxStore/slices/collections'; import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; import StyledWrapper from './StyledWrapper'; -const WsseAuth = ({ item, collection }) => { +const WsseAuth = ({ item, collection, updateAuth, request, save }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); - const wsseAuth = item.draft ? get(item, 'draft.request.auth.wsse', {}) : get(item, 'request.auth.wsse', {}); + const wsseAuth = get(request, 'auth.wsse', {}); const handleRun = () => dispatch(sendRequest(item, collection.uid)); - const handleSave = () => dispatch(saveRequest(item.uid, collection.uid)); + + const handleSave = () => { + save(); + }; const handleUserChange = (username) => { dispatch( @@ -55,6 +58,7 @@ const WsseAuth = ({ item, collection }) => { onChange={(val) => handleUserChange(val)} onRun={handleRun} collection={collection} + item={item} />
@@ -67,6 +71,8 @@ const WsseAuth = ({ item, collection }) => { onChange={(val) => handlePasswordChange(val)} onRun={handleRun} collection={collection} + item={item} + isSecret={true} />
diff --git a/packages/bruno-app/src/components/RequestPane/Auth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/index.js index 4cb8897d35..8ca23ab8de 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/index.js @@ -7,6 +7,8 @@ import BasicAuth from './BasicAuth'; import DigestAuth from './DigestAuth'; import WsseAuth from './WsseAuth'; import NTLMAuth from './NTLMAuth'; +import { updateAuth } from 'providers/ReduxStore/slices/collections'; +import { saveRequest } from 'providers/ReduxStore/slices/collections/actions'; import ApiKeyAuth from './ApiKeyAuth'; import StyledWrapper from './StyledWrapper'; @@ -27,6 +29,16 @@ const getTreePathFromCollectionToItem = (collection, _item) => { const Auth = ({ item, collection }) => { const authMode = item.draft ? get(item, 'draft.request.auth.mode') : get(item, 'request.auth.mode'); const requestTreePath = getTreePathFromCollectionToItem(collection, item); + + // Create a request object to pass to the auth components + const request = item.draft + ? get(item, 'draft.request', {}) + : get(item, 'request', {}); + + // Save function for request level + const save = () => { + return saveRequest(item.uid, collection.uid); + }; const getEffectiveAuthSource = () => { if (authMode !== 'inherit') return null; @@ -59,28 +71,28 @@ const Auth = ({ item, collection }) => { const getAuthView = () => { switch (authMode) { case 'awsv4': { - return ; + return ; } case 'basic': { - return ; + return ; } case 'bearer': { - return ; + return ; } case 'digest': { - return ; + return ; } case 'ntlm': { - return ; + return ; } case 'oauth2': { - return ; + return ; } case 'wsse': { - return ; + return ; } case 'apikey': { - return ; + return ; } case 'inherit': { const source = getEffectiveAuthSource(); diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/index.js index 42f0bc8ca9..5c5640ca01 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/index.js @@ -4,12 +4,60 @@ import CodeView from './CodeView'; import StyledWrapper from './StyledWrapper'; import { isValidUrl } from 'utils/url'; import { get } from 'lodash'; -import { findEnvironmentInCollection } from 'utils/collections'; +import { findEnvironmentInCollection, findItemInCollection, findParentItemInCollection } from 'utils/collections'; import { interpolateUrl, interpolateUrlPathParams } from 'utils/url/index'; import { getLanguages } from 'utils/codegenerator/targets'; import { useSelector } from 'react-redux'; import { getGlobalEnvironmentVariables } from 'utils/collections/index'; +const getTreePathFromCollectionToItem = (collection, _itemUid) => { + let path = []; + let item = findItemInCollection(collection, _itemUid); + while (item) { + path.unshift(item); + item = findParentItemInCollection(collection, item?.uid); + } + return path; +}; + +// Function to resolve inherited auth +const resolveInheritedAuth = (item, collection) => { + const request = item.draft?.request || item.request; + const authMode = request?.auth?.mode; + + // If auth is not inherit or no auth defined, return the request as is + if (!authMode || authMode !== 'inherit') { + return { + ...request + }; + } + + // Get the tree path from collection to item + const requestTreePath = getTreePathFromCollectionToItem(collection, item.uid); + + // Default to collection auth + const collectionAuth = get(collection, 'root.request.auth'); + let effectiveAuth = collectionAuth; + let source = 'collection'; + + // Check folders in reverse to find the closest auth configuration + for (let i of [...requestTreePath].reverse()) { + if (i.type === 'folder') { + const folderAuth = get(i, 'root.request.auth'); + if (folderAuth && folderAuth.mode && folderAuth.mode !== 'none' && folderAuth.mode !== 'inherit') { + effectiveAuth = folderAuth; + source = 'folder'; + break; + } + } + } + + return { + ...request, + auth: effectiveAuth + }; +}; + const GenerateCodeItem = ({ collectionUid, item, onClose }) => { const languages = getLanguages(); @@ -46,6 +94,9 @@ const GenerateCodeItem = ({ collectionUid, item, onClose }) => { get(item, 'draft.request.params') !== undefined ? get(item, 'draft.request.params') : get(item, 'request.params') ); + // Resolve auth inheritance + const resolvedRequest = resolveInheritedAuth(item, collection); + const [selectedLanguage, setSelectedLanguage] = useState(languages[0]); return ( @@ -94,16 +145,10 @@ const GenerateCodeItem = ({ collectionUid, item, onClose }) => { language={selectedLanguage} item={{ ...item, - request: - item.request.url !== '' - ? { - ...item.request, - url: finalUrl - } - : { - ...item.draft.request, - url: finalUrl - } + request: { + ...resolvedRequest, + url: finalUrl + } }} /> ) : ( diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index 86ad618aae..8a90c7aea6 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -380,7 +380,12 @@ export const newFolder = (folderName, directoryName, collectionUid, itemUid) => meta: { name: folderName, seq: items?.length + 1 - } + }, + request: { + auth: { + mode: 'inherit' + } + } } }; ipcRenderer @@ -416,7 +421,12 @@ export const newFolder = (folderName, directoryName, collectionUid, itemUid) => meta: { name: folderName, seq: items?.length + 1 - } + }, + request: { + auth: { + mode: 'inherit' + } + } } }; ipcRenderer diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js index d3098a936a..95287000a5 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -1593,6 +1593,27 @@ export const collectionsSlice = createSlice({ case 'oauth2': set(folder, 'root.request.auth.oauth2', action.payload.content); break; + case 'basic': + set(folder, 'root.request.auth.basic', action.payload.content); + break; + case 'bearer': + set(folder, 'root.request.auth.bearer', action.payload.content); + break; + case 'digest': + set(folder, 'root.request.auth.digest', action.payload.content); + break; + case 'ntlm': + set(folder, 'root.request.auth.ntlm', action.payload.content); + break; + case 'apikey': + set(folder, 'root.request.auth.apikey', action.payload.content); + break; + case 'awsv4': + set(folder, 'root.request.auth.awsv4', action.payload.content); + break; + case 'wsse': + set(folder, 'root.request.auth.wsse', action.payload.content); + break; } } },