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..c6bc3f8d4 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) && (modelAttributes.indexOf(key) < 0)) { + 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..bed9d4596 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 = {