Solvedmongoose Need to check if password field has been modified in pre findOneAndUpdate

I ran into a problem last night and still haven't been able to figure out a solution so hopefully someone can offer some advice. I am allowing users to update their username and password, and whenever the password field has been modified I need to hash it before storing it in mongo.

So far I had been using this code to hash the user's password:

UserSchema.pre('save', function(next) {
    if (this.isModified('password')) // If the pw has been modified, then encrypt it again
    this.password = this.encryptPassword(this.password);

    next();
});

The encryptPassword function is a custom one I added to my User Schema:

// Add custom methods to our User schema
UserSchema.methods = {
    // Hash the password
    encryptPassword: function(plainTextPassword) {
        if (!plainTextPassword) {
            return ''
        } else {
            var salt = bcrypt.genSaltSync(10);
            return bcrypt.hashSync(plainTextPassword, salt);
        }
    }
};

This only works though when the user first signs up and I create a new user instance and then save it. I need the same functionality when updating the user as well.

I was doing research in past issues here and came across this one: pre, post middleware are not executed on findByIdAndUpdate, and came across a comment where another dev was trying to do something very similar to what I am taking about: #964 (comment)

@vkarpov15 responded to his question with the following code suggestion:

TodoSchema.pre('findOneAndUpdate', function() {
  this.findOneAndUpdate({}, { password: hashPassword(this.getUpdate().$set.password) });
});

The only problem is, that this will cause us to rehash the password every time you update a user, which is wrong. You should only be hashing the password when the user initially signs up, and whenever they send a request up to change their password. If you run the above code every single time the user is updated, you will just end up rehashing the old hash, so when the user enters in their password next time on the client, it wont match with the hash stored in mongo anymore...

Is there any way I can use something like this.isModified('password') inside of the .pre('findOneAndUpdate', so that I only hash the password when the user has updated it?

At this point I'm open to any and all suggestions thanks.

25 Answers

✔️Accepted Answer

I'm currently using this solution and it works. You don't need to execute another query.

schema.pre("update", function(next) {
            const password = this.getUpdate().$set.password;
            if (!password) {
                return next();
            }
            try {
                const salt = Bcrypt.genSaltSync();
                const hash = Bcrypt.hashSync(password, salt);
                this.getUpdate().$set.password = hash;
                next();
            } catch (error) {
                return next(error);
            }
        });

Other Answers:

@NicolasBlois that works. There's a potential race condition there but it'll work for most cases.