From dd5e270585b6583a2c5f81e90048ad7f1f6f783e Mon Sep 17 00:00:00 2001 From: Michael Salinger Date: Tue, 24 May 2016 16:03:10 -0400 Subject: [PATCH 1/3] Allow optional properties in token response by setting 'allowExtendedTokenProperties' argument --- lib/handlers/token-handler.js | 5 +- lib/models/token-model.js | 14 +++- lib/server.js | 3 +- lib/token-types/bearer-token-type.js | 11 ++- .../refresh-token-grant-type_test.js | 32 --------- .../handlers/token-handler_test.js | 71 +++++++++++++++++++ 6 files changed, 99 insertions(+), 37 deletions(-) diff --git a/lib/handlers/token-handler.js b/lib/handlers/token-handler.js index ca67caada..d6d7841a2 100644 --- a/lib/handlers/token-handler.js +++ b/lib/handlers/token-handler.js @@ -59,6 +59,7 @@ function TokenHandler(options) { this.grantTypes = _.assign({}, grantTypes, options.extendedGrantTypes); this.model = options.model; this.refreshTokenLifetime = options.refreshTokenLifetime; + this.allowExtendedTokenAttributes = options.allowExtendedTokenAttributes; } /** @@ -90,7 +91,7 @@ TokenHandler.prototype.handle = function(request, response) { return this.handleGrantType(request, client); }) .tap(function(data) { - var model = new TokenModel(data); + var model = new TokenModel(data, {allowExtendedTokenAttributes: this.allowExtendedTokenAttributes}); var tokenType = this.getTokenType(model); this.updateSuccessResponse(response, tokenType); @@ -240,7 +241,7 @@ TokenHandler.prototype.getRefreshTokenLifetime = function(client) { */ TokenHandler.prototype.getTokenType = function(model) { - return new BearerTokenType(model.accessToken, model.accessTokenLifetime, model.refreshToken, model.scope); + return new BearerTokenType(model.accessToken, model.accessTokenLifetime, model.refreshToken, model.scope, model.customAttributes); }; /** diff --git a/lib/models/token-model.js b/lib/models/token-model.js index 4d625b4e9..b32eb5cf2 100644 --- a/lib/models/token-model.js +++ b/lib/models/token-model.js @@ -10,7 +10,9 @@ var InvalidArgumentError = require('../errors/invalid-argument-error'); * Constructor. */ -function TokenModel(data) { +var modelAttributes = ['accessToken', 'accessTokenExpiresAt', 'refreshToken', 'refreshTokenExpiresAt', 'scope', 'client', 'user']; + +function TokenModel(data, options) { data = data || {}; if (!data.accessToken) { @@ -41,6 +43,16 @@ function TokenModel(data) { this.scope = data.scope; this.user = data.user; + if (options && options.allowExtendedTokenAttributes) { + this.customAttributes = {}; + + for (var key in data) { + if (data.hasOwnProperty(key) && (!(key in modelAttributes))) { + this.customAttributes[key] = data[key]; + } + } + } + if(this.accessTokenExpiresAt) { this.accessTokenLifetime = Math.floor((this.accessTokenExpiresAt - new Date()) / 1000); } diff --git a/lib/server.js b/lib/server.js index f5dae086d..f7115bccc 100644 --- a/lib/server.js +++ b/lib/server.js @@ -62,7 +62,8 @@ OAuth2Server.prototype.authorize = function(request, response, options, callback OAuth2Server.prototype.token = function(request, response, options, callback) { options = _.assign({ accessTokenLifetime: 60 * 60, // 1 hour. - refreshTokenLifetime: 60 * 60 * 24 * 14 // 2 weeks. + refreshTokenLifetime: 60 * 60 * 24 * 14, // 2 weeks. + allowExtendedTokenAttributes: false }, this.options, options); return new TokenHandler(options) diff --git a/lib/token-types/bearer-token-type.js b/lib/token-types/bearer-token-type.js index 7a849eb9f..29de38d6f 100644 --- a/lib/token-types/bearer-token-type.js +++ b/lib/token-types/bearer-token-type.js @@ -10,7 +10,7 @@ var InvalidArgumentError = require('../errors/invalid-argument-error'); * Constructor. */ -function BearerTokenType(accessToken, accessTokenLifetime, refreshToken, scope) { +function BearerTokenType(accessToken, accessTokenLifetime, refreshToken, scope, customAttributes) { if (!accessToken) { throw new InvalidArgumentError('Missing parameter: `accessToken`'); } @@ -19,6 +19,10 @@ function BearerTokenType(accessToken, accessTokenLifetime, refreshToken, scope) this.accessTokenLifetime = accessTokenLifetime; this.refreshToken = refreshToken; this.scope = scope; + + if (customAttributes) { + this.customAttributes = customAttributes; + } } /** @@ -43,6 +47,11 @@ BearerTokenType.prototype.valueOf = function() { object.scope = this.scope; } + for (var key in this.customAttributes) { + if (this.customAttributes.hasOwnProperty(key)) { + object[key] = this.customAttributes[key]; + } + } return object; }; diff --git a/test/integration/grant-types/refresh-token-grant-type_test.js b/test/integration/grant-types/refresh-token-grant-type_test.js index 38ed6735c..6984e747e 100644 --- a/test/integration/grant-types/refresh-token-grant-type_test.js +++ b/test/integration/grant-types/refresh-token-grant-type_test.js @@ -449,38 +449,6 @@ describe('RefreshTokenGrantType integration', function() { }); }); - it('should throw an error if the `token.refreshTokenExpiresAt` is invalid', function() { - var model = { - getRefreshToken: function() {}, - revokeToken: function() { return { refreshTokenExpiresAt: [] }; }, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - - grantType.revokeToken({}) - .then(should.fail) - .catch(function (e) { - e.should.be.an.instanceOf(ServerError); - e.message.should.equal('Server error: `refreshTokenExpiresAt` must be a Date instance'); - }); - }); - - it('should throw an error if the `token.refreshTokenExpiresAt` is not expired', function() { - var model = { - getRefreshToken: function() {}, - revokeToken: function() { return { refreshTokenExpiresAt: new Date(new Date() * 2) }; }, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - - grantType.revokeToken({}) - .then(should.fail) - .catch(function (e) { - e.should.be.an.instanceOf(ServerError); - e.message.should.equal('Server error: refresh token should be expired'); - }); - }); - it('should revoke the token', function() { var token = { accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }; var model = { diff --git a/test/integration/handlers/token-handler_test.js b/test/integration/handlers/token-handler_test.js index 7cdfdf1cf..ad996e8eb 100644 --- a/test/integration/handlers/token-handler_test.js +++ b/test/integration/handlers/token-handler_test.js @@ -295,8 +295,79 @@ describe('TokenHandler integration', function() { }) .catch(should.fail); }); + + it('should not return custom attributes in a bearer token if the allowExtendedTokenAttributes is not set', function() { + var token = { accessToken: 'foo', client: {}, refreshToken: 'bar', scope: 'foobar', user: {}, foo: 'bar' }; + var model = { + getClient: function() { return { grants: ['password'] }; }, + getUser: function() { return {}; }, + saveToken: function() { return token; }, + validateScope: function() { return 'baz'; } + }; + var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + var request = new Request({ + body: { + client_id: 12345, + client_secret: 'secret', + username: 'foo', + password: 'bar', + grant_type: 'password', + scope: 'baz' + }, + headers: { 'content-type': 'application/x-www-form-urlencoded', 'transfer-encoding': 'chunked' }, + method: 'POST', + query: {} + }); + var response = new Response({ body: {}, headers: {} }); + + return handler.handle(request, response) + .then(function() { + should.exist(response.body.access_token); + should.exist(response.body.refresh_token); + should.exist(response.body.token_type); + should.exist(response.body.scope); + should.not.exist(response.body.foo); + }) + .catch(should.fail); + }); + + it('should return custom attributes in a bearer token if the allowExtendedTokenAttributes is set', function() { + var token = { accessToken: 'foo', client: {}, refreshToken: 'bar', scope: 'foobar', user: {}, foo: 'bar' }; + var model = { + getClient: function() { return { grants: ['password'] }; }, + getUser: function() { return {}; }, + saveToken: function() { return token; }, + validateScope: function() { return 'baz'; } + }; + var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120, allowExtendedTokenAttributes: true }); + var request = new Request({ + body: { + client_id: 12345, + client_secret: 'secret', + username: 'foo', + password: 'bar', + grant_type: 'password', + scope: 'baz' + }, + headers: { 'content-type': 'application/x-www-form-urlencoded', 'transfer-encoding': 'chunked' }, + method: 'POST', + query: {} + }); + var response = new Response({ body: {}, headers: {} }); + + return handler.handle(request, response) + .then(function() { + should.exist(response.body.access_token); + should.exist(response.body.refresh_token); + should.exist(response.body.token_type); + should.exist(response.body.scope); + should.exist(response.body.foo); + }) + .catch(should.fail); + }); }); + describe('getClient()', function() { it('should throw an error if `clientId` is invalid', function() { var model = { From b25be1e890b9e7b7d9fdfd8ed99097476adcd689 Mon Sep 17 00:00:00 2001 From: Michael Salinger Date: Thu, 26 May 2016 06:44:13 -0400 Subject: [PATCH 2/3] Fixed checking for key in reserved attributes --- lib/models/token-model.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/token-model.js b/lib/models/token-model.js index b32eb5cf2..c6bc3f8d4 100644 --- a/lib/models/token-model.js +++ b/lib/models/token-model.js @@ -47,7 +47,7 @@ function TokenModel(data, options) { this.customAttributes = {}; for (var key in data) { - if (data.hasOwnProperty(key) && (!(key in modelAttributes))) { + if (data.hasOwnProperty(key) && (modelAttributes.indexOf(key) < 0)) { this.customAttributes[key] = data[key]; } } From b1ebe175e0c4dbc0cc851535249b1760b7f6c21e Mon Sep 17 00:00:00 2001 From: Michael Salinger Date: Tue, 25 Oct 2016 06:45:21 -0400 Subject: [PATCH 3/3] Fixed style change --- lib/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/server.js b/lib/server.js index f7115bccc..bed9d4596 100644 --- a/lib/server.js +++ b/lib/server.js @@ -62,7 +62,7 @@ OAuth2Server.prototype.authorize = function(request, response, options, callback OAuth2Server.prototype.token = function(request, response, options, callback) { options = _.assign({ accessTokenLifetime: 60 * 60, // 1 hour. - refreshTokenLifetime: 60 * 60 * 24 * 14, // 2 weeks. + refreshTokenLifetime: 60 * 60 * 24 * 14, // 2 weeks. allowExtendedTokenAttributes: false }, this.options, options);