diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/index.js index 20d02f2b17..8b43f6746f 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/index.js @@ -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 ( diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth1/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth1/StyledWrapper.js new file mode 100644 index 0000000000..356d4ecf1e --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth1/StyledWrapper.js @@ -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; diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth1/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth1/index.js new file mode 100644 index 0000000000..6c927be61a --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth1/index.js @@ -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 ( + + {inputsConfig.map((input) => { + const { key, label, type, options } = input; + return ( +
+ + {type === 'Dropdown' ? +
+ {optionDisplayName(options, oAuth1[key])} +
)} + onCreate={(ref) => setRef(ref, key)} + placement="bottom-end" + children={options.map((option) => ( +
{ + hideDropdown(key); + handleChange(key, option.key ? option.key : option); + }}> + {option.label ? t(option.label) : option} +
+ ))} + /> +
+ : ''} + {type === 'SingleLineEditor' ? +
+ handleChange(key, val)} + collection={collection} + /> +
+ : ''} + {type === 'FilePickerEditor' ? +
+ handleChange(key, relativeFile(val[0]))} + collection={collection} + /> +
+ : ''} + + ); + })} +
+ ); +}; + +export default OAuth1; diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth1/inputsConfig.js b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth1/inputsConfig.js new file mode 100644 index 0000000000..eefdcd66ec --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth1/inputsConfig.js @@ -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 }; diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/index.js index 05efc17b23..fe657e3581 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/index.js @@ -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 ; } + case 'oauth1': { + return ; + } case 'oauth2': { return ; } 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 fdc515e94d..7a1fb68f48 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -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; diff --git a/packages/bruno-electron/src/ipc/network/prepare-request.js b/packages/bruno-electron/src/ipc/network/prepare-request.js index 3a94df5c98..f05d07f865 100644 --- a/packages/bruno-electron/src/ipc/network/prepare-request.js +++ b/packages/bruno-electron/src/ipc/network/prepare-request.js @@ -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; } } diff --git a/packages/bruno-lang/v2/src/collectionBruToJson.js b/packages/bruno-lang/v2/src/collectionBruToJson.js index 5180f0193d..d6322a2293 100644 --- a/packages/bruno-lang/v2/src/collectionBruToJson.js +++ b/packages/bruno-lang/v2/src/collectionBruToJson.js @@ -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); diff --git a/packages/bruno-lang/v2/src/jsonToCollectionBru.js b/packages/bruno-lang/v2/src/jsonToCollectionBru.js index 8b162b7a6f..6eae5eac30 100644 --- a/packages/bruno-lang/v2/src/jsonToCollectionBru.js +++ b/packages/bruno-lang/v2/src/jsonToCollectionBru.js @@ -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 || ''}`)} +} + `; }