From 0e4a4307ad64384f3b245c3c3729439a8cb110bf Mon Sep 17 00:00:00 2001 From: Mateusz Pietryga Date: Thu, 25 Apr 2024 09:04:56 +0200 Subject: [PATCH] feat: OAuth 2.0 Client Credentials as Basic Auth - request logic --- .../bruno-electron/src/ipc/network/index.js | 36 ++++--- .../src/ipc/network/interpolate-vars.js | 42 ++------ .../src/ipc/network/oauth2-helper.js | 102 ++++++++++++------ .../src/ipc/network/prepare-request.js | 3 + 4 files changed, 104 insertions(+), 79 deletions(-) diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index b0dfbfb6..da1d3f9a 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -268,34 +268,36 @@ const configureRequest = async ( if (request.oauth2) { let requestCopy = cloneDeep(request); switch (request?.oauth2?.grantType) { - case 'authorization_code': + case 'authorization_code': { interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars); - const { data: authorizationCodeData, url: authorizationCodeAccessTokenUrl } = + const { accessTokenRequestHeaders, accessTokenRequestData, accessTokenRequestUrl } = await resolveOAuth2AuthorizationCodeAccessToken(requestCopy, collectionUid); request.method = 'POST'; - request.headers['content-type'] = 'application/x-www-form-urlencoded'; - request.data = authorizationCodeData; - request.url = authorizationCodeAccessTokenUrl; + request.headers = accessTokenRequestHeaders; + request.data = accessTokenRequestData; + request.url = accessTokenRequestUrl; break; - case 'client_credentials': + } + case 'client_credentials': { interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars); - const { data: clientCredentialsData, url: clientCredentialsAccessTokenUrl } = + const { accessTokenRequestHeaders, accessTokenRequestData, accessTokenRequestUrl } = await transformClientCredentialsRequest(requestCopy); request.method = 'POST'; - request.headers['content-type'] = 'application/x-www-form-urlencoded'; - request.data = clientCredentialsData; - request.url = clientCredentialsAccessTokenUrl; + request.headers = accessTokenRequestHeaders; + request.data = accessTokenRequestData; + request.url = accessTokenRequestUrl; break; - case 'password': + } + case 'password': { interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars); - const { data: passwordData, url: passwordAccessTokenUrl } = await transformPasswordCredentialsRequest( - requestCopy - ); + const { accessTokenRequestHeaders, accessTokenRequestData, accessTokenRequestUrl } = + await transformPasswordCredentialsRequest(requestCopy); request.method = 'POST'; - request.headers['content-type'] = 'application/x-www-form-urlencoded'; - request.data = passwordData; - request.url = passwordAccessTokenUrl; + request.headers = accessTokenRequestHeaders; + request.data = accessTokenRequestData; + request.url = accessTokenRequestUrl; break; + } } } diff --git a/packages/bruno-electron/src/ipc/network/interpolate-vars.js b/packages/bruno-electron/src/ipc/network/interpolate-vars.js index 90b07265..2a328078 100644 --- a/packages/bruno-electron/src/ipc/network/interpolate-vars.js +++ b/packages/bruno-electron/src/ipc/network/interpolate-vars.js @@ -146,28 +146,15 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc } if (request?.oauth2?.grantType) { - let username, password, scope, clientId, clientSecret; switch (request.oauth2.grantType) { case 'password': - username = _interpolate(request.oauth2.username) || ''; - password = _interpolate(request.oauth2.password) || ''; - clientId = _interpolate(request.oauth2.clientId) || ''; - clientSecret = _interpolate(request.oauth2.clientSecret) || ''; - scope = _interpolate(request.oauth2.scope) || ''; request.oauth2.accessTokenUrl = _interpolate(request.oauth2.accessTokenUrl) || ''; - request.oauth2.username = username; - request.oauth2.password = password; - request.oauth2.clientId = clientId; - request.oauth2.clientSecret = clientSecret; - request.oauth2.scope = scope; - request.data = { - grant_type: 'password', - username, - password, - client_id: clientId, - client_secret: clientSecret, - scope - }; + request.oauth2.username = _interpolate(request.oauth2.username) || ''; + request.oauth2.password = _interpolate(request.oauth2.password) || ''; + request.oauth2.clientId = _interpolate(request.oauth2.clientId) || ''; + request.oauth2.clientSecret = _interpolate(request.oauth2.clientSecret) || ''; + request.oauth2.clientSecretMethod = _interpolate(request.oauth2.clientSecretMethod) || ''; + request.oauth2.scope = _interpolate(request.oauth2.scope) || ''; break; case 'authorization_code': request.oauth2.callbackUrl = _interpolate(request.oauth2.callbackUrl) || ''; @@ -175,24 +162,17 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc request.oauth2.accessTokenUrl = _interpolate(request.oauth2.accessTokenUrl) || ''; request.oauth2.clientId = _interpolate(request.oauth2.clientId) || ''; request.oauth2.clientSecret = _interpolate(request.oauth2.clientSecret) || ''; + request.oauth2.clientSecretMethod = _interpolate(request.oauth2.clientSecretMethod) || ''; request.oauth2.scope = _interpolate(request.oauth2.scope) || ''; request.oauth2.state = _interpolate(request.oauth2.state) || ''; request.oauth2.pkce = _interpolate(request.oauth2.pkce) || false; break; case 'client_credentials': - clientId = _interpolate(request.oauth2.clientId) || ''; - clientSecret = _interpolate(request.oauth2.clientSecret) || ''; - scope = _interpolate(request.oauth2.scope) || ''; request.oauth2.accessTokenUrl = _interpolate(request.oauth2.accessTokenUrl) || ''; - request.oauth2.clientId = clientId; - request.oauth2.clientSecret = clientSecret; - request.oauth2.scope = scope; - request.data = { - grant_type: 'client_credentials', - client_id: clientId, - client_secret: clientSecret, - scope - }; + request.oauth2.clientId = _interpolate(request.oauth2.clientId) || ''; + request.oauth2.clientSecret = _interpolate(request.oauth2.clientSecret) || ''; + request.oauth2.clientSecretMethod = _interpolate(request.oauth2.clientSecretMethod) || ''; + request.oauth2.scope = _interpolate(request.oauth2.scope) || ''; break; default: break; diff --git a/packages/bruno-electron/src/ipc/network/oauth2-helper.js b/packages/bruno-electron/src/ipc/network/oauth2-helper.js index 14454241..20c93846 100644 --- a/packages/bruno-electron/src/ipc/network/oauth2-helper.js +++ b/packages/bruno-electron/src/ipc/network/oauth2-helper.js @@ -3,6 +3,10 @@ const crypto = require('crypto'); const { authorizeUserInWindow } = require('./authorize-user-in-window'); const Oauth2Store = require('../../store/oauth2'); +const encodeClientCredentials = (clientId, clientSecret) => { + return 'Basic ' + Buffer.from(clientId + ':' + clientSecret).toString('base64'); +}; + const generateCodeVerifier = () => { return crypto.randomBytes(22).toString('hex'); }; @@ -23,22 +27,34 @@ const resolveOAuth2AuthorizationCodeAccessToken = async (request, collectionUid) let requestCopy = cloneDeep(request); const { authorizationCode } = await getOAuth2AuthorizationCode(requestCopy, codeChallenge, collectionUid); 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 - }; - if (pkce) { - data['code_verifier'] = codeVerifier; + const { clientId, clientSecret, clientSecretMethod, callbackUrl, pkce } = oAuth; + + const accessTokenRequestHeaders = request.headers; + accessTokenRequestHeaders['content-type'] = 'application/x-www-form-urlencoded'; + if (clientSecretMethod === 'client_credentials_basic') { + accessTokenRequestHeaders['authorization'] = encodeClientCredentials(clientId, clientSecret); } - const url = requestCopy?.oauth2?.accessTokenUrl; + const accessTokenRequestData = { + grant_type: 'authorization_code', + code: authorizationCode, + redirect_uri: callbackUrl + }; + + if (clientSecretMethod === 'client_credentials_post') { + accessTokenRequestData['client_id'] = clientId; + accessTokenRequestData['client_secret'] = clientSecret; + } + + if (pkce) { + accessTokenRequestData['code_verifier'] = codeVerifier; + } + + const accessTokenRequestUrl = requestCopy?.oauth2?.accessTokenUrl; return { - data, - url + accessTokenRequestHeaders, + accessTokenRequestData, + accessTokenRequestUrl }; }; @@ -82,19 +98,30 @@ const getOAuth2AuthorizationCode = (request, codeChallenge, collectionUid) => { 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 + const { clientId, clientSecret, clientSecretMethod, scope } = oAuth; + + const accessTokenRequestHeaders = request.headers; + accessTokenRequestHeaders['content-type'] = 'application/x-www-form-urlencoded'; + if (clientSecretMethod === 'client_credentials_basic') { + accessTokenRequestHeaders['authorization'] = encodeClientCredentials(clientId, clientSecret); + } + + const accessTokenRequestData = { + grant_type: 'client_credentials' }; if (scope) { - data.scope = scope; + accessTokenRequestData.scope = scope; } - const url = requestCopy?.oauth2?.accessTokenUrl; + if (clientSecretMethod === 'client_credentials_post') { + accessTokenRequestData['client_id'] = clientId; + accessTokenRequestData['client_secret'] = clientSecret; + } + + const accessTokenRequestUrl = requestCopy?.oauth2?.accessTokenUrl; return { - data, - url + accessTokenRequestHeaders, + accessTokenRequestData, + accessTokenRequestUrl }; }; @@ -103,21 +130,34 @@ const transformClientCredentialsRequest = async (request) => { const transformPasswordCredentialsRequest = async (request) => { let requestCopy = cloneDeep(request); const oAuth = get(requestCopy, 'oauth2', {}); - const { username, password, clientId, clientSecret, scope } = oAuth; - const data = { + const { username, password, clientId, clientSecret, clientSecretMethod, scope } = oAuth; + + const accessTokenRequestHeaders = request.headers; + accessTokenRequestHeaders['content-type'] = 'application/x-www-form-urlencoded'; + if (clientSecretMethod === 'client_credentials_basic') { + accessTokenRequestHeaders['authorization'] = encodeClientCredentials(clientId, clientSecret); + } + + const accessTokenRequestData = { grant_type: 'password', username, - password, - client_id: clientId, - client_secret: clientSecret + password }; if (scope) { - data.scope = scope; + accessTokenRequestData.scope = scope; } - const url = requestCopy?.oauth2?.accessTokenUrl; + if (clientSecretMethod === 'client_credentials_post') { + accessTokenRequestData['client_id'] = clientId; + if(clientSecret) { + accessTokenRequestData['client_secret'] = clientSecret; + } + } + + const accessTokenRequestUrl = requestCopy?.oauth2?.accessTokenUrl; return { - data, - url + accessTokenRequestHeaders, + accessTokenRequestData, + accessTokenRequestUrl }; }; diff --git a/packages/bruno-electron/src/ipc/network/prepare-request.js b/packages/bruno-electron/src/ipc/network/prepare-request.js index 0bac42af..5fa22a43 100644 --- a/packages/bruno-electron/src/ipc/network/prepare-request.js +++ b/packages/bruno-electron/src/ipc/network/prepare-request.js @@ -286,6 +286,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => { password: get(request, 'auth.oauth2.password'), clientId: get(request, 'auth.oauth2.clientId'), clientSecret: get(request, 'auth.oauth2.clientSecret'), + clientSecretMethod: get(request, 'auth.oauth2.clientSecretMethod'), scope: get(request, 'auth.oauth2.scope') }; break; @@ -297,6 +298,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => { accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'), clientId: get(request, 'auth.oauth2.clientId'), clientSecret: get(request, 'auth.oauth2.clientSecret'), + clientSecretMethod: get(request, 'auth.oauth2.clientSecretMethod'), scope: get(request, 'auth.oauth2.scope'), state: get(request, 'auth.oauth2.state'), pkce: get(request, 'auth.oauth2.pkce') @@ -308,6 +310,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => { accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'), clientId: get(request, 'auth.oauth2.clientId'), clientSecret: get(request, 'auth.oauth2.clientSecret'), + clientSecretMethod: get(request, 'auth.oauth2.clientSecretMethod'), scope: get(request, 'auth.oauth2.scope') }; break;