Merge pull request #2580 from strongloop/bcrypt

Add bcrypt validation
This commit is contained in:
Loay 2016-08-13 00:37:05 -04:00 committed by GitHub
commit 21bdb28d37
3 changed files with 97 additions and 6 deletions

View File

@ -14,7 +14,7 @@ var utils = require('../../lib/utils');
var path = require('path'); var path = require('path');
var SALT_WORK_FACTOR = 10; var SALT_WORK_FACTOR = 10;
var crypto = require('crypto'); var crypto = require('crypto');
var MAX_PASSWORD_LENGTH = 72;
var bcrypt; var bcrypt;
try { try {
// Try the native module first // Try the native module first
@ -548,7 +548,6 @@ module.exports = function(User) {
cb = cb || utils.createPromiseCallback(); cb = cb || utils.createPromiseCallback();
var UserModel = this; var UserModel = this;
var ttl = UserModel.settings.resetPasswordTokenTTL || DEFAULT_RESET_PW_TTL; var ttl = UserModel.settings.resetPasswordTokenTTL || DEFAULT_RESET_PW_TTL;
options = options || {}; options = options || {};
if (typeof options.email !== 'string') { if (typeof options.email !== 'string') {
var err = new Error(g.f('Email is required')); var err = new Error(g.f('Email is required'));
@ -558,6 +557,13 @@ module.exports = function(User) {
return cb.promise; 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) { UserModel.findOne({ where: { email: options.email }}, function(err, user) {
if (err) { if (err) {
return cb(err); return cb(err);
@ -596,14 +602,20 @@ module.exports = function(User) {
}; };
User.validatePassword = function(plain) { User.validatePassword = function(plain) {
if (typeof plain === 'string' && plain) { var err;
if (plain && typeof plain === 'string' && plain.length <= MAX_PASSWORD_LENGTH) {
return true; 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; err.statusCode = 422;
throw err; throw err;
}; };
/*! /*!
* Setup an extended user model. * Setup an extended user model.
*/ */

View File

@ -4,7 +4,7 @@
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
var SG = require('strong-globalize'); var SG = require('strong-globalize');
SG.SetRootDir(__dirname, {autonomousMsgLoading: 'all'}); SG.SetRootDir(__dirname, { autonomousMsgLoading: 'all' });
/** /**
* loopback ~ public api * loopback ~ public api

View File

@ -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() { 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) { it('Should not throw an error if the query does not contain {where: }', function(done) {
User.find({}, function(err) { User.find({}, function(err) {
@ -678,6 +740,23 @@ describe('User', function() {
done(); 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) { function assertGoodToken(accessToken) {