Skip to content

Commit

Permalink
Merge pull request #270 from lelylan/feature/support-per-call-headers
Browse files Browse the repository at this point in the history
[support-per-call-headers] Add support for per-call http options. Closes #211
  • Loading branch information
jonathansamines authored Oct 9, 2019
2 parents c6d8e2d + beb0c31 commit 7fe4b02
Show file tree
Hide file tree
Showing 14 changed files with 206 additions and 34 deletions.
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,12 @@ const tokenConfig = {
scope: '<scope>', // also can be an array of multiple scopes, ex. ['<scope1>, '<scope2>', '...']
};

// Optional per-call http options
const httpOptions = {};

// Save the access token
try {
const result = await oauth2.authorizationCode.getToken(tokenConfig)
const result = await oauth2.authorizationCode.getToken(tokenConfig, httpOptions);
const accessToken = oauth2.accessToken.create(result);
} catch (error) {
console.log('Access Token Error', error.message);
Expand All @@ -167,9 +170,12 @@ const tokenConfig = {
scope: '<scope>', // also can be an array of multiple scopes, ex. ['<scope1>, '<scope2>', '...']
};

// Optional per-call http options
const httpOptions = {};

// Save the access token
try {
const result = await oauth2.ownerPassword.getToken(tokenConfig);
const result = await oauth2.ownerPassword.getToken(tokenConfig, httpOptions);
const accessToken = oauth2.accessToken.create(result);
} catch (error) {
console.log('Access Token Error', error.message);
Expand All @@ -186,9 +192,12 @@ const tokenConfig = {
scope: '<scope>', // also can be an array of multiple scopes, ex. ['<scope1>, '<scope2>', '...']
};

// Optional per-call http options
const httpOptions = {};

// Get the access token object for the client
try {
const result = await oauth2.clientCredentials.getToken(tokenConfig);
const result = await oauth2.clientCredentials.getToken(tokenConfig, httpOptions);
const accessToken = oauth2.accessToken.create(result);
} catch (error) {
console.log('Access Token error', error.message);
Expand Down
13 changes: 7 additions & 6 deletions lib/client.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
'use strict';

const Hoek = require('@hapi/hoek');
const Wreck = require('@hapi/wreck');
const debug = require('debug')('simple-oauth2:client');
const RequestOptions = require('./request-options');

const defaultHeaders = {
Accept: 'application/json',
};

const defaultHttpOptions = {
json: 'strict',
redirects: 20,
headers: {
Accept: 'application/json',
},
};

module.exports = class Client {
constructor(config) {
const httpOptions = Object.assign({}, defaultHttpOptions, config.http, {
const configHttpOptions = Hoek.applyToDefaults(config.http || {}, {
baseUrl: config.auth.tokenHost,
headers: Object.assign({}, defaultHeaders, (config.http && config.http.headers)),
});

const httpOptions = Hoek.applyToDefaults(defaultHttpOptions, configHttpOptions);

this.config = config;
this.client = Wreck.defaults(httpOptions);
}
Expand Down
4 changes: 2 additions & 2 deletions lib/grant-params.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ module.exports = class GrantParams {
}

constructor(baseParams, params) {
this.params = params;
this.baseParams = baseParams;
this.params = Object.assign({}, params);
this.baseParams = Object.assign({}, baseParams);
this.scopeParams = getScopeParam(this.params.scope);
}

Expand Down
5 changes: 3 additions & 2 deletions lib/grants/authorization-code.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ module.exports = class AuthorizationCode {
* @param {String} params.code Authorization code (from previous step)
* @param {String} params.redirecURI A string representing the registered application URI where the user is redirected after authentication
* @param {String|Array<String>} [params.scope] A String or array of strings representing the application privileges
* @param {Object} [httpOptions] Optional http options passed through the underlying http library
* @return {Promise}
*/
async getToken(params = {}) {
async getToken(params, httpOptions) {
const parameters = GrantParams.forGrant('authorization_code', params);

return this.client.request(this.config.auth.tokenPath, parameters.toObject());
return this.client.request(this.config.auth.tokenPath, parameters.toObject(), httpOptions);
}
};
6 changes: 3 additions & 3 deletions lib/grants/client-credentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ module.exports = class ClientCredentials {
*
* @param {Object} params
* @param {String|Array<String>} [params.scope] A String or array of strings representing the application privileges
*
* @param {Object} [httpOptions] Optional http options passed through the underlying http library
* @return {Promise}
*/
async getToken(params = {}) {
async getToken(params, httpOptions) {
const parameters = GrantParams.forGrant('client_credentials', params);

return this.client.request(this.config.auth.tokenPath, parameters.toObject());
return this.client.request(this.config.auth.tokenPath, parameters.toObject(), httpOptions);
}
};
6 changes: 3 additions & 3 deletions lib/grants/password-owner.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ module.exports = class PasswordOwner {
* @param {String} params.username A string representing the registered username
* @param {String} params.password A string representing the registered password
* @param {String|Array<String>} [params.scope] A String or array of strings representing the application privileges
*
* @param {Object} [httpOptions] Optional http options passed through the underlying http library
* @return {Promise}
*/
async getToken(params = {}) {
async getToken(params, httpOptions) {
const parameters = GrantParams.forGrant('password', params);

return this.client.request(this.config.auth.tokenPath, parameters.toObject());
return this.client.request(this.config.auth.tokenPath, parameters.toObject(), httpOptions);
}
};
5 changes: 3 additions & 2 deletions lib/request-options.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';

const Hoek = require('@hapi/hoek');
const querystring = require('querystring');
const debug = require('debug')('simple-oauth2:request-options');
const encoding = require('./encoding');
Expand Down Expand Up @@ -53,7 +54,7 @@ module.exports = class RequestOptions {
return requestOptions;
}

toObject(requestOptions) {
return Object.assign({}, requestOptions, this.requestOptions);
toObject(requestOptions = {}) {
return Hoek.applyToDefaults(requestOptions, this.requestOptions);
}
};
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"credentials"
],
"dependencies": {
"@hapi/hoek": "^8.3.0",
"@hapi/joi": "^16.1.7",
"@hapi/wreck": "^15.1.0",
"date-fns": "^2.4.1",
Expand Down
14 changes: 7 additions & 7 deletions test/_authorization-server-mock.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

const { URL } = require('url');
const nock = require('nock');
const merge = require('lodash/merge');
const Hoek = require('@hapi/hoek');
const Boom = require('@hapi/boom');

const accessToken = {
Expand Down Expand Up @@ -120,26 +120,26 @@ function getAccessToken() {
return accessToken;
}

function getJSONEncodingScopeOptions(options) {
return merge({
function getJSONEncodingScopeOptions(options = {}) {
return Hoek.applyToDefaults({
reqheaders: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
}, options);
}

function getFormEncodingScopeOptions(options) {
return merge({
function getFormEncodingScopeOptions(options = {}) {
return Hoek.applyToDefaults({
reqheaders: {
Accept: 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
},
}, options);
}

function getHeaderCredentialsScopeOptions(options) {
return merge({
function getHeaderCredentialsScopeOptions(options = {}) {
return Hoek.applyToDefaults({
reqheaders: {
Accept: 'application/json',
Authorization: 'Basic dGhlK2NsaWVudCtpZDp0aGUrY2xpZW50K3NlY3JldA==',
Expand Down
6 changes: 3 additions & 3 deletions test/_module-config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

const { merge } = require('lodash');
const Hoek = require('@hapi/hoek');

const baseConfig = {
client: {
Expand All @@ -12,8 +12,8 @@ const baseConfig = {
},
};

function createModuleConfig(config) {
return merge({}, baseConfig, config);
function createModuleConfig(config = {}) {
return Hoek.applyToDefaults(baseConfig, config);
}

module.exports = {
Expand Down
53 changes: 53 additions & 0 deletions test/authorization-code.js
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,59 @@ test.serial('@getToken => resolves to an access token with no params', async (t)
t.deepEqual(token, getAccessToken());
});

test.serial('@getToken => resolves to an access token with custom (inline) http options', async (t) => {
const expectedRequestParams = {
grant_type: 'authorization_code',
};

const scopeOptions = getHeaderCredentialsScopeOptions({
reqheaders: {
'X-REQUEST-ID': 123,
},
});

const server = createAuthorizationServer('https://authorization-server.org:443');
const scope = server.tokenSuccess(scopeOptions, expectedRequestParams);

const config = createModuleConfig();
const oauth2 = oauth2Module.create(config);

const httpOptions = {
headers: {
'X-REQUEST-ID': 123,
},
};

const token = await oauth2.authorizationCode.getToken(null, httpOptions);

scope.done();
t.deepEqual(token, getAccessToken());
});

test.serial('@getToken => resolves to an access token with custom (inline) http options without overriding (required) http options', async (t) => {
const expectedRequestParams = {
grant_type: 'authorization_code',
};

const scopeOptions = getHeaderCredentialsScopeOptions();
const server = createAuthorizationServer('https://authorization-server.org:443');
const scope = server.tokenSuccess(scopeOptions, expectedRequestParams);

const config = createModuleConfig();
const oauth2 = oauth2Module.create(config);

const httpOptions = {
headers: {
Authorization: 'Basic credentials',
},
};

const token = await oauth2.authorizationCode.getToken(null, httpOptions);

scope.done();
t.deepEqual(token, getAccessToken());
});

test.serial('@getToken => rejects the operation when a non json response is received', async (t) => {
const expectedRequestParams = {
grant_type: 'authorization_code',
Expand Down
53 changes: 53 additions & 0 deletions test/client-credentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,59 @@ test.serial('@getToken => resolves to an access token with no params', async (t)
t.deepEqual(token, getAccessToken());
});

test.serial('@getToken => resolves to an access token with custom (inline) http options', async (t) => {
const expectedRequestParams = {
grant_type: 'client_credentials',
};

const scopeOptions = getHeaderCredentialsScopeOptions({
reqheaders: {
'X-REQUEST-ID': 123,
},
});

const server = createAuthorizationServer('https://authorization-server.org:443');
const scope = server.tokenSuccess(scopeOptions, expectedRequestParams);

const config = createModuleConfig();
const oauth2 = oauth2Module.create(config);

const httpOptions = {
headers: {
'X-REQUEST-ID': 123,
},
};

const token = await oauth2.clientCredentials.getToken(null, httpOptions);

scope.done();
t.deepEqual(token, getAccessToken());
});

test.serial('@getToken => resolves to an access token with custom (inline) http options without overriding (required) http options', async (t) => {
const expectedRequestParams = {
grant_type: 'client_credentials',
};

const scopeOptions = getHeaderCredentialsScopeOptions();
const server = createAuthorizationServer('https://authorization-server.org:443');
const scope = server.tokenSuccess(scopeOptions, expectedRequestParams);

const config = createModuleConfig();
const oauth2 = oauth2Module.create(config);

const httpOptions = {
headers: {
Authorization: 'Basic credentials',
},
};

const token = await oauth2.clientCredentials.getToken(null, httpOptions);

scope.done();
t.deepEqual(token, getAccessToken());
});

test.serial('@getToken => rejects the operation when a non json response is received', async (t) => {
const expectedRequestParams = {
grant_type: 'client_credentials',
Expand Down
Loading

0 comments on commit 7fe4b02

Please sign in to comment.