From c35f762d4eed33a9cac01b0260944106cb0e324d Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 27 Jan 2014 14:31:38 -0800 Subject: [PATCH 1/6] Add debug information for user.login --- lib/models/user.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/models/user.js b/lib/models/user.js index 4ad2fde7..61a67313 100644 --- a/lib/models/user.js +++ b/lib/models/user.js @@ -18,6 +18,7 @@ var Model = require('../loopback').Model , ACL = require('./acl').ACL , assert = require('assert'); +var debug = require('debug')('loopback:user'); /** * Default User properties. */ @@ -143,20 +144,24 @@ User.login = function (credentials, fn) { var defaultError = new Error('login failed'); if(err) { + debug('User: error reported from User.findOne: %j', err); fn(defaultError); } else if(user) { user.hasPassword(credentials.password, function(err, isMatch) { if(err) { + debug('User: error reported from User.hasPassword: %j', err); fn(defaultError); } else if(isMatch) { user.accessTokens.create({ ttl: Math.min(credentials.ttl || User.settings.ttl, User.settings.maxTTL) }, fn); } else { + debug('User: invalid password for user %s', query.email || query.username); fn(defaultError); } }); } else { + debug('User: no matching record found for user %s', query.email || query.username); fn(defaultError); } }); From 938cafeb7747648766caaa41fdf1dd7c389177dd Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 27 Jan 2014 14:47:48 -0800 Subject: [PATCH 2/6] Remove message prefix as debug will print it --- lib/models/user.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/models/user.js b/lib/models/user.js index 61a67313..7a062da7 100644 --- a/lib/models/user.js +++ b/lib/models/user.js @@ -144,24 +144,24 @@ User.login = function (credentials, fn) { var defaultError = new Error('login failed'); if(err) { - debug('User: error reported from User.findOne: %j', err); + debug('An error is reported from User.findOne: %j', err); fn(defaultError); } else if(user) { user.hasPassword(credentials.password, function(err, isMatch) { if(err) { - debug('User: error reported from User.hasPassword: %j', err); + debug('An error is reported from User.hasPassword: %j', err); fn(defaultError); } else if(isMatch) { user.accessTokens.create({ ttl: Math.min(credentials.ttl || User.settings.ttl, User.settings.maxTTL) }, fn); } else { - debug('User: invalid password for user %s', query.email || query.username); + debug('The password is invalid for user %s', query.email || query.username); fn(defaultError); } }); } else { - debug('User: no matching record found for user %s', query.email || query.username); + debug('No matching record is found for user %s', query.email || query.username); fn(defaultError); } }); From 57796a41cd6eb70a02a090c1735622609cf260dd Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 30 Jan 2014 09:02:12 -0800 Subject: [PATCH 3/6] Remove the generated flag for access token id The generated flag is used to indicate if the id is automatically generated by the backend store. If it's set, the data type will be updated when the model is attached to a datasource. The AccessToken model defines a string id, which is set in the beforeCreate hook. So it's client provided id. --- lib/models/access-token.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/access-token.js b/lib/models/access-token.js index acf8484e..e920cde9 100644 --- a/lib/models/access-token.js +++ b/lib/models/access-token.js @@ -17,7 +17,7 @@ var Model = require('../loopback').Model */ var properties = { - id: {type: String, generated: true, id: 1}, + id: {type: String, id: true}, ttl: {type: Number, ttl: true, default: DEFAULT_TTL}, // time to live in seconds created: {type: Date, default: function() { return new Date(); From 03cb2f0556f9b7fa6745b44513e22fe38f988534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Thu, 30 Jan 2014 14:35:49 +0100 Subject: [PATCH 4/6] Describe `access_token` param of `User.logout` Add an explicit note that clients are not supposed to send the `access_token` parameter, since it is extracted from request headers. --- lib/models/user.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/models/user.js b/lib/models/user.js index 4ad2fde7..cdaa8703 100644 --- a/lib/models/user.js +++ b/lib/models/user.js @@ -403,7 +403,10 @@ User.setup = function () { var tokenID = accessToken && accessToken.id; return tokenID; - }} + }, description: + 'Do not supply this argument, it is automatically extracted ' + + 'from request headers.' + } ], http: {verb: 'all'} } From d6f0b5f5a66869af97beb21e428e5d3f5b047c61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Thu, 30 Jan 2014 14:33:45 +0100 Subject: [PATCH 5/6] Add `include=user` param to `User.login` Allow LB clients to get details of the currently logged-in user as part of the login response. Improve method's `description` to mention this new option. --- lib/models/user.js | 38 ++++++++++++++++++++++++++++++++------ test/user.test.js | 26 ++++++++++++++++++++++---- 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/lib/models/user.js b/lib/models/user.js index cdaa8703..bdb656c0 100644 --- a/lib/models/user.js +++ b/lib/models/user.js @@ -127,10 +127,15 @@ var User = module.exports = Model.extend('User', properties, options); * @param {AccessToken} token */ -User.login = function (credentials, fn) { - var UserCtor = this; +User.login = function (credentials, include, fn) { + if (typeof include === 'function') { + fn = include; + include = undefined; + } + + include = (include || '').toLowerCase(); + var query = {}; - if(credentials.email) { query.email = credentials.email; } else if(credentials.username) { @@ -151,7 +156,19 @@ User.login = function (credentials, fn) { } else if(isMatch) { user.accessTokens.create({ ttl: Math.min(credentials.ttl || User.settings.ttl, User.settings.maxTTL) - }, fn); + }, function(err, token) { + if (err) return fn(err); + if (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); + }); } else { fn(defaultError); } @@ -386,9 +403,18 @@ User.setup = function () { UserModel.login, { accepts: [ - {arg: 'credentials', type: 'object', required: true, http: {source: 'body'}} + {arg: 'credentials', type: 'object', required: true, http: {source: 'body'}}, + {arg: 'include', type: 'string', http: {source: 'query' }, description: + 'Related objects to include in the response. ' + + 'See the description of return value for more details.'} ], - returns: {arg: 'accessToken', type: 'object', root: true}, + returns: { + arg: 'accessToken', type: 'object', root: true, description: + 'The response body contains properties of the AccessToken created on login.\n' + + 'Depending on the value of `include` parameter, the body may contain ' + + 'additional properties:\n\n' + + ' - `user` - `{User}` - Data of the currently logged in user. (`include=user`)\n\n' + }, http: {verb: 'post'} } ); diff --git a/test/user.test.js b/test/user.test.js index 20271335..9a056180 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -8,6 +8,7 @@ var userMemory = loopback.createDataSource({ }); describe('User', function(){ + var validCredentials = {email: 'foo@bar.com', password: 'bar'}; beforeEach(function() { User = loopback.User.extend('user'); User.email = loopback.Email.extend('email'); @@ -25,7 +26,7 @@ describe('User', function(){ app.use(loopback.rest()); app.model(User); - User.create({email: 'foo@bar.com', password: 'bar'}, done); + User.create(validCredentials, done); }); afterEach(function (done) { @@ -105,7 +106,7 @@ describe('User', function(){ describe('User.login', function() { it('Login a user by providing credentials', function(done) { - User.login({email: 'foo@bar.com', password: 'bar'}, function (err, accessToken) { + User.login(validCredentials, function (err, accessToken) { assert(accessToken.userId); assert(accessToken.id); assert.equal(accessToken.id.length, 64); @@ -119,7 +120,7 @@ describe('User', function(){ .post('/users/login') .expect('Content-Type', /json/) .expect(200) - .send({email: 'foo@bar.com', password: 'bar'}) + .send(validCredentials) .end(function(err, res){ if(err) return done(err); var accessToken = res.body; @@ -127,11 +128,28 @@ describe('User', function(){ assert(accessToken.userId); assert(accessToken.id); assert.equal(accessToken.id.length, 64); + assert(accessToken.user === undefined); done(); }); }); - + + it('Returns current user when `include` is `USER`', function(done) { + request(app) + .post('/users/login?include=USER') + .send(validCredentials) + .expect(200) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) return done(err); + var token = res.body; + expect(token.user, 'body.user').to.not.equal(undefined); + expect(token.user, 'body.user') + .to.have.property('email', validCredentials.email); + done(); + }); + }); + it('Login should only allow correct credentials', function(done) { User.create({email: 'foo22@bar.com', password: 'bar'}, function(user, err) { User.login({email: 'foo44@bar.com', password: 'bar'}, function(err, accessToken) { From dc3583123f76b7430927b45523333a98551c1e54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Thu, 30 Jan 2014 19:35:05 +0100 Subject: [PATCH 6/6] v1.6.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cd766b48..d504b4f6 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "Platform", "mBaaS" ], - "version": "1.6.0", + "version": "1.6.1", "scripts": { "test": "mocha -R spec" },