Skip to content

Commit

Permalink
feat: OAuth 2.0 Client Credentials as Basic Auth - request logic
Browse files Browse the repository at this point in the history
  • Loading branch information
pietrygamat committed Aug 22, 2024
1 parent 2e7f622 commit 2e053ed
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 77 deletions.
36 changes: 19 additions & 17 deletions packages/bruno-electron/src/ipc/network/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,34 +215,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;
}
}
}

Expand Down
42 changes: 11 additions & 31 deletions packages/bruno-electron/src/ipc/network/interpolate-vars.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,53 +141,33 @@ const interpolateVars = (request, envVars = {}, runtimeVariables = {}, processEn
}

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) || '';
request.oauth2.authorizationUrl = _interpolate(request.oauth2.authorizationUrl) || '';
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;
Expand Down
92 changes: 64 additions & 28 deletions packages/bruno-electron/src/ipc/network/oauth2-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
};
Expand All @@ -23,23 +27,33 @@ 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, state, pkce } = oAuth;
const data = {
const { clientId, clientSecret, clientSecretMethod, callbackUrl, scope, state, 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 accessTokenRequestData = {
grant_type: 'authorization_code',
code: authorizationCode,
redirect_uri: callbackUrl,
client_id: clientId,
client_secret: clientSecret,
state: state
redirect_uri: callbackUrl
};

if (pkce) {
data['code_verifier'] = codeVerifier;
accessTokenRequestData['code_verifier'] = codeVerifier;
}
if (clientSecretMethod === 'client_credentials_post') {
accessTokenRequestData['client_id'] = clientId;
accessTokenRequestData['client_secret'] = clientSecret;
}

const url = requestCopy?.oauth2?.accessTokenUrl;
const accessTokenRequestUrl = requestCopy?.oauth2?.accessTokenUrl;
return {
data,
url
accessTokenRequestHeaders,
accessTokenRequestData,
accessTokenRequestUrl
};
};

Expand Down Expand Up @@ -83,19 +97,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;
}
if (clientSecretMethod === 'client_credentials_post') {
accessTokenRequestData['client_id'] = clientId;
accessTokenRequestData['client_secret'] = clientSecret;
}
const url = requestCopy?.oauth2?.accessTokenUrl;

const accessTokenRequestUrl = requestCopy?.oauth2?.accessTokenUrl;
return {
data,
url
accessTokenRequestHeaders,
accessTokenRequestData,
accessTokenRequestUrl
};
};

Expand All @@ -104,21 +129,32 @@ 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;
accessTokenRequestData['client_secret'] = clientSecret;
}

const accessTokenRequestUrl = requestCopy?.oauth2?.accessTokenUrl;
return {
data,
url
accessTokenRequestHeaders,
accessTokenRequestData,
accessTokenRequestUrl
};
};

Expand Down
5 changes: 4 additions & 1 deletion packages/bruno-electron/src/ipc/network/prepare-request.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const os = require('os');
const { get, each, filter, extend, compact } = require('lodash');
const decomment = require('decomment');
var JSONbig = require('json-bigint');
const JSONbig = require('json-bigint');
const FormData = require('form-data');
const fs = require('fs');
const path = require('path');
Expand Down Expand Up @@ -258,6 +258,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;
Expand All @@ -269,6 +270,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')
Expand All @@ -280,6 +282,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;
Expand Down

0 comments on commit 2e053ed

Please sign in to comment.