8

Hi i want save with hashed password only if password is change, so i used isModified function in pre-save, but its always return false even i changed the password. The reason that i am trying to do this is because i dont want to change and save my password when i change other properties.

router.post('/changepw', isAuthenticated, function (req, res, next) {
    User.findOneAndUpdate({_id: req.user._id}, {$set: req.body},{ new: true }, function (err, user){

        if (err) {
          return err;
        } 
        else {

          if (req.body.password) {
            user.password = req.body.password;
            user.save();
          } else { 

          }

        }
        res.redirect('/profile');
    });
});

like here i dont want to change my password when i change my graduated value.

router.post('/edit', isAuthenticated, function (req, res, next) {
    User.findOneAndUpdate({
        _id: req.user._id
    }, {
        $set: {
            name: req.body.name,
            phone: req.body.phone,
            classc: req.body.classc,
            major: req.body.major,
            minor: req.body.minor,
            linkedin: req.body.linkedin,
            bio: req.body.bio
        }
    }, {
        new: true
    }, function (err, user, done) {

        if (err) {
            return err;
        } else {

            if (typeof req.body.graduated == 'undefined') {
                user.graduated = false;


            } else if (typeof req.body.graduated == 'string') {

                user.graduated = true;

            }

            user.save();
        }
        res.redirect('/profile');
    });
});
userSchema.pre('save', function(next) {
console.log(this.isModified('password'));                                                                                                                                        
    if(this.password && this.isModified('password')){                                                                                                                                                                                                                                                                                      
        this.password  = bcrypt.hashSync(this.password, bcrypt.genSaltSync(8),null);                                                                                                             
    }

    next()                                                                                                                                                                     
}); 

any suggestions?

Scott Kim
  • 233
  • 1
  • 4
  • 13

3 Answers3

10

note that when you're using findAndUpdate() method, the pre-save hook is not triggered. Check the Mongoose documentation by using new hooks: http://mongoosejs.com/docs/middleware.html#notes.

rphonika
  • 1,071
  • 10
  • 11
9

Try this

userSchema.pre('save', async function (next) {
  // Only run this function if password was moddified (not on other update functions)
  if (!this.isModified('password')) return next();
  // Hash password with strength of 12
  this.password = await bcrypt.hash(this.password, 12);
  //remove the confirm field 
  this.passwordConfirm = undefined;
});
Mohamed Daher
  • 609
  • 1
  • 10
  • 23
  • 2
    This is not going to be called during `User.findOneAndUpdate`. – Dirk Oct 20 '21 at 13:24
  • As per the docs, it does https://mongoosejs.com/docs/middleware.html#pre – Mohamed Daher Oct 20 '21 at 18:34
  • 1
    Definitely not. Further down the page you linked to there is a chapter "Notes on findAndUpdate() and Query Middleware". It says "Pre and post `save()` hooks are not executed on `update()`, `findOneAndUpdate()`, etc." Also, "You cannot access the document being updated in `pre('updateOne')` or `pre('findOneAndUpdate')` query middleware." The OP modifies `user` twice. Maybe a combination of `findById()` and `user.save()`would be better. I will add an answer with a suggestion. – Dirk Oct 22 '21 at 11:07
4

findOneAndUpdate()already updates user, so in the callback function you provided user is already up to date. When you call user.save() in that callback, the pre save() hook will be called, but isModified('password') will be false.

If you provide a new password with req.body, the password will land in the database unhashed, since "Pre and post save() hooks are not executed on update(), findOneAndUpdate(), etc." (see documentation). If you then compare the password in req.body and user, they will be the same and you cannot decide whether to trigger the hash function with a save() or not.

Leave your pre save() hook as is and do the update as a combination of findById() and save():

  User.findById(req.user._id, (err, user) => {
    if (err)
      handleYourErrorAndLeave(err)
  
    // Update all user attributes which are different or missing from user with values from req.body
    Object.assign(user, req.body)

    // Save the updated user object.
    // pre save() hook will be triggered and isModified('password') should be correct
    user.save()
      .then(savedUser => {
        res.redirect('/profile')
      }) 
  })
Dirk
  • 2,335
  • 24
  • 36