From fcce3ccd0240c7a6a56215c555317d25b6e35ac8 Mon Sep 17 00:00:00 2001 From: Blake Embrey Date: Mon, 14 Mar 2016 16:13:29 -0700 Subject: [PATCH] Merge options per every method, remove omit data --- README.md | 2 + client-oauth2.js | 95 ++++++++++++++++++++++++------------------------ test/user.js | 3 +- 3 files changed, 52 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 2036516..1be94f4 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,8 @@ token.request({ You can override the request mechanism if you need a custom implementation by setting `githubAuth.request = function (opts) { return new Promise(...) }`. You will need to make sure that the custom request mechanism supports the correct input and output objects. +**P.S.** All authorization methods accept `options` as the last argument, useful for overriding the global configuration on a per-request basis. + ### [Authorization Code Grant](http://tools.ietf.org/html/rfc6749#section-4.1) > The authorization code grant type is used to obtain both access tokens and refresh tokens and is optimized for confidential clients. Since this is a redirection-based flow, the client must be capable of interacting with the resource owner's user-agent (typically a web browser) and capable of receiving incoming requests (via redirection) from the authorization server. diff --git a/client-oauth2.js b/client-oauth2.js index a4442ed..d37abb0 100644 --- a/client-oauth2.js +++ b/client-oauth2.js @@ -2,7 +2,6 @@ var extend = require('xtend') var popsicle = require('popsicle') var parseQuery = require('querystring').parse var parseUrl = require('url').parse -var omit = require('object.omit') var btoa = typeof Buffer === 'function' ? btoaBuffer : window.btoa @@ -200,6 +199,18 @@ function string (str) { return str == null ? '' : String(str) } +/** + * Merge request options from an options object. + */ +function requestOptions (requestOptions, options) { + return extend(requestOptions, { + body: extend(options.body, requestOptions.body), + query: extend(options.query, requestOptions.query), + headers: extend(options.headers, requestOptions.headers), + options: extend(options.options, requestOptions.options) + }) +} + /** * Construct an object that can handle the multiple OAuth 2.0 flows. * @@ -246,11 +257,11 @@ ClientOAuth2.prototype.createToken = function (access, refresh, type, data) { * Using the built-in request method, we'll automatically attempt to parse * the response. * - * @param {Object} options + * @param {Object} requestObject * @return {Promise} */ -ClientOAuth2.prototype._request = function (options) { - return this.request(this._requestOptions(options)) +ClientOAuth2.prototype._request = function (requestObject) { + return this.request(requestObject) .then(function (res) { if (res.status < 200 || res.status >= 399) { var err = new Error('HTTP status ' + res.status) @@ -263,15 +274,6 @@ ClientOAuth2.prototype._request = function (options) { }) } -ClientOAuth2.prototype._requestOptions = function (options) { - return extend(options, { - body: extend(this.options.body, options.body), - query: extend(this.options.query, options.query), - headers: extend(this.options.headers, options.headers), - options: extend(this.options.options, options.options) - }) -} - /** * Set `popsicle` as the default request method. */ @@ -285,12 +287,7 @@ ClientOAuth2.prototype.request = popsicle.request */ function ClientOAuth2Token (client, data) { this.client = client - - this.data = omit(data, [ - 'access_token', 'refresh_token', 'token_type', 'expires_in', 'scope', - 'state', 'error', 'error_description', 'error_uri' - ]) - + this.data = data this.tokenType = data.token_type && data.token_type.toLowerCase() this.accessToken = data.access_token this.refreshToken = data.refresh_token @@ -318,44 +315,44 @@ ClientOAuth2Token.prototype.expiresIn = function (duration) { /** * Sign a standardised request object with user authentication information. * - * @param {Object} opts + * @param {Object} requestOptions * @return {Object} */ -ClientOAuth2Token.prototype.sign = function (opts) { +ClientOAuth2Token.prototype.sign = function (requestObject) { if (!this.accessToken) { throw new Error('Unable to sign without access token') } - opts.headers = opts.headers || {} + requestObject.headers = requestObject.headers || {} if (this.tokenType === 'bearer') { - opts.headers.Authorization = 'Bearer ' + this.accessToken + requestObject.headers.Authorization = 'Bearer ' + this.accessToken } else { - var parts = opts.url.split('#') + var parts = requestObject.url.split('#') var token = 'access_token=' + this.accessToken var url = parts[0].replace(/[?&]access_token=[^&#]/, '') var fragment = parts[1] ? '#' + parts[1] : '' // Prepend the correct query string parameter to the url. - opts.url = url + (url.indexOf('?') > -1 ? '&' : '?') + token + fragment + requestObject.url = url + (url.indexOf('?') > -1 ? '&' : '?') + token + fragment // Attempt to avoid storing the url in proxies, since the access token // is exposed in the query parameters. - opts.headers.Pragma = 'no-store' - opts.headers['Cache-Control'] = 'no-store' + requestObject.headers.Pragma = 'no-store' + requestObject.headers['Cache-Control'] = 'no-store' } - return opts + return requestObject } /** * Make a HTTP request as the user. * - * @param {Object} opts + * @param {Object} options * @return {Promise} */ -ClientOAuth2Token.prototype.request = function (opts) { - return this.client.request(this.client._requestOptions(this.sign(opts))) +ClientOAuth2Token.prototype.request = function (options) { + return this.client.request(requestOptions(this.sign(options), this.client.options)) } /** @@ -363,15 +360,16 @@ ClientOAuth2Token.prototype.request = function (opts) { * * @return {Promise} */ -ClientOAuth2Token.prototype.refresh = function () { +ClientOAuth2Token.prototype.refresh = function (options) { var self = this - var options = this.client.options + + options = extend(this.client.options, options) if (!this.refreshToken) { return Promise.reject(new Error('No refresh token set')) } - return this.client._request({ + return this.client._request(requestOptions({ url: options.accessTokenUri, method: 'POST', headers: extend(DEFAULT_HEADERS, { @@ -381,7 +379,7 @@ ClientOAuth2Token.prototype.refresh = function () { refresh_token: this.refreshToken, grant_type: 'refresh_token' } - }) + }, options)) .then(handleAuthResponse) .then(function (data) { self.accessToken = data.access_token @@ -429,7 +427,7 @@ OwnerFlow.prototype.getToken = function (username, password, options) { options = extend(this.client.options, options) - return this.client._request({ + return this.client._request(requestOptions({ url: options.accessTokenUri, method: 'POST', headers: extend(DEFAULT_HEADERS, { @@ -441,7 +439,7 @@ OwnerFlow.prototype.getToken = function (username, password, options) { password: password, grant_type: 'password' } - }) + }, options)) .then(handleAuthResponse) .then(function (data) { return new ClientOAuth2Token(self.client, data) @@ -476,10 +474,11 @@ TokenFlow.prototype.getUri = function (options) { * * @param {String} uri * @param {String} [state] + * @param {Object} [options] * @return {Promise} */ -TokenFlow.prototype.getToken = function (uri, state) { - var options = this.client.options +TokenFlow.prototype.getToken = function (uri, state, options) { + options = extend(this.client.options, options) // Make sure the uri matches our expected redirect uri. if (uri.substr(0, options.redirectUri.length) !== options.redirectUri) { @@ -546,7 +545,7 @@ CredentialsFlow.prototype.getToken = function (options) { 'accessTokenUri' ]) - return this.client._request({ + return this.client._request(requestOptions({ url: options.accessTokenUri, method: 'POST', headers: extend(DEFAULT_HEADERS, { @@ -556,7 +555,7 @@ CredentialsFlow.prototype.getToken = function (options) { scope: sanitizeScope(options.scopes), grant_type: 'client_credentials' } - }) + }, options)) .then(handleAuthResponse) .then(function (data) { return new ClientOAuth2Token(self.client, data) @@ -591,11 +590,13 @@ CodeFlow.prototype.getUri = function (options) { * * @param {String} uri * @param {String} [state] + * @param {Object} [options] * @return {Promise} */ -CodeFlow.prototype.getToken = function (uri, state) { +CodeFlow.prototype.getToken = function (uri, state, options) { var self = this - var options = this.client.options + + options = extend(this.client.options, options) expects(options, [ 'clientId', @@ -631,7 +632,7 @@ CodeFlow.prototype.getToken = function (uri, state) { return Promise.reject(new Error('Missing code, unable to request token')) } - return this.client._request({ + return this.client._request(requestOptions({ url: options.accessTokenUri, method: 'POST', headers: extend(DEFAULT_HEADERS), @@ -642,7 +643,7 @@ CodeFlow.prototype.getToken = function (uri, state) { client_id: options.clientId, client_secret: options.clientSecret } - }) + }, options)) .then(handleAuthResponse) .then(function (data) { return new ClientOAuth2Token(self.client, data) @@ -684,7 +685,7 @@ JwtBearerFlow.prototype.getToken = function (token, options) { headers['Authorization'] = auth(options.clientId, options.clientSecret) } - return this.client._request({ + return this.client._request(requestOptions({ url: options.accessTokenUri, method: 'POST', headers: headers, @@ -693,7 +694,7 @@ JwtBearerFlow.prototype.getToken = function (token, options) { grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', assertion: token } - }) + }, options)) .then(handleAuthResponse) .then(function (data) { return new ClientOAuth2Token(self.client, data) diff --git a/test/user.js b/test/user.js index b34d640..3500a47 100644 --- a/test/user.js +++ b/test/user.js @@ -26,6 +26,7 @@ describe('user', function () { expect(req.headers.Authorization).to.equal('Basic ' + btoa('abc:123')) expect(req.body.grant_type).to.equal('refresh_token') expect(req.body.refresh_token).to.equal(refreshToken) + expect(req.body.test).to.equal(true) return Promise.resolve({ status: 200, @@ -93,7 +94,7 @@ describe('user', function () { expect(user.accessToken).to.equal(accessToken) expect(user.tokenType).to.equal('bearer') - return user.refresh() + return user.refresh({ body: { test: true } }) .then(function (token) { expect(token).to.an.instanceOf(ClientOAuth2.Token) expect(token.accessToken).to.equal(refreshAccessToken)