Skip to content

Commit

Permalink
feat: Implement OAuth 1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
pietrygamat committed Sep 15, 2024
1 parent 494c45c commit a5cb10d
Show file tree
Hide file tree
Showing 19 changed files with 678 additions and 7 deletions.
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/bruno-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
"html-loader": "^3.0.1",
"html-webpack-plugin": "^5.5.0",
"mini-css-extract-plugin": "^2.4.5",
"oauth-1.0a": "^2.2.6",
"postcss": "^8.4.35",
"style-loader": "^3.3.1",
"tailwindcss": "^3.4.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const AuthMode = ({ item, collection }) => {
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const authMode = item.draft ? get(item, 'draft.request.auth.mode') : get(item, 'request.auth.mode');

const authModes = ['awsv4', 'basic', 'bearer', 'digest', 'oauth2', 'inherit', 'none'];
const authModes = ['awsv4', 'basic', 'bearer', 'digest', 'oauth1', 'oauth2', 'inherit', 'none'];

const Icon = forwardRef((props, ref) => {
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
131 changes: 131 additions & 0 deletions packages/bruno-app/src/components/RequestPane/Auth/OAuth1/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
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 { updateAuth } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } 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 = ({ item, collection }) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const { storedTheme } = useTheme();

const oAuth1 = item.draft ? get(item, 'draft.request.auth.oauth1', {}) : get(item, 'request.auth.oauth1', {});

const handleRun = () => dispatch(sendRequest(item, collection.uid));
const handleSave = () => dispatch(saveRequest(item.uid, 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(
updateAuth({
mode: 'oauth1',
collectionUid: collection.uid,
itemUid: item.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)}
onRun={handleRun}
collection={collection}
item={item}
/>
</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;
Original file line number Diff line number Diff line change
@@ -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: 'authorizationMethod',
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 };
10 changes: 7 additions & 3 deletions packages/bruno-app/src/components/RequestPane/Auth/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React from 'react';
import get from 'lodash/get';
import StyledWrapper from './StyledWrapper';
import { humanizeRequestAuthMode } from 'utils/collections';
import AuthMode from './AuthMode';
import AwsV4Auth from './AwsV4Auth';
import BearerAuth from './BearerAuth';
import BasicAuth from './BasicAuth';
import DigestAuth from './DigestAuth';
import StyledWrapper from './StyledWrapper';
import { humanizeRequestAuthMode } from 'utils/collections/index';
import OAuth2 from './OAuth2/index';
import OAuth1 from './OAuth1';
import OAuth2 from './OAuth2';

const Auth = ({ item, collection }) => {
const authMode = item.draft ? get(item, 'draft.request.auth.mode') : get(item, 'request.auth.mode');
Expand All @@ -29,6 +30,9 @@ const Auth = ({ item, collection }) => {
case 'digest': {
return <DigestAuth collection={collection} item={item} />;
}
case 'oauth1': {
return <OAuth1 collection={collection} item={item} />;
}
case 'oauth2': {
return <OAuth2 collection={collection} item={item} />;
}
Expand Down
21 changes: 21 additions & 0 deletions packages/bruno-app/src/i18n/translation/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,26 @@
"COLLECTION_IMPORT_SUCCESS": "Collection imported successfully",
"COLLECTION_IMPORT_ERROR": "An error occurred while importing the collection. Check the logs for more information.",
"COLLECTION_OPEN_ERROR": "An error occurred while opening the collection"
},
"AUTHORIZATION": {
"OAUTH1": {
"CONSUMER_KEY_FIELD": "Consumer Key",
"CONSUMER_SECRET_FIELD": "Consumer Secret",
"REQUEST_TOKEN_URL_FIELD": "Request Token URL",
"ACCESS_TOKEN_URL_FIELD": "Access Token URL",
"AUTHORIZE_URL_FIELD": "Authorization URL",
"CALLBACK_TOKEN_URL_FIELD": "Callback URL",
"OAUTH_VERIFIER_FIELD": "Verifier code",
"ACCESS_TOKEN_FIELD": "Access Token",
"ACCESS_TOKEN_SECRET_FIELD": "Access Token Secret",
"RSA_PRIVATE_KEY_FIELD": "RSA Private Key",
"SIGNATURE_METHOD_FIELD": "Signature Method",
"PARAM_TRANSMISSION_METHOD_FIELD": "Parameter Transmission Method",
"PARAM_TRANSMISSION_METHOD": {
"AUTHORIZATION_HEADER": "Authorization Header",
"REQUEST_BODY": "Request Body",
"QUERY_PARAM": "Query Parameters"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,10 @@ export const collectionsSlice = createSlice({
item.draft.request.auth.mode = 'digest';
item.draft.request.auth.digest = action.payload.content;
break;
case 'oauth1':
item.draft.request.auth.mode = 'oauth1';
item.draft.request.auth.oauth1 = action.payload.content;
break;
case 'oauth2':
item.draft.request.auth.mode = 'oauth2';
item.draft.request.auth.oauth2 = action.payload.content;
Expand Down
20 changes: 20 additions & 0 deletions packages/bruno-app/src/utils/collections/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,22 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {}
password: get(si.request, 'auth.digest.password', '')
};
break;
case 'oauth1':
di.request.auth.oauth1 = {
consumerKey: get(si.request, 'auth.oauth1.consumerKey', ''),
consumerSecret: get(si.request, 'auth.oauth1.consumerSecret', ''),
requestTokenUrl: get(si.request, 'auth.oauth1.requestTokenUrl', ''),
accessTokenUrl: get(si.request, 'auth.oauth1.accessTokenUrl', ''),
authorizeUrl: get(si.request, 'auth.oauth1.authorizeUrl', ''),
callbackUrl: get(si.request, 'auth.oauth1.callbackUrl', ''),
verifier: get(si.request, 'auth.oauth1.verifier', ''),
accessToken: get(si.request, 'auth.oauth1.accessToken', ''),
accessTokenSecret: get(si.request, 'auth.oauth1.accessTokenSecret', ''),
rsaPrivateKey: get(si.request, 'auth.oauth1.rsaPrivateKey', ''),
parameterTransmissionMethod: get(si.request, 'auth.oauth1.parameterTransmissionMethod', ''),
signatureMethod: get(si.request, 'auth.oauth1.signatureMethod', '')
};
break;
case 'oauth2':
let grantType = get(si.request, 'auth.oauth2.grantType', '');
switch (grantType) {
Expand Down Expand Up @@ -657,6 +673,10 @@ export const humanizeRequestAuthMode = (mode) => {
label = 'Digest Auth';
break;
}
case 'oauth1': {
label = 'OAuth 1.0';
break;
}
case 'oauth2': {
label = 'OAuth 2.0';
break;
Expand Down
5 changes: 5 additions & 0 deletions packages/bruno-electron/src/ipc/network/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const { addDigestInterceptor } = require('./digestauth-helper');
const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../../utils/proxy-util');
const { chooseFileToSave, writeBinaryFile, writeFile } = require('../../utils/filesystem');
const { getCookieStringForUrl, addCookieToJar, getDomainsWithCookies } = require('../../utils/cookies');
const { addOAuth1Authorization } = require('./oauth1-helper');
const {
resolveOAuth2AuthorizationCodeAccessToken,
transformClientCredentialsRequest,
Expand Down Expand Up @@ -262,6 +263,10 @@ const configureRequest = async (
});
}
const axiosInstance = makeAxiosInstance();
if (request.oauth1) {
interpolateVars(request, envVars, runtimeVariables, processEnvVars);
await addOAuth1Authorization(request, collectionPath);
}

if (request.oauth2) {
let requestCopy = cloneDeep(request);
Expand Down
Loading

0 comments on commit a5cb10d

Please sign in to comment.