From 78550a9bc5813382be0d90658dc60ba8112ba46e Mon Sep 17 00:00:00 2001
From: Raymond Feng <raymond@strongloop.com>
Date: Mon, 2 Mar 2015 14:48:08 -0800
Subject: [PATCH] Pass options from User.login to createAccessToken

It will allow subclass of User to create access token based on additional
properties such as 'scope'.
---
 common/models/user.js | 48 +++++++++++++++++++++++++++++--------------
 test/user.test.js     | 31 ++++++++++++++++++++++++++++
 2 files changed, 64 insertions(+), 15 deletions(-)

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);