mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-07 16:44:27 +01:00
Merge pull request #318 from usebruno/feature/auth-support
Feature/auth support
This commit is contained in:
commit
52428c6b35
@ -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;
|
@ -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 (
|
||||
<div ref={ref} className="flex items-center justify-center pl-3 py-1 select-none">
|
||||
{humanizeRequestAuthMode(authMode)} <IconCaretDown className="caret ml-2 mr-2" size={14} strokeWidth={2} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const onModeChange = (value) => {
|
||||
dispatch(
|
||||
updateRequestAuthMode({
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid,
|
||||
mode: value
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="inline-flex items-center cursor-pointer auth-mode-selector">
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('basic');
|
||||
}}
|
||||
>
|
||||
Basic Auth
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('bearer');
|
||||
}}
|
||||
>
|
||||
Bearer Token
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('none');
|
||||
}}
|
||||
>
|
||||
No Auth
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
export default AuthMode;
|
@ -0,0 +1,5 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div``;
|
||||
|
||||
export default Wrapper;
|
32
packages/bruno-app/src/components/RequestPane/Auth/index.js
Normal file
32
packages/bruno-app/src/components/RequestPane/Auth/index.js
Normal file
@ -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 <div>Basic Auth</div>;
|
||||
}
|
||||
|
||||
if (authMode === 'bearer') {
|
||||
return <div>Bearer Token</div>;
|
||||
}
|
||||
|
||||
return <StyledWrapper className="w-full">No Auth</StyledWrapper>;
|
||||
};
|
||||
export default RequestBody;
|
@ -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 <RequestHeaders item={item} collection={collection} />;
|
||||
}
|
||||
case 'auth': {
|
||||
return <Auth item={item} collection={collection} />;
|
||||
}
|
||||
case 'vars': {
|
||||
return <Vars item={item} collection={collection} />;
|
||||
}
|
||||
@ -83,6 +88,9 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
|
||||
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>
|
||||
Headers
|
||||
</div>
|
||||
<div className={getTabClassname('auth')} role="tab" onClick={() => selectTab('auth')}>
|
||||
Auth
|
||||
</div>
|
||||
<div className={getTabClassname('vars')} role="tab" onClick={() => selectTab('vars')}>
|
||||
Vars
|
||||
</div>
|
||||
@ -95,13 +103,16 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
|
||||
<div className={getTabClassname('tests')} role="tab" onClick={() => selectTab('tests')}>
|
||||
Tests
|
||||
</div>
|
||||
{/* Moved to post mvp */}
|
||||
{/* <div className={getTabClassname('auth')} role="tab" onClick={() => selectTab('auth')}>Auth</div> */}
|
||||
{focusedTab.requestPaneTab === 'body' ? (
|
||||
<div className="flex flex-grow justify-end items-center">
|
||||
<RequestBodyMode item={item} collection={collection} />
|
||||
</div>
|
||||
) : null}
|
||||
{focusedTab.requestPaneTab === 'auth' ? (
|
||||
<div className="flex flex-grow justify-end items-center">
|
||||
<AuthMode item={item} collection={collection} />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<section className={`flex w-full ${['script', 'vars'].includes(focusedTab.requestPaneTab) ? '' : 'mt-5'}`}>
|
||||
{getTabPanel(focusedTab.requestPaneTab)}
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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: {
|
||||
|
@ -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: {
|
||||
|
@ -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)}
|
||||
|
@ -21,6 +21,15 @@ headers {
|
||||
~transaction-id: {{transactionId}}
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: john
|
||||
password: secret
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: 123
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"hello": "world"
|
||||
|
@ -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",
|
||||
|
@ -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(),
|
||||
|
Loading…
Reference in New Issue
Block a user