feat: add support for state param for OAuth2 Authorization Code flow (#2330)

* feat: add support for state param

* chore: revert package-lock.json

* test: update tests with state param

* chore: revert package-lock.json

* chore: add state to missing places

* Adding state to PKCE toggle

---------

Co-authored-by: Kadam Dhananjay <kadamdhananjay@johndeere.com>
This commit is contained in:
Dhananjay Kadam 2024-05-31 15:35:27 +05:30 committed by GitHub
parent 3ded960938
commit 32b1ba1c92
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 37 additions and 5 deletions

View File

@ -22,7 +22,7 @@ const OAuth2AuthorizationCode = ({ collection }) => {
const handleSave = () => dispatch(saveCollectionRoot(collection.uid)); const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
const { callbackUrl, authorizationUrl, accessTokenUrl, clientId, clientSecret, scope, pkce } = oAuth; const { callbackUrl, authorizationUrl, accessTokenUrl, clientId, clientSecret, scope, state, pkce } = oAuth;
const handleChange = (key, value) => { const handleChange = (key, value) => {
dispatch( dispatch(
@ -37,6 +37,7 @@ const OAuth2AuthorizationCode = ({ collection }) => {
clientId, clientId,
clientSecret, clientSecret,
scope, scope,
state,
pkce, pkce,
[key]: value [key]: value
} }
@ -57,6 +58,7 @@ const OAuth2AuthorizationCode = ({ collection }) => {
clientId, clientId,
clientSecret, clientSecret,
scope, scope,
state,
pkce: !Boolean(oAuth?.['pkce']) pkce: !Boolean(oAuth?.['pkce'])
} }
}) })

View File

@ -22,6 +22,10 @@ const inputsConfig = [
{ {
key: 'scope', key: 'scope',
label: 'Scope' label: 'Scope'
},
{
key: 'state',
label: 'State'
} }
]; ];

View File

@ -22,7 +22,7 @@ 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 { callbackUrl, authorizationUrl, accessTokenUrl, clientId, clientSecret, scope, state, pkce } = oAuth;
const handleChange = (key, value) => { const handleChange = (key, value) => {
dispatch( dispatch(
@ -37,6 +37,7 @@ const OAuth2AuthorizationCode = ({ item, collection }) => {
accessTokenUrl, accessTokenUrl,
clientId, clientId,
clientSecret, clientSecret,
state,
scope, scope,
pkce, pkce,
[key]: value [key]: value
@ -58,6 +59,7 @@ const OAuth2AuthorizationCode = ({ item, collection }) => {
accessTokenUrl, accessTokenUrl,
clientId, clientId,
clientSecret, clientSecret,
state,
scope, scope,
pkce: !Boolean(oAuth?.['pkce']) pkce: !Boolean(oAuth?.['pkce'])
} }

View File

@ -22,6 +22,10 @@ const inputsConfig = [
{ {
key: 'scope', key: 'scope',
label: 'Scope' label: 'Scope'
},
{
key: 'state',
label: 'State'
} }
]; ];

View File

@ -169,6 +169,7 @@ 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.state = _interpolate(request.oauth2.state) || '';
request.oauth2.pkce = _interpolate(request.oauth2.pkce) || false; request.oauth2.pkce = _interpolate(request.oauth2.pkce) || false;
break; break;
case 'client_credentials': case 'client_credentials':

View File

@ -23,14 +23,15 @@ const resolveOAuth2AuthorizationCodeAccessToken = async (request, collectionUid)
let requestCopy = cloneDeep(request); let requestCopy = cloneDeep(request);
const { authorizationCode } = await getOAuth2AuthorizationCode(requestCopy, codeChallenge, collectionUid); const { authorizationCode } = await getOAuth2AuthorizationCode(requestCopy, codeChallenge, collectionUid);
const oAuth = get(requestCopy, 'oauth2', {}); const oAuth = get(requestCopy, 'oauth2', {});
const { clientId, clientSecret, callbackUrl, scope, pkce } = oAuth; const { clientId, clientSecret, callbackUrl, scope, state, pkce } = oAuth;
const data = { const data = {
grant_type: 'authorization_code', grant_type: 'authorization_code',
code: authorizationCode, code: authorizationCode,
redirect_uri: callbackUrl, redirect_uri: callbackUrl,
client_id: clientId, client_id: clientId,
client_secret: clientSecret, client_secret: clientSecret,
scope: scope scope: scope,
state: state
}; };
if (pkce) { if (pkce) {
data['code_verifier'] = codeVerifier; data['code_verifier'] = codeVerifier;
@ -46,7 +47,7 @@ const resolveOAuth2AuthorizationCodeAccessToken = async (request, collectionUid)
const getOAuth2AuthorizationCode = (request, codeChallenge, collectionUid) => { const getOAuth2AuthorizationCode = (request, codeChallenge, collectionUid) => {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const { oauth2 } = request; const { oauth2 } = request;
const { callbackUrl, clientId, authorizationUrl, scope, pkce } = oauth2; const { callbackUrl, clientId, authorizationUrl, scope, state, pkce } = oauth2;
let oauth2QueryParams = let oauth2QueryParams =
(authorizationUrl.indexOf('?') > -1 ? '&' : '?') + `client_id=${clientId}&response_type=code`; (authorizationUrl.indexOf('?') > -1 ? '&' : '?') + `client_id=${clientId}&response_type=code`;
@ -59,6 +60,10 @@ const getOAuth2AuthorizationCode = (request, codeChallenge, collectionUid) => {
if (pkce) { if (pkce) {
oauth2QueryParams += `&code_challenge=${codeChallenge}&code_challenge_method=S256`; oauth2QueryParams += `&code_challenge=${codeChallenge}&code_challenge_method=S256`;
} }
if (state) {
oauth2QueryParams += `&state=${state}`;
}
const authorizationUrlWithQueryParams = authorizationUrl + oauth2QueryParams; const authorizationUrlWithQueryParams = authorizationUrl + oauth2QueryParams;
try { try {
const oauth2Store = new Oauth2Store(); const oauth2Store = new Oauth2Store();

View File

@ -112,6 +112,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
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'),
state: get(request, 'auth.oauth2.state'),
pkce: get(request, 'auth.oauth2.pkce') pkce: get(request, 'auth.oauth2.pkce')
}; };
break; break;

View File

@ -444,6 +444,7 @@ 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 stateKey = _.find(auth, { name: 'state' });
const pkceKey = _.find(auth, { name: 'pkce' }); const pkceKey = _.find(auth, { name: 'pkce' });
return { return {
auth: { auth: {
@ -467,6 +468,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
clientId: clientIdKey ? clientIdKey.value : '', clientId: clientIdKey ? clientIdKey.value : '',
clientSecret: clientSecretKey ? clientSecretKey.value : '', clientSecret: clientSecretKey ? clientSecretKey.value : '',
scope: scopeKey ? scopeKey.value : '', scope: scopeKey ? scopeKey.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'

View File

@ -254,6 +254,7 @@ 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 stateKey = _.find(auth, { name: 'state' });
const pkceKey = _.find(auth, { name: 'pkce' }); const pkceKey = _.find(auth, { name: 'pkce' });
return { return {
auth: { auth: {
@ -277,6 +278,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
clientId: clientIdKey ? clientIdKey.value : '', clientId: clientIdKey ? clientIdKey.value : '',
clientSecret: clientSecretKey ? clientSecretKey.value : '', clientSecret: clientSecretKey ? clientSecretKey.value : '',
scope: scopeKey ? scopeKey.value : '', scope: scopeKey ? scopeKey.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'

View File

@ -180,6 +180,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(`state: ${auth?.oauth2?.state || ''}`)}
${indentString(`pkce: ${(auth?.oauth2?.pkce || false).toString()}`)} ${indentString(`pkce: ${(auth?.oauth2?.pkce || false).toString()}`)}
} }

View File

@ -138,6 +138,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(`state: ${auth?.oauth2?.state || ''}`)}
${indentString(`pkce: ${(auth?.oauth2?.pkce || false).toString()}`)} ${indentString(`pkce: ${(auth?.oauth2?.pkce || false).toString()}`)}
} }

View File

@ -53,6 +53,7 @@ auth:oauth2 {
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
state: 807061d5f0be
pkce: false pkce: false
} }

View File

@ -72,6 +72,7 @@
"callbackUrl": "http://localhost:8080/api/auth/oauth2/authorization_code/callback", "callbackUrl": "http://localhost:8080/api/auth/oauth2/authorization_code/callback",
"accessTokenUrl": "http://localhost:8080/api/auth/oauth2/authorization_code/token", "accessTokenUrl": "http://localhost:8080/api/auth/oauth2/authorization_code/token",
"scope": "read write", "scope": "read write",
"state": "807061d5f0be",
"pkce": false "pkce": false
} }
}, },

View File

@ -163,6 +163,11 @@ const oauth2Schema = Yup.object({
then: Yup.string().nullable(), then: Yup.string().nullable(),
otherwise: Yup.string().nullable().strip() otherwise: Yup.string().nullable().strip()
}), }),
state: Yup.string().when('grantType', {
is: (val) => ['authorization_code'].includes(val),
then: Yup.string().nullable(),
otherwise: Yup.string().nullable().strip()
}),
pkce: Yup.boolean().when('grantType', { pkce: Yup.boolean().when('grantType', {
is: (val) => ['authorization_code'].includes(val), is: (val) => ['authorization_code'].includes(val),
then: Yup.boolean().default(false), then: Yup.boolean().default(false),