diff --git a/packages/bruno-app/src/components/Modal/StyledWrapper.js b/packages/bruno-app/src/components/Modal/StyledWrapper.js
index f0cb79353..4ec5d4a25 100644
--- a/packages/bruno-app/src/components/Modal/StyledWrapper.js
+++ b/packages/bruno-app/src/components/Modal/StyledWrapper.js
@@ -1,6 +1,8 @@
import styled from 'styled-components';
const Wrapper = styled.div`
+ color: ${(props) => props.theme.text};
+
&.modal--animate-out {
animation: fade-out 0.5s forwards cubic-bezier(0.19, 1, 0.22, 1);
diff --git a/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/StyledWrapper.js
new file mode 100644
index 000000000..6613dd1f3
--- /dev/null
+++ b/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/StyledWrapper.js
@@ -0,0 +1,25 @@
+import styled from 'styled-components';
+
+const Wrapper = styled.div`
+ font-size: 0.8125rem;
+
+ .auth-mode-selector {
+ background: ${(props) => props.theme.requestTabPanel.bodyModeSelect.color};
+ border-radius: 3px;
+
+ .dropdown-item {
+ padding: 0.2rem 0.6rem !important;
+ }
+
+ .label-item {
+ padding: 0.2rem 0.6rem !important;
+ }
+ }
+
+ .caret {
+ color: rgb(140, 140, 140);
+ fill: rgb(140 140 140);
+ }
+`;
+
+export default Wrapper;
diff --git a/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js b/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js
new file mode 100644
index 000000000..3ad80ce13
--- /dev/null
+++ b/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js
@@ -0,0 +1,70 @@
+import React, { useRef, forwardRef } from 'react';
+import get from 'lodash/get';
+import { IconCaretDown } from '@tabler/icons';
+import Dropdown from 'components/Dropdown';
+import { useDispatch } from 'react-redux';
+import { updateRequestAuthMode } from 'providers/ReduxStore/slices/collections';
+import { humanizeRequestAuthMode } from 'utils/collections';
+import StyledWrapper from './StyledWrapper';
+
+const AuthMode = ({ item, collection }) => {
+ const dispatch = useDispatch();
+ const dropdownTippyRef = useRef();
+ const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
+ const authMode = item.draft ? get(item, 'draft.request.auth.mode') : get(item, 'request.auth.mode');
+
+ const Icon = forwardRef((props, ref) => {
+ return (
+
+ {humanizeRequestAuthMode(authMode)}
+
+ );
+ });
+
+ const onModeChange = (value) => {
+ dispatch(
+ updateRequestAuthMode({
+ itemUid: item.uid,
+ collectionUid: collection.uid,
+ mode: value
+ })
+ );
+ };
+
+ return (
+
+
+
} placement="bottom-end">
+
{
+ dropdownTippyRef.current.hide();
+ onModeChange('basic');
+ }}
+ >
+ Basic Auth
+
+
{
+ dropdownTippyRef.current.hide();
+ onModeChange('bearer');
+ }}
+ >
+ Bearer Token
+
+
{
+ dropdownTippyRef.current.hide();
+ onModeChange('none');
+ }}
+ >
+ No Auth
+
+
+
+
+ );
+};
+export default AuthMode;
diff --git a/packages/bruno-app/src/components/RequestPane/Auth/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Auth/StyledWrapper.js
new file mode 100644
index 000000000..e49220854
--- /dev/null
+++ b/packages/bruno-app/src/components/RequestPane/Auth/StyledWrapper.js
@@ -0,0 +1,5 @@
+import styled from 'styled-components';
+
+const Wrapper = styled.div``;
+
+export default Wrapper;
diff --git a/packages/bruno-app/src/components/RequestPane/Auth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/index.js
new file mode 100644
index 000000000..e2f345d23
--- /dev/null
+++ b/packages/bruno-app/src/components/RequestPane/Auth/index.js
@@ -0,0 +1,32 @@
+import React from 'react';
+import get from 'lodash/get';
+import { useDispatch } from 'react-redux';
+import { updateRequestBody } from 'providers/ReduxStore/slices/collections';
+import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
+import StyledWrapper from './StyledWrapper';
+
+const RequestBody = ({ item, collection }) => {
+ const dispatch = useDispatch();
+ const authMode = item.draft ? get(item, 'draft.request.auth.mode') : get(item, 'request.auth.mode');
+
+ const onEdit = (value) => {
+ // dispatch(
+ // updateRequestBody({
+ // content: value,
+ // itemUid: item.uid,
+ // collectionUid: collection.uid
+ // })
+ // );
+ };
+
+ if (authMode === 'basic') {
+ return Basic Auth
;
+ }
+
+ if (authMode === 'bearer') {
+ return Bearer Token
;
+ }
+
+ return No Auth;
+};
+export default RequestBody;
diff --git a/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js b/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js
index caace776f..652414a62 100644
--- a/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js
+++ b/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js
@@ -7,6 +7,8 @@ import QueryParams from 'components/RequestPane/QueryParams';
import RequestHeaders from 'components/RequestPane/RequestHeaders';
import RequestBody from 'components/RequestPane/RequestBody';
import RequestBodyMode from 'components/RequestPane/RequestBody/RequestBodyMode';
+import Auth from 'components/RequestPane/Auth';
+import AuthMode from 'components/RequestPane/Auth/AuthMode';
import Vars from 'components/RequestPane/Vars';
import Assertions from 'components/RequestPane/Assertions';
import Script from 'components/RequestPane/Script';
@@ -38,6 +40,9 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
case 'headers': {
return ;
}
+ case 'auth': {
+ return ;
+ }
case 'vars': {
return ;
}
@@ -83,6 +88,9 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
selectTab('headers')}>
Headers
+ selectTab('auth')}>
+ Auth
+
selectTab('vars')}>
Vars
@@ -95,13 +103,16 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
selectTab('tests')}>
Tests
- {/* Moved to post mvp */}
- {/* selectTab('auth')}>Auth
*/}
{focusedTab.requestPaneTab === 'body' ? (
) : null}
+ {focusedTab.requestPaneTab === 'auth' ? (
+
+ ) : null}
{getTabPanel(focusedTab.requestPaneTab)}
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 213761029..176926e19 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
@@ -578,6 +578,20 @@ export const collectionsSlice = createSlice({
}
}
},
+ updateRequestAuthMode: (state, action) => {
+ const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
+
+ if (collection && collection.items && collection.items.length) {
+ const item = findItemInCollection(collection, action.payload.itemUid);
+
+ if (item && isItemARequest(item)) {
+ if (!item.draft) {
+ item.draft = cloneDeep(item);
+ }
+ item.draft.request.auth.mode = action.payload.mode;
+ }
+ }
+ },
updateRequestBodyMode: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
@@ -1186,6 +1200,7 @@ export const {
addMultipartFormParam,
updateMultipartFormParam,
deleteMultipartFormParam,
+ updateRequestAuthMode,
updateRequestBodyMode,
updateRequestBody,
updateRequestGraphqlQuery,
diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js
index 0a20cb448..a1688890a 100644
--- a/packages/bruno-app/src/utils/collections/index.js
+++ b/packages/bruno-app/src/utils/collections/index.js
@@ -449,6 +449,22 @@ export const humanizeRequestBodyMode = (mode) => {
return label;
};
+export const humanizeRequestAuthMode = (mode) => {
+ let label = 'No Auth';
+ switch (mode) {
+ case 'basic': {
+ label = 'Basic Auth';
+ break;
+ }
+ case 'bearer': {
+ label = 'Bearer Token';
+ break;
+ }
+ }
+
+ return label;
+};
+
export const refreshUidsInItem = (item) => {
item.uid = uuid();
diff --git a/packages/bruno-app/src/utils/common/index.js b/packages/bruno-app/src/utils/common/index.js
index c5eaa93ab..992ec233e 100644
--- a/packages/bruno-app/src/utils/common/index.js
+++ b/packages/bruno-app/src/utils/common/index.js
@@ -51,12 +51,12 @@ export const safeStringifyJSON = (obj, indent = false) => {
}
};
-export const safeParseXML = (str) => {
+export const safeParseXML = (str, options) => {
if (!str || !str.length || typeof str !== 'string') {
return str;
}
try {
- return xmlFormat(str);
+ return xmlFormat(str, options);
} catch (e) {
return str;
}
diff --git a/packages/bruno-electron/src/bru/index.js b/packages/bruno-electron/src/bru/index.js
index 45b10004f..a28c04a7b 100644
--- a/packages/bruno-electron/src/bru/index.js
+++ b/packages/bruno-electron/src/bru/index.js
@@ -61,6 +61,7 @@ const bruToJson = (bru) => {
url: _.get(json, 'http.url'),
params: _.get(json, 'query', []),
headers: _.get(json, 'headers', []),
+ auth: _.get(json, 'auth', {}),
body: _.get(json, 'body', {}),
script: _.get(json, 'script', {}),
vars: _.get(json, 'vars', {}),
@@ -69,6 +70,7 @@ const bruToJson = (bru) => {
}
};
+ transformedJson.request.auth.mode = _.get(json, 'http.auth', 'none');
transformedJson.request.body.mode = _.get(json, 'http.body', 'none');
return transformedJson;
@@ -104,10 +106,12 @@ const jsonToBru = (json) => {
http: {
method: _.lowerCase(_.get(json, 'request.method')),
url: _.get(json, 'request.url'),
+ auth: _.get(json, 'request.auth.mode', 'none'),
body: _.get(json, 'request.body.mode', 'none')
},
query: _.get(json, 'request.params', []),
headers: _.get(json, 'request.headers', []),
+ auth: _.get(json, 'request.auth', {}),
body: _.get(json, 'request.body', {}),
script: _.get(json, 'request.script', {}),
vars: {
diff --git a/packages/bruno-lang/v2/src/bruToJson.js b/packages/bruno-lang/v2/src/bruToJson.js
index 2241ca554..576c58c24 100644
--- a/packages/bruno-lang/v2/src/bruToJson.js
+++ b/packages/bruno-lang/v2/src/bruToJson.js
@@ -22,7 +22,8 @@ const { outdentString } = require('../../v1/src/utils');
*
*/
const grammar = ohm.grammar(`Bru {
- BruFile = (meta | http | query | headers | bodies | varsandassert | script | tests | docs)*
+ BruFile = (meta | http | query | headers | auths | bodies | varsandassert | script | tests | docs)*
+ auths = authbasic | authbearer
bodies = bodyjson | bodytext | bodyxml | bodygraphql | bodygraphqlvars | bodyforms | body
bodyforms = bodyformurlencoded | bodymultipart
@@ -75,6 +76,9 @@ const grammar = ohm.grammar(`Bru {
varsres = "vars:post-response" dictionary
assert = "assert" assertdictionary
+ authbasic = "auth:basic" dictionary
+ authbearer = "auth:bearer" dictionary
+
body = "body" st* "{" nl* textblock tagend
bodyjson = "body:json" st* "{" nl* textblock tagend
bodytext = "body:text" st* "{" nl* textblock tagend
@@ -92,13 +96,21 @@ const grammar = ohm.grammar(`Bru {
docs = "docs" st* "{" nl* textblock tagend
}`);
-const mapPairListToKeyValPairs = (pairList = []) => {
+const mapPairListToKeyValPairs = (pairList = [], parseEnabled = true) => {
if (!pairList.length) {
return [];
}
return _.map(pairList[0], (pair) => {
let name = _.keys(pair)[0];
let value = pair[name];
+
+ if (!parseEnabled) {
+ return {
+ name,
+ value
+ };
+ }
+
let enabled = true;
if (name && name.length && name.charAt(0) === '~') {
name = name.slice(1);
@@ -282,6 +294,33 @@ const sem = grammar.createSemantics().addAttribute('ast', {
headers: mapPairListToKeyValPairs(dictionary.ast)
};
},
+ authbasic(_1, dictionary) {
+ const auth = mapPairListToKeyValPairs(dictionary.ast, false);
+ const usernameKey = _.find(auth, { name: 'username' });
+ const passwordKey = _.find(auth, { name: 'password' });
+ const username = usernameKey ? usernameKey.value : '';
+ const password = passwordKey ? passwordKey.value : '';
+ return {
+ auth: {
+ basic: {
+ username,
+ password
+ }
+ }
+ };
+ },
+ authbearer(_1, dictionary) {
+ const auth = mapPairListToKeyValPairs(dictionary.ast, false);
+ const tokenKey = _.find(auth, { name: 'token' });
+ const token = tokenKey ? tokenKey.value : '';
+ return {
+ auth: {
+ bearer: {
+ token
+ }
+ }
+ };
+ },
bodyformurlencoded(_1, dictionary) {
return {
body: {
diff --git a/packages/bruno-lang/v2/src/jsonToBru.js b/packages/bruno-lang/v2/src/jsonToBru.js
index 818d7c9cb..b9a5779fa 100644
--- a/packages/bruno-lang/v2/src/jsonToBru.js
+++ b/packages/bruno-lang/v2/src/jsonToBru.js
@@ -13,7 +13,7 @@ const stripLastLine = (text) => {
};
const jsonToBru = (json) => {
- const { meta, http, query, headers, body, script, tests, vars, assertions, docs } = json;
+ const { meta, http, query, headers, auth, body, script, tests, vars, assertions, docs } = json;
let bru = '';
@@ -82,6 +82,23 @@ const jsonToBru = (json) => {
bru += '\n}\n\n';
}
+ if (auth && auth.basic) {
+ bru += `auth:basic {
+${indentString(`username: ${auth.basic.username}`)}
+${indentString(`password: ${auth.basic.password}`)}
+}
+
+`;
+ }
+
+ if (auth && auth.bearer) {
+ bru += `auth:bearer {
+${indentString(`token: ${auth.bearer.token}`)}
+}
+
+`;
+ }
+
if (body && body.json && body.json.length) {
bru += `body:json {
${indentString(body.json)}
diff --git a/packages/bruno-lang/v2/tests/fixtures/request.bru b/packages/bruno-lang/v2/tests/fixtures/request.bru
index ae7318da5..f340bb9a4 100644
--- a/packages/bruno-lang/v2/tests/fixtures/request.bru
+++ b/packages/bruno-lang/v2/tests/fixtures/request.bru
@@ -21,6 +21,15 @@ headers {
~transaction-id: {{transactionId}}
}
+auth:basic {
+ username: john
+ password: secret
+}
+
+auth:bearer {
+ token: 123
+}
+
body:json {
{
"hello": "world"
diff --git a/packages/bruno-lang/v2/tests/fixtures/request.json b/packages/bruno-lang/v2/tests/fixtures/request.json
index 867229de8..47995832d 100644
--- a/packages/bruno-lang/v2/tests/fixtures/request.json
+++ b/packages/bruno-lang/v2/tests/fixtures/request.json
@@ -43,6 +43,15 @@
"enabled": false
}
],
+ "auth": {
+ "basic": {
+ "username": "john",
+ "password": "secret"
+ },
+ "bearer": {
+ "token": "123"
+ }
+ },
"body": {
"json": "{\n \"hello\": \"world\"\n}",
"text": "This is a text body",
diff --git a/packages/bruno-schema/src/collections/index.js b/packages/bruno-schema/src/collections/index.js
index 7076175b5..81cd2528b 100644
--- a/packages/bruno-schema/src/collections/index.js
+++ b/packages/bruno-schema/src/collections/index.js
@@ -69,6 +69,27 @@ const requestBodySchema = Yup.object({
.noUnknown(true)
.strict();
+const authBasicSchema = Yup.object({
+ username: Yup.string().nullable(),
+ password: Yup.string().nullable()
+})
+ .noUnknown(true)
+ .strict();
+
+const authBearerSchema = Yup.object({
+ token: Yup.string().nullable()
+})
+ .noUnknown(true)
+ .strict();
+
+const authSchema = Yup.object({
+ mode: Yup.string().oneOf(['none', 'basic', 'bearer']).required('mode is required'),
+ basic: authBasicSchema.nullable(),
+ bearer: authBearerSchema.nullable()
+})
+ .noUnknown(true)
+ .strict();
+
// Right now, the request schema is very tightly coupled with http request
// As we introduce more request types in the future, we will improve the definition to support
// schema structure based on other request type
@@ -77,6 +98,7 @@ const requestSchema = Yup.object({
method: requestMethodSchema,
headers: Yup.array().of(keyValueSchema).required('headers are required'),
params: Yup.array().of(keyValueSchema).required('params are required'),
+ auth: authSchema,
body: requestBodySchema,
script: Yup.object({
req: Yup.string().nullable(),