mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-25 09:23:17 +01:00
feat(#1003): collection level oauth2, access_token_url & scope for 'Client Credentials' and 'Password Credentials' grant types (#1691)
* feat(#1003): authorization_code grant type PKCE support, code cleanup.. --------- Co-authored-by: lohit-1 <lohit@usebruno.com>
This commit is contained in:
parent
9d3df0a86a
commit
858536e13d
@ -43,6 +43,7 @@ if (!SERVER_RENDERED) {
|
|||||||
'req.getUrl()',
|
'req.getUrl()',
|
||||||
'req.setUrl(url)',
|
'req.setUrl(url)',
|
||||||
'req.getMethod()',
|
'req.getMethod()',
|
||||||
|
'req.getAuthMode()',
|
||||||
'req.setMethod(method)',
|
'req.setMethod(method)',
|
||||||
'req.getHeader(name)',
|
'req.getHeader(name)',
|
||||||
'req.getHeaders()',
|
'req.getHeaders()',
|
||||||
|
@ -70,6 +70,15 @@ const AuthMode = ({ collection }) => {
|
|||||||
>
|
>
|
||||||
Digest Auth
|
Digest Auth
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
onModeChange('oauth2');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Oauth2
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className="dropdown-item"
|
className="dropdown-item"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -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 (
|
||||||
|
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
||||||
|
{inputsConfig.map((input) => {
|
||||||
|
const { key, label } = input;
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
||||||
|
<label className="block font-medium">{label}</label>
|
||||||
|
<div className="single-line-editor-wrapper">
|
||||||
|
<SingleLineEditor
|
||||||
|
value={oAuth[key] || ''}
|
||||||
|
theme={storedTheme}
|
||||||
|
onSave={handleSave}
|
||||||
|
onChange={(val) => handleChange(key, val)}
|
||||||
|
onRun={handleRun}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<div className="flex flex-row w-full gap-4" key="pkce">
|
||||||
|
<label className="block font-medium">Use PKCE</label>
|
||||||
|
<input
|
||||||
|
className="cursor-pointer"
|
||||||
|
type="checkbox"
|
||||||
|
checked={Boolean(oAuth?.['pkce'])}
|
||||||
|
onChange={handlePKCEToggle}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button onClick={handleRun} className="submit btn btn-sm btn-secondary w-fit">
|
||||||
|
Get Access Token
|
||||||
|
</button>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OAuth2AuthorizationCode;
|
@ -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 };
|
@ -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;
|
@ -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 (
|
||||||
|
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
||||||
|
{inputsConfig.map((input) => {
|
||||||
|
const { key, label } = input;
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
||||||
|
<label className="block font-medium">{label}</label>
|
||||||
|
<div className="single-line-editor-wrapper">
|
||||||
|
<SingleLineEditor
|
||||||
|
value={oAuth[key] || ''}
|
||||||
|
theme={storedTheme}
|
||||||
|
onSave={handleSave}
|
||||||
|
onChange={(val) => handleChange(key, val)}
|
||||||
|
onRun={handleRun}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<button onClick={handleRun} className="submit btn btn-sm btn-secondary w-fit">
|
||||||
|
Get Access Token
|
||||||
|
</button>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OAuth2ClientCredentials;
|
@ -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 };
|
@ -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;
|
@ -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 (
|
||||||
|
<div ref={ref} className="flex items-center justify-end grant-type-label select-none">
|
||||||
|
{humanizeGrantType(oAuth?.grantType)} <IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<StyledWrapper>
|
||||||
|
<label className="block font-medium mb-2">Grant Type</label>
|
||||||
|
<div className="inline-flex items-center cursor-pointer grant-type-mode-selector w-fit">
|
||||||
|
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
onGrantTypeChange('password');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Password Credentials
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
onGrantTypeChange('authorization_code');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Authorization Code
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
onGrantTypeChange('client_credentials');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Client Credentials
|
||||||
|
</div>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default GrantTypeSelector;
|
@ -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;
|
@ -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 (
|
||||||
|
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
||||||
|
{inputsConfig.map((input) => {
|
||||||
|
const { key, label } = input;
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
||||||
|
<label className="block font-medium">{label}</label>
|
||||||
|
<div className="single-line-editor-wrapper">
|
||||||
|
<SingleLineEditor
|
||||||
|
value={oAuth[key] || ''}
|
||||||
|
theme={storedTheme}
|
||||||
|
onSave={handleSave}
|
||||||
|
onChange={(val) => handleChange(key, val)}
|
||||||
|
onRun={handleRun}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<button onClick={handleRun} className="submit btn btn-sm btn-secondary w-fit">
|
||||||
|
Get Access Token
|
||||||
|
</button>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OAuth2AuthorizationCode;
|
@ -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 };
|
@ -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;
|
@ -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 <OAuth2PasswordCredentials collection={collection} />;
|
||||||
|
break;
|
||||||
|
case 'authorization_code':
|
||||||
|
return <OAuth2AuthorizationCode collection={collection} />;
|
||||||
|
break;
|
||||||
|
case 'client_credentials':
|
||||||
|
return <OAuth2ClientCredentials collection={collection} />;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return <div>TBD</div>;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const OAuth2 = ({ collection }) => {
|
||||||
|
const oAuth = get(collection, 'root.request.auth.oauth2', {});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="mt-2 w-full">
|
||||||
|
<GrantTypeSelector collection={collection} />
|
||||||
|
{grantTypeComponentMap(oAuth?.grantType, collection)}
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OAuth2;
|
@ -8,6 +8,7 @@ import BasicAuth from './BasicAuth';
|
|||||||
import DigestAuth from './DigestAuth';
|
import DigestAuth from './DigestAuth';
|
||||||
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import OAuth2 from './OAuth2';
|
||||||
|
|
||||||
const Auth = ({ collection }) => {
|
const Auth = ({ collection }) => {
|
||||||
const authMode = get(collection, 'root.request.auth.mode');
|
const authMode = get(collection, 'root.request.auth.mode');
|
||||||
@ -29,6 +30,9 @@ const Auth = ({ collection }) => {
|
|||||||
case 'digest': {
|
case 'digest': {
|
||||||
return <DigestAuth collection={collection} />;
|
return <DigestAuth collection={collection} />;
|
||||||
}
|
}
|
||||||
|
case 'oauth2': {
|
||||||
|
return <OAuth2 collection={collection} />;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -38,7 +42,6 @@ const Auth = ({ collection }) => {
|
|||||||
<AuthMode collection={collection} />
|
<AuthMode collection={collection} />
|
||||||
</div>
|
</div>
|
||||||
{getAuthView()}
|
{getAuthView()}
|
||||||
|
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
|
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
|
||||||
Save
|
Save
|
||||||
|
@ -81,71 +81,72 @@ const EnvironmentVariables = ({ environment, collection }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="w-full mt-6 mb-6">
|
<StyledWrapper className="w-full mt-6 mb-6">
|
||||||
<table>
|
<div className="h-[50vh] overflow-y-auto w-full">
|
||||||
<thead>
|
<table>
|
||||||
<tr>
|
<thead>
|
||||||
<td>Enabled</td>
|
<tr>
|
||||||
<td>Name</td>
|
<td>Enabled</td>
|
||||||
<td>Value</td>
|
<td>Name</td>
|
||||||
<td>Secret</td>
|
<td>Value</td>
|
||||||
<td></td>
|
<td>Secret</td>
|
||||||
</tr>
|
<td></td>
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{formik.values.map((variable, index) => (
|
|
||||||
<tr key={variable.uid}>
|
|
||||||
<td className="text-center">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="mr-3 mousetrap"
|
|
||||||
name={`${index}.enabled`}
|
|
||||||
checked={variable.enabled}
|
|
||||||
onChange={formik.handleChange}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
autoComplete="off"
|
|
||||||
autoCorrect="off"
|
|
||||||
autoCapitalize="off"
|
|
||||||
spellCheck="false"
|
|
||||||
className="mousetrap"
|
|
||||||
id={`${index}.name`}
|
|
||||||
name={`${index}.name`}
|
|
||||||
value={variable.name}
|
|
||||||
onChange={formik.handleChange}
|
|
||||||
/>
|
|
||||||
<ErrorMessage name={`${index}.name`} />
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<SingleLineEditor
|
|
||||||
theme={storedTheme}
|
|
||||||
collection={collection}
|
|
||||||
name={`${index}.value`}
|
|
||||||
value={variable.value}
|
|
||||||
onChange={(newValue) => formik.setFieldValue(`${index}.value`, newValue, true)}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="mr-3 mousetrap"
|
|
||||||
name={`${index}.secret`}
|
|
||||||
checked={variable.secret}
|
|
||||||
onChange={formik.handleChange}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button onClick={() => handleRemoveVar(variable.uid)}>
|
|
||||||
<IconTrash strokeWidth={1.5} size={20} />
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
</thead>
|
||||||
</tbody>
|
<tbody>
|
||||||
</table>
|
{formik.values.map((variable, index) => (
|
||||||
|
<tr key={variable.uid}>
|
||||||
|
<td className="text-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="mr-3 mousetrap"
|
||||||
|
name={`${index}.enabled`}
|
||||||
|
checked={variable.enabled}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
className="mousetrap"
|
||||||
|
id={`${index}.name`}
|
||||||
|
name={`${index}.name`}
|
||||||
|
value={variable.name}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
/>
|
||||||
|
<ErrorMessage name={`${index}.name`} />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<SingleLineEditor
|
||||||
|
theme={storedTheme}
|
||||||
|
collection={collection}
|
||||||
|
name={`${index}.value`}
|
||||||
|
value={variable.value}
|
||||||
|
onChange={(newValue) => formik.setFieldValue(`${index}.value`, newValue, true)}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="mr-3 mousetrap"
|
||||||
|
name={`${index}.secret`}
|
||||||
|
checked={variable.secret}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button onClick={() => handleRemoveVar(variable.uid)}>
|
||||||
|
<IconTrash strokeWidth={1.5} size={20} />
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={addVariable}>
|
<button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={addVariable}>
|
||||||
+ Add Variable
|
+ Add Variable
|
||||||
|
@ -20,6 +20,8 @@ const OAuth2AuthorizationCode = ({ item, collection }) => {
|
|||||||
|
|
||||||
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||||
|
|
||||||
|
const { callbackUrl, authorizationUrl, accessTokenUrl, clientId, clientSecret, scope, pkce } = oAuth;
|
||||||
|
|
||||||
const handleChange = (key, value) => {
|
const handleChange = (key, value) => {
|
||||||
dispatch(
|
dispatch(
|
||||||
updateAuth({
|
updateAuth({
|
||||||
@ -28,13 +30,39 @@ const OAuth2AuthorizationCode = ({ item, collection }) => {
|
|||||||
itemUid: item.uid,
|
itemUid: item.uid,
|
||||||
content: {
|
content: {
|
||||||
grantType: 'authorization_code',
|
grantType: 'authorization_code',
|
||||||
...oAuth,
|
callbackUrl,
|
||||||
|
authorizationUrl,
|
||||||
|
accessTokenUrl,
|
||||||
|
clientId,
|
||||||
|
clientSecret,
|
||||||
|
scope,
|
||||||
|
pkce,
|
||||||
[key]: value
|
[key]: value
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handlePKCEToggle = (e) => {
|
||||||
|
dispatch(
|
||||||
|
updateAuth({
|
||||||
|
mode: 'oauth2',
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
itemUid: item.uid,
|
||||||
|
content: {
|
||||||
|
grantType: 'authorization_code',
|
||||||
|
callbackUrl,
|
||||||
|
authorizationUrl,
|
||||||
|
accessTokenUrl,
|
||||||
|
clientId,
|
||||||
|
clientSecret,
|
||||||
|
scope,
|
||||||
|
pkce: !Boolean(oAuth?.['pkce'])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
||||||
{inputsConfig.map((input) => {
|
{inputsConfig.map((input) => {
|
||||||
@ -48,13 +76,22 @@ const OAuth2AuthorizationCode = ({ item, collection }) => {
|
|||||||
theme={storedTheme}
|
theme={storedTheme}
|
||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
onChange={(val) => handleChange(key, val)}
|
onChange={(val) => handleChange(key, val)}
|
||||||
onRun={() => {}}
|
onRun={handleRun}
|
||||||
collection={collection}
|
collection={collection}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
<div className="flex flex-row w-full gap-4" key="pkce">
|
||||||
|
<label className="block font-medium">Use PKCE</label>
|
||||||
|
<input
|
||||||
|
className="cursor-pointer"
|
||||||
|
type="checkbox"
|
||||||
|
checked={Boolean(oAuth?.['pkce'])}
|
||||||
|
onChange={handlePKCEToggle}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<button onClick={handleRun} className="submit btn btn-sm btn-secondary w-fit">
|
<button onClick={handleRun} className="submit btn btn-sm btn-secondary w-fit">
|
||||||
Get Access Token
|
Get Access Token
|
||||||
</button>
|
</button>
|
||||||
|
@ -5,7 +5,7 @@ const inputsConfig = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'authorizationUrl',
|
key: 'authorizationUrl',
|
||||||
label: 'Auth URL'
|
label: 'Authorization URL'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'accessTokenUrl',
|
key: 'accessTokenUrl',
|
||||||
|
@ -4,8 +4,9 @@ import { useTheme } from 'providers/Theme';
|
|||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import SingleLineEditor from 'components/SingleLineEditor';
|
import SingleLineEditor from 'components/SingleLineEditor';
|
||||||
import { updateAuth } from 'providers/ReduxStore/slices/collections';
|
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 StyledWrapper from './StyledWrapper';
|
||||||
|
import { inputsConfig } from './inputsConfig';
|
||||||
|
|
||||||
const OAuth2ClientCredentials = ({ item, collection }) => {
|
const OAuth2ClientCredentials = ({ item, collection }) => {
|
||||||
const dispatch = useDispatch();
|
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 oAuth = item.draft ? get(item, 'draft.request.auth.oauth2', {}) : get(item, 'request.auth.oauth2', {});
|
||||||
|
|
||||||
const handleRun = () => dispatch(sendRequest(item, collection.uid));
|
const handleRun = async () => {
|
||||||
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
dispatch(sendRequest(item, 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 handleClientSecretChange = (clientSecret) => {
|
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||||
|
|
||||||
|
const { accessTokenUrl, clientId, clientSecret, scope } = oAuth;
|
||||||
|
|
||||||
|
const handleChange = (key, value) => {
|
||||||
dispatch(
|
dispatch(
|
||||||
updateAuth({
|
updateAuth({
|
||||||
mode: 'oauth2',
|
mode: 'oauth2',
|
||||||
@ -39,38 +30,39 @@ const OAuth2ClientCredentials = ({ item, collection }) => {
|
|||||||
itemUid: item.uid,
|
itemUid: item.uid,
|
||||||
content: {
|
content: {
|
||||||
grantType: 'client_credentials',
|
grantType: 'client_credentials',
|
||||||
clientId: oAuth.clientId,
|
accessTokenUrl,
|
||||||
clientSecret: clientSecret
|
clientId,
|
||||||
|
clientSecret,
|
||||||
|
scope,
|
||||||
|
[key]: value
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="mt-2 w-full">
|
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
||||||
<label className="block font-medium mb-2">Client Id</label>
|
{inputsConfig.map((input) => {
|
||||||
<div className="single-line-editor-wrapper mb-2">
|
const { key, label } = input;
|
||||||
<SingleLineEditor
|
return (
|
||||||
value={oAuth.clientId || ''}
|
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
||||||
theme={storedTheme}
|
<label className="block font-medium">{label}</label>
|
||||||
onSave={handleSave}
|
<div className="single-line-editor-wrapper">
|
||||||
onChange={(val) => handleClientIdChange(val)}
|
<SingleLineEditor
|
||||||
onRun={handleRun}
|
value={oAuth[key] || ''}
|
||||||
collection={collection}
|
theme={storedTheme}
|
||||||
/>
|
onSave={handleSave}
|
||||||
</div>
|
onChange={(val) => handleChange(key, val)}
|
||||||
|
onRun={handleRun}
|
||||||
<label className="block font-medium mb-2">Client Secret</label>
|
collection={collection}
|
||||||
<div className="single-line-editor-wrapper">
|
/>
|
||||||
<SingleLineEditor
|
</div>
|
||||||
value={oAuth.clientSecret || ''}
|
</div>
|
||||||
theme={storedTheme}
|
);
|
||||||
onSave={handleSave}
|
})}
|
||||||
onChange={(val) => handleClientSecretChange(val)}
|
<button onClick={handleRun} className="submit btn btn-sm btn-secondary w-fit">
|
||||||
onRun={handleRun}
|
Get Access Token
|
||||||
collection={collection}
|
</button>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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 };
|
@ -37,7 +37,7 @@ const GrantTypeSelector = ({ item, collection }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// initalize redux state with a default oauth2 auth type
|
// initalize redux state with a default oauth2 grant type
|
||||||
// authorization_code - default option
|
// authorization_code - default option
|
||||||
!oAuth?.grantType &&
|
!oAuth?.grantType &&
|
||||||
dispatch(
|
dispatch(
|
||||||
@ -64,7 +64,7 @@ const GrantTypeSelector = ({ item, collection }) => {
|
|||||||
onGrantTypeChange('password');
|
onGrantTypeChange('password');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Resource Owner Password Credentials
|
Password Credentials
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="dropdown-item"
|
className="dropdown-item"
|
||||||
|
@ -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;
|
@ -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 (
|
||||||
|
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
||||||
|
{inputsConfig.map((input) => {
|
||||||
|
const { key, label } = input;
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
||||||
|
<label className="block font-medium">{label}</label>
|
||||||
|
<div className="single-line-editor-wrapper">
|
||||||
|
<SingleLineEditor
|
||||||
|
value={oAuth[key] || ''}
|
||||||
|
theme={storedTheme}
|
||||||
|
onSave={handleSave}
|
||||||
|
onChange={(val) => handleChange(key, val)}
|
||||||
|
onRun={handleRun}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<button onClick={handleRun} className="submit btn btn-sm btn-secondary w-fit">
|
||||||
|
Get Access Token
|
||||||
|
</button>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OAuth2AuthorizationCode;
|
@ -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 };
|
@ -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 (
|
|
||||||
<StyledWrapper className="mt-2 w-full">
|
|
||||||
<label className="block font-medium mb-2">Username</label>
|
|
||||||
<div className="single-line-editor-wrapper mb-2">
|
|
||||||
<SingleLineEditor
|
|
||||||
value={oAuth.username || ''}
|
|
||||||
theme={storedTheme}
|
|
||||||
onSave={handleSave}
|
|
||||||
onChange={(val) => handleUsernameChange(val)}
|
|
||||||
onRun={handleRun}
|
|
||||||
collection={collection}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<label className="block font-medium mb-2">Password</label>
|
|
||||||
<div className="single-line-editor-wrapper">
|
|
||||||
<SingleLineEditor
|
|
||||||
value={oAuth.password || ''}
|
|
||||||
theme={storedTheme}
|
|
||||||
onSave={handleSave}
|
|
||||||
onChange={(val) => handlePasswordChange(val)}
|
|
||||||
onRun={handleRun}
|
|
||||||
collection={collection}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</StyledWrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default OAuth2Ropc;
|
|
@ -2,14 +2,14 @@ import React from 'react';
|
|||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import GrantTypeSelector from './GrantTypeSelector/index';
|
import GrantTypeSelector from './GrantTypeSelector/index';
|
||||||
import OAuth2Ropc from './Ropc/index';
|
import OAuth2PasswordCredentials from './PasswordCredentials/index';
|
||||||
import OAuth2AuthorizationCode from './AuthorizationCode/index';
|
import OAuth2AuthorizationCode from './AuthorizationCode/index';
|
||||||
import OAuth2ClientCredentials from './ClientCredentials/index';
|
import OAuth2ClientCredentials from './ClientCredentials/index';
|
||||||
|
|
||||||
const grantTypeComponentMap = (grantType, item, collection) => {
|
const grantTypeComponentMap = (grantType, item, collection) => {
|
||||||
switch (grantType) {
|
switch (grantType) {
|
||||||
case 'password':
|
case 'password':
|
||||||
return <OAuth2Ropc item={item} collection={collection} />;
|
return <OAuth2PasswordCredentials item={item} collection={collection} />;
|
||||||
break;
|
break;
|
||||||
case 'authorization_code':
|
case 'authorization_code':
|
||||||
return <OAuth2AuthorizationCode item={item} collection={collection} />;
|
return <OAuth2AuthorizationCode item={item} collection={collection} />;
|
||||||
|
@ -35,8 +35,20 @@ const Auth = ({ item, collection }) => {
|
|||||||
case 'inherit': {
|
case 'inherit': {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row w-full mt-2 gap-2">
|
<div className="flex flex-row w-full mt-2 gap-2">
|
||||||
<div>Auth inherited from the Collection: </div>
|
{collectionAuth?.mode === 'oauth2' ? (
|
||||||
<div className="inherit-mode-text">{humanizeRequestAuthMode(collectionAuth?.mode)}</div>
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="flex flex-row gap-1">
|
||||||
|
<div>Collection level auth is: </div>
|
||||||
|
<div className="inherit-mode-text">{humanizeRequestAuthMode(collectionAuth?.mode)}</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-sm opacity-50">Cannot inherit Oauth2 from collection.</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div>Auth inherited from the Collection: </div>
|
||||||
|
<div className="inherit-mode-text">{humanizeRequestAuthMode(collectionAuth?.mode)}</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ import { each } from 'lodash';
|
|||||||
import { closeAllCollectionTabs } from 'providers/ReduxStore/slices/tabs';
|
import { closeAllCollectionTabs } from 'providers/ReduxStore/slices/tabs';
|
||||||
import { resolveRequestFilename } from 'utils/common/platform';
|
import { resolveRequestFilename } from 'utils/common/platform';
|
||||||
import { parseQueryParams, splitOnFirst } from 'utils/url/index';
|
import { parseQueryParams, splitOnFirst } from 'utils/url/index';
|
||||||
|
import { sendCollectionOauth2Request as _sendCollectionOauth2Request } from 'utils/network/index';
|
||||||
|
|
||||||
export const renameCollection = (newName, collectionUid) => (dispatch, getState) => {
|
export const renameCollection = (newName, collectionUid) => (dispatch, getState) => {
|
||||||
const state = 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) => {
|
export const sendRequest = (item, collectionUid) => (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
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'));
|
return reject(new Error('Collection not found'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemCopy = cloneDeep(item);
|
const itemCopy = cloneDeep(item || {});
|
||||||
const collectionCopy = cloneDeep(collection);
|
const collectionCopy = cloneDeep(collection);
|
||||||
|
|
||||||
const environment = findEnvironmentInCollection(collectionCopy, collection.activeEnvironmentUid);
|
const environment = findEnvironmentInCollection(collectionCopy, collection.activeEnvironmentUid);
|
||||||
|
@ -678,6 +678,7 @@ export const collectionsSlice = createSlice({
|
|||||||
if (!item.draft) {
|
if (!item.draft) {
|
||||||
item.draft = cloneDeep(item);
|
item.draft = cloneDeep(item);
|
||||||
}
|
}
|
||||||
|
item.draft.request.auth = {};
|
||||||
item.draft.request.auth.mode = action.payload.mode;
|
item.draft.request.auth.mode = action.payload.mode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -978,6 +979,7 @@ export const collectionsSlice = createSlice({
|
|||||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||||
|
|
||||||
if (collection) {
|
if (collection) {
|
||||||
|
set(collection, 'root.request.auth', {});
|
||||||
set(collection, 'root.request.auth.mode', action.payload.mode);
|
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);
|
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||||
|
|
||||||
if (collection) {
|
if (collection) {
|
||||||
|
set(collection, 'root.request.auth', {});
|
||||||
|
set(collection, 'root.request.auth.mode', action.payload.mode);
|
||||||
switch (action.payload.mode) {
|
switch (action.payload.mode) {
|
||||||
case 'awsv4':
|
case 'awsv4':
|
||||||
set(collection, 'root.request.auth.awsv4', action.payload.content);
|
set(collection, 'root.request.auth.awsv4', action.payload.content);
|
||||||
@ -998,6 +1002,9 @@ export const collectionsSlice = createSlice({
|
|||||||
case 'digest':
|
case 'digest':
|
||||||
set(collection, 'root.request.auth.digest', action.payload.content);
|
set(collection, 'root.request.auth.digest', action.payload.content);
|
||||||
break;
|
break;
|
||||||
|
case 'oauth2':
|
||||||
|
set(collection, 'root.request.auth.oauth2', action.payload.content);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -522,7 +522,7 @@ export const humanizeGrantType = (mode) => {
|
|||||||
let label = 'No Auth';
|
let label = 'No Auth';
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case 'password': {
|
case 'password': {
|
||||||
label = 'Resource Owner Password Credentials';
|
label = 'Password Credentials';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'authorization_code': {
|
case 'authorization_code': {
|
||||||
|
@ -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) => {
|
export const fetchGqlSchema = async (endpoint, environment, request, collection) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const { ipcRenderer } = window;
|
const { ipcRenderer } = window;
|
||||||
|
@ -105,7 +105,7 @@ const interpolateVars = (request, envVars = {}, collectionVariables = {}, proces
|
|||||||
const password = _interpolate(request.auth.password) || '';
|
const password = _interpolate(request.auth.password) || '';
|
||||||
|
|
||||||
// use auth header based approach and delete the request.auth object
|
// 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;
|
delete request.auth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ const prepareRequest = (request, collectionRoot) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (collectionAuth.mode === 'bearer') {
|
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') {
|
if (request.auth.mode === 'bearer') {
|
||||||
axiosRequest.headers['authorization'] = `Bearer ${get(request, 'auth.bearer.token')}`;
|
axiosRequest.headers['Authorization'] = `Bearer ${get(request, 'auth.bearer.token')}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ const authorizeUserInWindow = ({ authorizeUrl, callbackUrl }) => {
|
|||||||
const callbackUrlWithCode = new URL(finalUrl);
|
const callbackUrlWithCode = new URL(finalUrl);
|
||||||
const authorizationCode = callbackUrlWithCode.searchParams.get('code');
|
const authorizationCode = callbackUrlWithCode.searchParams.get('code');
|
||||||
|
|
||||||
return resolve(authorizationCode);
|
return resolve({ authorizationCode });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return reject(error);
|
return reject(error);
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ const { ipcMain } = require('electron');
|
|||||||
const { isUndefined, isNull, each, get, compact, cloneDeep } = require('lodash');
|
const { isUndefined, isNull, each, get, compact, cloneDeep } = require('lodash');
|
||||||
const { VarsRuntime, AssertRuntime, ScriptRuntime, TestRuntime } = require('@usebruno/js');
|
const { VarsRuntime, AssertRuntime, ScriptRuntime, TestRuntime } = require('@usebruno/js');
|
||||||
const prepareRequest = require('./prepare-request');
|
const prepareRequest = require('./prepare-request');
|
||||||
|
const prepareCollectionRequest = require('./prepare-collection-request');
|
||||||
const prepareGqlIntrospectionRequest = require('./prepare-gql-introspection-request');
|
const prepareGqlIntrospectionRequest = require('./prepare-gql-introspection-request');
|
||||||
const { cancelTokens, saveCancelToken, deleteCancelToken } = require('../../utils/cancel-token');
|
const { cancelTokens, saveCancelToken, deleteCancelToken } = require('../../utils/cancel-token');
|
||||||
const { uuid } = require('../../utils/common');
|
const { uuid } = require('../../utils/common');
|
||||||
@ -29,7 +30,11 @@ const { addDigestInterceptor } = require('./digestauth-helper');
|
|||||||
const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../../utils/proxy-util');
|
const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../../utils/proxy-util');
|
||||||
const { chooseFileToSave, writeBinaryFile } = require('../../utils/filesystem');
|
const { chooseFileToSave, writeBinaryFile } = require('../../utils/filesystem');
|
||||||
const { getCookieStringForUrl, addCookieToJar, getDomainsWithCookies } = require('../../utils/cookies');
|
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
|
// override the default escape function to prevent escaping
|
||||||
Mustache.escape = function (value) {
|
Mustache.escape = function (value) {
|
||||||
@ -191,12 +196,30 @@ const configureRequest = async (
|
|||||||
const axiosInstance = makeAxiosInstance();
|
const axiosInstance = makeAxiosInstance();
|
||||||
|
|
||||||
if (request.oauth2) {
|
if (request.oauth2) {
|
||||||
if (request?.oauth2?.grantType == 'authorization_code') {
|
let requestCopy = cloneDeep(request);
|
||||||
let requestCopy = cloneDeep(request);
|
switch (request?.oauth2?.grantType) {
|
||||||
interpolateVars(requestCopy, envVars, collectionVariables, processEnvVars);
|
case 'authorization_code':
|
||||||
const { data, url } = await resolveOAuth2AuthorizationCodecessToken(requestCopy);
|
interpolateVars(requestCopy, envVars, collectionVariables, processEnvVars);
|
||||||
request.data = data;
|
const { data: authorizationCodeData, url: authorizationCodeAccessTokenUrl } =
|
||||||
request.url = url;
|
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;
|
request.headers['cookie'] = cookieString;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return axiosInstance;
|
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) => {
|
ipcMain.handle('cancel-http-request', async (event, cancelTokenUid) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (cancelTokenUid && cancelTokens[cancelTokenUid]) {
|
if (cancelTokenUid && cancelTokens[cancelTokenUid]) {
|
||||||
|
@ -104,21 +104,26 @@ const interpolateVars = (request, envVars = {}, collectionVariables = {}, proces
|
|||||||
const username = _interpolate(request.auth.username) || '';
|
const username = _interpolate(request.auth.username) || '';
|
||||||
const password = _interpolate(request.auth.password) || '';
|
const password = _interpolate(request.auth.password) || '';
|
||||||
// use auth header based approach and delete the request.auth object
|
// 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;
|
delete request.auth;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request?.oauth2?.grantType) {
|
if (request?.oauth2?.grantType) {
|
||||||
|
let username, password, scope, clientId, clientSecret;
|
||||||
switch (request.oauth2.grantType) {
|
switch (request.oauth2.grantType) {
|
||||||
case 'password':
|
case 'password':
|
||||||
let username = _interpolate(request.oauth2.username) || '';
|
username = _interpolate(request.oauth2.username) || '';
|
||||||
let password = _interpolate(request.oauth2.password) || '';
|
password = _interpolate(request.oauth2.password) || '';
|
||||||
|
scope = _interpolate(request.oauth2.scope) || '';
|
||||||
|
request.oauth2.accessTokenUrl = _interpolate(request.oauth2.accessTokenUrl) || '';
|
||||||
request.oauth2.username = username;
|
request.oauth2.username = username;
|
||||||
request.oauth2.password = password;
|
request.oauth2.password = password;
|
||||||
|
request.oauth2.scope = scope;
|
||||||
request.data = {
|
request.data = {
|
||||||
grant_type: 'password',
|
grant_type: 'password',
|
||||||
username,
|
username,
|
||||||
password
|
password,
|
||||||
|
scope
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case 'authorization_code':
|
case 'authorization_code':
|
||||||
@ -128,16 +133,21 @@ const interpolateVars = (request, envVars = {}, collectionVariables = {}, proces
|
|||||||
request.oauth2.clientId = _interpolate(request.oauth2.clientId) || '';
|
request.oauth2.clientId = _interpolate(request.oauth2.clientId) || '';
|
||||||
request.oauth2.clientSecret = _interpolate(request.oauth2.clientSecret) || '';
|
request.oauth2.clientSecret = _interpolate(request.oauth2.clientSecret) || '';
|
||||||
request.oauth2.scope = _interpolate(request.oauth2.scope) || '';
|
request.oauth2.scope = _interpolate(request.oauth2.scope) || '';
|
||||||
|
request.oauth2.pkce = _interpolate(request.oauth2.pkce) || false;
|
||||||
break;
|
break;
|
||||||
case 'client_credentials':
|
case 'client_credentials':
|
||||||
let clientId = _interpolate(request.oauth2.clientId) || '';
|
clientId = _interpolate(request.oauth2.clientId) || '';
|
||||||
let clientSecret = _interpolate(request.oauth2.clientSecret) || '';
|
clientSecret = _interpolate(request.oauth2.clientSecret) || '';
|
||||||
|
scope = _interpolate(request.oauth2.scope) || '';
|
||||||
|
request.oauth2.accessTokenUrl = _interpolate(request.oauth2.accessTokenUrl) || '';
|
||||||
request.oauth2.clientId = clientId;
|
request.oauth2.clientId = clientId;
|
||||||
request.oauth2.clientSecret = clientSecret;
|
request.oauth2.clientSecret = clientSecret;
|
||||||
|
request.oauth2.scope = scope;
|
||||||
request.data = {
|
request.data = {
|
||||||
grant_type: 'client_credentials',
|
grant_type: 'client_credentials',
|
||||||
client_id: clientId,
|
client_id: clientId,
|
||||||
client_secret: clientSecret
|
client_secret: clientSecret,
|
||||||
|
scope
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -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
|
|
||||||
};
|
|
109
packages/bruno-electron/src/ipc/network/oauth2-helper.js
Normal file
109
packages/bruno-electron/src/ipc/network/oauth2-helper.js
Normal file
@ -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
|
||||||
|
};
|
@ -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;
|
@ -61,7 +61,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case 'bearer':
|
case 'bearer':
|
||||||
axiosRequest.headers['authorization'] = `Bearer ${get(collectionAuth, 'bearer.token')}`;
|
axiosRequest.headers['Authorization'] = `Bearer ${get(collectionAuth, 'bearer.token')}`;
|
||||||
break;
|
break;
|
||||||
case 'digest':
|
case 'digest':
|
||||||
axiosRequest.digestConfig = {
|
axiosRequest.digestConfig = {
|
||||||
@ -69,36 +69,6 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
|
|||||||
password: get(collectionAuth, 'digest.password')
|
password: get(collectionAuth, 'digest.password')
|
||||||
};
|
};
|
||||||
break;
|
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;
|
break;
|
||||||
case 'bearer':
|
case 'bearer':
|
||||||
axiosRequest.headers['authorization'] = `Bearer ${get(request, 'auth.bearer.token')}`;
|
axiosRequest.headers['Authorization'] = `Bearer ${get(request, 'auth.bearer.token')}`;
|
||||||
break;
|
break;
|
||||||
case 'digest':
|
case 'digest':
|
||||||
axiosRequest.digestConfig = {
|
axiosRequest.digestConfig = {
|
||||||
@ -135,8 +105,10 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
|
|||||||
case 'password':
|
case 'password':
|
||||||
axiosRequest.oauth2 = {
|
axiosRequest.oauth2 = {
|
||||||
grantType: grantType,
|
grantType: grantType,
|
||||||
|
accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'),
|
||||||
username: get(request, 'auth.oauth2.username'),
|
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;
|
break;
|
||||||
case 'authorization_code':
|
case 'authorization_code':
|
||||||
@ -147,14 +119,17 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
|
|||||||
accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'),
|
accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'),
|
||||||
clientId: get(request, 'auth.oauth2.clientId'),
|
clientId: get(request, 'auth.oauth2.clientId'),
|
||||||
clientSecret: get(request, 'auth.oauth2.clientSecret'),
|
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;
|
break;
|
||||||
case 'client_credentials':
|
case 'client_credentials':
|
||||||
axiosRequest.oauth2 = {
|
axiosRequest.oauth2 = {
|
||||||
grantType: grantType,
|
grantType: grantType,
|
||||||
|
accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'),
|
||||||
clientId: get(request, 'auth.oauth2.clientId'),
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,22 @@ class BrunoRequest {
|
|||||||
return this.req.method;
|
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) {
|
setMethod(method) {
|
||||||
this.req.method = method;
|
this.req.method = method;
|
||||||
}
|
}
|
||||||
|
@ -392,14 +392,17 @@ const sem = grammar.createSemantics().addAttribute('ast', {
|
|||||||
const clientIdKey = _.find(auth, { name: 'client_id' });
|
const clientIdKey = _.find(auth, { name: 'client_id' });
|
||||||
const clientSecretKey = _.find(auth, { name: 'client_secret' });
|
const clientSecretKey = _.find(auth, { name: 'client_secret' });
|
||||||
const scopeKey = _.find(auth, { name: 'scope' });
|
const scopeKey = _.find(auth, { name: 'scope' });
|
||||||
|
const pkceKey = _.find(auth, { name: 'pkce' });
|
||||||
return {
|
return {
|
||||||
auth: {
|
auth: {
|
||||||
oauth2:
|
oauth2:
|
||||||
grantTypeKey?.value && grantTypeKey?.value == 'password'
|
grantTypeKey?.value && grantTypeKey?.value == 'password'
|
||||||
? {
|
? {
|
||||||
grantType: grantTypeKey ? grantTypeKey.value : '',
|
grantType: grantTypeKey ? grantTypeKey.value : '',
|
||||||
|
accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '',
|
||||||
username: usernameKey ? usernameKey.value : '',
|
username: usernameKey ? usernameKey.value : '',
|
||||||
password: passwordKey ? passwordKey.value : ''
|
password: passwordKey ? passwordKey.value : '',
|
||||||
|
scope: scopeKey ? scopeKey.value : ''
|
||||||
}
|
}
|
||||||
: grantTypeKey?.value && grantTypeKey?.value == 'authorization_code'
|
: grantTypeKey?.value && grantTypeKey?.value == 'authorization_code'
|
||||||
? {
|
? {
|
||||||
@ -409,13 +412,16 @@ const sem = grammar.createSemantics().addAttribute('ast', {
|
|||||||
accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '',
|
accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '',
|
||||||
clientId: clientIdKey ? clientIdKey.value : '',
|
clientId: clientIdKey ? clientIdKey.value : '',
|
||||||
clientSecret: clientSecretKey ? clientSecretKey.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'
|
: grantTypeKey?.value && grantTypeKey?.value == 'client_credentials'
|
||||||
? {
|
? {
|
||||||
grantType: grantTypeKey ? grantTypeKey.value : '',
|
grantType: grantTypeKey ? grantTypeKey.value : '',
|
||||||
|
accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '',
|
||||||
clientId: clientIdKey ? clientIdKey.value : '',
|
clientId: clientIdKey ? clientIdKey.value : '',
|
||||||
clientSecret: clientSecretKey ? clientSecretKey.value : ''
|
clientSecret: clientSecretKey ? clientSecretKey.value : '',
|
||||||
|
scope: scopeKey ? scopeKey.value : ''
|
||||||
}
|
}
|
||||||
: {}
|
: {}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ const { outdentString } = require('../../v1/src/utils');
|
|||||||
|
|
||||||
const grammar = ohm.grammar(`Bru {
|
const grammar = ohm.grammar(`Bru {
|
||||||
BruFile = (meta | query | headers | auth | auths | vars | script | tests | docs)*
|
BruFile = (meta | query | headers | auth | auths | vars | script | tests | docs)*
|
||||||
auths = authawsv4 | authbasic | authbearer | authdigest
|
auths = authawsv4 | authbasic | authbearer | authdigest | authOAuth2
|
||||||
|
|
||||||
nl = "\\r"? "\\n"
|
nl = "\\r"? "\\n"
|
||||||
st = " " | "\\t"
|
st = " " | "\\t"
|
||||||
@ -42,6 +42,7 @@ const grammar = ohm.grammar(`Bru {
|
|||||||
authbasic = "auth:basic" dictionary
|
authbasic = "auth:basic" dictionary
|
||||||
authbearer = "auth:bearer" dictionary
|
authbearer = "auth:bearer" dictionary
|
||||||
authdigest = "auth:digest" dictionary
|
authdigest = "auth:digest" dictionary
|
||||||
|
authOAuth2 = "auth:oauth2" dictionary
|
||||||
|
|
||||||
script = scriptreq | scriptres
|
script = scriptreq | scriptres
|
||||||
scriptreq = "script:pre-request" st* "{" nl* textblock tagend
|
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) {
|
varsreq(_1, dictionary) {
|
||||||
const vars = mapPairListToKeyValPairs(dictionary.ast);
|
const vars = mapPairListToKeyValPairs(dictionary.ast);
|
||||||
_.each(vars, (v) => {
|
_.each(vars, (v) => {
|
||||||
|
@ -131,8 +131,10 @@ ${indentString(`password: ${auth?.digest?.password || ''}`)}
|
|||||||
case 'password':
|
case 'password':
|
||||||
bru += `auth:oauth2 {
|
bru += `auth:oauth2 {
|
||||||
${indentString(`grant_type: password`)}
|
${indentString(`grant_type: password`)}
|
||||||
|
${indentString(`access_token_url: ${auth?.oauth2?.accessTokenUrl || ''}`)}
|
||||||
${indentString(`username: ${auth?.oauth2?.username || ''}`)}
|
${indentString(`username: ${auth?.oauth2?.username || ''}`)}
|
||||||
${indentString(`password: ${auth?.oauth2?.password || ''}`)}
|
${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_id: ${auth?.oauth2?.clientId || ''}`)}
|
||||||
${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)}
|
${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)}
|
||||||
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
|
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
|
||||||
|
${indentString(`pkce: ${(auth?.oauth2?.pkce || false).toString()}`)}
|
||||||
}
|
}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
@ -153,8 +156,10 @@ ${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
|
|||||||
case 'client_credentials':
|
case 'client_credentials':
|
||||||
bru += `auth:oauth2 {
|
bru += `auth:oauth2 {
|
||||||
${indentString(`grant_type: client_credentials`)}
|
${indentString(`grant_type: client_credentials`)}
|
||||||
|
${indentString(`access_token_url: ${auth?.oauth2?.accessTokenUrl || ''}`)}
|
||||||
${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)}
|
${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)}
|
||||||
${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)}
|
${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)}
|
||||||
|
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
|
||||||
}
|
}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
@ -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 reqvars = _.get(vars, 'req');
|
||||||
let resvars = _.get(vars, 'res');
|
let resvars = _.get(vars, 'res');
|
||||||
if (reqvars && reqvars.length) {
|
if (reqvars && reqvars.length) {
|
||||||
|
@ -47,9 +47,9 @@ auth:digest {
|
|||||||
|
|
||||||
auth:oauth2 {
|
auth:oauth2 {
|
||||||
grant_type: authorization_code
|
grant_type: authorization_code
|
||||||
callback_url: http://localhost:8080/api/auth/oauth2/ac/callback
|
callback_url: http://localhost:8080/api/auth/oauth2/authorization_code/callback
|
||||||
authorization_url: http://localhost:8080/api/auth/oauth2/ac/authorize
|
authorization_url: http://localhost:8080/api/auth/oauth2/authorization_code/authorize
|
||||||
access_token_url: http://localhost:8080/api/auth/oauth2/ac/token
|
access_token_url: http://localhost:8080/api/auth/oauth2/authorization_code/token
|
||||||
client_id: client_id_1
|
client_id: client_id_1
|
||||||
client_secret: client_secret_1
|
client_secret: client_secret_1
|
||||||
scope: read write
|
scope: read write
|
||||||
|
@ -68,9 +68,9 @@
|
|||||||
"grantType": "authorization_code",
|
"grantType": "authorization_code",
|
||||||
"clientId": "client_id_1",
|
"clientId": "client_id_1",
|
||||||
"clientSecret": "client_secret_1",
|
"clientSecret": "client_secret_1",
|
||||||
"authorizationUrl": "http://localhost:8080/api/auth/oauth2/ac/authorize",
|
"authorizationUrl": "http://localhost:8080/api/auth/oauth2/authorization_code/authorize",
|
||||||
"callbackUrl": "http://localhost:8080/api/auth/oauth2/ac/callback",
|
"callbackUrl": "http://localhost:8080/api/auth/oauth2/authorization_code/callback",
|
||||||
"accessTokenUrl": "http://localhost:8080/api/auth/oauth2/ac/token",
|
"accessTokenUrl": "http://localhost:8080/api/auth/oauth2/authorization_code/token",
|
||||||
"scope": "read write"
|
"scope": "read write"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -144,7 +144,7 @@ const oauth2Schema = Yup.object({
|
|||||||
otherwise: Yup.string().nullable().strip()
|
otherwise: Yup.string().nullable().strip()
|
||||||
}),
|
}),
|
||||||
accessTokenUrl: Yup.string().when('grantType', {
|
accessTokenUrl: Yup.string().when('grantType', {
|
||||||
is: (val) => ['authorization_code'].includes(val),
|
is: (val) => ['client_credentials', 'password', 'authorization_code'].includes(val),
|
||||||
then: Yup.string().nullable(),
|
then: Yup.string().nullable(),
|
||||||
otherwise: Yup.string().nullable().strip()
|
otherwise: Yup.string().nullable().strip()
|
||||||
}),
|
}),
|
||||||
@ -159,9 +159,14 @@ const oauth2Schema = Yup.object({
|
|||||||
otherwise: Yup.string().nullable().strip()
|
otherwise: Yup.string().nullable().strip()
|
||||||
}),
|
}),
|
||||||
scope: Yup.string().when('grantType', {
|
scope: Yup.string().when('grantType', {
|
||||||
is: (val) => ['authorization_code'].includes(val),
|
is: (val) => ['client_credentials', 'password', 'authorization_code'].includes(val),
|
||||||
then: Yup.string().nullable(),
|
then: Yup.string().nullable(),
|
||||||
otherwise: Yup.string().nullable().strip()
|
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)
|
.noUnknown(true)
|
||||||
|
@ -3,16 +3,7 @@ headers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auth {
|
auth {
|
||||||
mode: bearer
|
mode: none
|
||||||
}
|
|
||||||
|
|
||||||
auth:basic {
|
|
||||||
username: bruno
|
|
||||||
password: {{basicAuthPassword}}
|
|
||||||
}
|
|
||||||
|
|
||||||
auth:bearer {
|
|
||||||
token: {{bearer_auth_token}}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
docs {
|
docs {
|
||||||
|
@ -4,11 +4,11 @@ vars {
|
|||||||
basic_auth_password: della
|
basic_auth_password: della
|
||||||
client_id: client_id_1
|
client_id: client_id_1
|
||||||
client_secret: client_secret_1
|
client_secret: client_secret_1
|
||||||
auth_url: http://localhost:8080/api/auth/oauth2/ac/authorize
|
auth_url: http://localhost:8080/api/auth/oauth2/authorization_code/authorize
|
||||||
callback_url: http://localhost:8080/api/auth/oauth2/ac/callback
|
callback_url: http://localhost:8080/api/auth/oauth2/authorization_code/callback
|
||||||
access_token_url: http://localhost:8080/api/auth/oauth2/ac/token
|
access_token_url: http://localhost:8080/api/auth/oauth2/authorization_code/token
|
||||||
ropc_username: foo
|
passwordCredentials_username: foo
|
||||||
ropc_password: bar
|
passwordCredentials_password: bar
|
||||||
github_authorize_url: https://github.com/login/oauth/authorize
|
github_authorize_url: https://github.com/login/oauth/authorize
|
||||||
github_access_token_url: https://github.com/login/oauth/access_token
|
github_access_token_url: https://github.com/login/oauth/access_token
|
||||||
google_auth_url: https://accounts.google.com/o/oauth2/auth
|
google_auth_url: https://accounts.google.com/o/oauth2/auth
|
||||||
@ -21,8 +21,8 @@ vars:secret [
|
|||||||
google_client_id,
|
google_client_id,
|
||||||
google_client_secret,
|
google_client_secret,
|
||||||
github_authorization_code,
|
github_authorization_code,
|
||||||
ropc_access_token,
|
passwordCredentials_access_token,
|
||||||
cc_access_token,
|
client_credentials_access_token,
|
||||||
ac_access_token,
|
authorization_code_access_token,
|
||||||
github_access_token
|
github_access_token
|
||||||
]
|
]
|
||||||
|
1
packages/bruno-tests/collection_level_oauth2/.gitignore
vendored
Normal file
1
packages/bruno-tests/collection_level_oauth2/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
!.env
|
1
packages/bruno-tests/collection_level_oauth2/.nvmrc
Normal file
1
packages/bruno-tests/collection_level_oauth2/.nvmrc
Normal file
@ -0,0 +1 @@
|
|||||||
|
v18
|
18
packages/bruno-tests/collection_level_oauth2/bruno.json
Normal file
18
packages/bruno-tests/collection_level_oauth2/bruno.json
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
}
|
30
packages/bruno-tests/collection_level_oauth2/collection.bru
Normal file
30
packages/bruno-tests/collection_level_oauth2/collection.bru
Normal file
@ -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
|
||||||
|
}
|
@ -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
|
||||||
|
]
|
@ -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}}
|
||||||
|
}
|
30
packages/bruno-tests/collection_level_oauth2/package-lock.json
generated
Normal file
30
packages/bruno-tests/collection_level_oauth2/package-lock.json
generated
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "@usebruno/test-collection",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"dependencies": {
|
||||||
|
"@faker-js/faker": "^8.4.0"
|
||||||
|
}
|
||||||
|
}
|
3
packages/bruno-tests/collection_level_oauth2/readme.md
Normal file
3
packages/bruno-tests/collection_level_oauth2/readme.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# bruno-tests collection
|
||||||
|
|
||||||
|
API Collection to run sanity tests on Bruno CLI.
|
15
packages/bruno-tests/collection_level_oauth2/resource.bru
Normal file
15
packages/bruno-tests/collection_level_oauth2/resource.bru
Normal file
@ -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}}
|
||||||
|
}
|
@ -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]);
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
@ -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}}"
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
@ -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]);
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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}}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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}}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
@ -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}}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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}}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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}}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
@ -1,19 +1,7 @@
|
|||||||
{
|
{
|
||||||
"version": "1",
|
"version": "1",
|
||||||
"name": "bruno-testbench",
|
"name": "collection_oauth2",
|
||||||
"type": "collection",
|
"type": "collection",
|
||||||
"proxy": {
|
|
||||||
"enabled": false,
|
|
||||||
"protocol": "http",
|
|
||||||
"hostname": "{{proxyHostname}}",
|
|
||||||
"port": 4000,
|
|
||||||
"auth": {
|
|
||||||
"enabled": false,
|
|
||||||
"username": "anoop",
|
|
||||||
"password": "password"
|
|
||||||
},
|
|
||||||
"bypassProxy": ""
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"moduleWhitelist": ["crypto"],
|
"moduleWhitelist": ["crypto"],
|
||||||
"filesystemAccess": {
|
"filesystemAccess": {
|
||||||
@ -25,7 +13,6 @@
|
|||||||
"certs": []
|
"certs": []
|
||||||
},
|
},
|
||||||
"presets": {
|
"presets": {
|
||||||
"requestType": "http",
|
"requestType": "http"
|
||||||
"requestUrl": "http://localhost:6000"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,15 +6,6 @@ auth {
|
|||||||
mode: none
|
mode: none
|
||||||
}
|
}
|
||||||
|
|
||||||
auth:basic {
|
|
||||||
username: bruno
|
|
||||||
password: {{basicAuthPassword}}
|
|
||||||
}
|
|
||||||
|
|
||||||
auth:bearer {
|
|
||||||
token: {{bearerAuthToken}}
|
|
||||||
}
|
|
||||||
|
|
||||||
docs {
|
docs {
|
||||||
# bruno-testbench 🐶
|
# bruno-testbench 🐶
|
||||||
|
|
||||||
|
@ -4,23 +4,29 @@ vars {
|
|||||||
basic_auth_password: della
|
basic_auth_password: della
|
||||||
client_id: client_id_1
|
client_id: client_id_1
|
||||||
client_secret: client_secret_1
|
client_secret: client_secret_1
|
||||||
auth_url: http://localhost:8080/api/auth/oauth2/ac/authorize
|
password_credentials_access_token_url: http://localhost:8080/api/auth/oauth2/password_credentials/token
|
||||||
callback_url: http://localhost:8080/api/auth/oauth2/ac/callback
|
password_credentials_username: foo
|
||||||
access_token_url: http://localhost:8080/api/auth/oauth2/ac/token
|
password_credentials_password: bar
|
||||||
ropc_username: foo
|
password_credentials_scope:
|
||||||
ropc_password: bar
|
authorization_code_authorize_url: http://localhost:8080/api/auth/oauth2/authorization_code/authorize
|
||||||
github_authorize_url: https://github.com/login/oauth/authorize
|
authorization_code_callback_url: http://localhost:8080/api/auth/oauth2/authorization_code/callback
|
||||||
github_access_token_url: https://github.com/login/oauth/access_token
|
authorization_code_access_token_url: http://localhost:8080/api/auth/oauth2/authorization_code/token
|
||||||
google_auth_url: https://accounts.google.com/o/oauth2/auth
|
authorization_code_google_auth_url: https://accounts.google.com/o/oauth2/auth
|
||||||
google_access_token_url: https://accounts.google.com/o/oauth2/token
|
authorization_code_google_access_token_url: https://accounts.google.com/o/oauth2/token
|
||||||
google_scope: https://www.googleapis.com/auth/userinfo.email
|
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 [
|
vars:secret [
|
||||||
github_client_secret,
|
authorization_code_google_client_id,
|
||||||
github_client_id,
|
authorization_code_google_client_secret,
|
||||||
google_client_id,
|
authorization_code_github_client_secret,
|
||||||
google_client_secret,
|
authorization_code_github_client_id,
|
||||||
github_authorization_code,
|
authorization_code_github_authorization_code,
|
||||||
github_access_token,
|
authorization_code_github_access_token
|
||||||
ac_access_token
|
|
||||||
]
|
]
|
||||||
|
@ -4,13 +4,13 @@ const router = express.Router();
|
|||||||
const authBearer = require('./bearer');
|
const authBearer = require('./bearer');
|
||||||
const authBasic = require('./basic');
|
const authBasic = require('./basic');
|
||||||
const authCookie = require('./cookie');
|
const authCookie = require('./cookie');
|
||||||
const authOAuth2Ropc = require('./oauth2/ropc');
|
const authOAuth2PasswordCredentials = require('./oauth2/passwordCredentials');
|
||||||
const authOAuth2AuthorizationCode = require('./oauth2/ac');
|
const authOAuth2AuthorizationCode = require('./oauth2/authorizationCode');
|
||||||
const authOAuth2Cc = require('./oauth2/cc');
|
const authOAuth2ClientCredentials = require('./oauth2/clientCredentials');
|
||||||
|
|
||||||
router.use('/oauth2/ropc', authOAuth2Ropc);
|
router.use('/oauth2/password_credentials', authOAuth2PasswordCredentials);
|
||||||
router.use('/oauth2/ac', authOAuth2AuthorizationCode);
|
router.use('/oauth2/authorization_code', authOAuth2AuthorizationCode);
|
||||||
router.use('/oauth2/cc', authOAuth2Cc);
|
router.use('/oauth2/client_credentials', authOAuth2ClientCredentials);
|
||||||
router.use('/bearer', authBearer);
|
router.use('/bearer', authBearer);
|
||||||
router.use('/basic', authBasic);
|
router.use('/basic', authBasic);
|
||||||
router.use('/cookie', authCookie);
|
router.use('/cookie', authCookie);
|
||||||
|
@ -17,8 +17,16 @@ function generateUniqueString() {
|
|||||||
return crypto.randomBytes(16).toString('hex');
|
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) => {
|
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') {
|
if (response_type !== 'code') {
|
||||||
return res.status(401).json({ error: 'Invalid Response type, expected "code"' });
|
return res.status(401).json({ error: 'Invalid Response type, expected "code"' });
|
||||||
}
|
}
|
||||||
@ -37,7 +45,8 @@ router.get('/authorize', (req, res) => {
|
|||||||
authCodes.push({
|
authCodes.push({
|
||||||
authCode: authorization_code,
|
authCode: authorization_code,
|
||||||
client_id,
|
client_id,
|
||||||
redirect_uri
|
redirect_uri,
|
||||||
|
code_challenge
|
||||||
});
|
});
|
||||||
|
|
||||||
const redirectUrl = `${redirect_uri}?code=${authorization_code}`;
|
const redirectUrl = `${redirect_uri}?code=${authorization_code}`;
|
||||||
@ -72,6 +81,7 @@ router.get('/authorize', (req, res) => {
|
|||||||
|
|
||||||
// Handle the authorization callback
|
// Handle the authorization callback
|
||||||
router.get('/callback', (req, res) => {
|
router.get('/callback', (req, res) => {
|
||||||
|
console.log('authorization code callback', req.query);
|
||||||
const { code } = req.query;
|
const { code } = req.query;
|
||||||
|
|
||||||
// Check if the authCode is valid.
|
// Check if the authCode is valid.
|
||||||
@ -85,13 +95,15 @@ router.get('/callback', (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.post('/token', (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) {
|
if (req?.body?.grant_type) {
|
||||||
grant_type = req?.body?.grant_type;
|
grant_type = req?.body?.grant_type;
|
||||||
code = req?.body?.code;
|
code = req?.body?.code;
|
||||||
redirect_uri = req?.body?.redirect_uri;
|
redirect_uri = req?.body?.redirect_uri;
|
||||||
client_id = req?.body?.client_id;
|
client_id = req?.body?.client_id;
|
||||||
client_secret = req?.body?.client_secret;
|
client_secret = req?.body?.client_secret;
|
||||||
|
code_verifier = req?.body?.code_verifier;
|
||||||
}
|
}
|
||||||
if (req?.headers?.grant_type) {
|
if (req?.headers?.grant_type) {
|
||||||
grant_type = 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;
|
redirect_uri = req?.headers?.redirect_uri;
|
||||||
client_id = req?.headers?.client_id;
|
client_id = req?.headers?.client_id;
|
||||||
client_secret = req?.headers?.client_secret;
|
client_secret = req?.headers?.client_secret;
|
||||||
|
code_verifier = req?.headers?.code_verifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (grant_type !== 'authorization_code') {
|
if (grant_type !== 'authorization_code') {
|
||||||
@ -110,7 +123,13 @@ router.post('/token', (req, res) => {
|
|||||||
// return res.status(401).json({ error: 'Invalid client credentials' });
|
// 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) {
|
if (!storedAuthCode) {
|
||||||
return res.status(401).json({ error: 'Invalid Authorization Code' });
|
return res.status(401).json({ error: 'Invalid Authorization Code' });
|
||||||
@ -127,6 +146,7 @@ router.post('/token', (req, res) => {
|
|||||||
|
|
||||||
router.post('/resource', (req, res) => {
|
router.post('/resource', (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
console.log('authorization code resource', req.query, tokens);
|
||||||
const { token } = req.query;
|
const { token } = req.query;
|
||||||
const storedToken = tokens.find((t) => t.accessToken === token);
|
const storedToken = tokens.find((t) => t.accessToken === token);
|
||||||
if (!storedToken) {
|
if (!storedToken) {
|
@ -4,7 +4,8 @@ const crypto = require('crypto');
|
|||||||
const clients = [
|
const clients = [
|
||||||
{
|
{
|
||||||
client_id: 'client_id_1',
|
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) => {
|
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) {
|
if (req?.body?.grant_type) {
|
||||||
grant_type = req?.body?.grant_type;
|
grant_type = req?.body?.grant_type;
|
||||||
client_id = req?.body?.client_id;
|
client_id = req?.body?.client_id;
|
||||||
client_secret = req?.body?.client_secret;
|
client_secret = req?.body?.client_secret;
|
||||||
|
scope = req?.body?.scope;
|
||||||
} else if (req?.headers?.grant_type) {
|
} else if (req?.headers?.grant_type) {
|
||||||
grant_type = req?.headers?.grant_type;
|
grant_type = req?.headers?.grant_type;
|
||||||
client_id = req?.headers?.client_id;
|
client_id = req?.headers?.client_id;
|
||||||
client_secret = req?.headers?.client_secret;
|
client_secret = req?.headers?.client_secret;
|
||||||
|
scope = req?.headers?.scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('client_cred', client_id, client_secret, scope);
|
||||||
if (grant_type !== 'client_credentials') {
|
if (grant_type !== 'client_credentials') {
|
||||||
return res.status(401).json({ error: 'Invalid Grant Type, expected "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) {
|
if (!client) {
|
||||||
return res.status(401).json({ error: 'Invalid client' });
|
return res.status(401).json({ error: 'Invalid client details or scope' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = generateUniqueString();
|
const token = generateUniqueString();
|
||||||
tokens.push({
|
tokens.push({
|
||||||
token,
|
token,
|
||||||
client_id,
|
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) => {
|
router.get('/resource', (req, res) => {
|
@ -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) => {
|
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') {
|
if (grant_type !== 'password') {
|
||||||
return res.status(401).json({ error: 'Invalid Grant Type' });
|
return res.status(401).json({ error: 'Invalid Grant Type' });
|
Loading…
Reference in New Issue
Block a user