Merge pull request #4609 from pooja-bruno/feat/extend-support-for-more-auth-for-folder-level

feat: extend support for more auth in folder level
This commit is contained in:
lohit
2025-05-16 20:20:33 +05:30
committed by GitHub
14 changed files with 377 additions and 51 deletions

View File

@@ -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;

View File

@@ -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 (
<BasicAuth
collection={collection}
item={folder}
updateAuth={updateFolderAuth}
request={request}
save={() => handleSave()}
/>
);
}
case 'bearer': {
return (
<BearerAuth
collection={collection}
item={folder}
updateAuth={updateFolderAuth}
request={request}
save={() => handleSave()}
/>
);
}
case 'digest': {
return (
<DigestAuth
collection={collection}
item={folder}
updateAuth={updateFolderAuth}
request={request}
save={() => handleSave()}
/>
);
}
case 'ntlm': {
return (
<NTLMAuth
collection={collection}
item={folder}
updateAuth={updateFolderAuth}
request={request}
save={() => handleSave()}
/>
);
}
case 'wsse': {
return (
<WsseAuth
collection={collection}
item={folder}
updateAuth={updateFolderAuth}
request={request}
save={() => handleSave()}
/>
);
}
case 'apikey': {
return (
<ApiKeyAuth
collection={collection}
item={folder}
updateAuth={updateFolderAuth}
request={request}
save={() => handleSave()}
/>
);
}
case 'awsv4': {
return (
<AwsV4Auth
collection={collection}
item={folder}
updateAuth={updateFolderAuth}
request={request}
save={() => handleSave()}
/>
);
}
case 'oauth2': {
return (
<>
@@ -56,6 +184,17 @@ const Auth = ({ collection, folder }) => {
</>
);
}
case 'inherit': {
const source = getEffectiveAuthSource();
return (
<>
<div className="flex flex-row w-full mt-2 gap-2">
<div>Auth inherited from {source.name}: </div>
<div className="inherit-mode-text">{humanizeRequestAuthMode(source.auth?.mode)}</div>
</div>
</>
);
}
case 'none': {
return null;
}
@@ -64,6 +203,7 @@ const Auth = ({ collection, folder }) => {
}
};
return (
<StyledWrapper className="w-full">
<div className="text-xs mb-4 text-muted">

View File

@@ -35,6 +35,51 @@ const AuthMode = ({ collection, folder }) => {
<StyledWrapper>
<div className="inline-flex items-center cursor-pointer">
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('awsv4');
}}
>
AWS Sig v4
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('basic');
}}
>
Basic Auth
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('bearer');
}}
>
Bearer Token
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('digest');
}}
>
Digest Auth
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('ntlm');
}}
>
NTLM Auth
</div>
<div
className="dropdown-item"
onClick={() => {
@@ -44,6 +89,33 @@ const AuthMode = ({ collection, folder }) => {
>
OAuth 2.0
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('wsse');
}}
>
WSSE Auth
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('apikey');
}}
>
API Key
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('inherit');
}}
>
Inherit
</div>
<div
className="dropdown-item"
onClick={() => {

View File

@@ -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 }) => {
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
dropdownTippyRef?.current?.hide();
handleAuthChange('placement', 'header');
}}
>
@@ -99,11 +101,11 @@ const ApiKeyAuth = ({ item, collection }) => {
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
dropdownTippyRef?.current?.hide();
handleAuthChange('placement', 'queryparams');
}}
>
Query Params
Query Param
</div>
</Dropdown>
</div>

View File

@@ -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(

View File

@@ -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(

View File

@@ -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(

View File

@@ -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(

View File

@@ -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
}
})
);

View File

@@ -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}
/>
</div>
@@ -67,6 +71,8 @@ const WsseAuth = ({ item, collection }) => {
onChange={(val) => handlePasswordChange(val)}
onRun={handleRun}
collection={collection}
item={item}
isSecret={true}
/>
</div>
</StyledWrapper>

View File

@@ -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 <AwsV4Auth collection={collection} item={item} />;
return <AwsV4Auth collection={collection} item={item} request={request} save={save} updateAuth={updateAuth} />;
}
case 'basic': {
return <BasicAuth collection={collection} item={item} />;
return <BasicAuth collection={collection} item={item} request={request} save={save} updateAuth={updateAuth} />;
}
case 'bearer': {
return <BearerAuth collection={collection} item={item} />;
return <BearerAuth collection={collection} item={item} request={request} save={save} updateAuth={updateAuth} />;
}
case 'digest': {
return <DigestAuth collection={collection} item={item} />;
return <DigestAuth collection={collection} item={item} request={request} save={save} updateAuth={updateAuth} />;
}
case 'ntlm': {
return <NTLMAuth collection={collection} item={item} />;
return <NTLMAuth collection={collection} item={item} request={request} save={save} updateAuth={updateAuth} />;
}
case 'oauth2': {
return <OAuth2 collection={collection} item={item} />;
return <OAuth2 collection={collection} item={item} request={request} save={save} updateAuth={updateAuth} />;
}
case 'wsse': {
return <WsseAuth collection={collection} item={item} />;
return <WsseAuth collection={collection} item={item} request={request} save={save} updateAuth={updateAuth} />;
}
case 'apikey': {
return <ApiKeyAuth collection={collection} item={item} />;
return <ApiKeyAuth collection={collection} item={item} request={request} save={save} updateAuth={updateAuth} />;
}
case 'inherit': {
const source = getEffectiveAuthSource();

View File

@@ -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 (
<Modal size="lg" title="Generate Code" handleCancel={onClose} hideFooter={true}>
@@ -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
}
}}
/>
) : (

View File

@@ -381,7 +381,12 @@ export const newFolder = (folderName, directoryName, collectionUid, itemUid) =>
meta: {
name: folderName,
seq: items?.length + 1
}
},
request: {
auth: {
mode: 'inherit'
}
}
}
};
ipcRenderer
@@ -417,7 +422,12 @@ export const newFolder = (folderName, directoryName, collectionUid, itemUid) =>
meta: {
name: folderName,
seq: items?.length + 1
}
},
request: {
auth: {
mode: 'inherit'
}
}
}
};
ipcRenderer

View File

@@ -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;
}
}
},