diff --git a/packages/bruno-app/src/components/CodeEditor/index.js b/packages/bruno-app/src/components/CodeEditor/index.js index d1759bf4b..b3a192b42 100644 --- a/packages/bruno-app/src/components/CodeEditor/index.js +++ b/packages/bruno-app/src/components/CodeEditor/index.js @@ -43,6 +43,7 @@ if (!SERVER_RENDERED) { 'req.getUrl()', 'req.setUrl(url)', 'req.getMethod()', + 'req.getAuthMode()', 'req.setMethod(method)', 'req.getHeader(name)', 'req.getHeaders()', 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 747ee4d61..7280e6729 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/index.js @@ -70,6 +70,15 @@ const AuthMode = ({ collection }) => { > Digest Auth +
{ + dropdownTippyRef.current.hide(); + onModeChange('oauth2'); + }} + > + Oauth2 +
{ diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Ropc/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/AuthorizationCode/StyledWrapper.js similarity index 100% rename from packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Ropc/StyledWrapper.js rename to packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/AuthorizationCode/StyledWrapper.js diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/AuthorizationCode/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/AuthorizationCode/index.js new file mode 100644 index 000000000..13b94a20a --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/AuthorizationCode/index.js @@ -0,0 +1,100 @@ +import React from 'react'; +import get from 'lodash/get'; +import { useTheme } from 'providers/Theme'; +import { useDispatch } from 'react-redux'; +import SingleLineEditor from 'components/SingleLineEditor'; +import { saveCollectionRoot, sendCollectionOauth2Request } from 'providers/ReduxStore/slices/collections/actions'; +import StyledWrapper from './StyledWrapper'; +import { inputsConfig } from './inputsConfig'; +import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections/index'; + +const OAuth2AuthorizationCode = ({ collection }) => { + const dispatch = useDispatch(); + const { storedTheme } = useTheme(); + + const oAuth = get(collection, 'root.request.auth.oauth2', {}); + + const handleRun = async () => { + dispatch(sendCollectionOauth2Request(collection.uid)); + }; + + const handleSave = () => dispatch(saveCollectionRoot(collection.uid)); + + const { callbackUrl, authorizationUrl, accessTokenUrl, clientId, clientSecret, scope, pkce } = oAuth; + + const handleChange = (key, value) => { + dispatch( + updateCollectionAuth({ + mode: 'oauth2', + collectionUid: collection.uid, + content: { + grantType: 'authorization_code', + callbackUrl, + authorizationUrl, + accessTokenUrl, + clientId, + clientSecret, + scope, + pkce, + [key]: value + } + }) + ); + }; + + const handlePKCEToggle = (e) => { + dispatch( + updateCollectionAuth({ + mode: 'oauth2', + collectionUid: collection.uid, + content: { + grantType: 'authorization_code', + callbackUrl, + authorizationUrl, + accessTokenUrl, + clientId, + clientSecret, + scope, + pkce: !Boolean(oAuth?.['pkce']) + } + }) + ); + }; + + return ( + + {inputsConfig.map((input) => { + const { key, label } = input; + return ( +
+ +
+ handleChange(key, val)} + onRun={handleRun} + collection={collection} + /> +
+
+ ); + })} +
+ + +
+ +
+ ); +}; + +export default OAuth2AuthorizationCode; diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/AuthorizationCode/inputsConfig.js b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/AuthorizationCode/inputsConfig.js new file mode 100644 index 000000000..f7cc7801a --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/AuthorizationCode/inputsConfig.js @@ -0,0 +1,28 @@ +const inputsConfig = [ + { + key: 'callbackUrl', + label: 'Callback URL' + }, + { + key: 'authorizationUrl', + label: 'Authorization URL' + }, + { + key: 'accessTokenUrl', + label: 'Access Token URL' + }, + { + key: 'clientId', + label: 'Client ID' + }, + { + key: 'clientSecret', + label: 'Client Secret' + }, + { + key: 'scope', + label: 'Scope' + } +]; + +export { inputsConfig }; diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/ClientCredentials/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/ClientCredentials/StyledWrapper.js new file mode 100644 index 000000000..856f35b9b --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/ClientCredentials/StyledWrapper.js @@ -0,0 +1,16 @@ +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}; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/ClientCredentials/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/ClientCredentials/index.js new file mode 100644 index 000000000..5be4fde1d --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/ClientCredentials/index.js @@ -0,0 +1,69 @@ +import React from 'react'; +import get from 'lodash/get'; +import { useTheme } from 'providers/Theme'; +import { useDispatch } from 'react-redux'; +import SingleLineEditor from 'components/SingleLineEditor'; +import { saveCollectionRoot, sendCollectionOauth2Request } from 'providers/ReduxStore/slices/collections/actions'; +import StyledWrapper from './StyledWrapper'; +import { inputsConfig } from './inputsConfig'; +import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections/index'; + +const OAuth2ClientCredentials = ({ collection }) => { + const dispatch = useDispatch(); + const { storedTheme } = useTheme(); + + const oAuth = get(collection, 'root.request.auth.oauth2', {}); + + const handleRun = async () => { + dispatch(sendCollectionOauth2Request(collection.uid)); + }; + + const handleSave = () => dispatch(saveCollectionRoot(collection.uid)); + + const { accessTokenUrl, clientId, clientSecret, scope } = oAuth; + + const handleChange = (key, value) => { + dispatch( + updateCollectionAuth({ + mode: 'oauth2', + collectionUid: collection.uid, + content: { + grantType: 'client_credentials', + accessTokenUrl, + clientId, + clientSecret, + scope, + [key]: value + } + }) + ); + }; + + return ( + + {inputsConfig.map((input) => { + const { key, label } = input; + return ( +
+ +
+ handleChange(key, val)} + onRun={handleRun} + collection={collection} + /> +
+
+ ); + })} + +
+ ); +}; + +export default OAuth2ClientCredentials; diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/ClientCredentials/inputsConfig.js b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/ClientCredentials/inputsConfig.js new file mode 100644 index 000000000..164dcaab4 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/ClientCredentials/inputsConfig.js @@ -0,0 +1,20 @@ +const inputsConfig = [ + { + key: 'accessTokenUrl', + label: 'Access Token URL' + }, + { + key: 'clientId', + label: 'Client ID' + }, + { + key: 'clientSecret', + label: 'Client Secret' + }, + { + key: 'scope', + label: 'Scope' + } +]; + +export { inputsConfig }; diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/GrantTypeSelector/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/GrantTypeSelector/StyledWrapper.js new file mode 100644 index 000000000..bb42bdb49 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/GrantTypeSelector/StyledWrapper.js @@ -0,0 +1,54 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + font-size: 0.8125rem; + + .grant-type-mode-selector { + padding: 0.5rem 0px; + border-radius: 3px; + border: solid 1px ${(props) => props.theme.input.border}; + background-color: ${(props) => props.theme.input.bg}; + + .dropdown { + width: fit-content; + + div[data-tippy-root] { + width: fit-content; + } + .tippy-box { + width: fit-content; + max-width: none !important; + + .tippy-content: { + width: fit-content; + max-width: none !important; + } + } + } + + .grant-type-label { + width: fit-content; + color: ${(props) => props.theme.colors.text.yellow}; + justify-content: space-between; + padding: 0 0.5rem; + } + + .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); + } + label { + font-size: 0.8125rem; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/GrantTypeSelector/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/GrantTypeSelector/index.js new file mode 100644 index 000000000..690010c08 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/GrantTypeSelector/index.js @@ -0,0 +1,98 @@ +import React, { useRef, forwardRef } from 'react'; +import get from 'lodash/get'; +import Dropdown from 'components/Dropdown'; +import { useDispatch } from 'react-redux'; +import StyledWrapper from './StyledWrapper'; +import { IconCaretDown } from '@tabler/icons'; +import { updateAuth } from 'providers/ReduxStore/slices/collections'; +import { humanizeGrantType } from 'utils/collections'; +import { useEffect } from 'react'; +import { updateCollectionAuth, updateCollectionAuthMode } from 'providers/ReduxStore/slices/collections/index'; + +const GrantTypeSelector = ({ collection }) => { + const dispatch = useDispatch(); + const dropdownTippyRef = useRef(); + const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref); + + const oAuth = get(collection, 'root.request.auth.oauth2', {}); + + const Icon = forwardRef((props, ref) => { + return ( +
+ {humanizeGrantType(oAuth?.grantType)} +
+ ); + }); + + const onGrantTypeChange = (grantType) => { + dispatch( + updateCollectionAuth({ + mode: 'oauth2', + collectionUid: collection.uid, + content: { + grantType + } + }) + ); + }; + + useEffect(() => { + // initalize redux state with a default oauth2 grant type + // authorization_code - default option + !oAuth?.grantType && + dispatch( + updateCollectionAuthMode({ + mode: 'oauth2', + collectionUid: collection.uid + }) + ); + !oAuth?.grantType && + dispatch( + updateCollectionAuth({ + mode: 'oauth2', + collectionUid: collection.uid, + content: { + grantType: 'authorization_code' + } + }) + ); + }, [oAuth]); + + return ( + + +
+ } placement="bottom-end"> +
{ + dropdownTippyRef.current.hide(); + onGrantTypeChange('password'); + }} + > + Password Credentials +
+
{ + dropdownTippyRef.current.hide(); + onGrantTypeChange('authorization_code'); + }} + > + Authorization Code +
+
{ + dropdownTippyRef.current.hide(); + onGrantTypeChange('client_credentials'); + }} + > + Client Credentials +
+
+
+
+ ); +}; +export default GrantTypeSelector; diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/PasswordCredentials/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/PasswordCredentials/StyledWrapper.js new file mode 100644 index 000000000..856f35b9b --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/PasswordCredentials/StyledWrapper.js @@ -0,0 +1,16 @@ +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}; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/PasswordCredentials/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/PasswordCredentials/index.js new file mode 100644 index 000000000..70f134766 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/PasswordCredentials/index.js @@ -0,0 +1,69 @@ +import React from 'react'; +import get from 'lodash/get'; +import { useTheme } from 'providers/Theme'; +import { useDispatch } from 'react-redux'; +import SingleLineEditor from 'components/SingleLineEditor'; +import { saveCollectionRoot, sendCollectionOauth2Request } from 'providers/ReduxStore/slices/collections/actions'; +import StyledWrapper from './StyledWrapper'; +import { inputsConfig } from './inputsConfig'; +import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections/index'; + +const OAuth2AuthorizationCode = ({ item, collection }) => { + const dispatch = useDispatch(); + const { storedTheme } = useTheme(); + + const oAuth = get(collection, 'root.request.auth.oauth2', {}); + + const handleRun = async () => { + dispatch(sendCollectionOauth2Request(collection.uid)); + }; + + const handleSave = () => dispatch(saveCollectionRoot(collection.uid)); + + const { accessTokenUrl, username, password, scope } = oAuth; + + const handleChange = (key, value) => { + dispatch( + updateCollectionAuth({ + mode: 'oauth2', + collectionUid: collection.uid, + content: { + grantType: 'password', + accessTokenUrl, + username, + password, + scope, + [key]: value + } + }) + ); + }; + + return ( + + {inputsConfig.map((input) => { + const { key, label } = input; + return ( +
+ +
+ handleChange(key, val)} + onRun={handleRun} + collection={collection} + /> +
+
+ ); + })} + +
+ ); +}; + +export default OAuth2AuthorizationCode; diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/PasswordCredentials/inputsConfig.js b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/PasswordCredentials/inputsConfig.js new file mode 100644 index 000000000..1a20fed83 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/PasswordCredentials/inputsConfig.js @@ -0,0 +1,20 @@ +const inputsConfig = [ + { + key: 'accessTokenUrl', + label: 'Access Token URL' + }, + { + key: 'username', + label: 'Username' + }, + { + key: 'password', + label: 'Password' + }, + { + key: 'scope', + label: 'Scope' + } +]; + +export { inputsConfig }; diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/StyledWrapper.js new file mode 100644 index 000000000..856f35b9b --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/StyledWrapper.js @@ -0,0 +1,16 @@ +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}; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/index.js new file mode 100644 index 000000000..1aa674ab9 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/index.js @@ -0,0 +1,37 @@ +import React from 'react'; +import get from 'lodash/get'; +import StyledWrapper from './StyledWrapper'; +import GrantTypeSelector from './GrantTypeSelector/index'; +import OAuth2PasswordCredentials from './PasswordCredentials/index'; +import OAuth2AuthorizationCode from './AuthorizationCode/index'; +import OAuth2ClientCredentials from './ClientCredentials/index'; + +const grantTypeComponentMap = (grantType, collection) => { + switch (grantType) { + case 'password': + return ; + break; + case 'authorization_code': + return ; + break; + case 'client_credentials': + return ; + break; + default: + return
TBD
; + break; + } +}; + +const OAuth2 = ({ collection }) => { + const oAuth = get(collection, 'root.request.auth.oauth2', {}); + + return ( + + + {grantTypeComponentMap(oAuth?.grantType, collection)} + + ); +}; + +export default OAuth2; diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/index.js index 7873cfcd0..c874e2782 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/index.js @@ -8,6 +8,7 @@ import BasicAuth from './BasicAuth'; import DigestAuth from './DigestAuth'; import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions'; import StyledWrapper from './StyledWrapper'; +import OAuth2 from './OAuth2'; const Auth = ({ collection }) => { const authMode = get(collection, 'root.request.auth.mode'); @@ -29,6 +30,9 @@ const Auth = ({ collection }) => { case 'digest': { return ; } + case 'oauth2': { + return ; + } } }; @@ -38,7 +42,6 @@ const Auth = ({ collection }) => {
{getAuthView()} -
+ + + ))} + + +
); })} +
+ + +
diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AuthorizationCode/inputsConfig.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AuthorizationCode/inputsConfig.js index a1f3f3d45..f7cc7801a 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AuthorizationCode/inputsConfig.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/AuthorizationCode/inputsConfig.js @@ -5,7 +5,7 @@ const inputsConfig = [ }, { key: 'authorizationUrl', - label: 'Auth URL' + label: 'Authorization URL' }, { key: 'accessTokenUrl', diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/ClientCredentials/index.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/ClientCredentials/index.js index b8c460cac..7edb8bb25 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/ClientCredentials/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/ClientCredentials/index.js @@ -4,8 +4,9 @@ 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 { saveRequest, sendRequest } from 'providers/ReduxStore/slices/collections/actions'; import StyledWrapper from './StyledWrapper'; +import { inputsConfig } from './inputsConfig'; const OAuth2ClientCredentials = ({ item, collection }) => { const dispatch = useDispatch(); @@ -13,25 +14,15 @@ const OAuth2ClientCredentials = ({ item, collection }) => { const oAuth = item.draft ? get(item, 'draft.request.auth.oauth2', {}) : get(item, 'request.auth.oauth2', {}); - const handleRun = () => dispatch(sendRequest(item, collection.uid)); - const handleSave = () => dispatch(saveRequest(item.uid, collection.uid)); - - const handleClientIdChange = (clientId) => { - dispatch( - updateAuth({ - mode: 'oauth2', - collectionUid: collection.uid, - itemUid: item.uid, - content: { - grantType: 'client_credentials', - clientId: clientId, - clientSecret: oAuth.clientSecret - } - }) - ); + const handleRun = async () => { + dispatch(sendRequest(item, collection.uid)); }; - const handleClientSecretChange = (clientSecret) => { + const handleSave = () => dispatch(saveRequest(item.uid, collection.uid)); + + const { accessTokenUrl, clientId, clientSecret, scope } = oAuth; + + const handleChange = (key, value) => { dispatch( updateAuth({ mode: 'oauth2', @@ -39,38 +30,39 @@ const OAuth2ClientCredentials = ({ item, collection }) => { itemUid: item.uid, content: { grantType: 'client_credentials', - clientId: oAuth.clientId, - clientSecret: clientSecret + accessTokenUrl, + clientId, + clientSecret, + scope, + [key]: value } }) ); }; return ( - - -
- handleClientIdChange(val)} - onRun={handleRun} - collection={collection} - /> -
- - -
- handleClientSecretChange(val)} - onRun={handleRun} - collection={collection} - /> -
+ + {inputsConfig.map((input) => { + const { key, label } = input; + return ( +
+ +
+ handleChange(key, val)} + onRun={handleRun} + collection={collection} + /> +
+
+ ); + })} +
); }; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/ClientCredentials/inputsConfig.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/ClientCredentials/inputsConfig.js new file mode 100644 index 000000000..164dcaab4 --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/ClientCredentials/inputsConfig.js @@ -0,0 +1,20 @@ +const inputsConfig = [ + { + key: 'accessTokenUrl', + label: 'Access Token URL' + }, + { + key: 'clientId', + label: 'Client ID' + }, + { + key: 'clientSecret', + label: 'Client Secret' + }, + { + key: 'scope', + label: 'Scope' + } +]; + +export { inputsConfig }; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/GrantTypeSelector/index.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/GrantTypeSelector/index.js index ed3c8937d..62ae27194 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/GrantTypeSelector/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/GrantTypeSelector/index.js @@ -37,7 +37,7 @@ const GrantTypeSelector = ({ item, collection }) => { }; useEffect(() => { - // initalize redux state with a default oauth2 auth type + // initalize redux state with a default oauth2 grant type // authorization_code - default option !oAuth?.grantType && dispatch( @@ -64,7 +64,7 @@ const GrantTypeSelector = ({ item, collection }) => { onGrantTypeChange('password'); }} > - Resource Owner Password Credentials + Password Credentials
props.theme.input.border}; + background-color: ${(props) => props.theme.input.bg}; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/PasswordCredentials/index.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/PasswordCredentials/index.js new file mode 100644 index 000000000..be56ba1e1 --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/PasswordCredentials/index.js @@ -0,0 +1,70 @@ +import React 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 { saveRequest, sendRequest } from 'providers/ReduxStore/slices/collections/actions'; +import StyledWrapper from './StyledWrapper'; +import { inputsConfig } from './inputsConfig'; + +const OAuth2AuthorizationCode = ({ item, collection }) => { + const dispatch = useDispatch(); + const { storedTheme } = useTheme(); + + const oAuth = item.draft ? get(item, 'draft.request.auth.oauth2', {}) : get(item, 'request.auth.oauth2', {}); + + const handleRun = async () => { + dispatch(sendRequest(item, collection.uid)); + }; + + const handleSave = () => dispatch(saveRequest(item.uid, collection.uid)); + + const { accessTokenUrl, username, password, scope } = oAuth; + + const handleChange = (key, value) => { + dispatch( + updateAuth({ + mode: 'oauth2', + collectionUid: collection.uid, + itemUid: item.uid, + content: { + grantType: 'password', + accessTokenUrl, + username, + password, + scope, + [key]: value + } + }) + ); + }; + + return ( + + {inputsConfig.map((input) => { + const { key, label } = input; + return ( +
+ +
+ handleChange(key, val)} + onRun={handleRun} + collection={collection} + /> +
+
+ ); + })} + +
+ ); +}; + +export default OAuth2AuthorizationCode; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/PasswordCredentials/inputsConfig.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/PasswordCredentials/inputsConfig.js new file mode 100644 index 000000000..1a20fed83 --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/PasswordCredentials/inputsConfig.js @@ -0,0 +1,20 @@ +const inputsConfig = [ + { + key: 'accessTokenUrl', + label: 'Access Token URL' + }, + { + key: 'username', + label: 'Username' + }, + { + key: 'password', + label: 'Password' + }, + { + key: 'scope', + label: 'Scope' + } +]; + +export { inputsConfig }; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Ropc/index.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Ropc/index.js deleted file mode 100644 index 104ebdd77..000000000 --- a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Ropc/index.js +++ /dev/null @@ -1,78 +0,0 @@ -import React 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'; - -const OAuth2Ropc = ({ item, collection }) => { - const dispatch = useDispatch(); - const { storedTheme } = useTheme(); - - const oAuth = item.draft ? get(item, 'draft.request.auth.oauth2', {}) : get(item, 'request.auth.oauth2', {}); - - const handleRun = () => dispatch(sendRequest(item, collection.uid)); - const handleSave = () => dispatch(saveRequest(item.uid, collection.uid)); - - const handleUsernameChange = (username) => { - dispatch( - updateAuth({ - mode: 'oauth2', - collectionUid: collection.uid, - itemUid: item.uid, - content: { - grantType: 'password', - username: username, - password: oAuth.password - } - }) - ); - }; - - const handlePasswordChange = (password) => { - dispatch( - updateAuth({ - mode: 'oauth2', - collectionUid: collection.uid, - itemUid: item.uid, - content: { - grantType: 'password', - username: oAuth.username, - password: password - } - }) - ); - }; - - return ( - - -
- handleUsernameChange(val)} - onRun={handleRun} - collection={collection} - /> -
- - -
- handlePasswordChange(val)} - onRun={handleRun} - collection={collection} - /> -
-
- ); -}; - -export default OAuth2Ropc; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/index.js b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/index.js index 1d51962a5..3965c8d3e 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/OAuth2/index.js @@ -2,14 +2,14 @@ import React from 'react'; import get from 'lodash/get'; import StyledWrapper from './StyledWrapper'; import GrantTypeSelector from './GrantTypeSelector/index'; -import OAuth2Ropc from './Ropc/index'; +import OAuth2PasswordCredentials from './PasswordCredentials/index'; import OAuth2AuthorizationCode from './AuthorizationCode/index'; import OAuth2ClientCredentials from './ClientCredentials/index'; const grantTypeComponentMap = (grantType, item, collection) => { switch (grantType) { case 'password': - return ; + return ; break; case 'authorization_code': return ; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/index.js index c959d5faf..f525b065c 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/index.js @@ -35,8 +35,20 @@ const Auth = ({ item, collection }) => { case 'inherit': { return (
-
Auth inherited from the Collection:
-
{humanizeRequestAuthMode(collectionAuth?.mode)}
+ {collectionAuth?.mode === 'oauth2' ? ( +
+
+
Collection level auth is:
+
{humanizeRequestAuthMode(collectionAuth?.mode)}
+
+
Cannot inherit Oauth2 from collection.
+
+ ) : ( + <> +
Auth inherited from the Collection:
+
{humanizeRequestAuthMode(collectionAuth?.mode)}
+ + )}
); } diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index f08335826..8143cbc04 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -40,6 +40,7 @@ import { each } from 'lodash'; import { closeAllCollectionTabs } from 'providers/ReduxStore/slices/tabs'; import { resolveRequestFilename } from 'utils/common/platform'; import { parseQueryParams, splitOnFirst } from 'utils/url/index'; +import { sendCollectionOauth2Request as _sendCollectionOauth2Request } from 'utils/network/index'; export const renameCollection = (newName, collectionUid) => (dispatch, getState) => { const state = getState(); @@ -138,6 +139,35 @@ export const saveCollectionRoot = (collectionUid) => (dispatch, getState) => { }); }; +export const sendCollectionOauth2Request = (collectionUid) => (dispatch, getState) => { + const state = getState(); + const collection = findCollectionByUid(state.collections.collections, collectionUid); + + return new Promise((resolve, reject) => { + if (!collection) { + return reject(new Error('Collection not found')); + } + + const collectionCopy = cloneDeep(collection); + + const environment = findEnvironmentInCollection(collectionCopy, collection.activeEnvironmentUid); + + _sendCollectionOauth2Request(collection, environment, collectionCopy.collectionVariables) + .then((response) => { + if (response?.data?.error) { + toast.error(response?.data?.error); + } else { + toast.success('Request made successfully'); + } + return response; + }) + .then(resolve) + .catch((err) => { + toast.error(err.message); + }); + }); +}; + export const sendRequest = (item, collectionUid) => (dispatch, getState) => { const state = getState(); const collection = findCollectionByUid(state.collections.collections, collectionUid); @@ -147,7 +177,7 @@ export const sendRequest = (item, collectionUid) => (dispatch, getState) => { return reject(new Error('Collection not found')); } - const itemCopy = cloneDeep(item); + const itemCopy = cloneDeep(item || {}); const collectionCopy = cloneDeep(collection); const environment = findEnvironmentInCollection(collectionCopy, collection.activeEnvironmentUid); 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 41b284149..7494c6ae7 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -678,6 +678,7 @@ export const collectionsSlice = createSlice({ if (!item.draft) { item.draft = cloneDeep(item); } + item.draft.request.auth = {}; item.draft.request.auth.mode = action.payload.mode; } } @@ -978,6 +979,7 @@ export const collectionsSlice = createSlice({ const collection = findCollectionByUid(state.collections, action.payload.collectionUid); if (collection) { + set(collection, 'root.request.auth', {}); set(collection, 'root.request.auth.mode', action.payload.mode); } }, @@ -985,6 +987,8 @@ export const collectionsSlice = createSlice({ const collection = findCollectionByUid(state.collections, action.payload.collectionUid); if (collection) { + set(collection, 'root.request.auth', {}); + set(collection, 'root.request.auth.mode', action.payload.mode); switch (action.payload.mode) { case 'awsv4': set(collection, 'root.request.auth.awsv4', action.payload.content); @@ -998,6 +1002,9 @@ export const collectionsSlice = createSlice({ case 'digest': set(collection, 'root.request.auth.digest', 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 a77a789d4..a5b109e15 100644 --- a/packages/bruno-app/src/utils/collections/index.js +++ b/packages/bruno-app/src/utils/collections/index.js @@ -522,7 +522,7 @@ export const humanizeGrantType = (mode) => { let label = 'No Auth'; switch (mode) { case 'password': { - label = 'Resource Owner Password Credentials'; + label = 'Password Credentials'; break; } case 'authorization_code': { diff --git a/packages/bruno-app/src/utils/network/index.js b/packages/bruno-app/src/utils/network/index.js index 4ee055fda..2c2951592 100644 --- a/packages/bruno-app/src/utils/network/index.js +++ b/packages/bruno-app/src/utils/network/index.js @@ -33,6 +33,16 @@ const sendHttpRequest = async (item, collection, environment, collectionVariable }); }; +export const sendCollectionOauth2Request = async (collection, environment, collectionVariables) => { + return new Promise((resolve, reject) => { + const { ipcRenderer } = window; + ipcRenderer + .invoke('send-collection-oauth2-request', collection, environment, collectionVariables) + .then(resolve) + .catch(reject); + }); +}; + export const fetchGqlSchema = async (endpoint, environment, request, collection) => { return new Promise((resolve, reject) => { const { ipcRenderer } = window; diff --git a/packages/bruno-cli/src/runner/interpolate-vars.js b/packages/bruno-cli/src/runner/interpolate-vars.js index 2585c1e3c..f3466fa20 100644 --- a/packages/bruno-cli/src/runner/interpolate-vars.js +++ b/packages/bruno-cli/src/runner/interpolate-vars.js @@ -105,7 +105,7 @@ const interpolateVars = (request, envVars = {}, collectionVariables = {}, proces const password = _interpolate(request.auth.password) || ''; // use auth header based approach and delete the request.auth object - request.headers['authorization'] = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`; + request.headers['Authorization'] = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`; delete request.auth; } diff --git a/packages/bruno-cli/src/runner/prepare-request.js b/packages/bruno-cli/src/runner/prepare-request.js index b6c9e74e0..78c5f7538 100644 --- a/packages/bruno-cli/src/runner/prepare-request.js +++ b/packages/bruno-cli/src/runner/prepare-request.js @@ -52,7 +52,7 @@ const prepareRequest = (request, collectionRoot) => { } if (collectionAuth.mode === 'bearer') { - axiosRequest.headers['authorization'] = `Bearer ${get(collectionAuth, 'bearer.token')}`; + axiosRequest.headers['Authorization'] = `Bearer ${get(collectionAuth, 'bearer.token')}`; } } @@ -76,7 +76,7 @@ const prepareRequest = (request, collectionRoot) => { } if (request.auth.mode === 'bearer') { - axiosRequest.headers['authorization'] = `Bearer ${get(request, 'auth.bearer.token')}`; + axiosRequest.headers['Authorization'] = `Bearer ${get(request, 'auth.bearer.token')}`; } } diff --git a/packages/bruno-electron/src/ipc/network/authorize-user-in-window.js b/packages/bruno-electron/src/ipc/network/authorize-user-in-window.js index e4439f612..57cccd29c 100644 --- a/packages/bruno-electron/src/ipc/network/authorize-user-in-window.js +++ b/packages/bruno-electron/src/ipc/network/authorize-user-in-window.js @@ -30,7 +30,7 @@ const authorizeUserInWindow = ({ authorizeUrl, callbackUrl }) => { const callbackUrlWithCode = new URL(finalUrl); const authorizationCode = callbackUrlWithCode.searchParams.get('code'); - return resolve(authorizationCode); + return resolve({ authorizationCode }); } catch (error) { return reject(error); } diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 3446d5256..6260d1dc7 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -12,6 +12,7 @@ const { ipcMain } = require('electron'); const { isUndefined, isNull, each, get, compact, cloneDeep } = require('lodash'); const { VarsRuntime, AssertRuntime, ScriptRuntime, TestRuntime } = require('@usebruno/js'); const prepareRequest = require('./prepare-request'); +const prepareCollectionRequest = require('./prepare-collection-request'); const prepareGqlIntrospectionRequest = require('./prepare-gql-introspection-request'); const { cancelTokens, saveCancelToken, deleteCancelToken } = require('../../utils/cancel-token'); const { uuid } = require('../../utils/common'); @@ -29,7 +30,11 @@ const { addDigestInterceptor } = require('./digestauth-helper'); const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../../utils/proxy-util'); const { chooseFileToSave, writeBinaryFile } = require('../../utils/filesystem'); const { getCookieStringForUrl, addCookieToJar, getDomainsWithCookies } = require('../../utils/cookies'); -const { resolveOAuth2AuthorizationCodecessToken } = require('./oauth2-authorization-code-helper'); +const { + resolveOAuth2AuthorizationCodeAccessToken, + transformClientCredentialsRequest, + transformPasswordCredentialsRequest +} = require('./oauth2-helper'); // override the default escape function to prevent escaping Mustache.escape = function (value) { @@ -191,12 +196,30 @@ const configureRequest = async ( const axiosInstance = makeAxiosInstance(); if (request.oauth2) { - if (request?.oauth2?.grantType == 'authorization_code') { - let requestCopy = cloneDeep(request); - interpolateVars(requestCopy, envVars, collectionVariables, processEnvVars); - const { data, url } = await resolveOAuth2AuthorizationCodecessToken(requestCopy); - request.data = data; - request.url = url; + let requestCopy = cloneDeep(request); + switch (request?.oauth2?.grantType) { + case 'authorization_code': + interpolateVars(requestCopy, envVars, collectionVariables, processEnvVars); + const { data: authorizationCodeData, url: authorizationCodeAccessTokenUrl } = + await resolveOAuth2AuthorizationCodeAccessToken(requestCopy); + request.data = authorizationCodeData; + request.url = authorizationCodeAccessTokenUrl; + break; + case 'client_credentials': + interpolateVars(requestCopy, envVars, collectionVariables, processEnvVars); + const { data: clientCredentialsData, url: clientCredentialsAccessTokenUrl } = + await transformClientCredentialsRequest(requestCopy); + request.data = clientCredentialsData; + request.url = clientCredentialsAccessTokenUrl; + break; + case 'password': + interpolateVars(requestCopy, envVars, collectionVariables, processEnvVars); + const { data: passwordData, url: passwordAccessTokenUrl } = await transformPasswordCredentialsRequest( + requestCopy + ); + request.data = passwordData; + request.url = passwordAccessTokenUrl; + break; } } @@ -219,7 +242,6 @@ const configureRequest = async ( request.headers['cookie'] = cookieString; } } - return axiosInstance; }; @@ -594,6 +616,79 @@ const registerNetworkIpc = (mainWindow) => { } }); + ipcMain.handle('send-collection-oauth2-request', async (event, collection, environment, collectionVariables) => { + try { + const collectionUid = collection.uid; + const collectionPath = collection.pathname; + const requestUid = uuid(); + + const collectionRoot = get(collection, 'root', {}); + const _request = collectionRoot?.request; + const request = prepareCollectionRequest(_request, collectionRoot, collectionPath); + const envVars = getEnvVars(environment); + const processEnvVars = getProcessEnvVars(collectionUid); + const brunoConfig = getBrunoConfig(collectionUid); + const scriptingConfig = get(brunoConfig, 'scripts', {}); + + await runPreRequest( + request, + requestUid, + envVars, + collectionPath, + collectionRoot, + collectionUid, + collectionVariables, + processEnvVars, + scriptingConfig + ); + + interpolateVars(request, envVars, collection.collectionVariables, processEnvVars); + const axiosInstance = await configureRequest( + collection.uid, + request, + envVars, + collection.collectionVariables, + processEnvVars, + collectionPath + ); + + try { + response = await axiosInstance(request); + } catch (error) { + if (error?.response) { + response = error.response; + } else { + return Promise.reject(error); + } + } + + const { data } = parseDataFromResponse(response); + response.data = data; + + await runPostResponse( + request, + response, + requestUid, + envVars, + collectionPath, + collectionRoot, + collectionUid, + collectionVariables, + processEnvVars, + scriptingConfig + ); + + return { + status: response.status, + statusText: response.statusText, + headers: response.headers, + data: response.data + }; + } catch (error) { + return Promise.reject(error); + } + }); + ipcMain.handle('cancel-http-request', async (event, cancelTokenUid) => { return new Promise((resolve, reject) => { if (cancelTokenUid && cancelTokens[cancelTokenUid]) { diff --git a/packages/bruno-electron/src/ipc/network/interpolate-vars.js b/packages/bruno-electron/src/ipc/network/interpolate-vars.js index abf06bd86..4fd0dfe2b 100644 --- a/packages/bruno-electron/src/ipc/network/interpolate-vars.js +++ b/packages/bruno-electron/src/ipc/network/interpolate-vars.js @@ -104,21 +104,26 @@ const interpolateVars = (request, envVars = {}, collectionVariables = {}, proces const username = _interpolate(request.auth.username) || ''; const password = _interpolate(request.auth.password) || ''; // use auth header based approach and delete the request.auth object - request.headers['authorization'] = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`; + request.headers['Authorization'] = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`; delete request.auth; } if (request?.oauth2?.grantType) { + let username, password, scope, clientId, clientSecret; switch (request.oauth2.grantType) { case 'password': - let username = _interpolate(request.oauth2.username) || ''; - let password = _interpolate(request.oauth2.password) || ''; + username = _interpolate(request.oauth2.username) || ''; + password = _interpolate(request.oauth2.password) || ''; + scope = _interpolate(request.oauth2.scope) || ''; + request.oauth2.accessTokenUrl = _interpolate(request.oauth2.accessTokenUrl) || ''; request.oauth2.username = username; request.oauth2.password = password; + request.oauth2.scope = scope; request.data = { grant_type: 'password', username, - password + password, + scope }; break; case 'authorization_code': @@ -128,16 +133,21 @@ const interpolateVars = (request, envVars = {}, collectionVariables = {}, proces request.oauth2.clientId = _interpolate(request.oauth2.clientId) || ''; request.oauth2.clientSecret = _interpolate(request.oauth2.clientSecret) || ''; request.oauth2.scope = _interpolate(request.oauth2.scope) || ''; + request.oauth2.pkce = _interpolate(request.oauth2.pkce) || false; break; case 'client_credentials': - let clientId = _interpolate(request.oauth2.clientId) || ''; - let clientSecret = _interpolate(request.oauth2.clientSecret) || ''; + clientId = _interpolate(request.oauth2.clientId) || ''; + clientSecret = _interpolate(request.oauth2.clientSecret) || ''; + scope = _interpolate(request.oauth2.scope) || ''; + request.oauth2.accessTokenUrl = _interpolate(request.oauth2.accessTokenUrl) || ''; request.oauth2.clientId = clientId; request.oauth2.clientSecret = clientSecret; + request.oauth2.scope = scope; request.data = { grant_type: 'client_credentials', client_id: clientId, - client_secret: clientSecret + client_secret: clientSecret, + scope }; break; default: diff --git a/packages/bruno-electron/src/ipc/network/oauth2-authorization-code-helper.js b/packages/bruno-electron/src/ipc/network/oauth2-authorization-code-helper.js deleted file mode 100644 index 303af8170..000000000 --- a/packages/bruno-electron/src/ipc/network/oauth2-authorization-code-helper.js +++ /dev/null @@ -1,41 +0,0 @@ -const { get, cloneDeep } = require('lodash'); -const { authorizeUserInWindow } = require('./authorize-user-in-window'); - -const resolveOAuth2AuthorizationCodecessToken = async (request) => { - let requestCopy = cloneDeep(request); - const authorization_code = await getOAuth2AuthorizationCode(requestCopy); - const oAuth = get(requestCopy, 'oauth2', {}); - const { clientId, clientSecret, callbackUrl, scope } = oAuth; - const data = { - grant_type: 'authorization_code', - code: authorization_code, - redirect_uri: callbackUrl, - client_id: clientId, - client_secret: clientSecret, - scope: scope - }; - const url = requestCopy?.oauth2?.accessTokenUrl; - return { - data, - url - }; -}; - -const getOAuth2AuthorizationCode = (request) => { - return new Promise(async (resolve, reject) => { - const { oauth2 } = request; - const { callbackUrl, clientId, authorizationUrl, scope } = oauth2; - const authorizationUrlWithQueryParams = `${authorizationUrl}?client_id=${clientId}&redirect_uri=${callbackUrl}&response_type=code&scope=${scope}`; - try { - const code = await authorizeUserInWindow({ authorizeUrl: authorizationUrlWithQueryParams, callbackUrl }); - resolve(code); - } catch (err) { - reject(err); - } - }); -}; - -module.exports = { - resolveOAuth2AuthorizationCodecessToken, - getOAuth2AuthorizationCode -}; diff --git a/packages/bruno-electron/src/ipc/network/oauth2-helper.js b/packages/bruno-electron/src/ipc/network/oauth2-helper.js new file mode 100644 index 000000000..1367523a0 --- /dev/null +++ b/packages/bruno-electron/src/ipc/network/oauth2-helper.js @@ -0,0 +1,109 @@ +const { get, cloneDeep } = require('lodash'); +const crypto = require('crypto'); +const { authorizeUserInWindow } = require('./authorize-user-in-window'); + +const generateCodeVerifier = () => { + return crypto.randomBytes(16).toString('hex'); +}; + +const generateCodeChallenge = (codeVerifier) => { + const hash = crypto.createHash('sha256'); + hash.update(codeVerifier); + const base64Hash = hash.digest('base64'); + return base64Hash.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); +}; + +// AUTHORIZATION CODE + +const resolveOAuth2AuthorizationCodeAccessToken = async (request) => { + let codeVerifier = generateCodeVerifier(); + let codeChallenge = generateCodeChallenge(codeVerifier); + + let requestCopy = cloneDeep(request); + const { authorizationCode } = await getOAuth2AuthorizationCode(requestCopy, codeChallenge); + const oAuth = get(requestCopy, 'oauth2', {}); + const { clientId, clientSecret, callbackUrl, scope, pkce } = oAuth; + const data = { + grant_type: 'authorization_code', + code: authorizationCode, + redirect_uri: callbackUrl, + client_id: clientId, + client_secret: clientSecret, + scope: scope + }; + if (pkce) { + data['code_verifier'] = codeVerifier; + } + + const url = requestCopy?.oauth2?.accessTokenUrl; + return { + data, + url + }; +}; + +const getOAuth2AuthorizationCode = (request, codeChallenge) => { + return new Promise(async (resolve, reject) => { + const { oauth2 } = request; + const { callbackUrl, clientId, authorizationUrl, scope, pkce } = oauth2; + + let authorizationUrlWithQueryParams = `${authorizationUrl}?client_id=${clientId}&redirect_uri=${callbackUrl}&response_type=code&scope=${scope}`; + if (pkce) { + authorizationUrlWithQueryParams += `&code_challenge=${codeChallenge}&code_challenge_method=S256`; + } + try { + const { authorizationCode } = await authorizeUserInWindow({ + authorizeUrl: authorizationUrlWithQueryParams, + callbackUrl + }); + resolve({ authorizationCode }); + } catch (err) { + reject(err); + } + }); +}; + +// CLIENT CREDENTIALS + +const transformClientCredentialsRequest = async (request) => { + let requestCopy = cloneDeep(request); + const oAuth = get(requestCopy, 'oauth2', {}); + const { clientId, clientSecret, scope } = oAuth; + const data = { + grant_type: 'client_credentials', + client_id: clientId, + client_secret: clientSecret, + scope + }; + const url = requestCopy?.oauth2?.accessTokenUrl; + return { + data, + url + }; +}; + +// PASSWORD CREDENTIALS + +const transformPasswordCredentialsRequest = async (request) => { + let requestCopy = cloneDeep(request); + const oAuth = get(requestCopy, 'oauth2', {}); + const { username, password, scope } = oAuth; + const data = { + grant_type: 'password', + username, + password, + scope + }; + const url = requestCopy?.oauth2?.accessTokenUrl; + return { + data, + url + }; +}; + +module.exports = { + resolveOAuth2AuthorizationCodeAccessToken, + getOAuth2AuthorizationCode, + transformClientCredentialsRequest, + transformPasswordCredentialsRequest +}; diff --git a/packages/bruno-electron/src/ipc/network/prepare-collection-request.js b/packages/bruno-electron/src/ipc/network/prepare-collection-request.js new file mode 100644 index 000000000..5fd630594 --- /dev/null +++ b/packages/bruno-electron/src/ipc/network/prepare-collection-request.js @@ -0,0 +1,49 @@ +const { get, each } = require('lodash'); +const { setAuthHeaders } = require('./prepare-request'); + +const prepareCollectionRequest = (request, collectionRoot) => { + const headers = {}; + let contentTypeDefined = false; + let url = request.url; + + // collection headers + each(get(collectionRoot, 'request.headers', []), (h) => { + if (h.enabled) { + headers[h.name] = h.value; + if (h.name.toLowerCase() === 'content-type') { + contentTypeDefined = true; + } + } + }); + + each(request.headers, (h) => { + if (h.enabled) { + headers[h.name] = h.value; + if (h.name.toLowerCase() === 'content-type') { + contentTypeDefined = true; + } + } + }); + + let axiosRequest = { + mode: request?.body?.mode, + method: request.method, + url, + headers, + responseType: 'arraybuffer' + }; + + axiosRequest = setAuthHeaders(axiosRequest, request, collectionRoot); + + if (request.script) { + axiosRequest.script = request.script; + } + + axiosRequest.vars = request.vars; + + axiosRequest.method = 'POST'; + + return axiosRequest; +}; + +module.exports = prepareCollectionRequest; diff --git a/packages/bruno-electron/src/ipc/network/prepare-request.js b/packages/bruno-electron/src/ipc/network/prepare-request.js index b4ec49d5b..4577e733a 100644 --- a/packages/bruno-electron/src/ipc/network/prepare-request.js +++ b/packages/bruno-electron/src/ipc/network/prepare-request.js @@ -61,7 +61,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => { }; break; case 'bearer': - axiosRequest.headers['authorization'] = `Bearer ${get(collectionAuth, 'bearer.token')}`; + axiosRequest.headers['Authorization'] = `Bearer ${get(collectionAuth, 'bearer.token')}`; break; case 'digest': axiosRequest.digestConfig = { @@ -69,36 +69,6 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => { password: get(collectionAuth, 'digest.password') }; break; - case 'oauth2': - const grantType = get(collectionAuth, 'auth.oauth2.grantType'); - switch (grantType) { - case 'password': - axiosRequest.oauth2 = { - grantType: grantType, - username: get(collectionAuth, 'auth.oauth2.username'), - password: get(collectionAuth, 'auth.oauth2.password') - }; - break; - case 'authorization_code': - axiosRequest.oauth2 = { - grantType: grantType, - callbackUrl: get(collectionAuth, 'auth.oauth2.callbackUrl'), - authorizationUrl: get(collectionAuth, 'auth.oauth2.authorizationUrl'), - accessTokenUrl: get(collectionAuth, 'auth.oauth2.accessTokenUrl'), - clientId: get(collectionAuth, 'auth.oauth2.clientId'), - clientSecret: get(collectionAuth, 'auth.oauth2.clientSecret'), - scope: get(collectionAuth, 'auth.oauth2.scope') - }; - break; - case 'client_credentials': - axiosRequest.oauth2 = { - grantType: grantType, - clientId: get(collectionAuth, 'auth.oauth2.clientId'), - clientSecret: get(collectionAuth, 'auth.oauth2.clientSecret') - }; - break; - } - break; } } @@ -121,7 +91,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => { }; break; case 'bearer': - axiosRequest.headers['authorization'] = `Bearer ${get(request, 'auth.bearer.token')}`; + axiosRequest.headers['Authorization'] = `Bearer ${get(request, 'auth.bearer.token')}`; break; case 'digest': axiosRequest.digestConfig = { @@ -135,8 +105,10 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => { case 'password': axiosRequest.oauth2 = { grantType: grantType, + accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'), username: get(request, 'auth.oauth2.username'), - password: get(request, 'auth.oauth2.password') + password: get(request, 'auth.oauth2.password'), + scope: get(request, 'auth.oauth2.scope') }; break; case 'authorization_code': @@ -147,14 +119,17 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => { accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'), clientId: get(request, 'auth.oauth2.clientId'), clientSecret: get(request, 'auth.oauth2.clientSecret'), - scope: get(request, 'auth.oauth2.scope') + scope: get(request, 'auth.oauth2.scope'), + pkce: get(request, 'auth.oauth2.pkce') }; break; case 'client_credentials': axiosRequest.oauth2 = { grantType: grantType, + accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'), clientId: get(request, 'auth.oauth2.clientId'), - clientSecret: get(request, 'auth.oauth2.clientSecret') + clientSecret: get(request, 'auth.oauth2.clientSecret'), + scope: get(request, 'auth.oauth2.scope') }; break; } diff --git a/packages/bruno-js/src/bruno-request.js b/packages/bruno-js/src/bruno-request.js index afbf97873..909adf92a 100644 --- a/packages/bruno-js/src/bruno-request.js +++ b/packages/bruno-js/src/bruno-request.js @@ -20,6 +20,22 @@ class BrunoRequest { return this.req.method; } + getAuthMode() { + if (this.req?.oauth2) { + return 'oauth2'; + } else if (this.headers?.['Authorization']?.startsWith('Bearer')) { + return 'bearer'; + } else if (this.headers?.['Authorization']?.startsWith('Basic') || this.req?.auth?.username) { + return 'basic'; + } else if (this.req?.awsv4) { + return 'awsv4'; + } else if (this.req?.digestConfig) { + return 'digest'; + } else { + return 'none'; + } + } + setMethod(method) { this.req.method = method; } diff --git a/packages/bruno-lang/v2/src/bruToJson.js b/packages/bruno-lang/v2/src/bruToJson.js index 71c3e9e6c..1586838b9 100644 --- a/packages/bruno-lang/v2/src/bruToJson.js +++ b/packages/bruno-lang/v2/src/bruToJson.js @@ -392,14 +392,17 @@ const sem = grammar.createSemantics().addAttribute('ast', { const clientIdKey = _.find(auth, { name: 'client_id' }); const clientSecretKey = _.find(auth, { name: 'client_secret' }); const scopeKey = _.find(auth, { name: 'scope' }); + const pkceKey = _.find(auth, { name: 'pkce' }); return { auth: { oauth2: grantTypeKey?.value && grantTypeKey?.value == 'password' ? { grantType: grantTypeKey ? grantTypeKey.value : '', + accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '', username: usernameKey ? usernameKey.value : '', - password: passwordKey ? passwordKey.value : '' + password: passwordKey ? passwordKey.value : '', + scope: scopeKey ? scopeKey.value : '' } : grantTypeKey?.value && grantTypeKey?.value == 'authorization_code' ? { @@ -409,13 +412,16 @@ const sem = grammar.createSemantics().addAttribute('ast', { accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '', clientId: clientIdKey ? clientIdKey.value : '', clientSecret: clientSecretKey ? clientSecretKey.value : '', - scope: scopeKey ? scopeKey.value : '' + scope: scopeKey ? scopeKey.value : '', + pkce: pkceKey ? JSON.parse(pkceKey?.value || false) : false } : grantTypeKey?.value && grantTypeKey?.value == 'client_credentials' ? { grantType: grantTypeKey ? grantTypeKey.value : '', + accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '', clientId: clientIdKey ? clientIdKey.value : '', - clientSecret: clientSecretKey ? clientSecretKey.value : '' + clientSecret: clientSecretKey ? clientSecretKey.value : '', + scope: scopeKey ? scopeKey.value : '' } : {} } diff --git a/packages/bruno-lang/v2/src/collectionBruToJson.js b/packages/bruno-lang/v2/src/collectionBruToJson.js index c24f6e6ae..e408d4d95 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 + auths = authawsv4 | authbasic | authbearer | authdigest | authOAuth2 nl = "\\r"? "\\n" st = " " | "\\t" @@ -42,6 +42,7 @@ const grammar = ohm.grammar(`Bru { authbasic = "auth:basic" dictionary authbearer = "auth:bearer" dictionary authdigest = "auth:digest" dictionary + authOAuth2 = "auth:oauth2" dictionary script = scriptreq | scriptres scriptreq = "script:pre-request" st* "{" nl* textblock tagend @@ -242,6 +243,52 @@ const sem = grammar.createSemantics().addAttribute('ast', { } }; }, + authOAuth2(_1, dictionary) { + const auth = mapPairListToKeyValPairs(dictionary.ast, false); + const grantTypeKey = _.find(auth, { name: 'grant_type' }); + const usernameKey = _.find(auth, { name: 'username' }); + const passwordKey = _.find(auth, { name: 'password' }); + const callbackUrlKey = _.find(auth, { name: 'callback_url' }); + const authorizationUrlKey = _.find(auth, { name: 'authorization_url' }); + const accessTokenUrlKey = _.find(auth, { name: 'access_token_url' }); + const clientIdKey = _.find(auth, { name: 'client_id' }); + const clientSecretKey = _.find(auth, { name: 'client_secret' }); + const scopeKey = _.find(auth, { name: 'scope' }); + const pkceKey = _.find(auth, { name: 'pkce' }); + return { + auth: { + oauth2: + grantTypeKey?.value && grantTypeKey?.value == 'password' + ? { + grantType: grantTypeKey ? grantTypeKey.value : '', + accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '', + username: usernameKey ? usernameKey.value : '', + password: passwordKey ? passwordKey.value : '', + scope: scopeKey ? scopeKey.value : '' + } + : grantTypeKey?.value && grantTypeKey?.value == 'authorization_code' + ? { + grantType: grantTypeKey ? grantTypeKey.value : '', + callbackUrl: callbackUrlKey ? callbackUrlKey.value : '', + authorizationUrl: authorizationUrlKey ? authorizationUrlKey.value : '', + accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '', + clientId: clientIdKey ? clientIdKey.value : '', + clientSecret: clientSecretKey ? clientSecretKey.value : '', + scope: scopeKey ? scopeKey.value : '', + pkce: pkceKey ? JSON.parse(pkceKey?.value || false) : false + } + : grantTypeKey?.value && grantTypeKey?.value == 'client_credentials' + ? { + grantType: grantTypeKey ? grantTypeKey.value : '', + accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '', + clientId: clientIdKey ? clientIdKey.value : '', + clientSecret: clientSecretKey ? clientSecretKey.value : '', + scope: scopeKey ? scopeKey.value : '' + } + : {} + } + }; + }, varsreq(_1, dictionary) { const vars = mapPairListToKeyValPairs(dictionary.ast); _.each(vars, (v) => { diff --git a/packages/bruno-lang/v2/src/jsonToBru.js b/packages/bruno-lang/v2/src/jsonToBru.js index bd0eec919..e9b06691f 100644 --- a/packages/bruno-lang/v2/src/jsonToBru.js +++ b/packages/bruno-lang/v2/src/jsonToBru.js @@ -131,8 +131,10 @@ ${indentString(`password: ${auth?.digest?.password || ''}`)} case 'password': bru += `auth:oauth2 { ${indentString(`grant_type: password`)} +${indentString(`access_token_url: ${auth?.oauth2?.accessTokenUrl || ''}`)} ${indentString(`username: ${auth?.oauth2?.username || ''}`)} ${indentString(`password: ${auth?.oauth2?.password || ''}`)} +${indentString(`scope: ${auth?.oauth2?.scope || ''}`)} } `; @@ -146,6 +148,7 @@ ${indentString(`access_token_url: ${auth?.oauth2?.accessTokenUrl || ''}`)} ${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)} ${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)} ${indentString(`scope: ${auth?.oauth2?.scope || ''}`)} +${indentString(`pkce: ${(auth?.oauth2?.pkce || false).toString()}`)} } `; @@ -153,8 +156,10 @@ ${indentString(`scope: ${auth?.oauth2?.scope || ''}`)} case 'client_credentials': bru += `auth:oauth2 { ${indentString(`grant_type: client_credentials`)} +${indentString(`access_token_url: ${auth?.oauth2?.accessTokenUrl || ''}`)} ${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)} ${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)} +${indentString(`scope: ${auth?.oauth2?.scope || ''}`)} } `; diff --git a/packages/bruno-lang/v2/src/jsonToCollectionBru.js b/packages/bruno-lang/v2/src/jsonToCollectionBru.js index 08a3abad5..4d7e71f14 100644 --- a/packages/bruno-lang/v2/src/jsonToCollectionBru.js +++ b/packages/bruno-lang/v2/src/jsonToCollectionBru.js @@ -114,6 +114,47 @@ ${indentString(`password: ${auth.digest.password}`)} `; } + if (auth && auth.oauth2) { + switch (auth?.oauth2?.grantType) { + case 'password': + bru += `auth:oauth2 { +${indentString(`grant_type: password`)} +${indentString(`access_token_url: ${auth?.oauth2?.accessTokenUrl || ''}`)} +${indentString(`username: ${auth?.oauth2?.username || ''}`)} +${indentString(`password: ${auth?.oauth2?.password || ''}`)} +${indentString(`scope: ${auth?.oauth2?.scope || ''}`)} +} + +`; + break; + case 'authorization_code': + bru += `auth:oauth2 { +${indentString(`grant_type: authorization_code`)} +${indentString(`callback_url: ${auth?.oauth2?.callbackUrl || ''}`)} +${indentString(`authorization_url: ${auth?.oauth2?.authorizationUrl || ''}`)} +${indentString(`access_token_url: ${auth?.oauth2?.accessTokenUrl || ''}`)} +${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)} +${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)} +${indentString(`scope: ${auth?.oauth2?.scope || ''}`)} +${indentString(`pkce: ${(auth?.oauth2?.pkce || false).toString()}`)} +} + +`; + break; + case 'client_credentials': + bru += `auth:oauth2 { +${indentString(`grant_type: client_credentials`)} +${indentString(`access_token_url: ${auth?.oauth2?.accessTokenUrl || ''}`)} +${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)} +${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)} +${indentString(`scope: ${auth?.oauth2?.scope || ''}`)} +} + +`; + break; + } + } + let reqvars = _.get(vars, 'req'); let resvars = _.get(vars, 'res'); if (reqvars && reqvars.length) { diff --git a/packages/bruno-lang/v2/tests/fixtures/request.bru b/packages/bruno-lang/v2/tests/fixtures/request.bru index 94eb02fe9..56800154c 100644 --- a/packages/bruno-lang/v2/tests/fixtures/request.bru +++ b/packages/bruno-lang/v2/tests/fixtures/request.bru @@ -47,9 +47,9 @@ auth:digest { auth:oauth2 { grant_type: authorization_code - callback_url: http://localhost:8080/api/auth/oauth2/ac/callback - authorization_url: http://localhost:8080/api/auth/oauth2/ac/authorize - access_token_url: http://localhost:8080/api/auth/oauth2/ac/token + callback_url: http://localhost:8080/api/auth/oauth2/authorization_code/callback + authorization_url: http://localhost:8080/api/auth/oauth2/authorization_code/authorize + access_token_url: http://localhost:8080/api/auth/oauth2/authorization_code/token client_id: client_id_1 client_secret: client_secret_1 scope: read write diff --git a/packages/bruno-lang/v2/tests/fixtures/request.json b/packages/bruno-lang/v2/tests/fixtures/request.json index c7987839b..3f5f2b599 100644 --- a/packages/bruno-lang/v2/tests/fixtures/request.json +++ b/packages/bruno-lang/v2/tests/fixtures/request.json @@ -68,9 +68,9 @@ "grantType": "authorization_code", "clientId": "client_id_1", "clientSecret": "client_secret_1", - "authorizationUrl": "http://localhost:8080/api/auth/oauth2/ac/authorize", - "callbackUrl": "http://localhost:8080/api/auth/oauth2/ac/callback", - "accessTokenUrl": "http://localhost:8080/api/auth/oauth2/ac/token", + "authorizationUrl": "http://localhost:8080/api/auth/oauth2/authorization_code/authorize", + "callbackUrl": "http://localhost:8080/api/auth/oauth2/authorization_code/callback", + "accessTokenUrl": "http://localhost:8080/api/auth/oauth2/authorization_code/token", "scope": "read write" } }, diff --git a/packages/bruno-schema/src/collections/index.js b/packages/bruno-schema/src/collections/index.js index b3c5e8dc5..bbdda8d6f 100644 --- a/packages/bruno-schema/src/collections/index.js +++ b/packages/bruno-schema/src/collections/index.js @@ -144,7 +144,7 @@ const oauth2Schema = Yup.object({ otherwise: Yup.string().nullable().strip() }), accessTokenUrl: Yup.string().when('grantType', { - is: (val) => ['authorization_code'].includes(val), + is: (val) => ['client_credentials', 'password', 'authorization_code'].includes(val), then: Yup.string().nullable(), otherwise: Yup.string().nullable().strip() }), @@ -159,9 +159,14 @@ const oauth2Schema = Yup.object({ otherwise: Yup.string().nullable().strip() }), scope: Yup.string().when('grantType', { - is: (val) => ['authorization_code'].includes(val), + is: (val) => ['client_credentials', 'password', 'authorization_code'].includes(val), then: Yup.string().nullable(), otherwise: Yup.string().nullable().strip() + }), + pkce: Yup.boolean().when('grantType', { + is: (val) => ['authorization_code'].includes(val), + then: Yup.boolean().defined(), + otherwise: Yup.boolean() }) }) .noUnknown(true) diff --git a/packages/bruno-tests/collection/collection.bru b/packages/bruno-tests/collection/collection.bru index dfcac9859..a60283cd7 100644 --- a/packages/bruno-tests/collection/collection.bru +++ b/packages/bruno-tests/collection/collection.bru @@ -3,16 +3,7 @@ headers { } auth { - mode: bearer -} - -auth:basic { - username: bruno - password: {{basicAuthPassword}} -} - -auth:bearer { - token: {{bearer_auth_token}} + mode: none } docs { diff --git a/packages/bruno-tests/collection/environments/Local.bru b/packages/bruno-tests/collection/environments/Local.bru index 86e79139d..991077d97 100644 --- a/packages/bruno-tests/collection/environments/Local.bru +++ b/packages/bruno-tests/collection/environments/Local.bru @@ -4,11 +4,11 @@ vars { basic_auth_password: della client_id: client_id_1 client_secret: client_secret_1 - auth_url: http://localhost:8080/api/auth/oauth2/ac/authorize - callback_url: http://localhost:8080/api/auth/oauth2/ac/callback - access_token_url: http://localhost:8080/api/auth/oauth2/ac/token - ropc_username: foo - ropc_password: bar + auth_url: http://localhost:8080/api/auth/oauth2/authorization_code/authorize + callback_url: http://localhost:8080/api/auth/oauth2/authorization_code/callback + access_token_url: http://localhost:8080/api/auth/oauth2/authorization_code/token + passwordCredentials_username: foo + passwordCredentials_password: bar github_authorize_url: https://github.com/login/oauth/authorize github_access_token_url: https://github.com/login/oauth/access_token google_auth_url: https://accounts.google.com/o/oauth2/auth @@ -21,8 +21,8 @@ vars:secret [ google_client_id, google_client_secret, github_authorization_code, - ropc_access_token, - cc_access_token, - ac_access_token, + passwordCredentials_access_token, + client_credentials_access_token, + authorization_code_access_token, github_access_token ] diff --git a/packages/bruno-tests/collection_level_oauth2/.gitignore b/packages/bruno-tests/collection_level_oauth2/.gitignore new file mode 100644 index 000000000..1e18f275e --- /dev/null +++ b/packages/bruno-tests/collection_level_oauth2/.gitignore @@ -0,0 +1 @@ +!.env \ No newline at end of file diff --git a/packages/bruno-tests/collection_level_oauth2/.nvmrc b/packages/bruno-tests/collection_level_oauth2/.nvmrc new file mode 100644 index 000000000..0828ab794 --- /dev/null +++ b/packages/bruno-tests/collection_level_oauth2/.nvmrc @@ -0,0 +1 @@ +v18 \ No newline at end of file diff --git a/packages/bruno-tests/collection_level_oauth2/bruno.json b/packages/bruno-tests/collection_level_oauth2/bruno.json new file mode 100644 index 000000000..17f1d8ea0 --- /dev/null +++ b/packages/bruno-tests/collection_level_oauth2/bruno.json @@ -0,0 +1,18 @@ +{ + "version": "1", + "name": "collection_level_oauth2", + "type": "collection", + "scripts": { + "moduleWhitelist": ["crypto"], + "filesystemAccess": { + "allow": true + } + }, + "clientCertificates": { + "enabled": true, + "certs": [] + }, + "presets": { + "requestType": "http" + } +} diff --git a/packages/bruno-tests/collection_level_oauth2/collection.bru b/packages/bruno-tests/collection_level_oauth2/collection.bru new file mode 100644 index 000000000..a205016b1 --- /dev/null +++ b/packages/bruno-tests/collection_level_oauth2/collection.bru @@ -0,0 +1,30 @@ +headers { + check: again +} + +auth { + mode: oauth2 +} + +auth:oauth2 { + grant_type: authorization_code + callback_url: {{authorization_code_callback_url}} + authorization_url: {{authorization_code_authorize_url}} + access_token_url: {{authorization_code_access_token_url}} + client_id: {{client_id}} + client_secret: {{client_secret}} + scope: + pkce: true +} + +script:post-response { + if(req.getAuthMode() == 'oauth2' && res.body.access_token) { + bru.setEnvVar('access_token_set_by_collection',res.body.access_token) + } +} + +docs { + # bruno-testbench 🐶 + + This is a test collection that I am using to test various functionalities around bruno +} diff --git a/packages/bruno-tests/collection_level_oauth2/environments/Local.bru b/packages/bruno-tests/collection_level_oauth2/environments/Local.bru new file mode 100644 index 000000000..99658dac6 --- /dev/null +++ b/packages/bruno-tests/collection_level_oauth2/environments/Local.bru @@ -0,0 +1,34 @@ +vars { + host: http://localhost:8080 + bearer_auth_token: your_secret_token + basic_auth_password: della + client_id: client_id_1 + client_secret: client_secret_1 + password_credentials_access_token_url: http://localhost:8080/api/auth/oauth2/password_credentials/token + password_credentials_username: foo + password_credentials_password: bar + password_credentials_scope: + authorization_code_authorize_url: http://localhost:8080/api/auth/oauth2/authorization_code/authorize + authorization_code_callback_url: http://localhost:8080/api/auth/oauth2/authorization_code/callback + authorization_code_access_token_url: http://localhost:8080/api/auth/oauth2/authorization_code/token + authorization_code_google_auth_url: https://accounts.google.com/o/oauth2/auth + authorization_code_google_access_token_url: https://accounts.google.com/o/oauth2/token + authorization_code_google_scope: https://www.googleapis.com/auth/userinfo.email + authorization_code_github_authorize_url: https://github.com/login/oauth/authorize + authorization_code_github_access_token_url: https://github.com/login/oauth/access_token + authorization_code_access_token: null + client_credentials_access_token_url: http://localhost:8080/api/auth/oauth2/client_credentials/token + client_credentials_client_id: client_id_1 + client_credentials_client_secret: client_secret_1 + client_credentials_scope: admin + client_credentials_access_token: 9f1b1874f1e79b48a46d65569d830bbb + common_access_token: 9f1b1874f1e79b48a46d65569d830bbb +} +vars:secret [ + authorization_code_google_client_id, + authorization_code_google_client_secret, + authorization_code_github_client_secret, + authorization_code_github_client_id, + authorization_code_github_authorization_code, + authorization_code_github_access_token +] diff --git a/packages/bruno-tests/collection_level_oauth2/environments/Prod.bru b/packages/bruno-tests/collection_level_oauth2/environments/Prod.bru new file mode 100644 index 000000000..e6286f3b6 --- /dev/null +++ b/packages/bruno-tests/collection_level_oauth2/environments/Prod.bru @@ -0,0 +1,8 @@ +vars { + host: https://testbench-sanity.usebruno.com + bearer_auth_token: your_secret_token + basic_auth_password: della + env.var1: envVar1 + env-var2: envVar2 + bark: {{process.env.PROC_ENV_VAR}} +} diff --git a/packages/bruno-tests/collection_level_oauth2/package-lock.json b/packages/bruno-tests/collection_level_oauth2/package-lock.json new file mode 100644 index 000000000..717181ec3 --- /dev/null +++ b/packages/bruno-tests/collection_level_oauth2/package-lock.json @@ -0,0 +1,30 @@ +{ + "name": "@usebruno/test-collection", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@usebruno/test-collection", + "version": "0.0.1", + "dependencies": { + "@faker-js/faker": "^8.4.0" + } + }, + "node_modules/@faker-js/faker": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.0.tgz", + "integrity": "sha512-htW87352wzUCdX1jyUQocUcmAaFqcR/w082EC8iP/gtkF0K+aKcBp0hR5Arb7dzR8tQ1TrhE9DNa5EbJELm84w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0", + "npm": ">=6.14.13" + } + } + } +} diff --git a/packages/bruno-tests/collection_level_oauth2/package.json b/packages/bruno-tests/collection_level_oauth2/package.json new file mode 100644 index 000000000..23621129b --- /dev/null +++ b/packages/bruno-tests/collection_level_oauth2/package.json @@ -0,0 +1,7 @@ +{ + "name": "@usebruno/test-collection", + "version": "0.0.1", + "dependencies": { + "@faker-js/faker": "^8.4.0" + } +} diff --git a/packages/bruno-tests/collection_level_oauth2/readme.md b/packages/bruno-tests/collection_level_oauth2/readme.md new file mode 100644 index 000000000..a41582d22 --- /dev/null +++ b/packages/bruno-tests/collection_level_oauth2/readme.md @@ -0,0 +1,3 @@ +# bruno-tests collection + +API Collection to run sanity tests on Bruno CLI. diff --git a/packages/bruno-tests/collection_level_oauth2/resource.bru b/packages/bruno-tests/collection_level_oauth2/resource.bru new file mode 100644 index 000000000..53b67e2c3 --- /dev/null +++ b/packages/bruno-tests/collection_level_oauth2/resource.bru @@ -0,0 +1,15 @@ +meta { + name: resource + type: http + seq: 2 +} + +post { + url: {{host}}/api/auth/oauth2/authorization_code/resource?token={{access_token_set_by_collection}} + body: json + auth: none +} + +query { + token: {{access_token_set_by_collection}} +} diff --git a/packages/bruno-tests/collection_oauth2/auth/oauth2/ac/github token with authorize.bru b/packages/bruno-tests/collection_oauth2/auth/oauth2/ac/github token with authorize.bru deleted file mode 100644 index c18d2c6ed..000000000 --- a/packages/bruno-tests/collection_oauth2/auth/oauth2/ac/github token with authorize.bru +++ /dev/null @@ -1,25 +0,0 @@ -meta { - name: github token with authorize - type: http - seq: 1 -} - -post { - url: github.com - body: none - auth: oauth2 -} - -auth:oauth2 { - grant_type: authorization_code - callback_url: {{callback_url}} - authorization_url: {{github_authorize_url}} - access_token_url: {{github_access_token_url}} - client_id: {{github_client_id}} - client_secret: {{github_client_secret}} - scope: repo,gist -} - -script:post-response { - bru.setEnvVar('github_access_token',res.body.split('access_token=')[1]?.split('&scope')[0]); -} diff --git a/packages/bruno-tests/collection_oauth2/auth/oauth2/ac/google token with authorize.bru b/packages/bruno-tests/collection_oauth2/auth/oauth2/ac/google token with authorize.bru deleted file mode 100644 index 93ea7975e..000000000 --- a/packages/bruno-tests/collection_oauth2/auth/oauth2/ac/google token with authorize.bru +++ /dev/null @@ -1,25 +0,0 @@ -meta { - name: google token with authorize - type: http - seq: 4 -} - -post { - url: - body: none - auth: oauth2 -} - -auth:oauth2 { - grant_type: authorization_code - callback_url: {{callback_url}} - authorization_url: {{google_auth_url}} - access_token_url: {{google_access_token_url}} - client_id: {{google_client_id}} - client_secret: {{google_client_secret}} - scope: {{google_scope}} -} - -script:post-response { - bru.setEnvVar('ac_access_token', res.body.access_token); -} diff --git a/packages/bruno-tests/collection_oauth2/auth/oauth2/ac/resource.bru b/packages/bruno-tests/collection_oauth2/auth/oauth2/ac/resource.bru deleted file mode 100644 index ead30ec6b..000000000 --- a/packages/bruno-tests/collection_oauth2/auth/oauth2/ac/resource.bru +++ /dev/null @@ -1,27 +0,0 @@ -meta { - name: resource - type: http - seq: 3 -} - -post { - url: {{host}}/api/auth/oauth2/ac/resource?token={{ac_access_token}} - body: json - auth: none -} - -query { - token: {{ac_access_token}} -} - -auth:bearer { - token: -} - -body:json { - { - "code": "eb30dbf783b65bec4539ee1dcb068606", - "client_id": "{{client_id}}", - "client_secret": "{{client_secret}}" - } -} diff --git a/packages/bruno-tests/collection_oauth2/auth/oauth2/ac/token with authorize.bru b/packages/bruno-tests/collection_oauth2/auth/oauth2/ac/token with authorize.bru deleted file mode 100644 index e42fd7c77..000000000 --- a/packages/bruno-tests/collection_oauth2/auth/oauth2/ac/token with authorize.bru +++ /dev/null @@ -1,25 +0,0 @@ -meta { - name: token with authorize - type: http - seq: 4 -} - -post { - url: - body: none - auth: oauth2 -} - -auth:oauth2 { - grant_type: authorization_code - callback_url: {{callback_url}} - authorization_url: {{auth_url}} - access_token_url: {{access_token_url}} - client_id: {{client_id}} - client_secret: {{client_secret}} - scope: -} - -script:post-response { - bru.setEnvVar('ac_access_token', res.body.access_token); -} diff --git a/packages/bruno-tests/collection_oauth2/auth/oauth2/authorization_code/github token with authorize.bru b/packages/bruno-tests/collection_oauth2/auth/oauth2/authorization_code/github token with authorize.bru new file mode 100644 index 000000000..2114ccf45 --- /dev/null +++ b/packages/bruno-tests/collection_oauth2/auth/oauth2/authorization_code/github token with authorize.bru @@ -0,0 +1,25 @@ +meta { + name: github token with authorize + type: http + seq: 1 +} + +post { + url: + body: none + auth: oauth2 +} + +auth:oauth2 { + grant_type: authorization_code + callback_url: {{authorization_code_callback_url}} + authorization_url: {{authorization_code_github_authorize_url}} + access_token_url: {{authorization_code_github_access_token_url}} + client_id: {{authorization_code_github_client_id}} + client_secret: {{authorization_code_github_client_secret}} + scope: repo,gist +} + +script:post-response { + bru.setEnvVar('github_access_token',res.body.split('access_token=')[1]?.split('&scope')[0]); +} diff --git a/packages/bruno-tests/collection_oauth2/auth/oauth2/authorization_code/google token with authorize.bru b/packages/bruno-tests/collection_oauth2/auth/oauth2/authorization_code/google token with authorize.bru new file mode 100644 index 000000000..abace9d0c --- /dev/null +++ b/packages/bruno-tests/collection_oauth2/auth/oauth2/authorization_code/google token with authorize.bru @@ -0,0 +1,25 @@ +meta { + name: google token with authorize + type: http + seq: 4 +} + +post { + url: + body: none + auth: oauth2 +} + +auth:oauth2 { + grant_type: authorization_code + callback_url: {{authorization_code_callback_url}} + authorization_url: {{authorization_code_google_auth_url}} + access_token_url: {{authorization_code_google_access_token_url}} + client_id: {{authorization_code_google_client_id}} + client_secret: {{authorization_code_google_client_secret}} + scope: {{authorization_code_google_scope}} +} + +script:post-response { + bru.setEnvVar('authorization_code_access_token', res.body.access_token); +} diff --git a/packages/bruno-tests/collection_oauth2/auth/oauth2/authorization_code/resource.bru b/packages/bruno-tests/collection_oauth2/auth/oauth2/authorization_code/resource.bru new file mode 100644 index 000000000..a1f355fca --- /dev/null +++ b/packages/bruno-tests/collection_oauth2/auth/oauth2/authorization_code/resource.bru @@ -0,0 +1,15 @@ +meta { + name: resource + type: http + seq: 3 +} + +post { + url: {{host}}/api/auth/oauth2/authorization_code/resource?token={{authorization_code_access_token}} + body: json + auth: none +} + +query { + token: {{authorization_code_access_token}} +} \ No newline at end of file diff --git a/packages/bruno-tests/collection_oauth2/auth/oauth2/authorization_code/token with authorize.bru b/packages/bruno-tests/collection_oauth2/auth/oauth2/authorization_code/token with authorize.bru new file mode 100644 index 000000000..2b73c4da7 --- /dev/null +++ b/packages/bruno-tests/collection_oauth2/auth/oauth2/authorization_code/token with authorize.bru @@ -0,0 +1,26 @@ +meta { + name: token with authorize + type: http + seq: 4 +} + +post { + url: + body: none + auth: oauth2 +} + +auth:oauth2 { + grant_type: authorization_code + callback_url: {{authorization_code_callback_url}} + authorization_url: {{authorization_code_authorize_url}} + access_token_url: {{authorization_code_access_token_url}} + client_id: {{client_id}} + client_secret: {{client_secret}} + scope: + pkce: true +} + +script:post-response { + bru.setEnvVar('authorization_code_access_token', res.body.access_token); +} diff --git a/packages/bruno-tests/collection_oauth2/auth/oauth2/cc/resource.bru b/packages/bruno-tests/collection_oauth2/auth/oauth2/cc/resource.bru deleted file mode 100644 index c4a1ce399..000000000 --- a/packages/bruno-tests/collection_oauth2/auth/oauth2/cc/resource.bru +++ /dev/null @@ -1,15 +0,0 @@ -meta { - name: resource - type: http - seq: 2 -} - -get { - url: {{host}}/api/auth/oauth2/cc/resource?token={{cc_access_token}} - body: none - auth: none -} - -query { - token: {{cc_access_token}} -} diff --git a/packages/bruno-tests/collection_oauth2/auth/oauth2/cc/token.bru b/packages/bruno-tests/collection_oauth2/auth/oauth2/cc/token.bru deleted file mode 100644 index 13987b2eb..000000000 --- a/packages/bruno-tests/collection_oauth2/auth/oauth2/cc/token.bru +++ /dev/null @@ -1,21 +0,0 @@ -meta { - name: token - type: http - seq: 1 -} - -post { - url: {{host}}/api/auth/oauth2/cc/token - body: none - auth: oauth2 -} - -auth:oauth2 { - grant_type: client_credentials - client_id: {{client_id}} - client_secret: {{client_secret}} -} - -script:post-response { - bru.setEnvVar('cc_access_token', res.body.access_token); -} diff --git a/packages/bruno-tests/collection_oauth2/auth/oauth2/client_credentials/resource.bru b/packages/bruno-tests/collection_oauth2/auth/oauth2/client_credentials/resource.bru new file mode 100644 index 000000000..c4b28eea3 --- /dev/null +++ b/packages/bruno-tests/collection_oauth2/auth/oauth2/client_credentials/resource.bru @@ -0,0 +1,15 @@ +meta { + name: resource + type: http + seq: 2 +} + +get { + url: {{host}}/api/auth/oauth2/client_credentials/resource?token={{client_credentials_access_token}} + body: none + auth: none +} + +query { + token: {{client_credentials_access_token}} +} diff --git a/packages/bruno-tests/collection_oauth2/auth/oauth2/client_credentials/token.bru b/packages/bruno-tests/collection_oauth2/auth/oauth2/client_credentials/token.bru new file mode 100644 index 000000000..e0d11bbf0 --- /dev/null +++ b/packages/bruno-tests/collection_oauth2/auth/oauth2/client_credentials/token.bru @@ -0,0 +1,23 @@ +meta { + name: token + type: http + seq: 1 +} + +post { + url: + body: none + auth: oauth2 +} + +auth:oauth2 { + grant_type: client_credentials + access_token_url: {{client_credentials_access_token_url}} + client_id: {{client_credentials_client_id}} + client_secret: {{client_credentials_client_secret}} + scope: {{client_credentials_scope}} +} + +script:post-response { + bru.setEnvVar('client_credentials_access_token', res.body.access_token); +} diff --git a/packages/bruno-tests/collection_oauth2/auth/oauth2/password_credentials/resource.bru b/packages/bruno-tests/collection_oauth2/auth/oauth2/password_credentials/resource.bru new file mode 100644 index 000000000..b0e9d4388 --- /dev/null +++ b/packages/bruno-tests/collection_oauth2/auth/oauth2/password_credentials/resource.bru @@ -0,0 +1,15 @@ +meta { + name: resource + type: http + seq: 2 +} + +post { + url: {{host}}/api/auth/oauth2/password_credentials/resource + body: none + auth: bearer +} + +auth:bearer { + token: {{passwordCredentials_access_token}} +} diff --git a/packages/bruno-tests/collection_oauth2/auth/oauth2/password_credentials/token.bru b/packages/bruno-tests/collection_oauth2/auth/oauth2/password_credentials/token.bru new file mode 100644 index 000000000..a4eee5463 --- /dev/null +++ b/packages/bruno-tests/collection_oauth2/auth/oauth2/password_credentials/token.bru @@ -0,0 +1,23 @@ +meta { + name: token + type: http + seq: 1 +} + +post { + url: + body: none + auth: oauth2 +} + +auth:oauth2 { + grant_type: password + access_token_url: {{password_credentials_access_token_url}} + username: {{password_credentials_username}} + password: {{password_credentials_password}} + scope: +} + +script:post-response { + bru.setEnvVar('passwordCredentials_access_token', res.body.access_token); +} diff --git a/packages/bruno-tests/collection_oauth2/auth/oauth2/ropc/resource.bru b/packages/bruno-tests/collection_oauth2/auth/oauth2/ropc/resource.bru deleted file mode 100644 index 1395250ee..000000000 --- a/packages/bruno-tests/collection_oauth2/auth/oauth2/ropc/resource.bru +++ /dev/null @@ -1,15 +0,0 @@ -meta { - name: resource - type: http - seq: 2 -} - -post { - url: {{host}}/api/auth/oauth2/ropc/resource - body: none - auth: bearer -} - -auth:bearer { - token: {{ropc_access_token}} -} diff --git a/packages/bruno-tests/collection_oauth2/auth/oauth2/ropc/token.bru b/packages/bruno-tests/collection_oauth2/auth/oauth2/ropc/token.bru deleted file mode 100644 index 495655ab3..000000000 --- a/packages/bruno-tests/collection_oauth2/auth/oauth2/ropc/token.bru +++ /dev/null @@ -1,21 +0,0 @@ -meta { - name: token - type: http - seq: 1 -} - -post { - url: {{host}}/api/auth/oauth2/ropc/token - body: none - auth: oauth2 -} - -auth:oauth2 { - grant_type: password - username: {{ropc_username}} - password: {{ropc_password}} -} - -script:post-response { - bru.setEnvVar('ropc_access_token', res.body.access_token); -} diff --git a/packages/bruno-tests/collection_oauth2/bruno.json b/packages/bruno-tests/collection_oauth2/bruno.json index 79602ccd3..66949e685 100644 --- a/packages/bruno-tests/collection_oauth2/bruno.json +++ b/packages/bruno-tests/collection_oauth2/bruno.json @@ -1,19 +1,7 @@ { "version": "1", - "name": "bruno-testbench", + "name": "collection_oauth2", "type": "collection", - "proxy": { - "enabled": false, - "protocol": "http", - "hostname": "{{proxyHostname}}", - "port": 4000, - "auth": { - "enabled": false, - "username": "anoop", - "password": "password" - }, - "bypassProxy": "" - }, "scripts": { "moduleWhitelist": ["crypto"], "filesystemAccess": { @@ -25,7 +13,6 @@ "certs": [] }, "presets": { - "requestType": "http", - "requestUrl": "http://localhost:6000" + "requestType": "http" } } diff --git a/packages/bruno-tests/collection_oauth2/collection.bru b/packages/bruno-tests/collection_oauth2/collection.bru index e31b64995..a60283cd7 100644 --- a/packages/bruno-tests/collection_oauth2/collection.bru +++ b/packages/bruno-tests/collection_oauth2/collection.bru @@ -6,15 +6,6 @@ auth { mode: none } -auth:basic { - username: bruno - password: {{basicAuthPassword}} -} - -auth:bearer { - token: {{bearerAuthToken}} -} - docs { # bruno-testbench 🐶 diff --git a/packages/bruno-tests/collection_oauth2/environments/Local.bru b/packages/bruno-tests/collection_oauth2/environments/Local.bru index 99fff5991..396c97c61 100644 --- a/packages/bruno-tests/collection_oauth2/environments/Local.bru +++ b/packages/bruno-tests/collection_oauth2/environments/Local.bru @@ -4,23 +4,29 @@ vars { basic_auth_password: della client_id: client_id_1 client_secret: client_secret_1 - auth_url: http://localhost:8080/api/auth/oauth2/ac/authorize - callback_url: http://localhost:8080/api/auth/oauth2/ac/callback - access_token_url: http://localhost:8080/api/auth/oauth2/ac/token - ropc_username: foo - ropc_password: bar - github_authorize_url: https://github.com/login/oauth/authorize - github_access_token_url: https://github.com/login/oauth/access_token - google_auth_url: https://accounts.google.com/o/oauth2/auth - google_access_token_url: https://accounts.google.com/o/oauth2/token - google_scope: https://www.googleapis.com/auth/userinfo.email + password_credentials_access_token_url: http://localhost:8080/api/auth/oauth2/password_credentials/token + password_credentials_username: foo + password_credentials_password: bar + password_credentials_scope: + authorization_code_authorize_url: http://localhost:8080/api/auth/oauth2/authorization_code/authorize + authorization_code_callback_url: http://localhost:8080/api/auth/oauth2/authorization_code/callback + authorization_code_access_token_url: http://localhost:8080/api/auth/oauth2/authorization_code/token + authorization_code_google_auth_url: https://accounts.google.com/o/oauth2/auth + authorization_code_google_access_token_url: https://accounts.google.com/o/oauth2/token + authorization_code_google_scope: https://www.googleapis.com/auth/userinfo.email + authorization_code_github_authorize_url: https://github.com/login/oauth/authorize + authorization_code_github_access_token_url: https://github.com/login/oauth/access_token + authorization_code_access_token: null + client_credentials_access_token_url: http://localhost:8080/api/auth/oauth2/client_credentials/token + client_credentials_client_id: client_id_1 + client_credentials_client_secret: client_secret_1 + client_credentials_scope: admin } vars:secret [ - github_client_secret, - github_client_id, - google_client_id, - google_client_secret, - github_authorization_code, - github_access_token, - ac_access_token + authorization_code_google_client_id, + authorization_code_google_client_secret, + authorization_code_github_client_secret, + authorization_code_github_client_id, + authorization_code_github_authorization_code, + authorization_code_github_access_token ] diff --git a/packages/bruno-tests/src/auth/index.js b/packages/bruno-tests/src/auth/index.js index 0b5dc7f63..6d6ebfb55 100644 --- a/packages/bruno-tests/src/auth/index.js +++ b/packages/bruno-tests/src/auth/index.js @@ -4,13 +4,13 @@ const router = express.Router(); const authBearer = require('./bearer'); const authBasic = require('./basic'); const authCookie = require('./cookie'); -const authOAuth2Ropc = require('./oauth2/ropc'); -const authOAuth2AuthorizationCode = require('./oauth2/ac'); -const authOAuth2Cc = require('./oauth2/cc'); +const authOAuth2PasswordCredentials = require('./oauth2/passwordCredentials'); +const authOAuth2AuthorizationCode = require('./oauth2/authorizationCode'); +const authOAuth2ClientCredentials = require('./oauth2/clientCredentials'); -router.use('/oauth2/ropc', authOAuth2Ropc); -router.use('/oauth2/ac', authOAuth2AuthorizationCode); -router.use('/oauth2/cc', authOAuth2Cc); +router.use('/oauth2/password_credentials', authOAuth2PasswordCredentials); +router.use('/oauth2/authorization_code', authOAuth2AuthorizationCode); +router.use('/oauth2/client_credentials', authOAuth2ClientCredentials); router.use('/bearer', authBearer); router.use('/basic', authBasic); router.use('/cookie', authCookie); diff --git a/packages/bruno-tests/src/auth/oauth2/ac.js b/packages/bruno-tests/src/auth/oauth2/authorizationCode.js similarity index 78% rename from packages/bruno-tests/src/auth/oauth2/ac.js rename to packages/bruno-tests/src/auth/oauth2/authorizationCode.js index 840c2a778..1cc089a2c 100644 --- a/packages/bruno-tests/src/auth/oauth2/ac.js +++ b/packages/bruno-tests/src/auth/oauth2/authorizationCode.js @@ -17,8 +17,16 @@ function generateUniqueString() { return crypto.randomBytes(16).toString('hex'); } +const generateCodeChallenge = (codeVerifier) => { + const hash = crypto.createHash('sha256'); + hash.update(codeVerifier); + const base64Hash = hash.digest('base64'); + return base64Hash.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); +}; + router.get('/authorize', (req, res) => { - const { response_type, client_id, redirect_uri } = req.query; + const { response_type, client_id, redirect_uri, code_challenge } = req.query; + console.log('authorization code authorize', req.query); if (response_type !== 'code') { return res.status(401).json({ error: 'Invalid Response type, expected "code"' }); } @@ -37,7 +45,8 @@ router.get('/authorize', (req, res) => { authCodes.push({ authCode: authorization_code, client_id, - redirect_uri + redirect_uri, + code_challenge }); const redirectUrl = `${redirect_uri}?code=${authorization_code}`; @@ -72,6 +81,7 @@ router.get('/authorize', (req, res) => { // Handle the authorization callback router.get('/callback', (req, res) => { + console.log('authorization code callback', req.query); const { code } = req.query; // Check if the authCode is valid. @@ -85,13 +95,15 @@ router.get('/callback', (req, res) => { }); router.post('/token', (req, res) => { - let grant_type, code, redirect_uri, client_id, client_secret; + console.log('authorization code token', req.body, req.headers); + let grant_type, code, redirect_uri, client_id, client_secret, code_verifier; if (req?.body?.grant_type) { grant_type = req?.body?.grant_type; code = req?.body?.code; redirect_uri = req?.body?.redirect_uri; client_id = req?.body?.client_id; client_secret = req?.body?.client_secret; + code_verifier = req?.body?.code_verifier; } if (req?.headers?.grant_type) { grant_type = req?.headers?.grant_type; @@ -99,6 +111,7 @@ router.post('/token', (req, res) => { redirect_uri = req?.headers?.redirect_uri; client_id = req?.headers?.client_id; client_secret = req?.headers?.client_secret; + code_verifier = req?.headers?.code_verifier; } if (grant_type !== 'authorization_code') { @@ -110,7 +123,13 @@ router.post('/token', (req, res) => { // return res.status(401).json({ error: 'Invalid client credentials' }); // } - const storedAuthCode = authCodes.find((t) => t.authCode === code); + const storedAuthCode = authCodes.find((t) => { + if (!t?.code_challenge) { + return t.authCode === code; + } else { + return t.authCode === code && t.code_challenge === generateCodeChallenge(code_verifier); + } + }); if (!storedAuthCode) { return res.status(401).json({ error: 'Invalid Authorization Code' }); @@ -127,6 +146,7 @@ router.post('/token', (req, res) => { router.post('/resource', (req, res) => { try { + console.log('authorization code resource', req.query, tokens); const { token } = req.query; const storedToken = tokens.find((t) => t.accessToken === token); if (!storedToken) { diff --git a/packages/bruno-tests/src/auth/oauth2/cc.js b/packages/bruno-tests/src/auth/oauth2/clientCredentials.js similarity index 77% rename from packages/bruno-tests/src/auth/oauth2/cc.js rename to packages/bruno-tests/src/auth/oauth2/clientCredentials.js index dcaee3027..0c650d51a 100644 --- a/packages/bruno-tests/src/auth/oauth2/cc.js +++ b/packages/bruno-tests/src/auth/oauth2/clientCredentials.js @@ -4,7 +4,8 @@ const crypto = require('crypto'); const clients = [ { client_id: 'client_id_1', - client_secret: 'client_secret_1' + client_secret: 'client_secret_1', + scope: 'admin' } ]; @@ -15,35 +16,39 @@ function generateUniqueString() { } router.post('/token', (req, res) => { - let grant_type, client_id, client_secret; + let grant_type, client_id, client_secret, scope; if (req?.body?.grant_type) { grant_type = req?.body?.grant_type; client_id = req?.body?.client_id; client_secret = req?.body?.client_secret; + scope = req?.body?.scope; } else if (req?.headers?.grant_type) { grant_type = req?.headers?.grant_type; client_id = req?.headers?.client_id; client_secret = req?.headers?.client_secret; + scope = req?.headers?.scope; } + console.log('client_cred', client_id, client_secret, scope); if (grant_type !== 'client_credentials') { return res.status(401).json({ error: 'Invalid Grant Type, expected "client_credentials"' }); } - const client = clients.find((c) => c.client_id == client_id && c.client_secret == client_secret); + const client = clients.find((c) => c.client_id == client_id && c.client_secret == client_secret && c.scope == scope); if (!client) { - return res.status(401).json({ error: 'Invalid client' }); + return res.status(401).json({ error: 'Invalid client details or scope' }); } const token = generateUniqueString(); tokens.push({ token, client_id, - client_secret + client_secret, + scope }); - return res.json({ message: 'Authenticated successfully', access_token: token }); + return res.json({ message: 'Authenticated successfully', access_token: token, scope }); }); router.get('/resource', (req, res) => { diff --git a/packages/bruno-tests/src/auth/oauth2/ropc.js b/packages/bruno-tests/src/auth/oauth2/passwordCredentials.js similarity index 79% rename from packages/bruno-tests/src/auth/oauth2/ropc.js rename to packages/bruno-tests/src/auth/oauth2/passwordCredentials.js index 84bb979a7..2f6760f13 100644 --- a/packages/bruno-tests/src/auth/oauth2/ropc.js +++ b/packages/bruno-tests/src/auth/oauth2/passwordCredentials.js @@ -9,23 +9,10 @@ const users = [ } ]; -// P -// { -// grant_type: 'password', -// username: 'foo', -// password: 'bar' -// } - -// I -// { -// grant_type: 'password', -// username: 'foo', -// password: 'bar', -// client_id: 'client_id_1', -// client_secret: 'client_secret_1' -// } router.post('/token', (req, res) => { - const { grant_type, username, password, client_id, client_secret } = req.body; + const { grant_type, username, password, scope } = req.body; + + console.log('password_credentials', username, password, scope); if (grant_type !== 'password') { return res.status(401).json({ error: 'Invalid Grant Type' });