Merge branch 'release/1.6.1' into production

This commit is contained in:
Miroslav Bajtoš 2014-01-30 19:35:46 +01:00
commit bdaaa7cb44
4 changed files with 65 additions and 13 deletions

View File

@ -17,7 +17,7 @@ var Model = require('../loopback').Model
*/ */
var properties = { 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 ttl: {type: Number, ttl: true, default: DEFAULT_TTL}, // time to live in seconds
created: {type: Date, default: function() { created: {type: Date, default: function() {
return new Date(); return new Date();

View File

@ -18,6 +18,7 @@ var Model = require('../loopback').Model
, ACL = require('./acl').ACL , ACL = require('./acl').ACL
, assert = require('assert'); , assert = require('assert');
var debug = require('debug')('loopback:user');
/** /**
* Default User properties. * Default User properties.
*/ */
@ -127,10 +128,15 @@ var User = module.exports = Model.extend('User', properties, options);
* @param {AccessToken} token * @param {AccessToken} token
*/ */
User.login = function (credentials, fn) { User.login = function (credentials, include, fn) {
var UserCtor = this; if (typeof include === 'function') {
var query = {}; fn = include;
include = undefined;
}
include = (include || '').toLowerCase();
var query = {};
if(credentials.email) { if(credentials.email) {
query.email = credentials.email; query.email = credentials.email;
} else if(credentials.username) { } else if(credentials.username) {
@ -143,20 +149,36 @@ User.login = function (credentials, fn) {
var defaultError = new Error('login failed'); var defaultError = new Error('login failed');
if(err) { if(err) {
debug('An error is reported from User.findOne: %j', err);
fn(defaultError); fn(defaultError);
} else if(user) { } else if(user) {
user.hasPassword(credentials.password, function(err, isMatch) { user.hasPassword(credentials.password, function(err, isMatch) {
if(err) { if(err) {
debug('An error is reported from User.hasPassword: %j', err);
fn(defaultError); fn(defaultError);
} else if(isMatch) { } else if(isMatch) {
user.accessTokens.create({ user.accessTokens.create({
ttl: Math.min(credentials.ttl || User.settings.ttl, User.settings.maxTTL) 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 { } else {
debug('The password is invalid for user %s', query.email || query.username);
fn(defaultError); fn(defaultError);
} }
}); });
} else { } else {
debug('No matching record is found for user %s', query.email || query.username);
fn(defaultError); fn(defaultError);
} }
}); });
@ -386,9 +408,18 @@ User.setup = function () {
UserModel.login, UserModel.login,
{ {
accepts: [ 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'} http: {verb: 'post'}
} }
); );
@ -403,7 +434,10 @@ User.setup = function () {
var tokenID = accessToken && accessToken.id; var tokenID = accessToken && accessToken.id;
return tokenID; return tokenID;
}} }, description:
'Do not supply this argument, it is automatically extracted ' +
'from request headers.'
}
], ],
http: {verb: 'all'} http: {verb: 'all'}
} }

View File

@ -9,7 +9,7 @@
"Platform", "Platform",
"mBaaS" "mBaaS"
], ],
"version": "1.6.0", "version": "1.6.1",
"scripts": { "scripts": {
"test": "mocha -R spec" "test": "mocha -R spec"
}, },

View File

@ -8,6 +8,7 @@ var userMemory = loopback.createDataSource({
}); });
describe('User', function(){ describe('User', function(){
var validCredentials = {email: 'foo@bar.com', password: 'bar'};
beforeEach(function() { beforeEach(function() {
User = loopback.User.extend('user'); User = loopback.User.extend('user');
User.email = loopback.Email.extend('email'); User.email = loopback.Email.extend('email');
@ -25,7 +26,7 @@ describe('User', function(){
app.use(loopback.rest()); app.use(loopback.rest());
app.model(User); app.model(User);
User.create({email: 'foo@bar.com', password: 'bar'}, done); User.create(validCredentials, done);
}); });
afterEach(function (done) { afterEach(function (done) {
@ -105,7 +106,7 @@ describe('User', function(){
describe('User.login', function() { describe('User.login', function() {
it('Login a user by providing credentials', function(done) { 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.userId);
assert(accessToken.id); assert(accessToken.id);
assert.equal(accessToken.id.length, 64); assert.equal(accessToken.id.length, 64);
@ -119,7 +120,7 @@ describe('User', function(){
.post('/users/login') .post('/users/login')
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200) .expect(200)
.send({email: 'foo@bar.com', password: 'bar'}) .send(validCredentials)
.end(function(err, res){ .end(function(err, res){
if(err) return done(err); if(err) return done(err);
var accessToken = res.body; var accessToken = res.body;
@ -127,11 +128,28 @@ describe('User', function(){
assert(accessToken.userId); assert(accessToken.userId);
assert(accessToken.id); assert(accessToken.id);
assert.equal(accessToken.id.length, 64); assert.equal(accessToken.id.length, 64);
assert(accessToken.user === undefined);
done(); 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) { it('Login should only allow correct credentials', function(done) {
User.create({email: 'foo22@bar.com', password: 'bar'}, function(user, err) { User.create({email: 'foo22@bar.com', password: 'bar'}, function(user, err) {
User.login({email: 'foo44@bar.com', password: 'bar'}, function(err, accessToken) { User.login({email: 'foo44@bar.com', password: 'bar'}, function(err, accessToken) {