OAuth2: Implement OAuth 2.0 Implicit Grant

#2056
This commit is contained in:
Mateusz Pietryga 2024-03-26 16:23:13 +01:00 committed by Mateusz Pietryga
parent 6320a80cbe
commit 51e2917ef9
No known key found for this signature in database
GPG Key ID: 549A107FB8327670
21 changed files with 519 additions and 41 deletions

View File

@ -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>

View File

@ -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;

View File

@ -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;

View File

@ -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 };

View File

@ -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;

View File

@ -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>

View File

@ -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;

View File

@ -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;

View File

@ -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 };

View File

@ -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;

View File

@ -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;

View File

@ -5,10 +5,7 @@ const matchesCallbackUrl = (url, callbackUrl) => {
return url ? url.href.startsWith(callbackUrl.href) : false; return url ? url.href.startsWith(callbackUrl.href) : false;
}; };
const authorizeUserInWindow = ({ authorizeUrl, callbackUrl, session }) => { const openNewWindow = (session) => {
return new Promise(async (resolve, reject) => {
let finalUrl = null;
let allOpenWindows = BrowserWindow.getAllWindows(); let allOpenWindows = BrowserWindow.getAllWindows();
// main window id is '1' // main window id is '1'
@ -25,6 +22,7 @@ const authorizeUserInWindow = ({ authorizeUrl, callbackUrl, session }) => {
}, },
show: false show: false
}); });
window.on('ready-to-show', window.show.bind(window)); window.on('ready-to-show', window.show.bind(window));
// We want browser window to comply with "SSL/TLS Certificate Verification" toggle in Preferences // We want browser window to comply with "SSL/TLS Certificate Verification" toggle in Preferences
@ -33,6 +31,15 @@ const authorizeUserInWindow = ({ authorizeUrl, callbackUrl, session }) => {
callback(!preferencesUtil.shouldVerifyTls()); callback(!preferencesUtil.shouldVerifyTls());
}); });
return window;
};
const authorizeUserInWindow = ({ authorizeUrl, callbackUrl, session }) => {
return new Promise(async (resolve, reject) => {
let finalUrl = null;
let window = openNewWindow(session);
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
if (matchesCallbackUrl(new URL(url), new URL(callbackUrl))) { if (matchesCallbackUrl(new URL(url), new URL(callbackUrl))) {
@ -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
};

View File

@ -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;
} }
} }

View File

@ -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;
} }

View File

@ -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
}; };

View File

@ -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;
} }

View File

@ -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 : ''
}
: {} : {}
} }
}; };

View File

@ -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 : ''
}
: {} : {}
} }
}; };

View File

@ -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;
} }

View File

@ -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;
} }

View File

@ -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()
}), }),