mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-28 10:53:13 +01:00
Feat/api key auth (#2478)
* feat: Added ApiKeyAuth component * feat: Add support for API Key authentication - Added the ApiKeyAuth component to handle API Key authentication mode. - Updated the AuthMode component to include an option for API Key authentication. - Updated the collections schema to include validation for API Key authentication. - Updated the collectionsSlice to handle API Key authentication in the Redux store. * refactor: input value handlers - Removed the separate handleKeyChange, handlePlacementChange and handleValueChange functions and consolidated them into handleAuthChange. * feat: Update prepare-request to handle API Key authentication in query parameters * refactor: handling the queryparams placement api key values in the ConfigureRequest function * refactor: added collection level api key auth * refactor: updated collection export function * refactor: add default placement for API key authentication in ApiKeyAuth component * refactor: add default placement for API key authentication in ApiKeyAuth component in CollectionSettings * refactor: update generateAuth function to handle API key authentication in postman collection exporter * refactor: fix typo in API key placement for collection export * Made minor changes in the logic. * Updated the importers for postman to handle new auth type.
This commit is contained in:
parent
b60c799645
commit
637e53421e
@ -0,0 +1,56 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
label {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.single-line-editor-wrapper {
|
||||||
|
padding: 0.15rem 0.4rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: solid 1px ${(props) => props.theme.input.border};
|
||||||
|
background-color: ${(props) => props.theme.input.bg};
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-placement-selector {
|
||||||
|
padding: 0.5rem 0px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: solid 1px ${(props) => props.theme.input.border};
|
||||||
|
background-color: ${(props) => props.theme.input.bg};
|
||||||
|
|
||||||
|
.dropdown {
|
||||||
|
width: fit-content;
|
||||||
|
|
||||||
|
div[data-tippy-root] {
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
.tippy-box {
|
||||||
|
width: fit-content;
|
||||||
|
max-width: none !important;
|
||||||
|
|
||||||
|
.tippy-content: {
|
||||||
|
width: fit-content;
|
||||||
|
max-width: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-type-label {
|
||||||
|
width: fit-content;
|
||||||
|
color: ${(props) => props.theme.colors.text.yellow};
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item {
|
||||||
|
padding: 0.2rem 0.6rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.caret {
|
||||||
|
color: rgb(140, 140, 140);
|
||||||
|
fill: rgb(140 140 140);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Wrapper;
|
@ -0,0 +1,109 @@
|
|||||||
|
import React, { useRef, forwardRef, useEffect } from 'react';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import { IconCaretDown } from '@tabler/icons';
|
||||||
|
import Dropdown from 'components/Dropdown';
|
||||||
|
import { useTheme } from 'providers/Theme';
|
||||||
|
import SingleLineEditor from 'components/SingleLineEditor';
|
||||||
|
import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections';
|
||||||
|
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import { humanizeRequestAPIKeyPlacement } from 'utils/collections';
|
||||||
|
|
||||||
|
const ApiKeyAuth = ({ collection }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { storedTheme } = useTheme();
|
||||||
|
const dropdownTippyRef = useRef();
|
||||||
|
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||||
|
|
||||||
|
const apikeyAuth = get(collection, 'root.request.auth.apikey', {});
|
||||||
|
|
||||||
|
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
|
||||||
|
|
||||||
|
const Icon = forwardRef((props, ref) => {
|
||||||
|
return (
|
||||||
|
<div ref={ref} className="flex items-center justify-end auth-type-label select-none">
|
||||||
|
{humanizeRequestAPIKeyPlacement(apikeyAuth?.placement)}
|
||||||
|
<IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleAuthChange = (property, value) => {
|
||||||
|
dispatch(
|
||||||
|
updateCollectionAuth({
|
||||||
|
mode: 'apikey',
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
content: {
|
||||||
|
...apikeyAuth,
|
||||||
|
[property]: value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
!apikeyAuth?.placement &&
|
||||||
|
dispatch(
|
||||||
|
updateCollectionAuth({
|
||||||
|
mode: 'apikey',
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
content: {
|
||||||
|
placement: 'header'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, [apikeyAuth]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="mt-2 w-full">
|
||||||
|
<label className="block font-medium mb-2">Key</label>
|
||||||
|
<div className="single-line-editor-wrapper mb-2">
|
||||||
|
<SingleLineEditor
|
||||||
|
value={apikeyAuth.key || ''}
|
||||||
|
theme={storedTheme}
|
||||||
|
onSave={handleSave}
|
||||||
|
onChange={(val) => handleAuthChange('key', val)}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label className="block font-medium mb-2">Value</label>
|
||||||
|
<div className="single-line-editor-wrapper mb-2">
|
||||||
|
<SingleLineEditor
|
||||||
|
value={apikeyAuth.value || ''}
|
||||||
|
theme={storedTheme}
|
||||||
|
onSave={handleSave}
|
||||||
|
onChange={(val) => handleAuthChange('value', val)}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label className="block font-medium mb-2">Add To</label>
|
||||||
|
<div className="inline-flex items-center cursor-pointer auth-placement-selector w-fit">
|
||||||
|
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
handleAuthChange('placement', 'header');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Header
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
handleAuthChange('placement', 'queryparams');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Query Params
|
||||||
|
</div>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ApiKeyAuth;
|
@ -79,6 +79,15 @@ const AuthMode = ({ collection }) => {
|
|||||||
>
|
>
|
||||||
Oauth2
|
Oauth2
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
onModeChange('apikey');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
API Key
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className="dropdown-item"
|
className="dropdown-item"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -6,6 +6,7 @@ import AwsV4Auth from './AwsV4Auth';
|
|||||||
import BearerAuth from './BearerAuth';
|
import BearerAuth from './BearerAuth';
|
||||||
import BasicAuth from './BasicAuth';
|
import BasicAuth from './BasicAuth';
|
||||||
import DigestAuth from './DigestAuth';
|
import DigestAuth from './DigestAuth';
|
||||||
|
import ApiKeyAuth from './ApiKeyAuth/';
|
||||||
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import OAuth2 from './OAuth2';
|
import OAuth2 from './OAuth2';
|
||||||
@ -33,6 +34,9 @@ const Auth = ({ collection }) => {
|
|||||||
case 'oauth2': {
|
case 'oauth2': {
|
||||||
return <OAuth2 collection={collection} />;
|
return <OAuth2 collection={collection} />;
|
||||||
}
|
}
|
||||||
|
case 'apikey': {
|
||||||
|
return <ApiKeyAuth collection={collection} />;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
label {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.single-line-editor-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};
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-placement-selector {
|
||||||
|
padding: 0.5rem 0px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: solid 1px ${(props) => props.theme.input.border};
|
||||||
|
background-color: ${(props) => props.theme.input.bg};
|
||||||
|
|
||||||
|
.dropdown {
|
||||||
|
width: fit-content;
|
||||||
|
|
||||||
|
div[data-tippy-root] {
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
.tippy-box {
|
||||||
|
width: fit-content;
|
||||||
|
max-width: none !important;
|
||||||
|
|
||||||
|
.tippy-content: {
|
||||||
|
width: fit-content;
|
||||||
|
max-width: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-type-label {
|
||||||
|
width: fit-content;
|
||||||
|
color: ${(props) => props.theme.colors.text.yellow};
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item {
|
||||||
|
padding: 0.2rem 0.6rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.caret {
|
||||||
|
color: rgb(140, 140, 140);
|
||||||
|
fill: rgb(140 140 140);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Wrapper;
|
@ -0,0 +1,114 @@
|
|||||||
|
import React, { useRef, forwardRef, useEffect } from 'react';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
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 StyledWrapper from './StyledWrapper';
|
||||||
|
import { humanizeRequestAPIKeyPlacement } from 'utils/collections';
|
||||||
|
|
||||||
|
const ApiKeyAuth = ({ item, collection }) => {
|
||||||
|
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 handleRun = () => dispatch(sendRequest(item, collection.uid));
|
||||||
|
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||||
|
|
||||||
|
const Icon = forwardRef((props, ref) => {
|
||||||
|
return (
|
||||||
|
<div ref={ref} className="flex items-center justify-end auth-type-label select-none">
|
||||||
|
{humanizeRequestAPIKeyPlacement(apikeyAuth?.placement)}
|
||||||
|
<IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleAuthChange = (property, value) => {
|
||||||
|
dispatch(
|
||||||
|
updateAuth({
|
||||||
|
mode: 'apikey',
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
itemUid: item.uid,
|
||||||
|
content: {
|
||||||
|
...apikeyAuth,
|
||||||
|
[property]: value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
!apikeyAuth?.placement &&
|
||||||
|
dispatch(
|
||||||
|
updateAuth({
|
||||||
|
mode: 'apikey',
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
itemUid: item.uid,
|
||||||
|
content: {
|
||||||
|
placement: 'header'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, [apikeyAuth]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="mt-2 w-full">
|
||||||
|
<label className="block font-medium mb-2">Key</label>
|
||||||
|
<div className="single-line-editor-wrapper mb-2">
|
||||||
|
<SingleLineEditor
|
||||||
|
value={apikeyAuth.key || ''}
|
||||||
|
theme={storedTheme}
|
||||||
|
onSave={handleSave}
|
||||||
|
onChange={(val) => handleAuthChange('key', val)}
|
||||||
|
onRun={handleRun}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label className="block font-medium mb-2">Value</label>
|
||||||
|
<div className="single-line-editor-wrapper mb-2">
|
||||||
|
<SingleLineEditor
|
||||||
|
value={apikeyAuth.value || ''}
|
||||||
|
theme={storedTheme}
|
||||||
|
onSave={handleSave}
|
||||||
|
onChange={(val) => handleAuthChange('value', val)}
|
||||||
|
onRun={handleRun}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label className="block font-medium mb-2">Add To</label>
|
||||||
|
<div className="inline-flex items-center cursor-pointer auth-placement-selector w-fit">
|
||||||
|
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
handleAuthChange('placement', 'header');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Header
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
handleAuthChange('placement', 'queryparams');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Query Params
|
||||||
|
</div>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ApiKeyAuth;
|
@ -80,6 +80,15 @@ const AuthMode = ({ item, collection }) => {
|
|||||||
>
|
>
|
||||||
OAuth 2.0
|
OAuth 2.0
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
dropdownTippyRef?.current?.hide();
|
||||||
|
onModeChange('apikey');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
API Key
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className="dropdown-item"
|
className="dropdown-item"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -5,6 +5,7 @@ import AwsV4Auth from './AwsV4Auth';
|
|||||||
import BearerAuth from './BearerAuth';
|
import BearerAuth from './BearerAuth';
|
||||||
import BasicAuth from './BasicAuth';
|
import BasicAuth from './BasicAuth';
|
||||||
import DigestAuth from './DigestAuth';
|
import DigestAuth from './DigestAuth';
|
||||||
|
import ApiKeyAuth from './ApiKeyAuth';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import { humanizeRequestAuthMode } from 'utils/collections/index';
|
import { humanizeRequestAuthMode } from 'utils/collections/index';
|
||||||
import OAuth2 from './OAuth2/index';
|
import OAuth2 from './OAuth2/index';
|
||||||
@ -32,6 +33,9 @@ const Auth = ({ item, collection }) => {
|
|||||||
case 'oauth2': {
|
case 'oauth2': {
|
||||||
return <OAuth2 collection={collection} item={item} />;
|
return <OAuth2 collection={collection} item={item} />;
|
||||||
}
|
}
|
||||||
|
case 'apikey': {
|
||||||
|
return <ApiKeyAuth collection={collection} item={item} />;
|
||||||
|
}
|
||||||
case 'inherit': {
|
case 'inherit': {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row w-full mt-2 gap-2">
|
<div className="flex flex-row w-full mt-2 gap-2">
|
||||||
|
@ -477,6 +477,10 @@ export const collectionsSlice = createSlice({
|
|||||||
item.draft.request.auth.mode = 'oauth2';
|
item.draft.request.auth.mode = 'oauth2';
|
||||||
item.draft.request.auth.oauth2 = action.payload.content;
|
item.draft.request.auth.oauth2 = action.payload.content;
|
||||||
break;
|
break;
|
||||||
|
case 'apikey':
|
||||||
|
item.draft.request.auth.mode = 'apikey';
|
||||||
|
item.draft.request.auth.apikey = action.payload.content;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1137,6 +1141,9 @@ export const collectionsSlice = createSlice({
|
|||||||
case 'oauth2':
|
case 'oauth2':
|
||||||
set(collection, 'root.request.auth.oauth2', action.payload.content);
|
set(collection, 'root.request.auth.oauth2', action.payload.content);
|
||||||
break;
|
break;
|
||||||
|
case 'apikey':
|
||||||
|
set(collection, 'root.request.auth.apikey', action.payload.content);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -372,6 +372,14 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {}
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'apikey':
|
||||||
|
di.request.auth.apikey = {
|
||||||
|
key: get(si.request, 'auth.apikey.key', ''),
|
||||||
|
value: get(si.request, 'auth.apikey.value', ''),
|
||||||
|
placement: get(si.request, 'auth.apikey.placement', 'header')
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -661,6 +669,26 @@ export const humanizeRequestAuthMode = (mode) => {
|
|||||||
label = 'OAuth 2.0';
|
label = 'OAuth 2.0';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'apikey': {
|
||||||
|
label = 'API Key';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return label;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const humanizeRequestAPIKeyPlacement = (placement) => {
|
||||||
|
let label = 'Header';
|
||||||
|
switch (placement) {
|
||||||
|
case 'header': {
|
||||||
|
label = 'Header';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'queryparams': {
|
||||||
|
label = 'Query Params';
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return label;
|
return label;
|
||||||
|
@ -246,7 +246,7 @@ export const exportCollection = (collection) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const generateAuth = (itemAuth) => {
|
const generateAuth = (itemAuth) => {
|
||||||
switch (itemAuth) {
|
switch (itemAuth?.mode) {
|
||||||
case 'bearer':
|
case 'bearer':
|
||||||
return {
|
return {
|
||||||
type: 'bearer',
|
type: 'bearer',
|
||||||
@ -273,6 +273,27 @@ export const exportCollection = (collection) => {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case 'apikey': {
|
||||||
|
return {
|
||||||
|
type: 'apikey',
|
||||||
|
apikey: [
|
||||||
|
{
|
||||||
|
key: 'key',
|
||||||
|
value: itemAuth.apikey.key,
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'value',
|
||||||
|
value: itemAuth.apikey.value,
|
||||||
|
type: 'string'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
console.error('Unsupported auth mode:', itemAuth.mode);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import each from 'lodash/each';
|
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import fileDialog from 'file-dialog';
|
import fileDialog from 'file-dialog';
|
||||||
import { uuid } from 'utils/common';
|
import { uuid } from 'utils/common';
|
||||||
@ -292,6 +291,13 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) =
|
|||||||
region: authValues.region,
|
region: authValues.region,
|
||||||
profileName: ''
|
profileName: ''
|
||||||
};
|
};
|
||||||
|
} else if (auth.type === 'apikey'){
|
||||||
|
brunoRequestItem.request.auth.mode = 'apikey';
|
||||||
|
brunoRequestItem.request.auth.apikey = {
|
||||||
|
key: authValues.key,
|
||||||
|
value: authValues.value,
|
||||||
|
placement: "header" //By default we are placing the apikey values in headers!
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,9 +317,24 @@ const configureRequest = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add API key to the URL
|
||||||
|
if (request.apiKeyAuthValueForQueryParams && request.apiKeyAuthValueForQueryParams.placement === 'queryparams') {
|
||||||
|
const urlObj = new URL(request.url);
|
||||||
|
|
||||||
|
// Interpolate key and value as they can be variables before adding to the URL.
|
||||||
|
const key = interpolateString(request.apiKeyAuthValueForQueryParams.key, interpolationOptions);
|
||||||
|
const value = interpolateString(request.apiKeyAuthValueForQueryParams.value, interpolationOptions);
|
||||||
|
|
||||||
|
urlObj.searchParams.set(key, value);
|
||||||
|
request.url = urlObj.toString();
|
||||||
|
}
|
||||||
|
|
||||||
// Remove pathParams, already in URL (Issue #2439)
|
// Remove pathParams, already in URL (Issue #2439)
|
||||||
delete request.pathParams;
|
delete request.pathParams;
|
||||||
|
|
||||||
|
// Remove apiKeyAuthValueForQueryParams, already interpolated and added to URL
|
||||||
|
delete request.apiKeyAuthValueForQueryParams;
|
||||||
|
|
||||||
return axiosInstance;
|
return axiosInstance;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -219,6 +219,15 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
|
|||||||
password: get(collectionAuth, 'digest.password')
|
password: get(collectionAuth, 'digest.password')
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
case 'apikey':
|
||||||
|
const apiKeyAuth = get(collectionAuth, 'apikey');
|
||||||
|
if (apiKeyAuth.placement === 'header') {
|
||||||
|
axiosRequest.headers[apiKeyAuth.key] = apiKeyAuth.value;
|
||||||
|
} else if (apiKeyAuth.placement === 'queryparams') {
|
||||||
|
// If the API key authentication is set and its placement is 'queryparams', add it to the axios request object. This will be used in the configureRequest function to append the API key to the query parameters of the request URL.
|
||||||
|
axiosRequest.apiKeyAuthValueForQueryParams = apiKeyAuth;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,6 +296,15 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'apikey':
|
||||||
|
const apiKeyAuth = get(request, 'auth.apikey');
|
||||||
|
if (apiKeyAuth.placement === 'header') {
|
||||||
|
axiosRequest.headers[apiKeyAuth.key] = apiKeyAuth.value;
|
||||||
|
} else if (apiKeyAuth.placement === 'queryparams') {
|
||||||
|
// If the API key authentication is set and its placement is 'queryparams', add it to the axios request object. This will be used in the configureRequest function to append the API key to the query parameters of the request URL.
|
||||||
|
axiosRequest.apiKeyAuthValueForQueryParams = apiKeyAuth;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ const { outdentString } = require('../../v1/src/utils');
|
|||||||
*/
|
*/
|
||||||
const grammar = ohm.grammar(`Bru {
|
const grammar = ohm.grammar(`Bru {
|
||||||
BruFile = (meta | http | query | params | headers | auths | bodies | varsandassert | script | tests | docs)*
|
BruFile = (meta | http | query | params | headers | auths | bodies | varsandassert | script | tests | docs)*
|
||||||
auths = authawsv4 | authbasic | authbearer | authdigest | authOAuth2
|
auths = authawsv4 | authbasic | authbearer | authdigest | authOAuth2 | authapikey
|
||||||
bodies = bodyjson | bodytext | bodyxml | bodysparql | bodygraphql | bodygraphqlvars | bodyforms | body
|
bodies = bodyjson | bodytext | bodyxml | bodysparql | bodygraphql | bodygraphqlvars | bodyforms | body
|
||||||
bodyforms = bodyformurlencoded | bodymultipart
|
bodyforms = bodyformurlencoded | bodymultipart
|
||||||
params = paramspath | paramsquery
|
params = paramspath | paramsquery
|
||||||
@ -88,6 +88,7 @@ const grammar = ohm.grammar(`Bru {
|
|||||||
authbearer = "auth:bearer" dictionary
|
authbearer = "auth:bearer" dictionary
|
||||||
authdigest = "auth:digest" dictionary
|
authdigest = "auth:digest" dictionary
|
||||||
authOAuth2 = "auth:oauth2" dictionary
|
authOAuth2 = "auth:oauth2" dictionary
|
||||||
|
authapikey = "auth:apikey" dictionary
|
||||||
|
|
||||||
body = "body" st* "{" nl* textblock tagend
|
body = "body" st* "{" nl* textblock tagend
|
||||||
bodyjson = "body:json" st* "{" nl* textblock tagend
|
bodyjson = "body:json" st* "{" nl* textblock tagend
|
||||||
@ -483,6 +484,28 @@ const sem = grammar.createSemantics().addAttribute('ast', {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
authapikey(_1, dictionary) {
|
||||||
|
const auth = mapPairListToKeyValPairs(dictionary.ast, false);
|
||||||
|
|
||||||
|
const findValueByName = (name) => {
|
||||||
|
const item = _.find(auth, { name });
|
||||||
|
return item ? item.value : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const key = findValueByName('key');
|
||||||
|
const value = findValueByName('value');
|
||||||
|
const placement = findValueByName('placement');
|
||||||
|
|
||||||
|
return {
|
||||||
|
auth: {
|
||||||
|
apikey: {
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
placement
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
bodyformurlencoded(_1, dictionary) {
|
bodyformurlencoded(_1, dictionary) {
|
||||||
return {
|
return {
|
||||||
body: {
|
body: {
|
||||||
|
@ -4,7 +4,7 @@ const { outdentString } = require('../../v1/src/utils');
|
|||||||
|
|
||||||
const grammar = ohm.grammar(`Bru {
|
const grammar = ohm.grammar(`Bru {
|
||||||
BruFile = (meta | query | headers | auth | auths | vars | script | tests | docs)*
|
BruFile = (meta | query | headers | auth | auths | vars | script | tests | docs)*
|
||||||
auths = authawsv4 | authbasic | authbearer | authdigest | authOAuth2
|
auths = authawsv4 | authbasic | authbearer | authdigest | authOAuth2 | authapikey
|
||||||
|
|
||||||
nl = "\\r"? "\\n"
|
nl = "\\r"? "\\n"
|
||||||
st = " " | "\\t"
|
st = " " | "\\t"
|
||||||
@ -43,6 +43,7 @@ const grammar = ohm.grammar(`Bru {
|
|||||||
authbearer = "auth:bearer" dictionary
|
authbearer = "auth:bearer" dictionary
|
||||||
authdigest = "auth:digest" dictionary
|
authdigest = "auth:digest" dictionary
|
||||||
authOAuth2 = "auth:oauth2" dictionary
|
authOAuth2 = "auth:oauth2" dictionary
|
||||||
|
authapikey = "auth:apikey" dictionary
|
||||||
|
|
||||||
script = scriptreq | scriptres
|
script = scriptreq | scriptres
|
||||||
scriptreq = "script:pre-request" st* "{" nl* textblock tagend
|
scriptreq = "script:pre-request" st* "{" nl* textblock tagend
|
||||||
@ -293,6 +294,28 @@ const sem = grammar.createSemantics().addAttribute('ast', {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
authapikey(_1, dictionary) {
|
||||||
|
const auth = mapPairListToKeyValPairs(dictionary.ast, false);
|
||||||
|
|
||||||
|
const findValueByName = (name) => {
|
||||||
|
const item = _.find(auth, { name });
|
||||||
|
return item ? item.value : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const key = findValueByName('key');
|
||||||
|
const value = findValueByName('value');
|
||||||
|
const placement = findValueByName('placement');
|
||||||
|
|
||||||
|
return {
|
||||||
|
auth: {
|
||||||
|
apikey: {
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
placement
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
varsreq(_1, dictionary) {
|
varsreq(_1, dictionary) {
|
||||||
const vars = mapPairListToKeyValPairs(dictionary.ast);
|
const vars = mapPairListToKeyValPairs(dictionary.ast);
|
||||||
_.each(vars, (v) => {
|
_.each(vars, (v) => {
|
||||||
|
@ -200,6 +200,16 @@ ${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auth && auth.apikey) {
|
||||||
|
bru += `auth:apikey {
|
||||||
|
${indentString(`key: ${auth?.apikey?.key || ''}`)}
|
||||||
|
${indentString(`value: ${auth?.apikey?.value || ''}`)}
|
||||||
|
${indentString(`placement: ${auth?.apikey?.placement || ''}`)}
|
||||||
|
}
|
||||||
|
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
if (body && body.json && body.json.length) {
|
if (body && body.json && body.json.length) {
|
||||||
bru += `body:json {
|
bru += `body:json {
|
||||||
${indentString(body.json)}
|
${indentString(body.json)}
|
||||||
|
@ -111,6 +111,15 @@ ${indentString(`username: ${auth.digest.username}`)}
|
|||||||
${indentString(`password: ${auth.digest.password}`)}
|
${indentString(`password: ${auth.digest.password}`)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auth && auth.apikey) {
|
||||||
|
bru += `auth:apikey {
|
||||||
|
${indentString(`key: ${auth?.apikey?.key || ''}`)}
|
||||||
|
${indentString(`value: ${auth?.apikey?.value || ''}`)}
|
||||||
|
${indentString(`placement: ${auth?.apikey?.placement || ''}`)}
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,15 +177,22 @@ const oauth2Schema = Yup.object({
|
|||||||
.noUnknown(true)
|
.noUnknown(true)
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
|
const authApiKeySchema = Yup.object({
|
||||||
|
key: Yup.string().nullable(),
|
||||||
|
value: Yup.string().nullable(),
|
||||||
|
placement: Yup.string().oneOf(['header', 'queryparams']).nullable()
|
||||||
|
});
|
||||||
|
|
||||||
const authSchema = Yup.object({
|
const authSchema = Yup.object({
|
||||||
mode: Yup.string()
|
mode: Yup.string()
|
||||||
.oneOf(['inherit', 'none', 'awsv4', 'basic', 'bearer', 'digest', 'oauth2'])
|
.oneOf(['inherit', 'none', 'awsv4', 'basic', 'bearer', 'digest', 'oauth2', 'apikey'])
|
||||||
.required('mode is required'),
|
.required('mode is required'),
|
||||||
awsv4: authAwsV4Schema.nullable(),
|
awsv4: authAwsV4Schema.nullable(),
|
||||||
basic: authBasicSchema.nullable(),
|
basic: authBasicSchema.nullable(),
|
||||||
bearer: authBearerSchema.nullable(),
|
bearer: authBearerSchema.nullable(),
|
||||||
digest: authDigestSchema.nullable(),
|
digest: authDigestSchema.nullable(),
|
||||||
oauth2: oauth2Schema.nullable()
|
oauth2: oauth2Schema.nullable(),
|
||||||
|
apikey: authApiKeySchema.nullable()
|
||||||
})
|
})
|
||||||
.noUnknown(true)
|
.noUnknown(true)
|
||||||
.strict()
|
.strict()
|
||||||
|
Loading…
Reference in New Issue
Block a user