diff --git a/common/models/user.js b/common/models/user.js index c85d359d..cd0122a7 100644 --- a/common/models/user.js +++ b/common/models/user.js @@ -14,7 +14,7 @@ var utils = require('../../lib/utils'); var path = require('path'); var SALT_WORK_FACTOR = 10; var crypto = require('crypto'); - +var MAX_PASSWORD_LENGTH = 72; var bcrypt; try { // Try the native module first @@ -548,7 +548,6 @@ module.exports = function(User) { cb = cb || utils.createPromiseCallback(); var UserModel = this; var ttl = UserModel.settings.resetPasswordTokenTTL || DEFAULT_RESET_PW_TTL; - options = options || {}; if (typeof options.email !== 'string') { var err = new Error(g.f('Email is required')); @@ -558,6 +557,13 @@ module.exports = function(User) { return cb.promise; } + try { + if (options.password) { + UserModel.validatePassword(options.password); + } + } catch (err) { + return cb(err); + } UserModel.findOne({ where: { email: options.email }}, function(err, user) { if (err) { return cb(err); @@ -596,14 +602,20 @@ module.exports = function(User) { }; User.validatePassword = function(plain) { - if (typeof plain === 'string' && plain) { + var err; + if (plain && typeof plain === 'string' && plain.length <= MAX_PASSWORD_LENGTH) { return true; } - var err = new Error(g.f('Invalid password: %s', plain)); + if (plain.length > MAX_PASSWORD_LENGTH) { + err = new Error (g.f('Password too long: %s', plain)); + err.code = 'PASSWORD_TOO_LONG'; + } else { + err = new Error(g.f('Invalid password: %s', plain)); + err.code = 'INVALID_PASSWORD'; + } err.statusCode = 422; throw err; }; - /*! * Setup an extended user model. */ diff --git a/index.js b/index.js index 8d7626c8..863d5fb8 100644 --- a/index.js +++ b/index.js @@ -4,7 +4,7 @@ // License text available at https://opensource.org/licenses/MIT var SG = require('strong-globalize'); -SG.SetRootDir(__dirname, {autonomousMsgLoading: 'all'}); +SG.SetRootDir(__dirname, { autonomousMsgLoading: 'all' }); /** * loopback ~ public api diff --git a/test/user.test.js b/test/user.test.js index 26a5fa02..e3c3c6ba 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -363,6 +363,68 @@ describe('User', function() { }); }); + describe('Password length validation', function() { + var pass72Char = new Array(70).join('a') + '012'; + var pass73Char = pass72Char + '3'; + var passTooLong = pass72Char + 'WXYZ1234'; + + it('rejects passwords longer than 72 characters', function(done) { + try { + User.create({ email: 'b@c.com', password: pass73Char }, function(err) { + if (err) return done (err); + done(new Error('User.create() should have thrown an error.')); + }); + } catch (e) { + expect(e).to.match(/Password too long/); + done(); + } + }); + + it('rejects a new user with password longer than 72 characters', function(done) { + try { + var u = new User({ username: 'foo', password: pass73Char }); + assert(false, 'Error should have been thrown'); + } catch (e) { + expect(e).to.match(/Password too long/); + done(); + } + }); + + it('accepts passwords that are exactly 72 characters long', function(done) { + User.create({ email: 'b@c.com', password: pass72Char }, function(err, user) { + if (err) return done(err); + User.findById(user.id, function(err, userFound) { + if (err) return done (err); + assert(userFound); + done(); + }); + }); + }); + + it('allows login with password exactly 72 characters long', function(done) { + User.create({ email: 'b@c.com', password: pass72Char }, function(err) { + if (err) return done(err); + User.login({ email: 'b@c.com', password: pass72Char }, function(err, accessToken) { + if (err) return done(err); + assertGoodToken(accessToken); + assert(accessToken.id); + done(); + }); + }); + }); + + it('rejects password reset when password is more than 72 chars', function(done) { + User.create({ email: 'b@c.com', password: pass72Char }, function(err) { + if (err) return done (err); + User.resetPassword({ email: 'b@c.com', password: pass73Char }, function(err) { + assert(err); + expect(err).to.match(/Password too long/); + done(); + }); + }); + }); + }); + describe('Access-hook for queries with email NOT case-sensitive', function() { it('Should not throw an error if the query does not contain {where: }', function(done) { User.find({}, function(err) { @@ -678,6 +740,23 @@ describe('User', function() { done(); }); }); + + it('allows login with password too long but created in old LB version', + function(done) { + var bcrypt = require('bcryptjs'); + var longPassword = new Array(80).join('a'); + var oldHash = bcrypt.hashSync(longPassword, bcrypt.genSaltSync(1)); + + User.create({ email: 'b@c.com', password: oldHash }, function(err) { + if (err) return done(err); + User.login({ email: 'b@c.com', password: longPassword }, function(err, accessToken) { + if (err) return done(err); + assert(accessToken.id); + // we are logged in, the test passed + done(); + }); + }); + }); }); function assertGoodToken(accessToken) {