diff --git a/common/models/user.js b/common/models/user.js index 34dbaae4..dcbd2813 100644 --- a/common/models/user.js +++ b/common/models/user.js @@ -69,11 +69,23 @@ module.exports = function(User) { * customize how access tokens are generated * * @param {Number} ttl The requested ttl - * @callack {Function} cb The callback function + * @param {Object} [options] The options for access token, such as scope, appId + * @callback {Function} cb The callback function * @param {String|Error} err The error string or object * @param {AccessToken} token The generated access token object */ - User.prototype.createAccessToken = function(ttl, cb) { + User.prototype.createAccessToken = function(ttl, options, cb) { + if (cb === undefined && typeof options === 'function') { + // createAccessToken(ttl, cb) + cb = options; + options = undefined; + } + if (typeof ttl === 'object' && !options) { + // createAccessToken(options, cb) + options = ttl; + ttl = options.ttl; + } + options = options || {}; var userModel = this.constructor; ttl = Math.min(ttl || userModel.settings.ttl, userModel.settings.maxTTL); this.accessTokens.create({ @@ -193,6 +205,20 @@ module.exports = function(User) { defaultError.statusCode = 401; defaultError.code = 'LOGIN_FAILED'; + function tokenHandler(err, token) { + if (err) return fn(err); + if (Array.isArray(include) ? include.indexOf('user') !== -1 : include === 'user') { + // NOTE(bajtos) We can't set token.user here: + // 1. token.user already exists, it's a function injected by + // "AccessToken belongsTo User" relation + // 2. ModelBaseClass.toJSON() ignores own properties, thus + // the value won't be included in the HTTP response + // See also loopback#161 and loopback#162 + token.__data.user = user; + } + fn(err, token); + } + if (err) { debug('An error is reported from User.findOne: %j', err); fn(defaultError); @@ -210,19 +236,11 @@ module.exports = function(User) { err.code = 'LOGIN_FAILED_EMAIL_NOT_VERIFIED'; return fn(err); } else { - user.createAccessToken(credentials.ttl, function(err, token) { - if (err) return fn(err); - if (Array.isArray(include) ? include.indexOf('user') !== -1 : include === 'user') { - // NOTE(bajtos) We can't set token.user here: - // 1. token.user already exists, it's a function injected by - // "AccessToken belongsTo User" relation - // 2. ModelBaseClass.toJSON() ignores own properties, thus - // the value won't be included in the HTTP response - // See also loopback#161 and loopback#162 - token.__data.user = user; - } - fn(err, token); - }); + if (user.createAccessToken.length === 2) { + user.createAccessToken(credentials.ttl, tokenHandler); + } else { + user.createAccessToken(credentials.ttl, credentials, tokenHandler); + } } } else { debug('The password is invalid for user %s', query.email || query.username); diff --git a/test/user.test.js b/test/user.test.js index 7f7c6f90..baf5d6cc 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -12,6 +12,7 @@ describe('User', function() { var validCredentialsEmailVerified = {email: 'foo1@bar.com', password: 'bar1', emailVerified: true}; var validCredentialsEmailVerifiedOverREST = {email: 'foo2@bar.com', password: 'bar2', emailVerified: true}; var validCredentialsWithTTL = {email: 'foo@bar.com', password: 'bar', ttl: 3600}; + var validCredentialsWithTTLAndScope = {email: 'foo@bar.com', password: 'bar', ttl: 3600, scope: 'all'}; var invalidCredentials = {email: 'foo1@bar.com', password: 'invalid'}; var incompleteCredentials = {password: 'bar1'}; @@ -248,6 +249,36 @@ describe('User', function() { }); }); + it('Login a user using a custom createAccessToken with options', + function(done) { + var createToken = User.prototype.createAccessToken; // Save the original method + // Override createAccessToken + User.prototype.createAccessToken = function(ttl, options, cb) { + // Reduce the ttl by half for testing purpose + this.accessTokens.create({ttl: ttl / 2, scopes: options.scope}, cb); + }; + User.login(validCredentialsWithTTLAndScope, function(err, accessToken) { + assert(accessToken.userId); + assert(accessToken.id); + assert.equal(accessToken.ttl, 1800); + assert.equal(accessToken.id.length, 64); + assert.equal(accessToken.scopes, 'all'); + + User.findById(accessToken.userId, function(err, user) { + user.createAccessToken(120, {scope: 'default'}, function(err, accessToken) { + assert(accessToken.userId); + assert(accessToken.id); + assert.equal(accessToken.ttl, 60); + assert.equal(accessToken.id.length, 64); + assert.equal(accessToken.scopes, 'default'); + // Restore create access token + User.prototype.createAccessToken = createToken; + done(); + }); + }); + }); + }); + it('Login should only allow correct credentials', function(done) { User.login(invalidCredentials, function(err, accessToken) { assert(err);