feat: Implement OAuth 1.0 - collection level

This commit is contained in:
Mateusz Pietryga 2024-09-15 19:47:22 +02:00
parent 9a3790b4cc
commit 87a4aeadbe
No known key found for this signature in database
GPG Key ID: 549A107FB8327670
9 changed files with 305 additions and 3 deletions

View File

@ -13,7 +13,7 @@ const AuthMode = ({ collection }) => {
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const authMode = get(collection, 'root.request.auth.mode');
const authModes = ['awsv4', 'basic', 'bearer', 'digest', 'oauth2', 'wsse', 'apikey', 'none'];
const authModes = ['awsv4', 'basic', 'bearer', 'digest', 'oauth1', 'oauth2', 'wsse', 'apikey', 'none'];
const Icon = forwardRef((props, ref) => {
return (

View File

@ -0,0 +1,20 @@
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};
}
.file-picker-wrapper {
max-width: 400px;
border-radius: 3px;
}
`;
export default Wrapper;

View File

@ -0,0 +1,127 @@
import React, { forwardRef, useCallback, useRef } from 'react';
import get from 'lodash/get';
import { useTheme } from 'providers/Theme';
import { useDispatch } from 'react-redux';
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 { inputsConfig } from './inputsConfig';
import { useTranslation } from 'react-i18next';
import Dropdown from 'components/Dropdown';
import { IconCaretDown } from '@tabler/icons';
import FilePickerEditor from 'components/FilePickerEditor';
import path from 'path';
import { isWindowsOS } from 'utils/common/platform';
import slash from 'utils/common/slash';
const OAuth1 = ({ collection }) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const { storedTheme } = useTheme();
const oAuth1 = get(collection, 'root.request.auth.oauth1', {});
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
const refs = useRef(new Map());
const setRef = useCallback((ref, key) => {
if (ref) {
refs.current.set(key, ref);
} else {
refs.current.delete(key);
}
}, []);
const hideDropdown = (key) => {
const dropdown = refs.current.get(key);
if (dropdown?.hide) {
dropdown.hide();
}
};
const handleChange = (key, val) => {
console.log(key, val);
dispatch(
updateCollectionAuth({
mode: 'oauth1',
collectionUid: collection.uid,
content: {
...oAuth1, [key]: val
}
})
);
};
const relativeFile = (file) => {
if (file) {
if (isWindowsOS()) {
return slash((path.win32.relative(collection.pathname, file)));
} else {
return path.posix.relative(collection.pathname, file);
}
} else {
return '';
}
};
const optionDisplayName = (options, key) => {
const option = (options.find(option => option.key === key));
return option?.label ? t(option.label) : key;
};
return (
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
{inputsConfig.map((input) => {
const { key, label, type, options } = input;
return (
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
<label className="block font-medium">{t(label)}</label>
{type === 'Dropdown' ?
<div className="inline-flex items-center cursor-pointer grant-type-mode-selector w-fit">
<Dropdown icon={(<div
className="flex items-center justify-end grant-type-label select-none">{optionDisplayName(options, oAuth1[key])}
<IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} /></div>)}
onCreate={(ref) => setRef(ref, key)}
placement="bottom-end"
children={options.map((option) => (
<div
className="dropdown-item"
onClick={() => {
hideDropdown(key);
handleChange(key, option.key ? option.key : option);
}}>
{option.label ? t(option.label) : option}
</div>
))}
/>
</div>
: ''}
{type === 'SingleLineEditor' ?
<div className="single-line-editor-wrapper">
<SingleLineEditor
value={oAuth1[key] || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleChange(key, val)}
collection={collection}
/>
</div>
: ''}
{type === 'FilePickerEditor' ?
<div className="file-picker-wrapper">
<FilePickerEditor
value={[oAuth1[key]] || []}
onChange={(val) => handleChange(key, relativeFile(val[0]))}
collection={collection}
/>
</div>
: ''}
</div>
);
})}
</StyledWrapper>
);
};
export default OAuth1;

View File

@ -0,0 +1,79 @@
const inputsConfig = [
{
key: 'consumerKey',
label: 'AUTHORIZATION.OAUTH1.CONSUMER_KEY_FIELD',
type: 'SingleLineEditor'
},
{
key: 'consumerSecret',
label: 'AUTHORIZATION.OAUTH1.CONSUMER_SECRET_FIELD',
type: 'SingleLineEditor'
},
{
key: 'requestTokenUrl',
label: 'AUTHORIZATION.OAUTH1.REQUEST_TOKEN_URL_FIELD',
type: 'SingleLineEditor'
},
{
key: 'accessTokenUrl',
label: 'AUTHORIZATION.OAUTH1.ACCESS_TOKEN_URL_FIELD',
type: 'SingleLineEditor'
},
{
key: 'authorizeUrl',
label: 'AUTHORIZATION.OAUTH1.AUTHORIZE_URL_FIELD',
type: 'SingleLineEditor'
},
{
key: 'callbackUrl',
label: 'AUTHORIZATION.OAUTH1.CALLBACK_TOKEN_URL_FIELD',
type: 'SingleLineEditor'
},
{
key: 'verifier',
label: 'AUTHORIZATION.OAUTH1.OAUTH_VERIFIER_FIELD',
type: 'SingleLineEditor'
},
{
key: 'accessToken',
label: 'AUTHORIZATION.OAUTH1.ACCESS_TOKEN_FIELD',
type: 'SingleLineEditor'
},
{
key: 'accessTokenSecret',
label: 'AUTHORIZATION.OAUTH1.ACCESS_TOKEN_SECRET_FIELD',
type: 'SingleLineEditor'
},
{
key: 'rsaPrivateKey',
label: 'AUTHORIZATION.OAUTH1.RSA_PRIVATE_KEY_FIELD',
type: 'FilePickerEditor'
},
{
key: 'signatureMethod',
label: 'AUTHORIZATION.OAUTH1.SIGNATURE_METHOD_FIELD',
type: 'Dropdown',
options: ['HMAC-SHA1', 'HMAC-SHA256', 'HMAC-SHA512', 'RSA-SHA1', 'RSA-SHA256', 'RSA-SHA512', 'PLAINTEXT']
},
{
key: 'parameterTransmissionMethod',
label: 'AUTHORIZATION.OAUTH1.PARAM_TRANSMISSION_METHOD_FIELD',
type: 'Dropdown',
options: [
{
key: 'authorization_header',
label: 'AUTHORIZATION.OAUTH1.PARAM_TRANSMISSION_METHOD.AUTHORIZATION_HEADER'
},
{
key: 'request_body',
label: 'AUTHORIZATION.OAUTH1.PARAM_TRANSMISSION_METHOD.REQUEST_BODY'
},
{
key: 'query_param',
label: 'AUTHORIZATION.OAUTH1.PARAM_TRANSMISSION_METHOD.QUERY_PARAM'
}
]
}
];
export { inputsConfig };

View File

@ -11,6 +11,7 @@ import ApiKeyAuth from './ApiKeyAuth/';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
import OAuth2 from './OAuth2';
import OAuth1 from 'components/CollectionSettings/Auth/OAuth1';
const Auth = ({ collection }) => {
const authMode = get(collection, 'root.request.auth.mode');
@ -32,6 +33,9 @@ const Auth = ({ collection }) => {
case 'digest': {
return <DigestAuth collection={collection} />;
}
case 'oauth1': {
return <OAuth1 collection={collection} />;
}
case 'oauth2': {
return <OAuth2 collection={collection} />;
}

View File

@ -1146,6 +1146,9 @@ export const collectionsSlice = createSlice({
case 'digest':
set(collection, 'root.request.auth.digest', action.payload.content);
break;
case 'oauth1':
set(collection, 'root.request.auth.oauth1', action.payload.content);
break;
case 'oauth2':
set(collection, 'root.request.auth.oauth2', action.payload.content);
break;

View File

@ -245,6 +245,22 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
axiosRequest.apiKeyAuthValueForQueryParams = apiKeyAuth;
}
break;
case 'oauth1':
axiosRequest.oauth1 = {
consumerKey: get(collectionAuth, 'oauth1.consumerKey'),
consumerSecret: get(collectionAuth, 'oauth1.consumerSecret'),
requestTokenUrl: get(collectionAuth, 'oauth1.requestTokenUrl'),
accessTokenUrl: get(collectionAuth, 'oauth1.accessTokenUrl'),
authorizeUrl: get(collectionAuth, 'oauth1.authorizeUrl'),
callbackUrl: get(collectionAuth, 'oauth1.callbackUrl'),
verifier: get(collectionAuth, 'oauth1.verifier'),
accessToken: get(collectionAuth, 'oauth1.accessToken'),
accessTokenSecret: get(collectionAuth, 'oauth1.accessTokenSecret'),
rsaPrivateKey: get(collectionAuth, 'oauth1.rsaPrivateKey'),
parameterTransmissionMethod: get(collectionAuth, 'oauth1.parameterTransmissionMethod'),
signatureMethod: get(collectionAuth, 'oauth1.signatureMethod')
};
break;
}
}

View File

@ -4,7 +4,7 @@ const { outdentString } = require('../../v1/src/utils');
const grammar = ohm.grammar(`Bru {
BruFile = (meta | query | headers | auth | auths | vars | script | tests | docs)*
auths = authawsv4 | authbasic | authbearer | authdigest | authOAuth2 | authwsse | authapikey
auths = authawsv4 | authbasic | authbearer | authdigest | authOAuth1 | authOAuth2 | authwsse | authapikey
nl = "\\r"? "\\n"
st = " " | "\\t"
@ -42,6 +42,7 @@ const grammar = ohm.grammar(`Bru {
authbasic = "auth:basic" dictionary
authbearer = "auth:bearer" dictionary
authdigest = "auth:digest" dictionary
authOAuth1 = "auth:oauth1" dictionary
authOAuth2 = "auth:oauth2" dictionary
authwsse = "auth:wsse" dictionary
authapikey = "auth:apikey" dictionary
@ -245,6 +246,39 @@ const sem = grammar.createSemantics().addAttribute('ast', {
}
};
},
authOAuth1(_1, dictionary) {
const auth = mapPairListToKeyValPairs(dictionary.ast, false);
const consumerKey = _.find(auth, { name: 'consumerKey' });
const consumerSecret = _.find(auth, { name: 'consumerSecret' });
const requestTokenUrl = _.find(auth, { name: 'requestTokenUrl' });
const accessTokenUrl = _.find(auth, { name: 'accessTokenUrl' });
const authorizeUrl = _.find(auth, { name: 'authorizeUrl' });
const verifier = _.find(auth, { name: 'verifier' });
const callbackUrl = _.find(auth, { name: 'callbackUrl' });
const accessToken = _.find(auth, { name: 'accessToken' });
const accessTokenSecret = _.find(auth, { name: 'accessTokenSecret' });
const rsaPrivateKey = _.find(auth, { name: 'rsaPrivateKey' });
const signatureMethod = _.find(auth, { name: 'signatureMethod' });
const parameterTransmissionMethod = _.find(auth, { name: 'parameterTransmissionMethod' });
return {
auth: {
oauth1: {
consumerKey: consumerKey ? consumerKey.value : '',
consumerSecret: consumerSecret ? consumerSecret.value : '',
requestTokenUrl: requestTokenUrl ? requestTokenUrl.value : '',
accessTokenUrl: accessTokenUrl ? accessTokenUrl.value : '',
authorizeUrl: authorizeUrl ? authorizeUrl.value : '',
callbackUrl: callbackUrl ? callbackUrl.value : '',
verifier: verifier ? verifier.value : '',
accessToken: accessToken ? accessToken.value : '',
accessTokenSecret: accessTokenSecret ? accessTokenSecret.value : '',
rsaPrivateKey: rsaPrivateKey ? rsaPrivateKey.value : '',
parameterTransmissionMethod: parameterTransmissionMethod ? parameterTransmissionMethod.value : '',
signatureMethod: signatureMethod ? signatureMethod.value : ''
}
}
};
},
authOAuth2(_1, dictionary) {
const auth = mapPairListToKeyValPairs(dictionary.ast, false);
const grantTypeKey = _.find(auth, { name: 'grant_type' });
@ -309,7 +343,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
}
}
}
},
},
authapikey(_1, dictionary) {
const auth = mapPairListToKeyValPairs(dictionary.ast, false);

View File

@ -129,6 +129,25 @@ ${indentString(`key: ${auth?.apikey?.key || ''}`)}
${indentString(`value: ${auth?.apikey?.value || ''}`)}
${indentString(`placement: ${auth?.apikey?.placement || ''}`)}
}
`;
}
if (auth && auth.oauth1) {
bru += `auth:oauth1 {
${indentString(`consumerKey: ${auth?.oauth1?.consumerKey || ''}`)}
${indentString(`consumerSecret: ${auth?.oauth1?.consumerSecret || ''}`)}
${indentString(`requestTokenUrl: ${auth?.oauth1?.requestTokenUrl || ''}`)}
${indentString(`accessTokenUrl: ${auth?.oauth1?.accessTokenUrl || ''}`)}
${indentString(`authorizeUrl: ${auth?.oauth1?.authorizeUrl || ''}`)}
${indentString(`callbackUrl: ${auth?.oauth1?.callbackUrl || ''}`)}
${indentString(`verifier: ${auth?.oauth1?.verifier || ''}`)}
${indentString(`accessToken: ${auth?.oauth1?.accessToken || ''}`)}
${indentString(`accessTokenSecret: ${auth?.oauth1?.accessTokenSecret || ''}`)}
${indentString(`rsaPrivateKey: ${auth?.oauth1?.rsaPrivateKey || ''}`)}
${indentString(`parameterTransmissionMethod: ${auth?.oauth1?.parameterTransmissionMethod || ''}`)}
${indentString(`signatureMethod: ${auth?.oauth1?.signatureMethod || ''}`)}
}
`;
}