Code cleanup; add tests

This commit is contained in:
Hugh Willson
2017-10-17 07:47:55 -04:00
committed by Ben Newman
parent 60b378be5d
commit 053bd79a49
2 changed files with 61 additions and 12 deletions

View File

@@ -52,6 +52,10 @@ var hashPassword = function (password) {
return bcryptHash(password, Accounts._bcryptRounds);
};
// Extract the number of rounds used in the specified bcrypt hash.
const getRoundsFromBcryptHash =
hash => hash ? Number(hash.substring(4, 6)) : null;
// Check whether the provided password matches the bcrypt'ed password in
// the database user record. `password` can be a string (in which case
// it will be run through SHA256 before bcrypt) or an object with
@@ -63,21 +67,19 @@ Accounts._checkPassword = function (user, password) {
userId: user._id
};
password = getPasswordString(password);
const formattedPassword = getPasswordString(password);
const hash = user.services.password.bcrypt;
const hashRounds = getRoundsFromBcryptHash(hash);
if (! bcryptCompare(password, user.services.password.bcrypt)) {
if (! bcryptCompare(formattedPassword, hash)) {
result.error = handleError("Incorrect password", false);
} else if (
user.services.password.bcrypt &&
Accounts._bcryptRounds >
Number(user.services.password.bcrypt.substring(4, 6))
) {
// password checks out, but user bcrypt may need update
} else if (hash && Accounts._bcryptRounds != hashRounds) {
// The password checks out, but the user's bcrypt hash needs to be updated.
Meteor.defer(() => {
Meteor.users.update({ _id: user._id }, {
$set: {
'services.password.bcrypt':
bcryptHash(password, Accounts._bcryptRounds)
$set: {
'services.password.bcrypt':
bcryptHash(formattedPassword, Accounts._bcryptRounds)
}
});
});
@@ -92,7 +94,7 @@ var checkPassword = Accounts._checkPassword;
///
const handleError = (msg, throwError = true) => {
const error = new Meteor.Error(
403,
403,
Accounts._options.ambiguousErrorMessages
? "Something went wrong. Please check your credentials."
: msg

View File

@@ -1886,4 +1886,51 @@ if (Meteor.isServer) (function () {
]);
});
Tinytest.addAsync(
'passwords - allow custom bcrypt rounds',
function (test, done) {
function getUserHashRounds(user) {
return Number(user.services.password.bcrypt.substring(4, 6));
}
// Verify that a bcrypt hash generated for a new account uses the
// default number of rounds.
let username = Random.id();
const password = 'abc123';
const userId1 = Accounts.createUser({ username, password });
let user1 = Meteor.users.findOne(userId1);
let rounds = getUserHashRounds(user1);
test.equal(rounds, Accounts._bcryptRounds);
// When a custom number of bcrypt rounds is set via Accounts.config,
// and an account was already created using the default number of rounds,
// make sure that a new hash is created (and stored) using the new number
// of rounds, the next time the password is checked.
const defaultRounds = Accounts._bcryptRounds;
const customRounds = 11;
Accounts._bcryptRounds = customRounds;
Accounts._checkPassword(user1, password);
Meteor.setTimeout(() => {
user1 = Meteor.users.findOne(userId1);
rounds = getUserHashRounds(user1);
test.equal(rounds, customRounds);
Accounts._options.bcryptRounds = null;
// When a custom number of bcrypt rounds is set, make sure it's
// used for new bcrypt password hashes.
username = Random.id();
const userId2 = Accounts.createUser({ username, password });
const user2 = Meteor.users.findOne(userId2);
rounds = getUserHashRounds(user2);
test.equal(rounds, customRounds);
// Cleanup
Accounts._bcryptRounds = defaultRounds;
Meteor.users.remove(userId1);
Meteor.users.remove(userId2);
done();
}, 5000);
}
);
}) ();