diff --git a/lib/authenticate.js b/lib/authenticate.js index 12a0759..948b897 100644 --- a/lib/authenticate.js +++ b/lib/authenticate.js @@ -11,8 +11,13 @@ module.exports = function authenticate(user, password, options, cb) { if (Date.now() - user.get(options.lastLoginField) < calculatedInterval) { user.set(options.lastLoginField, Date.now()); - user.save(); - return cb(null, false, new errors.AttemptTooSoonError(options.errorMessages.AttemptTooSoonError)); + user.save(function(saveErr) { + if (saveErr) { + return cb(saveErr); + } + return cb(null, false, new errors.AttemptTooSoonError(options.errorMessages.AttemptTooSoonError)); + }); + return; } if (user.get(options.attemptsField) >= options.maxAttempts) { @@ -30,12 +35,18 @@ module.exports = function authenticate(user, password, options, cb) { } if (scmp(hashBuffer, new Buffer(user.get(options.hashField), options.encoding))) { - if (options.limitAttempts) { - user.set(options.lastLoginField, Date.now()); - user.set(options.attemptsField, 0); - user.save(); - } - return cb(null, user); + if (options.limitAttempts) { + user.set(options.lastLoginField, Date.now()); + user.set(options.attemptsField, 0); + user.save(function(saveErr, user) { + if (saveErr) { + return cb(saveErr); + } + return cb(null, user); + }); + } else { + return cb(null, user); + } } else { if (options.limitAttempts) { user.set(options.lastLoginField, Date.now()); diff --git a/test/passport-local-mongoose.js b/test/passport-local-mongoose.js index b440989..5444f2f 100644 --- a/test/passport-local-mongoose.js +++ b/test/passport-local-mongoose.js @@ -241,6 +241,10 @@ describe('passportLocalMongoose', function() { }); describe('#authenticate()', function() { + + beforeEach(mongotest.prepareDb('mongodb://localhost/passportlocalmongoosetests')); + afterEach(mongotest.disconnect()); + this.timeout(5000); // Five seconds - heavy crypto in background it('should yield false in case user cannot be authenticated', function(done) { @@ -264,6 +268,159 @@ describe('passportLocalMongoose', function() { done(); }); }); + + it('should supply message when limiting attempts and authenticating too soon', function(done) { + this.timeout(5000); // Five seconds - mongo db access needed + + var UserSchema = new Schema({}); + UserSchema.plugin(passportLocalMongoose, { + limitAttempts: true, + interval: 20000 + }); + var User = mongoose.model('LimitAttemptsTooSoonUser', UserSchema); + + var user = new User({ + username: 'mark', + attempts: 1, + last: Date.now() + }); + user.setPassword('password', function(err) { + expect(err).to.not.exist; + + user.save(function(err) { + expect(err).to.not.exist; + + user.authenticate('password', function(err, user, message) { + expect(err).to.not.exist; + expect(user).to.be.false; + expect(message).to.be.instanceof(errors.AttemptTooSoonError); + done(); + }); + }); + }); + }); + + it('should get an error updating when limiting attempts and authenticating too soon', function(done) { + this.timeout(5000); // Five seconds - mongo db access needed + + var UserSchema = new Schema({}, {saveErrorIfNotFound: true}); + UserSchema.plugin(passportLocalMongoose, { + limitAttempts: true, + interval: 20000 + }); + var User = mongoose.model('LimitAttemptsTooSoonUpdateWithError', UserSchema); + + var user = new User({ + username: 'jimmy', + attempts: 1, + last: Date.now() + }); + user.setPassword('password', function(err) { + expect(err).to.not.exist; + + user.save(function(err) { + expect(err).to.not.exist; + + user._id = id = mongoose.Types.ObjectId(); + user.authenticate('password', function(err, user, message) { + expect(err).to.exist; + expect(user).to.not.exist; + expect(message).to.not.exist; + done(); + }); + }); + }); + }); + + it('should get an error updating the user on password match when limiting attempts', function(done) { + this.timeout(5000); // Five seconds - mongo db access needed + + var UserSchema = new Schema({}, {saveErrorIfNotFound: true}); + UserSchema.plugin(passportLocalMongoose, { + limitAttempts: true + }); + var User = mongoose.model('LimitAttemptsUpdateWithError', UserSchema); + + var user = new User({ + username: 'jane' + }); + user.setPassword('password', function(err) { + expect(err).to.not.exist; + + user.save(function(err) { + expect(err).to.not.exist; + + user._id = id = mongoose.Types.ObjectId(); + user.authenticate('password', function(err, user, message) { + expect(err).to.exist; + expect(user).to.not.exist; + expect(message).to.not.exist; + done(); + }); + }); + }); + }); + + it('should update the user on password match while limiting attempts', function(done) { + this.timeout(5000); // Five seconds - mongo db access needed + + var UserSchema = new Schema({}); + UserSchema.plugin(passportLocalMongoose, { + limitAttempts: true + }); + var User = mongoose.model('LimitAttemptsUpdateWithoutError', UserSchema); + + var user = new User({ + username: 'walter' + }); + user.setPassword('password', function(err) { + expect(err).to.not.exist; + + user.save(function(err) { + expect(err).to.not.exist; + + user.authenticate('password', function(err, result, message) { + expect(err).to.not.exist; + expect(result).to.exist; + expect(result.username).to.equal(user.username); + expect(message).to.not.exist; + done(); + }); + }); + }); + }); + + it('should fail to update the user on password mismatch while limiting attempts', function(done) { + this.timeout(5000); // Five seconds - mongo db access needed + + var UserSchema = new Schema({}, {saveErrorIfNotFound: true}); + UserSchema.plugin(passportLocalMongoose, { + limitAttempts: true, + interval: 20000 + }); + var User = mongoose.model('LimitAttemptsMismatchWithAnError', UserSchema); + + var user = new User({ + username: 'wendy' + }); + user.setPassword('password', function(err) { + expect(err).to.not.exist; + + user.save(function(err) { + expect(err).to.not.exist; + + user.hash = 'deadbeef'; // force an error on scmp with different length hex. + user._id = id = mongoose.Types.ObjectId(); // force the save to fail + user.authenticate('password', function(err, user, message) { + expect(err).to.exist; + expect(user).to.not.exist; + expect(message).to.not.exist; + done(); + }); + }); + }); + }); + }); describe('static #authenticate()', function() {