mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-25 01:14:23 +01:00
parent
6320a80cbe
commit
51e2917ef9
@ -4,10 +4,9 @@ import Dropdown from 'components/Dropdown';
|
|||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import { IconCaretDown } from '@tabler/icons';
|
import { IconCaretDown } from '@tabler/icons';
|
||||||
import { updateAuth } from 'providers/ReduxStore/slices/collections';
|
|
||||||
import { humanizeGrantType } from 'utils/collections';
|
import { humanizeGrantType } from 'utils/collections';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { updateCollectionAuth, updateCollectionAuthMode } from 'providers/ReduxStore/slices/collections/index';
|
import { updateCollectionAuth, updateCollectionAuthMode } from 'providers/ReduxStore/slices/collections';
|
||||||
|
|
||||||
const GrantTypeSelector = ({ collection }) => {
|
const GrantTypeSelector = ({ collection }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -90,6 +89,15 @@ const GrantTypeSelector = ({ collection }) => {
|
|||||||
>
|
>
|
||||||
Client Credentials
|
Client Credentials
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
onGrantTypeChange('implicit');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Implicit
|
||||||
|
</div>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
|
@ -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,87 @@
|
|||||||
|
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';
|
||||||
|
import { clearOauth2Cache } from 'utils/network';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
|
const OAuth2Implicit = ({ 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, clientId, scope, state } = oAuth;
|
||||||
|
|
||||||
|
const handleChange = (key, value) => {
|
||||||
|
dispatch(
|
||||||
|
updateCollectionAuth({
|
||||||
|
mode: 'oauth2',
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
content: {
|
||||||
|
grantType: 'implicit',
|
||||||
|
callbackUrl,
|
||||||
|
authorizationUrl,
|
||||||
|
clientId,
|
||||||
|
scope,
|
||||||
|
state,
|
||||||
|
[key]: value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClearCache = (e) => {
|
||||||
|
clearOauth2Cache(collection?.uid)
|
||||||
|
.then(() => {
|
||||||
|
toast.success('cleared cache successfully');
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error(err.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
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 gap-4">
|
||||||
|
<button onClick={handleRun} className="submit btn btn-sm btn-secondary w-fit">
|
||||||
|
Get Access Token
|
||||||
|
</button>
|
||||||
|
<button onClick={handleClearCache} className="submit btn btn-sm btn-secondary w-fit">
|
||||||
|
Clear Cache
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OAuth2Implicit;
|
@ -0,0 +1,24 @@
|
|||||||
|
const inputsConfig = [
|
||||||
|
{
|
||||||
|
key: 'callbackUrl',
|
||||||
|
label: 'Callback URL'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'authorizationUrl',
|
||||||
|
label: 'Authorization URL'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'clientId',
|
||||||
|
label: 'Client ID'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'scope',
|
||||||
|
label: 'Scope'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'state',
|
||||||
|
label: 'State'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export { inputsConfig };
|
@ -5,6 +5,7 @@ import GrantTypeSelector from './GrantTypeSelector/index';
|
|||||||
import OAuth2PasswordCredentials from './PasswordCredentials/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';
|
||||||
|
import OAuth2Implicit from './Implicit/index';
|
||||||
|
|
||||||
const grantTypeComponentMap = (grantType, collection) => {
|
const grantTypeComponentMap = (grantType, collection) => {
|
||||||
switch (grantType) {
|
switch (grantType) {
|
||||||
@ -17,6 +18,9 @@ const grantTypeComponentMap = (grantType, collection) => {
|
|||||||
case 'client_credentials':
|
case 'client_credentials':
|
||||||
return <OAuth2ClientCredentials collection={collection} />;
|
return <OAuth2ClientCredentials collection={collection} />;
|
||||||
break;
|
break;
|
||||||
|
case 'implicit':
|
||||||
|
return <OAuth2Implicit collection={collection} />;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return <div>TBD</div>;
|
return <div>TBD</div>;
|
||||||
break;
|
break;
|
||||||
|
@ -84,6 +84,15 @@ const GrantTypeSelector = ({ item, collection }) => {
|
|||||||
>
|
>
|
||||||
Client Credentials
|
Client Credentials
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
onGrantTypeChange('implicit');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Implicit
|
||||||
|
</div>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
|
@ -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,88 @@
|
|||||||
|
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';
|
||||||
|
import { clearOauth2Cache } from 'utils/network';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
|
const OAuth2Implicit = ({ 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 { callbackUrl, authorizationUrl, clientId, scope, state } = oAuth;
|
||||||
|
|
||||||
|
const handleChange = (key, value) => {
|
||||||
|
dispatch(
|
||||||
|
updateAuth({
|
||||||
|
mode: 'oauth2',
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
itemUid: item.uid,
|
||||||
|
content: {
|
||||||
|
grantType: 'implicit',
|
||||||
|
callbackUrl,
|
||||||
|
authorizationUrl,
|
||||||
|
clientId,
|
||||||
|
scope,
|
||||||
|
state,
|
||||||
|
[key]: value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClearCache = (e) => {
|
||||||
|
clearOauth2Cache(collection?.uid)
|
||||||
|
.then(() => {
|
||||||
|
toast.success('cleared cache successfully');
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error(err.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
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 gap-4">
|
||||||
|
<button onClick={handleRun} className="submit btn btn-sm btn-secondary w-fit">
|
||||||
|
Get Access Token
|
||||||
|
</button>
|
||||||
|
<button onClick={handleClearCache} className="submit btn btn-sm btn-secondary w-fit">
|
||||||
|
Clear Cache
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OAuth2Implicit;
|
@ -0,0 +1,24 @@
|
|||||||
|
const inputsConfig = [
|
||||||
|
{
|
||||||
|
key: 'callbackUrl',
|
||||||
|
label: 'Callback URL'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'authorizationUrl',
|
||||||
|
label: 'Authorization URL'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'clientId',
|
||||||
|
label: 'Client ID'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'scope',
|
||||||
|
label: 'Scope'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'state',
|
||||||
|
label: 'State'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export { inputsConfig };
|
@ -5,6 +5,7 @@ import GrantTypeSelector from './GrantTypeSelector/index';
|
|||||||
import OAuth2PasswordCredentials from './PasswordCredentials/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';
|
||||||
|
import OAuth2Implicit from './Implicit/index';
|
||||||
|
|
||||||
const grantTypeComponentMap = (grantType, item, collection) => {
|
const grantTypeComponentMap = (grantType, item, collection) => {
|
||||||
switch (grantType) {
|
switch (grantType) {
|
||||||
@ -17,6 +18,9 @@ const grantTypeComponentMap = (grantType, item, collection) => {
|
|||||||
case 'client_credentials':
|
case 'client_credentials':
|
||||||
return <OAuth2ClientCredentials item={item} collection={collection} />;
|
return <OAuth2ClientCredentials item={item} collection={collection} />;
|
||||||
break;
|
break;
|
||||||
|
case 'implicit':
|
||||||
|
return <OAuth2Implicit item={item} collection={collection} />;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return <div>TBD</div>;
|
return <div>TBD</div>;
|
||||||
break;
|
break;
|
||||||
|
@ -681,6 +681,10 @@ export const humanizeGrantType = (mode) => {
|
|||||||
label = 'Client Credentials';
|
label = 'Client Credentials';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'implicit': {
|
||||||
|
label = 'Implicit';
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return label;
|
return label;
|
||||||
|
@ -5,33 +5,40 @@ const matchesCallbackUrl = (url, callbackUrl) => {
|
|||||||
return url ? url.href.startsWith(callbackUrl.href) : false;
|
return url ? url.href.startsWith(callbackUrl.href) : false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openNewWindow = (session) => {
|
||||||
|
let allOpenWindows = BrowserWindow.getAllWindows();
|
||||||
|
|
||||||
|
// main window id is '1'
|
||||||
|
// get all other windows
|
||||||
|
let windowsExcludingMain = allOpenWindows.filter((w) => w.id != 1);
|
||||||
|
windowsExcludingMain.forEach((w) => {
|
||||||
|
w.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
const window = new BrowserWindow({
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: false,
|
||||||
|
partition: session
|
||||||
|
},
|
||||||
|
show: false
|
||||||
|
});
|
||||||
|
|
||||||
|
window.on('ready-to-show', window.show.bind(window));
|
||||||
|
|
||||||
|
// We want browser window to comply with "SSL/TLS Certificate Verification" toggle in Preferences
|
||||||
|
window.webContents.on('certificate-error', (event, url, error, certificate, callback) => {
|
||||||
|
event.preventDefault();
|
||||||
|
callback(!preferencesUtil.shouldVerifyTls());
|
||||||
|
});
|
||||||
|
|
||||||
|
return window;
|
||||||
|
};
|
||||||
|
|
||||||
const authorizeUserInWindow = ({ authorizeUrl, callbackUrl, session }) => {
|
const authorizeUserInWindow = ({ authorizeUrl, callbackUrl, session }) => {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
let finalUrl = null;
|
let finalUrl = null;
|
||||||
|
|
||||||
let allOpenWindows = BrowserWindow.getAllWindows();
|
let window = openNewWindow(session);
|
||||||
|
|
||||||
// main window id is '1'
|
|
||||||
// get all other windows
|
|
||||||
let windowsExcludingMain = allOpenWindows.filter((w) => w.id != 1);
|
|
||||||
windowsExcludingMain.forEach((w) => {
|
|
||||||
w.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
const window = new BrowserWindow({
|
|
||||||
webPreferences: {
|
|
||||||
nodeIntegration: false,
|
|
||||||
partition: session
|
|
||||||
},
|
|
||||||
show: false
|
|
||||||
});
|
|
||||||
window.on('ready-to-show', window.show.bind(window));
|
|
||||||
|
|
||||||
// We want browser window to comply with "SSL/TLS Certificate Verification" toggle in Preferences
|
|
||||||
window.webContents.on('certificate-error', (event, url, error, certificate, callback) => {
|
|
||||||
event.preventDefault();
|
|
||||||
callback(!preferencesUtil.shouldVerifyTls());
|
|
||||||
});
|
|
||||||
|
|
||||||
function onWindowRedirect(url) {
|
function onWindowRedirect(url) {
|
||||||
// check if the redirect is to the callback URL and if it contains an authorization code
|
// check if the redirect is to the callback URL and if it contains an authorization code
|
||||||
@ -97,4 +104,93 @@ const authorizeUserInWindow = ({ authorizeUrl, callbackUrl, session }) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = { authorizeUserInWindow, matchesCallbackUrl };
|
const authorizeUserInWindowImplicit = ({ authorizeUrl, callbackUrl, session }) => {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
let finalUrl = null;
|
||||||
|
|
||||||
|
let window = openNewWindow(session);
|
||||||
|
|
||||||
|
const handleRedirect = (_, url) => {
|
||||||
|
const currentUrl = new URL(url);
|
||||||
|
// Skip any intermediate redirects
|
||||||
|
if (currentUrl.href.startsWith(new URL(callbackUrl).href)) {
|
||||||
|
// If the resource owner grants the access request, the authorization
|
||||||
|
// server issues an access token and delivers it to the client by adding
|
||||||
|
// the following parameters to the FRAGMENT component of the redirection
|
||||||
|
// URI using the "application/x-www-form-urlencoded" format:
|
||||||
|
// access_token (REQUIRED),
|
||||||
|
// token_type (REQUIRED),
|
||||||
|
// expires_in (RECOMMENDED),
|
||||||
|
// scope (OPTIONAL),
|
||||||
|
// state (if sent from client)
|
||||||
|
// i.e. We expect the FRAGMENT component to be parsable as URLSearchParams
|
||||||
|
const uriFragmentWithResponse = new URLSearchParams(new URL(url).hash.slice(1));
|
||||||
|
|
||||||
|
if (uriFragmentWithResponse.has('access_token')) {
|
||||||
|
finalUrl = currentUrl;
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the resource owner denies the access request or if the request
|
||||||
|
// fails for reasons other than a missing or invalid redirection URI,
|
||||||
|
// the authorization server informs the client by adding the following
|
||||||
|
// parameters to the FRAGMENT component of the redirection URI using the
|
||||||
|
// "application/x-www-form-urlencoded" format:
|
||||||
|
// error (REQUIRED),
|
||||||
|
// errorDescription (OPTIONAL),
|
||||||
|
// error_uri (OPTIONAL),
|
||||||
|
// state (if sent from client)
|
||||||
|
if (uriFragmentWithResponse.has('error')) {
|
||||||
|
let errorData = {
|
||||||
|
message: 'Access Denied',
|
||||||
|
error: uriFragmentWithResponse.get('error'),
|
||||||
|
errorDescription: uriFragmentWithResponse.get('errorDescription'),
|
||||||
|
error_uri: uriFragmentWithResponse.get('error_uri')
|
||||||
|
};
|
||||||
|
|
||||||
|
reject(new Error(JSON.stringify(errorData)));
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
if (finalUrl) {
|
||||||
|
try {
|
||||||
|
const uriFragmentWithToken = new URLSearchParams(new URL(finalUrl).hash.slice(1));
|
||||||
|
const accessToken = uriFragmentWithToken.get('access_token');
|
||||||
|
return resolve({ accessToken });
|
||||||
|
} catch (error) {
|
||||||
|
return reject(error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return reject(new Error('Authorization window closed'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// wait for the window to navigate to the callback url
|
||||||
|
window.webContents.on('did-navigate', handleRedirect);
|
||||||
|
|
||||||
|
window.webContents.on('will-redirect', handleRedirect);
|
||||||
|
|
||||||
|
window.on('close', handleClose);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await window.loadURL(authorizeUrl);
|
||||||
|
} catch (error) {
|
||||||
|
// If browser redirects before load finished, loadURL throws an error with code ERR_ABORTED. This should be ignored.
|
||||||
|
if (error.code === 'ERR_ABORTED') {
|
||||||
|
console.debug('Ignoring ERR_ABORTED during authorizeUserInWindow');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
reject(error);
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
authorizeUserInWindow,
|
||||||
|
authorizeUserInWindowImplicit,
|
||||||
|
matchesCallbackUrl
|
||||||
|
};
|
||||||
|
@ -34,7 +34,8 @@ const { getCookieStringForUrl, addCookieToJar, getDomainsWithCookies } = require
|
|||||||
const {
|
const {
|
||||||
resolveOAuth2AuthorizationCodeAccessToken,
|
resolveOAuth2AuthorizationCodeAccessToken,
|
||||||
transformClientCredentialsRequest,
|
transformClientCredentialsRequest,
|
||||||
transformPasswordCredentialsRequest
|
transformPasswordCredentialsRequest,
|
||||||
|
getOAuth2ImplicitToken
|
||||||
} = require('./oauth2-helper');
|
} = require('./oauth2-helper');
|
||||||
const Oauth2Store = require('../../store/oauth2');
|
const Oauth2Store = require('../../store/oauth2');
|
||||||
const iconv = require('iconv-lite');
|
const iconv = require('iconv-lite');
|
||||||
@ -243,6 +244,11 @@ const configureRequest = async (
|
|||||||
request.data = passwordData;
|
request.data = passwordData;
|
||||||
request.url = passwordAccessTokenUrl;
|
request.url = passwordAccessTokenUrl;
|
||||||
break;
|
break;
|
||||||
|
case 'implicit':
|
||||||
|
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars);
|
||||||
|
const { accessToken } = await getOAuth2ImplicitToken(requestCopy, collectionUid);
|
||||||
|
request.headers['Authorization'] = `Bearer ${accessToken}`;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,6 +189,13 @@ const interpolateVars = (request, envVars = {}, runtimeVariables = {}, processEn
|
|||||||
scope
|
scope
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
case 'implicit':
|
||||||
|
request.oauth2.callbackUrl = _interpolate(request.oauth2.callbackUrl) || '';
|
||||||
|
request.oauth2.authorizationUrl = _interpolate(request.oauth2.authorizationUrl) || '';
|
||||||
|
request.oauth2.clientId = _interpolate(request.oauth2.clientId) || '';
|
||||||
|
request.oauth2.scope = _interpolate(request.oauth2.scope) || '';
|
||||||
|
request.oauth2.state = _interpolate(request.oauth2.state) || '';
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const { get, cloneDeep } = require('lodash');
|
const { get, cloneDeep } = require('lodash');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const { authorizeUserInWindow } = require('./authorize-user-in-window');
|
const { authorizeUserInWindow, authorizeUserInWindowImplicit } = require('./authorize-user-in-window');
|
||||||
const Oauth2Store = require('../../store/oauth2');
|
const Oauth2Store = require('../../store/oauth2');
|
||||||
|
|
||||||
const generateCodeVerifier = () => {
|
const generateCodeVerifier = () => {
|
||||||
@ -122,9 +122,42 @@ const transformPasswordCredentialsRequest = async (request) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// IMPLICIT
|
||||||
|
|
||||||
|
const getOAuth2ImplicitToken = async (request, collectionUid) => {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
const { oauth2 } = request;
|
||||||
|
const { callbackUrl, authorizationUrl, clientId, scope, state } = oauth2;
|
||||||
|
let oauth2QueryParams =
|
||||||
|
(authorizationUrl.indexOf('?') > -1 ? '&' : '?') + `client_id=${clientId}&response_type=token`;
|
||||||
|
if (callbackUrl) {
|
||||||
|
oauth2QueryParams += `&redirect_uri=${callbackUrl}`;
|
||||||
|
}
|
||||||
|
if (scope) {
|
||||||
|
oauth2QueryParams += `&scope=${scope}`;
|
||||||
|
}
|
||||||
|
if (state) {
|
||||||
|
oauth2QueryParams += `&state=${state}`;
|
||||||
|
}
|
||||||
|
const authorizationUrlWithQueryParams = authorizationUrl + oauth2QueryParams;
|
||||||
|
try {
|
||||||
|
const oauth2Store = new Oauth2Store();
|
||||||
|
const { accessToken } = await authorizeUserInWindowImplicit({
|
||||||
|
authorizeUrl: authorizationUrlWithQueryParams,
|
||||||
|
callbackUrl: callbackUrl,
|
||||||
|
session: oauth2Store.getSessionIdOfCollection(collectionUid)
|
||||||
|
});
|
||||||
|
resolve({ accessToken });
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
resolveOAuth2AuthorizationCodeAccessToken,
|
resolveOAuth2AuthorizationCodeAccessToken,
|
||||||
getOAuth2AuthorizationCode,
|
getOAuth2AuthorizationCode,
|
||||||
transformClientCredentialsRequest,
|
transformClientCredentialsRequest,
|
||||||
transformPasswordCredentialsRequest
|
transformPasswordCredentialsRequest,
|
||||||
|
getOAuth2ImplicitToken
|
||||||
};
|
};
|
||||||
|
@ -283,6 +283,16 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
|
|||||||
scope: get(request, 'auth.oauth2.scope')
|
scope: get(request, 'auth.oauth2.scope')
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
case 'implicit':
|
||||||
|
axiosRequest.oauth2 = {
|
||||||
|
grantType: grantType,
|
||||||
|
callbackUrl: get(request, 'auth.oauth2.callbackUrl'),
|
||||||
|
authorizationUrl: get(request, 'auth.oauth2.authorizationUrl'),
|
||||||
|
clientId: get(request, 'auth.oauth2.clientId'),
|
||||||
|
scope: get(request, 'auth.oauth2.scope'),
|
||||||
|
state: get(request, 'auth.oauth2.state')
|
||||||
|
};
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -449,7 +449,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
|
|||||||
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 : '',
|
accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '',
|
||||||
@ -459,7 +459,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
|
|||||||
clientSecret: clientSecretKey ? clientSecretKey.value : '',
|
clientSecret: clientSecretKey ? clientSecretKey.value : '',
|
||||||
scope: scopeKey ? scopeKey.value : ''
|
scope: scopeKey ? scopeKey.value : ''
|
||||||
}
|
}
|
||||||
: grantTypeKey?.value && grantTypeKey?.value == 'authorization_code'
|
: grantTypeKey?.value && grantTypeKey?.value === 'authorization_code'
|
||||||
? {
|
? {
|
||||||
grantType: grantTypeKey ? grantTypeKey.value : '',
|
grantType: grantTypeKey ? grantTypeKey.value : '',
|
||||||
callbackUrl: callbackUrlKey ? callbackUrlKey.value : '',
|
callbackUrl: callbackUrlKey ? callbackUrlKey.value : '',
|
||||||
@ -471,7 +471,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
|
|||||||
state: stateKey ? stateKey.value : '',
|
state: stateKey ? stateKey.value : '',
|
||||||
pkce: pkceKey ? JSON.parse(pkceKey?.value || false) : false
|
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 : '',
|
accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '',
|
||||||
@ -479,6 +479,15 @@ const sem = grammar.createSemantics().addAttribute('ast', {
|
|||||||
clientSecret: clientSecretKey ? clientSecretKey.value : '',
|
clientSecret: clientSecretKey ? clientSecretKey.value : '',
|
||||||
scope: scopeKey ? scopeKey.value : ''
|
scope: scopeKey ? scopeKey.value : ''
|
||||||
}
|
}
|
||||||
|
: grantTypeKey?.value && grantTypeKey?.value === 'implicit'
|
||||||
|
? {
|
||||||
|
grantType: grantTypeKey ? grantTypeKey.value : '',
|
||||||
|
callbackUrl: callbackUrlKey ? callbackUrlKey.value : '',
|
||||||
|
authorizationUrl: authorizationUrlKey ? authorizationUrlKey.value : '',
|
||||||
|
clientId: clientIdKey ? clientIdKey.value : '',
|
||||||
|
scope: scopeKey ? scopeKey.value : '',
|
||||||
|
state: stateKey ? stateKey.value : ''
|
||||||
|
}
|
||||||
: {}
|
: {}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -259,7 +259,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
|
|||||||
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 : '',
|
accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '',
|
||||||
@ -269,7 +269,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
|
|||||||
clientSecret: clientSecretKey ? clientSecretKey.value : '',
|
clientSecret: clientSecretKey ? clientSecretKey.value : '',
|
||||||
scope: scopeKey ? scopeKey.value : ''
|
scope: scopeKey ? scopeKey.value : ''
|
||||||
}
|
}
|
||||||
: grantTypeKey?.value && grantTypeKey?.value == 'authorization_code'
|
: grantTypeKey?.value && grantTypeKey?.value === 'authorization_code'
|
||||||
? {
|
? {
|
||||||
grantType: grantTypeKey ? grantTypeKey.value : '',
|
grantType: grantTypeKey ? grantTypeKey.value : '',
|
||||||
callbackUrl: callbackUrlKey ? callbackUrlKey.value : '',
|
callbackUrl: callbackUrlKey ? callbackUrlKey.value : '',
|
||||||
@ -281,7 +281,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
|
|||||||
state: stateKey ? stateKey.value : '',
|
state: stateKey ? stateKey.value : '',
|
||||||
pkce: pkceKey ? JSON.parse(pkceKey?.value || false) : false
|
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 : '',
|
accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '',
|
||||||
@ -289,6 +289,15 @@ const sem = grammar.createSemantics().addAttribute('ast', {
|
|||||||
clientSecret: clientSecretKey ? clientSecretKey.value : '',
|
clientSecret: clientSecretKey ? clientSecretKey.value : '',
|
||||||
scope: scopeKey ? scopeKey.value : ''
|
scope: scopeKey ? scopeKey.value : ''
|
||||||
}
|
}
|
||||||
|
: grantTypeKey?.value && grantTypeKey?.value === 'implicit'
|
||||||
|
? {
|
||||||
|
grantType: grantTypeKey ? grantTypeKey.value : '',
|
||||||
|
callbackUrl: callbackUrlKey ? callbackUrlKey.value : '',
|
||||||
|
authorizationUrl: authorizationUrlKey ? authorizationUrlKey.value : '',
|
||||||
|
clientId: clientIdKey ? clientIdKey.value : '',
|
||||||
|
scope: scopeKey ? scopeKey.value : '',
|
||||||
|
state: stateKey ? stateKey.value : ''
|
||||||
|
}
|
||||||
: {}
|
: {}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -195,6 +195,18 @@ ${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)}
|
|||||||
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
|
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
`;
|
||||||
|
break;
|
||||||
|
case 'implicit':
|
||||||
|
bru += `auth:oauth2 {
|
||||||
|
${indentString(`grant_type: implicit`)}
|
||||||
|
${indentString(`callback_url: ${auth?.oauth2?.callbackUrl || ''}`)}
|
||||||
|
${indentString(`authorization_url: ${auth?.oauth2?.authorizationUrl || ''}`)}
|
||||||
|
${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)}
|
||||||
|
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
|
||||||
|
${indentString(`state: ${auth?.oauth2?.state || ''}`)}
|
||||||
|
}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -153,6 +153,18 @@ ${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)}
|
|||||||
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
|
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
`;
|
||||||
|
break;
|
||||||
|
case 'implicit':
|
||||||
|
bru += `auth:oauth2 {
|
||||||
|
${indentString(`grant_type: implicit`)}
|
||||||
|
${indentString(`callback_url: ${auth?.oauth2?.callbackUrl || ''}`)}
|
||||||
|
${indentString(`authorization_url: ${auth?.oauth2?.authorizationUrl || ''}`)}
|
||||||
|
${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)}
|
||||||
|
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
|
||||||
|
${indentString(`state: ${auth?.oauth2?.state || ''}`)}
|
||||||
|
}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -121,7 +121,7 @@ const authDigestSchema = Yup.object({
|
|||||||
|
|
||||||
const oauth2Schema = Yup.object({
|
const oauth2Schema = Yup.object({
|
||||||
grantType: Yup.string()
|
grantType: Yup.string()
|
||||||
.oneOf(['client_credentials', 'password', 'authorization_code'])
|
.oneOf(['client_credentials', 'password', 'authorization_code', 'implicit'])
|
||||||
.required('grantType is required'),
|
.required('grantType is required'),
|
||||||
username: Yup.string().when('grantType', {
|
username: Yup.string().when('grantType', {
|
||||||
is: (val) => ['client_credentials', 'password'].includes(val),
|
is: (val) => ['client_credentials', 'password'].includes(val),
|
||||||
@ -134,12 +134,12 @@ const oauth2Schema = Yup.object({
|
|||||||
otherwise: Yup.string().nullable().strip()
|
otherwise: Yup.string().nullable().strip()
|
||||||
}),
|
}),
|
||||||
callbackUrl: Yup.string().when('grantType', {
|
callbackUrl: Yup.string().when('grantType', {
|
||||||
is: (val) => ['authorization_code'].includes(val),
|
is: (val) => ['authorization_code', 'implicit'].includes(val),
|
||||||
then: Yup.string().nullable(),
|
then: Yup.string().nullable(),
|
||||||
otherwise: Yup.string().nullable().strip()
|
otherwise: Yup.string().nullable().strip()
|
||||||
}),
|
}),
|
||||||
authorizationUrl: Yup.string().when('grantType', {
|
authorizationUrl: Yup.string().when('grantType', {
|
||||||
is: (val) => ['authorization_code'].includes(val),
|
is: (val) => ['authorization_code', 'implicit'].includes(val),
|
||||||
then: Yup.string().nullable(),
|
then: Yup.string().nullable(),
|
||||||
otherwise: Yup.string().nullable().strip()
|
otherwise: Yup.string().nullable().strip()
|
||||||
}),
|
}),
|
||||||
@ -149,7 +149,7 @@ const oauth2Schema = Yup.object({
|
|||||||
otherwise: Yup.string().nullable().strip()
|
otherwise: Yup.string().nullable().strip()
|
||||||
}),
|
}),
|
||||||
clientId: Yup.string().when('grantType', {
|
clientId: Yup.string().when('grantType', {
|
||||||
is: (val) => ['client_credentials', 'password', 'authorization_code'].includes(val),
|
is: (val) => ['client_credentials', 'password', 'authorization_code', 'implicit'].includes(val),
|
||||||
then: Yup.string().nullable(),
|
then: Yup.string().nullable(),
|
||||||
otherwise: Yup.string().nullable().strip()
|
otherwise: Yup.string().nullable().strip()
|
||||||
}),
|
}),
|
||||||
@ -159,12 +159,12 @@ 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) => ['client_credentials', 'password', 'authorization_code'].includes(val),
|
is: (val) => ['client_credentials', 'password', 'authorization_code', 'implicit'].includes(val),
|
||||||
then: Yup.string().nullable(),
|
then: Yup.string().nullable(),
|
||||||
otherwise: Yup.string().nullable().strip()
|
otherwise: Yup.string().nullable().strip()
|
||||||
}),
|
}),
|
||||||
state: Yup.string().when('grantType', {
|
state: Yup.string().when('grantType', {
|
||||||
is: (val) => ['authorization_code'].includes(val),
|
is: (val) => ['authorization_code', 'implicit'].includes(val),
|
||||||
then: Yup.string().nullable(),
|
then: Yup.string().nullable(),
|
||||||
otherwise: Yup.string().nullable().strip()
|
otherwise: Yup.string().nullable().strip()
|
||||||
}),
|
}),
|
||||||
|
Loading…
Reference in New Issue
Block a user