From 51ee37cf969991449aa47fe4ad2255be19a76505 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Fri, 29 Sep 2023 01:35:22 +0530 Subject: [PATCH 1/4] feat(#119): bru lang support for basic and bearer auth --- packages/bruno-lang/v2/src/bruToJson.js | 43 ++++++++++++++++++- packages/bruno-lang/v2/src/jsonToBru.js | 19 +++++++- .../bruno-lang/v2/tests/fixtures/request.bru | 9 ++++ .../bruno-lang/v2/tests/fixtures/request.json | 9 ++++ 4 files changed, 77 insertions(+), 3 deletions(-) diff --git a/packages/bruno-lang/v2/src/bruToJson.js b/packages/bruno-lang/v2/src/bruToJson.js index 2241ca55..576c58c2 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 818d7c9c..b9a5779f 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 ae7318da..f340bb9a 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 867229de..47995832 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", From 0f1e330dc5be469947dac27c0178f4588f0b893a Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Fri, 29 Sep 2023 02:11:04 +0530 Subject: [PATCH 2/4] feat(#119): ui placeholders for basic and bearer auths --- .../Auth/AuthMode/StyledWrapper.js | 25 +++++++ .../RequestPane/Auth/AuthMode/index.js | 70 +++++++++++++++++++ .../RequestPane/Auth/StyledWrapper.js | 5 ++ .../src/components/RequestPane/Auth/index.js | 32 +++++++++ .../RequestPane/HttpRequestPane/index.js | 15 +++- .../ReduxStore/slices/collections/index.js | 15 ++++ .../bruno-app/src/utils/collections/index.js | 16 +++++ packages/bruno-electron/src/bru/index.js | 4 ++ .../bruno-schema/src/collections/index.js | 22 ++++++ 9 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 packages/bruno-app/src/components/RequestPane/Auth/AuthMode/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js create mode 100644 packages/bruno-app/src/components/RequestPane/Auth/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/RequestPane/Auth/index.js 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 00000000..6613dd1f --- /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 00000000..3ad80ce1 --- /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 00000000..e4922085 --- /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 00000000..e2f345d2 --- /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 caace776..652414a6 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 495989eb..aca21025 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -563,6 +563,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); @@ -1170,6 +1184,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 80fe41dd..c982f414 100644 --- a/packages/bruno-app/src/utils/collections/index.js +++ b/packages/bruno-app/src/utils/collections/index.js @@ -445,6 +445,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-electron/src/bru/index.js b/packages/bruno-electron/src/bru/index.js index 45b10004..a28c04a7 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-schema/src/collections/index.js b/packages/bruno-schema/src/collections/index.js index 7076175b..81cd2528 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(), From d63e7371fe0df7e0d8b60b58815a608d5ca5c4de Mon Sep 17 00:00:00 2001 From: pedward99 Date: Mon, 2 Oct 2023 10:39:29 -0400 Subject: [PATCH 3/4] Fix for XML Formatting Options --- packages/bruno-app/src/utils/common/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bruno-app/src/utils/common/index.js b/packages/bruno-app/src/utils/common/index.js index c5eaa93a..992ec233 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; } From 7a80247fc52c6ba87691f6a85d8db2ff88ec1c94 Mon Sep 17 00:00:00 2001 From: Its-treason <39559178+Its-treason@users.noreply.github.com> Date: Tue, 3 Oct 2023 00:48:49 +0200 Subject: [PATCH 4/4] fix: Color in environment delete modal --- packages/bruno-app/src/components/Modal/StyledWrapper.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/bruno-app/src/components/Modal/StyledWrapper.js b/packages/bruno-app/src/components/Modal/StyledWrapper.js index f0cb7935..4ec5d4a2 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);