diff --git a/package-lock.json b/package-lock.json
index d7ee4064d3..e349ca559d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17392,6 +17392,12 @@
"integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==",
"license": "MIT"
},
+ "node_modules/oauth-1.0a": {
+ "version": "2.2.6",
+ "resolved": "https://registry.npmjs.org/oauth-1.0a/-/oauth-1.0a-2.2.6.tgz",
+ "integrity": "sha512-6bkxv3N4Gu5lty4viIcIAnq5GbxECviMBeKR3WX/q87SPQ8E8aursPZUtsXDnxCs787af09WPRBLqYrf/lwoYQ==",
+ "dev": true
+ },
"node_modules/oauth-sign": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
@@ -24102,6 +24108,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.47",
"style-loader": "^3.3.1",
"tailwindcss": "^3.4.1",
diff --git a/packages/bruno-app/package.json b/packages/bruno-app/package.json
index d4fb47bcf4..3d5c71b7b3 100644
--- a/packages/bruno-app/package.json
+++ b/packages/bruno-app/package.json
@@ -92,6 +92,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.47",
"style-loader": "^3.3.1",
"tailwindcss": "^3.4.1",
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 2c541cc289..04ce278959 100644
--- a/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/index.js
+++ b/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/index.js
@@ -13,6 +13,8 @@ const AuthMode = ({ collection }) => {
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const authMode = get(collection, 'root.request.auth.mode');
+ const authModes = ['awsv4', 'basic', 'bearer', 'digest', 'ntlm', 'oauth1', 'oauth2', 'wsse', 'apikey', 'none'];
+
const Icon = forwardRef((props, ref) => {
return (
@@ -34,87 +36,21 @@ const AuthMode = ({ collection }) => {
} placement="bottom-end">
-
{
- dropdownTippyRef.current.hide();
- onModeChange('awsv4');
- }}
- >
- AWS Sig v4
-
-
{
- dropdownTippyRef.current.hide();
- onModeChange('basic');
- }}
- >
- Basic Auth
-
-
{
- dropdownTippyRef.current.hide();
- onModeChange('wsse');
- }}
- >
- WSSE Auth
-
-
{
- dropdownTippyRef.current.hide();
- onModeChange('bearer');
- }}
- >
- Bearer Token
-
-
{
- dropdownTippyRef.current.hide();
- onModeChange('digest');
- }}
- >
- Digest Auth
-
-
{
- dropdownTippyRef.current.hide();
- onModeChange('ntlm');
- }}
- >
- NTLM Auth
-
-
{
- dropdownTippyRef.current.hide();
- onModeChange('oauth2');
- }}
- >
- Oauth2
-
-
{
- dropdownTippyRef.current.hide();
- onModeChange('apikey');
- }}
- >
- API Key
-
-
{
- dropdownTippyRef.current.hide();
- onModeChange('none');
- }}
- >
- No Auth
-
+ {authModes.map((mode) => {
+ return (
+ <>
+
{
+ dropdownTippyRef.current.hide();
+ onModeChange(mode);
+ }}
+ >
+ {humanizeRequestAuthMode(mode)}
+
+ >
+ );
+ })}
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 c19ae98738..8228686e7f 100644
--- a/packages/bruno-app/src/components/CollectionSettings/Auth/index.js
+++ b/packages/bruno-app/src/components/CollectionSettings/Auth/index.js
@@ -11,9 +11,9 @@ 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';
import NTLMAuth from './NTLMAuth';
-
const Auth = ({ collection }) => {
const authMode = get(collection, 'root.request.auth.mode');
const dispatch = useDispatch();
@@ -36,7 +36,10 @@ const Auth = ({ collection }) => {
}
case 'ntlm': {
return ;
- }
+ }
+ case 'oauth1': {
+ return ;
+ }
case 'oauth2': {
return ;
}
diff --git a/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js b/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js
index 1e3bedc2f3..53f30ecc16 100644
--- a/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js
+++ b/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js
@@ -13,6 +13,8 @@ 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', 'ntlm', 'oauth1', 'oauth2', 'wsse', 'apikey', 'inherit', 'none'];
+
const Icon = forwardRef((props, ref) => {
return (
@@ -34,96 +36,21 @@ const AuthMode = ({ item, collection }) => {
} placement="bottom-end">
-
{
- dropdownTippyRef?.current?.hide();
- onModeChange('awsv4');
- }}
- >
- AWS Sig v4
-
-
{
- dropdownTippyRef?.current?.hide();
- onModeChange('basic');
- }}
- >
- Basic Auth
-
-
{
- dropdownTippyRef?.current?.hide();
- onModeChange('bearer');
- }}
- >
- Bearer Token
-
-
{
- dropdownTippyRef?.current?.hide();
- onModeChange('digest');
- }}
- >
- Digest Auth
-
-
{
- dropdownTippyRef?.current?.hide();
- onModeChange('ntlm');
- }}
- >
- NTLM Auth
-
-
{
- dropdownTippyRef?.current?.hide();
- onModeChange('oauth2');
- }}
- >
- OAuth 2.0
-
-
{
- dropdownTippyRef?.current?.hide();
- onModeChange('wsse');
- }}
- >
- WSSE Auth
-
-
{
- dropdownTippyRef?.current?.hide();
- onModeChange('apikey');
- }}
- >
- API Key
-
-
{
- dropdownTippyRef?.current?.hide();
- onModeChange('inherit');
- }}
- >
- Inherit
-
-
{
- dropdownTippyRef?.current?.hide();
- onModeChange('none');
- }}
- >
- No Auth
-
+ {authModes.map((mode) => {
+ return (
+ <>
+
{
+ dropdownTippyRef?.current?.hide();
+ onModeChange(mode);
+ }}
+ >
+ {humanizeRequestAuthMode(mode)}
+
+ >
+ );
+ })}
diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth1/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth1/StyledWrapper.js
new file mode 100644
index 0000000000..356d4ecf1e
--- /dev/null
+++ b/packages/bruno-app/src/components/RequestPane/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/RequestPane/Auth/OAuth1/index.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth1/index.js
new file mode 100644
index 0000000000..4894d2a4f5
--- /dev/null
+++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth1/index.js
@@ -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 (
+
+ {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)}
+ onRun={handleRun}
+ collection={collection}
+ item={item}
+ />
+
+ : ''}
+ {type === 'FilePickerEditor' ?
+
+ handleChange(key, relativeFile(val[0]))}
+ collection={collection}
+ />
+
+ : ''}
+
+ );
+ })}
+
+ );
+};
+
+export default OAuth1;
diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth1/inputsConfig.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth1/inputsConfig.js
new file mode 100644
index 0000000000..eefdcd66ec
--- /dev/null
+++ b/packages/bruno-app/src/components/RequestPane/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/RequestPane/Auth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/index.js
index 743d23267e..868a663d60 100644
--- a/packages/bruno-app/src/components/RequestPane/Auth/index.js
+++ b/packages/bruno-app/src/components/RequestPane/Auth/index.js
@@ -1,17 +1,18 @@
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 OAuth1 from './OAuth1';
+import OAuth2 from './OAuth2';
import WsseAuth from './WsseAuth';
import NTLMAuth from './NTLMAuth';
import ApiKeyAuth from './ApiKeyAuth';
-import StyledWrapper from './StyledWrapper';
-import { humanizeRequestAuthMode } from 'utils/collections/index';
-import OAuth2 from './OAuth2/index';
const Auth = ({ item, collection }) => {
const authMode = item.draft ? get(item, 'draft.request.auth.mode') : get(item, 'request.auth.mode');
@@ -35,7 +36,10 @@ const Auth = ({ item, collection }) => {
}
case 'ntlm': {
return ;
- }
+ }
+ case 'oauth1': {
+ return ;
+ }
case 'oauth2': {
return ;
}
diff --git a/packages/bruno-app/src/i18n/translation/en.json b/packages/bruno-app/src/i18n/translation/en.json
index 7dda41e426..ce340ec31f 100644
--- a/packages/bruno-app/src/i18n/translation/en.json
+++ b/packages/bruno-app/src/i18n/translation/en.json
@@ -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"
+ }
+ }
}
}
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 d4ad489218..5f6c8ab6c6 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
@@ -476,7 +476,11 @@ export const collectionsSlice = createSlice({
case 'ntlm':
item.draft.request.auth.mode = 'ntlm';
item.draft.request.auth.ntlm = action.payload.content;
- break;
+ 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;
@@ -1150,7 +1154,10 @@ export const collectionsSlice = createSlice({
break;
case 'ntlm':
set(collection, 'root.request.auth.ntlm', action.payload.content);
- break;
+ 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-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js
index bc6c731f4d..d31c8eb6ed 100644
--- a/packages/bruno-app/src/utils/collections/index.js
+++ b/packages/bruno-app/src/utils/collections/index.js
@@ -346,7 +346,23 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {}
password: get(si.request, 'auth.ntlm.password', ''),
domain: get(si.request, 'auth.ntlm.domain', '')
};
- break;
+ 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) {
@@ -688,9 +704,13 @@ export const humanizeRequestAuthMode = (mode) => {
break;
}
case 'ntlm': {
- label = 'NTLM';
+ label = 'NTLM Auth';
break;
- }
+ }
+ case 'oauth1': {
+ label = 'OAuth 1.0';
+ break;
+ }
case 'oauth2': {
label = 'OAuth 2.0';
break;
diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js
index 1865426e0e..a1c3a7f674 100644
--- a/packages/bruno-electron/src/ipc/network/index.js
+++ b/packages/bruno-electron/src/ipc/network/index.js
@@ -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,
@@ -274,14 +275,17 @@ const configureRequest = async (
});
}
-
let axiosInstance = makeAxiosInstance();
-
+
if (request.ntlmConfig) {
axiosInstance=NtlmClient(request.ntlmConfig,axiosInstance)
delete request.ntlmConfig;
}
+ if (request.oauth1) {
+ interpolateVars(request, envVars, runtimeVariables, processEnvVars);
+ await addOAuth1Authorization(request, collectionPath);
+ }
if (request.oauth2) {
let requestCopy = cloneDeep(request);
diff --git a/packages/bruno-electron/src/ipc/network/interpolate-vars.js b/packages/bruno-electron/src/ipc/network/interpolate-vars.js
index 2b46327afb..3bceefdfbd 100644
--- a/packages/bruno-electron/src/ipc/network/interpolate-vars.js
+++ b/packages/bruno-electron/src/ipc/network/interpolate-vars.js
@@ -155,6 +155,10 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
delete request.auth;
}
+ if (request?.oauth1) {
+ Object.keys(request.oauth1).forEach(key => request.oauth1[key] = _interpolate(request.oauth1[key]));
+ }
+
if (request?.oauth2?.grantType) {
let username, password, scope, clientId, clientSecret;
switch (request.oauth2.grantType) {
diff --git a/packages/bruno-electron/src/ipc/network/oauth1-helper.js b/packages/bruno-electron/src/ipc/network/oauth1-helper.js
new file mode 100644
index 0000000000..b99669c39c
--- /dev/null
+++ b/packages/bruno-electron/src/ipc/network/oauth1-helper.js
@@ -0,0 +1,266 @@
+const OAuth = require('oauth-1.0a');
+const crypto = require('crypto');
+const fs = require('fs');
+const path = require('path');
+const axios = require('axios');
+const { BrowserWindow } = require('electron');
+const { preferencesUtil } = require('../../store/preferences');
+
+const addOAuth1Authorization = async (request, collectionPath) => {
+
+ const absoluteFilePath = (file) => {
+ return path.isAbsolute(file) ? file : path.join(collectionPath, file)
+ }
+
+ const hash_function = (signatureMethod) => {
+
+ // https://oauth.net/core/1.0a/#anchor15
+ // 9.2. HMAC-SHA1
+ if (signatureMethod.startsWith('HMAC')) {
+ return (base_string, key) => {
+ return crypto
+ .createHmac(signatureMethod.slice('HMAC-'.length).toLowerCase(), key)
+ .update(base_string)
+ .digest('base64')
+ }
+ }
+
+ // https://oauth.net/core/1.0a/#anchor18
+ // 9.3. RSA-SHA1
+ if (signatureMethod.startsWith('RSA')) {
+ return (base_string, unused_key) => {
+ const rsaPrivateKey = fs.readFileSync(absoluteFilePath(request.oauth1.rsaPrivateKey), 'utf8');
+ return crypto
+ .createSign(signatureMethod)
+ .update(base_string)
+ .sign(rsaPrivateKey, 'base64')
+ }
+ }
+
+ // https://oauth.net/core/1.0a/#anchor21
+ // 9.4. PLAINTEXT
+ if(signatureMethod === 'PLAINTEXT') {
+ return (base_string, key) => {
+ return key;
+ }
+ }
+
+ throw new Error('Unsupported signature method');
+ }
+
+ const oauth = OAuth({
+ consumer: {
+ key: request.oauth1.consumerKey,
+ secret: request.oauth1.consumerSecret
+ },
+ signature_method: request.oauth1.signatureMethod,
+ hash_function: hash_function(request.oauth1.signatureMethod)
+ });
+
+ // https://oauth.net/core/1.0a/#auth_step1
+ // 6.1. Obtaining an Unauthorized Request Token
+ const getRequestToken = async () => {
+ try {
+ const requestTokenRequest = {
+ url: request.oauth1.requestTokenUrl,
+ method: 'POST',
+ data: {
+ oauth_callback: request.oauth1.callbackUrl
+ }
+ };
+ const signingToken = {
+ // requestToken does not need signing
+ }
+ const authHeader = oauth.toHeader(oauth.authorize(requestTokenRequest, signingToken));
+ console.log("Requesting temporary request token from", requestTokenRequest.url);
+ const requestTokenResponse = await axios.post(requestTokenRequest.url, requestTokenRequest.data, { headers: authHeader });
+ const formattedResponse = Object.fromEntries(new URLSearchParams(requestTokenResponse.data));
+ console.info('Request Token Response:', requestTokenResponse.status, formattedResponse);
+ return formattedResponse;
+ } catch (error) {
+ console.error('Error obtaining request token:', error.message);
+ throw new Error(error.message);
+ }
+ };
+
+ // https://oauth.net/core/1.0a/#auth_step2
+ // 6.2. Obtaining User Authorization
+ const requestUserAuthorization = (oauthToken) => {
+ return new Promise(async (resolve, reject) => {
+ const matchesCallbackUrl = (url, callbackUrl) => {
+ return url ? url.href.startsWith(callbackUrl.href) : false;
+ };
+
+ let finalUrl;
+ let window;
+ const onRedirect = (_, url) => {
+ try {
+ if (matchesCallbackUrl(new URL(url), new URL(request.oauth1.callbackUrl))) {
+ finalUrl = url;
+ window.close();
+ }
+ } catch (error) {
+ console.error("Error parsing redirect URL:", error);
+ }
+ };
+
+ try {
+ window = new BrowserWindow({
+ webPreferences: {
+ nodeIntegration: false
+ }
+ });
+ window.on('ready-to-show', () => window.show());
+ window.webContents.on('certificate-error', (event, url, error, certificate, callback) => {
+ event.preventDefault();
+ callback(!preferencesUtil.shouldVerifyTls());
+ });
+ window.webContents.on('did-navigate', onRedirect);
+ window.webContents.on('will-redirect', onRedirect);
+
+ window.on('close', () => {
+ window.webContents.removeListener('did-navigate', onRedirect);
+ window.webContents.removeListener('will-redirect', onRedirect);
+
+ if (finalUrl) {
+ try {
+ const callbackUrlWithVerifier = new URL(finalUrl);
+ const params = Object.fromEntries(callbackUrlWithVerifier.searchParams);
+ console.log("Authorization successful:", params);
+ resolve(params);
+ } catch (error) {
+ console.error("Error processing final URL:", error);
+ reject(new Error('Invalid authorization response'));
+ }
+ } else {
+ console.warn('Authorization window closed by user');
+ reject(new Error('Authorization window closed by user'));
+ }
+ });
+
+ const authorizeUrl = new URL(request.oauth1.authorizeUrl);
+ authorizeUrl.searchParams.append('oauth_token', oauthToken);
+
+ console.log("Requesting user authorization from", authorizeUrl.toString());
+ window.loadURL(authorizeUrl.toString()).catch((error) => {
+ console.error("Error loading authorization URL:", error);
+ reject(new Error('Failed to load authorization URL'));
+ window.close();
+ });
+ } catch (error) {
+ console.error("Error during user authorization setup:", error);
+ if (window) window.close();
+ reject(new Error('User authorization setup failed'));
+ }
+ });
+ };
+
+ // https://oauth.net/core/1.0a/#auth_step3
+ // 6.3. Obtaining an Access Token
+ const exchangeRequestTokenForAccessToken = async (oauthToken, oauthTokenSecret, verifier) => {
+ try {
+ const accessTokenRequestData = {
+ url: request.oauth1.accessTokenUrl,
+ method: 'POST',
+ data: {
+ oauth_token: oauthToken,
+ oauth_verifier: verifier
+ }
+ };
+ const signingToken = {
+ key: request.oauth1.consumerSecret,
+ secret: oauthTokenSecret
+ }
+ const authHeader = oauth.toHeader(oauth.authorize(accessTokenRequestData, signingToken));
+ console.log("Requesting Access Token from", accessTokenRequestData.url);
+ const accessTokenResponse = await axios.post(accessTokenRequestData.url, {},{ headers: authHeader });
+ const formattedResponse = Object.fromEntries(new URLSearchParams(accessTokenResponse.data));
+ console.log("Access Token Response", accessTokenResponse.status, formattedResponse);
+ return formattedResponse;
+ } catch (error) {
+ console.error("Invalid Access Token Response", error.code, error.message);
+ throw new Error(error.message);
+ }
+ };
+
+ // https://oauth.net/core/1.0a/#anchor13
+ // 9.1.1. Normalize Request Parameters
+ const isPostWithUrlEncodedFormData = (request) => {
+ return request.headers['content-type'] === 'application/x-www-form-urlencoded' && request.method === 'POST';
+ }
+
+ // https://oauth.net/core/1.0a/#anchor12
+ // 7. Accessing Protected Resources
+ const evaluateOAuth1Parameters = (request, accessToken, accessTokenSecret) => {
+ let requestData;
+ if (isPostWithUrlEncodedFormData(request)) {
+ requestData = {
+ url: request.url,
+ method: request.method,
+ data: Object.fromEntries(new URLSearchParams(request.data).entries())
+ }
+ } else {
+ requestData = {
+ url: request.url,
+ method: request.method,
+ }
+ }
+
+ const signingToken = {
+ key: accessToken,
+ secret: accessTokenSecret
+ }
+
+ return oauth.authorize(requestData, signingToken);
+ }
+
+ // https://oauth.net/core/1.0a/#consumer_req_param
+ // 5.2. Consumer Request Parameters
+ const addAuthorizationToRequest = (request, oauth1RequestParameters, parameterTransmissionMethod) => {
+ switch (parameterTransmissionMethod) {
+ case 'authorization_header': {
+ const authHeader = oauth.toHeader(oauth1RequestParameters);
+ request.headers['authorization'] = authHeader.Authorization;
+ break;
+ }
+ case 'request_body': {
+ if(! isPostWithUrlEncodedFormData(request)) {
+ throw new Error('"Parameter Transmission Method: Request Body" is only supported ' +
+ 'for POST request with a content-type of application/x-www-form-urlencoded.');
+ }
+ request.data = new URLSearchParams(oauth1RequestParameters).toString();
+ break;
+ }
+ case 'query_param': {
+ const url = new URL(request.url);
+ Object.entries(oauth1RequestParameters).forEach(([key, value]) => {
+ url.searchParams.append(key, value);
+ })
+ request.url = url.toString();
+ break;
+ }
+ }
+ }
+
+ // With Access Token provided, we may skip directly to authorizing of user's request
+ // With verifier provided - we may skip user authorization (third leg) step
+ let { accessToken, accessTokenSecret, verifier, parameterTransmissionMethod } = request.oauth1;
+
+ try {
+ if(!accessToken || !accessTokenSecret) {
+ const { oauth_token: oauthToken, oauth_token_secret: oauthTokenSecret} = await getRequestToken();
+ if(!verifier) {
+ ({ oauth_verifier: verifier } = await requestUserAuthorization(oauthToken));
+ }
+ ({ oauth_token: accessToken, oauth_token_secret: accessTokenSecret } = await exchangeRequestTokenForAccessToken(oauthToken, oauthTokenSecret, verifier));
+ }
+ const requestParameters = evaluateOAuth1Parameters(request, accessToken, accessTokenSecret);
+ addAuthorizationToRequest(request, requestParameters, parameterTransmissionMethod)
+
+ } catch (error) {
+ console.error("OAuth flow failed", error.message);
+ throw error;
+ }
+};
+
+module.exports = { addOAuth1Authorization };
\ No newline at end of file
diff --git a/packages/bruno-electron/src/ipc/network/prepare-request.js b/packages/bruno-electron/src/ipc/network/prepare-request.js
index 6c7672e7d6..648090365c 100644
--- a/packages/bruno-electron/src/ipc/network/prepare-request.js
+++ b/packages/bruno-electron/src/ipc/network/prepare-request.js
@@ -40,7 +40,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
password: get(collectionAuth, 'ntlm.password'),
domain: get(collectionAuth, 'ntlm.domain')
};
- break;
+ break;
case 'wsse':
const username = get(request, 'auth.wsse.username', '');
const password = get(request, 'auth.wsse.password', '');
@@ -67,6 +67,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;
}
}
@@ -103,7 +119,23 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
password: get(request, 'auth.ntlm.password'),
domain: get(request, 'auth.ntlm.domain')
};
- break;
+ break;
+ case 'oauth1':
+ axiosRequest.oauth1 = {
+ consumerKey: get(request, 'auth.oauth1.consumerKey'),
+ consumerSecret: get(request, 'auth.oauth1.consumerSecret'),
+ requestTokenUrl: get(request, 'auth.oauth1.requestTokenUrl'),
+ accessTokenUrl: get(request, 'auth.oauth1.accessTokenUrl'),
+ authorizeUrl: get(request, 'auth.oauth1.authorizeUrl'),
+ callbackUrl: get(request, 'auth.oauth1.callbackUrl'),
+ verifier: get(request, 'auth.oauth1.verifier'),
+ accessToken: get(request, 'auth.oauth1.accessToken'),
+ accessTokenSecret: get(request, 'auth.oauth1.accessTokenSecret'),
+ rsaPrivateKey: get(request, 'auth.oauth1.rsaPrivateKey'),
+ parameterTransmissionMethod: get(request, 'auth.oauth1.parameterTransmissionMethod'),
+ signatureMethod: get(request, 'auth.oauth1.signatureMethod')
+ };
+ break;
case 'oauth2':
const grantType = get(request, 'auth.oauth2.grantType');
switch (grantType) {
diff --git a/packages/bruno-lang/v2/src/bruToJson.js b/packages/bruno-lang/v2/src/bruToJson.js
index 2fe5fb472a..f2ed868d41 100644
--- a/packages/bruno-lang/v2/src/bruToJson.js
+++ b/packages/bruno-lang/v2/src/bruToJson.js
@@ -23,7 +23,7 @@ const { outdentString } = require('../../v1/src/utils');
*/
const grammar = ohm.grammar(`Bru {
BruFile = (meta | http | query | params | headers | auths | bodies | varsandassert | script | tests | docs)*
- auths = authawsv4 | authbasic | authbearer | authdigest | authNTLM | authOAuth2 | authwsse | authapikey
+ auths = authawsv4 | authbasic | authbearer | authdigest | authNTLM | authOAuth1 | authOAuth2 | authwsse | authapikey
bodies = bodyjson | bodytext | bodyxml | bodysparql | bodygraphql | bodygraphqlvars | bodyforms | body
bodyforms = bodyformurlencoded | bodymultipart
params = paramspath | paramsquery
@@ -88,6 +88,7 @@ const grammar = ohm.grammar(`Bru {
authbearer = "auth:bearer" dictionary
authdigest = "auth:digest" dictionary
authNTLM = "auth:ntlm" dictionary
+ authOAuth1 = "auth:oauth1" dictionary
authOAuth2 = "auth:oauth2" dictionary
authwsse = "auth:wsse" dictionary
authapikey = "auth:apikey" dictionary
@@ -470,7 +471,40 @@ 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 callbackUrl = _.find(auth, { name: 'callbackUrl' });
+ const verifier = _.find(auth, { name: 'verifier' });
+ const accessToken = _.find(auth, { name: 'accessToken' });
+ const accessTokenSecret = _.find(auth, { name: 'accessTokenSecret' });
+ const rsaPrivateKey = _.find(auth, { name: 'rsaPrivateKey' });
+ const parameterTransmissionMethod = _.find(auth, { name: 'parameterTransmissionMethod' });
+ const signatureMethod = _.find(auth, { name: 'signatureMethod' });
+ 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' });
diff --git a/packages/bruno-lang/v2/src/collectionBruToJson.js b/packages/bruno-lang/v2/src/collectionBruToJson.js
index 61d373d91e..65a8c2881f 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 | authNTLM |authOAuth2 | authwsse | authapikey
+ auths = authawsv4 | authbasic | authbearer | authdigest | authNTLM | authOAuth1 | authOAuth2 | authwsse | authapikey
nl = "\\r"? "\\n"
st = " " | "\\t"
@@ -43,6 +43,7 @@ const grammar = ohm.grammar(`Bru {
authbearer = "auth:bearer" dictionary
authdigest = "auth:digest" dictionary
authNTLM = "auth:ntlm" dictionary
+ authOAuth1 = "auth:oauth1" dictionary
authOAuth2 = "auth:oauth2" dictionary
authwsse = "auth:wsse" dictionary
authapikey = "auth:apikey" dictionary
@@ -266,6 +267,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' });
@@ -330,7 +364,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
}
}
}
- },
+ },
authapikey(_1, dictionary) {
const auth = mapPairListToKeyValPairs(dictionary.ast, false);
diff --git a/packages/bruno-lang/v2/src/jsonToBru.js b/packages/bruno-lang/v2/src/jsonToBru.js
index 62b31c2f99..9fa0601090 100644
--- a/packages/bruno-lang/v2/src/jsonToBru.js
+++ b/packages/bruno-lang/v2/src/jsonToBru.js
@@ -165,7 +165,6 @@ ${indentString(`password: ${auth?.digest?.password || ''}`)}
`;
}
-
if (auth && auth.ntlm) {
bru += `auth:ntlm {
${indentString(`username: ${auth?.ntlm?.username || ''}`)}
@@ -175,7 +174,26 @@ ${indentString(`domain: ${auth?.ntlm?.domain || ''}`)}
}
`;
- }
+ }
+
+ 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 || ''}`)}
+}
+
+`;
+ }
if (auth && auth.oauth2) {
switch (auth?.oauth2?.grantType) {
diff --git a/packages/bruno-lang/v2/src/jsonToCollectionBru.js b/packages/bruno-lang/v2/src/jsonToCollectionBru.js
index c2a843dc6e..ad1cf1cfbb 100644
--- a/packages/bruno-lang/v2/src/jsonToCollectionBru.js
+++ b/packages/bruno-lang/v2/src/jsonToCollectionBru.js
@@ -140,6 +140,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 || ''}`)}
+}
+
`;
}
diff --git a/packages/bruno-schema/src/collections/index.js b/packages/bruno-schema/src/collections/index.js
index b6e044ae47..fa40bb3abf 100644
--- a/packages/bruno-schema/src/collections/index.js
+++ b/packages/bruno-schema/src/collections/index.js
@@ -127,8 +127,6 @@ const authDigestSchema = Yup.object({
.noUnknown(true)
.strict();
-
-
const authNTLMSchema = Yup.object({
username: Yup.string().nullable(),
password: Yup.string().nullable(),
@@ -136,7 +134,7 @@ const authDigestSchema = Yup.object({
})
.noUnknown(true)
- .strict();
+ .strict();
const authApiKeySchema = Yup.object({
key: Yup.string().nullable(),
@@ -146,6 +144,23 @@ const authApiKeySchema = Yup.object({
.noUnknown(true)
.strict();
+const oauth1Schema = Yup.object({
+ consumerKey: Yup.string().required(),
+ consumerSecret: Yup.string().required(),
+ requestTokenUrl: Yup.string().nullable(),
+ accessTokenUrl: Yup.string().nullable(),
+ authorizeUrl: Yup.string().nullable(),
+ callbackUrl: Yup.string().nullable(),
+ verifier: Yup.string().nullable(),
+ accessToken: Yup.string().nullable(),
+ accessTokenSecret: Yup.string().nullable(),
+ rsaPrivateKey: Yup.string().nullable(),
+ parameterTransmissionMethod: Yup.string().oneOf(['authorization_header', 'request_body', 'query_param']).nullable(),
+ signatureMethod: Yup.string().oneOf(['HMAC-SHA1', 'HMAC-SHA256', 'HMAC-SHA512', 'RSA-SHA1', 'RSA-SHA256', 'RSA-SHA512', 'PLAINTEXT']).nullable()
+})
+ .noUnknown(true)
+ .strict();
+
const oauth2Schema = Yup.object({
grantType: Yup.string()
.oneOf(['client_credentials', 'password', 'authorization_code'])
@@ -206,13 +221,14 @@ const oauth2Schema = Yup.object({
const authSchema = Yup.object({
mode: Yup.string()
- .oneOf(['inherit', 'none', 'awsv4', 'basic', 'bearer', 'digest', 'ntlm', 'oauth2', 'wsse', 'apikey'])
+ .oneOf(['inherit', 'none', 'awsv4', 'basic', 'bearer', 'digest', 'ntlm', 'oauth1', 'oauth2', 'wsse', 'apikey'])
.required('mode is required'),
awsv4: authAwsV4Schema.nullable(),
basic: authBasicSchema.nullable(),
bearer: authBearerSchema.nullable(),
ntlm: authNTLMSchema.nullable(),
digest: authDigestSchema.nullable(),
+ oauth1: oauth1Schema.nullable(),
oauth2: oauth2Schema.nullable(),
wsse: authWsseSchema.nullable(),
apikey: authApiKeySchema.nullable()