Password Reset In NodeJS

走远了吗. 提交于 2019-12-31 10:39:21

问题


I have made a set up to update a user's password using NodeJS/Passport. I followed this great guide: http://sahatyalkabov.com/how-to-implement-password-reset-in-nodejs/.

99% of this is working. I had to modify it a bit to include some stripe functionality. I am afraid however I have a critical error somewhere and I cannot find it. The user is currently able to go all the way through the process of having an email sent to them, enter a new password, and be logged in. Another email follows saying their password was successfully updated. All perfect. However. For some reason. The new password is not being SAVED. the user can only sign in with their old password. I have tried everything I can think of to fix this.

I have had several other programmers look at this and none of them have been able to figure out how in the world its not working.

Current thought is that the session might not be ending correctly but we tried destroying the session and it still did not work.

Any help greatly appreciated.

Full set up:

User Model:

var UserSchema = new mongoose.Schema({
    username:   { type: String, required: true, unique: true },
    password:  String,
    datapoint:  String,
    email:  { type: String, required: true, unique: true },
    resetPasswordToken: String,
    resetPasswordExpires: Date
});


UserSchema.pre('save', function(next) {
  var user = this;
  var SALT_FACTOR = 5;

  if (!user.isModified('password')) return next();

  bcrypt.genSalt(SALT_FACTOR, function(err, salt) {
    if (err) return next(err);

    bcrypt.hash(user.password, salt, null, function(err, hash) {
      if (err) return next(err);
      user.password = hash;
      next();
    });
  });
});

Register New Account (This also has stripe info in it unrelated but could cause the problem.)

var newUser = new User({username: req.body.username, email: req.body.email, datapoint: req.body.datapoint});
      User.register(newUser, req.body.password, function(err, user){


            if(err){
            console.log('Looks like there was an error:' + ' ' + err)
             res.redirect('/login')

          } else {
          passport.authenticate("local")(req, res, function(){

      var user = new User({
      username: req.body.username,
      email: req.body.email,
      password: req.body.password

      })


console.log('creating new account')
console.log('prepping charge')
var token = req.body.stripeToken; // Using Express
var charge = stripe.charges.create({
  amount: 749,
  currency: "usd",
  description: "Example charge",
  source: token,

}, function(err, charge) {
  // asynchronously called
  console.log('charged')
});
            res.redirect('/jobquiz')
             console.log(req.body.datapoint)
             console.log(req.body.email)

          });
          }
      });
});

Set up forgot password posting

app.post('/forgot', function(req, res, next) {
  async.waterfall([
    function(done) {
      crypto.randomBytes(20, function(err, buf) {
        var token = buf.toString('hex');
        done(err, token);
      });
    },
    function(token, done) {
      User.findOne({ email: req.body.email }, function(err, user) {
        if (!user) {
        //   console.log('error', 'No account with that email address exists.');
        req.flash('error', 'No account with that email address exists.');
          return res.redirect('/forgot');
        }
console.log('step 1')
        user.resetPasswordToken = token;
        user.resetPasswordExpires = Date.now() + 3600000; // 1 hour

        user.save(function(err) {
          done(err, token, user);
        });
      });
    },
    function(token, user, done) {
        console.log('step 2')


      var smtpTrans = nodemailer.createTransport({
         service: 'Gmail', 
         auth: {
          user: 'myemail',
          pass: 'mypassword'
        }
      });
      var mailOptions = {

        to: user.email,
        from: 'myemail',
        subject: 'Node.js Password Reset',
        text: 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' +
          'Please click on the following link, or paste this into your browser to complete the process:\n\n' +
          'http://' + req.headers.host + '/reset/' + token + '\n\n' +
          'If you did not request this, please ignore this email and your password will remain unchanged.\n'

      };
      console.log('step 3')

        smtpTrans.sendMail(mailOptions, function(err) {
        req.flash('success', 'An e-mail has been sent to ' + user.email + ' with further instructions.');
        console.log('sent')
        res.redirect('/forgot');
});
}
  ], function(err) {
    console.log('this err' + ' ' + err)
    res.redirect('/');
  });
});

app.get('/forgot', function(req, res) {
  res.render('forgot', {
    User: req.user
  });
});

Set up changing password post

app.get('/reset/:token', function(req, res) {
  User.findOne({ resetPasswordToken: req.params.token, resetPasswordExpires: { $gt: Date.now() } }, function(err, user) {
      console.log(user);
    if (!user) {
      req.flash('error', 'Password reset token is invalid or has expired.');
      return res.redirect('/forgot');
    }
    res.render('reset', {
     User: req.user
    });
  });
});




app.post('/reset/:token', function(req, res) {
  async.waterfall([
    function(done) {
      User.findOne({ resetPasswordToken: req.params.token, resetPasswordExpires: { $gt: Date.now() } }, function(err, user, next) {
        if (!user) {
          req.flash('error', 'Password reset token is invalid or has expired.');
          return res.redirect('back');
        }


        user.password = req.body.password;
        user.resetPasswordToken = undefined;
        user.resetPasswordExpires = undefined;
        console.log('password' + user.password  + 'and the user is' + user)

user.save(function(err) {
  if (err) {
      console.log('here')
       return res.redirect('back');
  } else { 
      console.log('here2')
    req.logIn(user, function(err) {
      done(err, user);
    });

  }
        });
      });
    },





    function(user, done) {
        // console.log('got this far 4')
      var smtpTrans = nodemailer.createTransport({
        service: 'Gmail',
        auth: {
          user: 'myemail',
          pass: 'mypass'
        }
      });
      var mailOptions = {
        to: user.email,
        from: 'myemail',
        subject: 'Your password has been changed',
        text: 'Hello,\n\n' +
          ' - This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n'
      };
      smtpTrans.sendMail(mailOptions, function(err) {
        // req.flash('success', 'Success! Your password has been changed.');
        done(err);
      });
    }
  ], function(err) {
    res.redirect('/');
  });
});

回答1:


I didn't (or haven't) find any problem with your code, but I have a suggestion to trace the bug.

This block of code is risky. You may accidentally update the password field and trigger the rehash password process.

UserSchema.pre('save', function(next) {
   var user = this;
   var SALT_FACTOR = 12; // 12 or more for better security

   if (!user.isModified('password')) return next();

   console.log(user.password) // Check accident password update

   bcrypt.genSalt(SALT_FACTOR, function(err, salt) {
      if (err) return next(err);

      bcrypt.hash(user.password, salt, null, function(err, hash) {
         if (err) return next(err);
         user.password = hash;
         next();
      });
   });
});

Put a console.log right after the if (!user.isModified('password')) to check for unexpected password update. Now retry forget the password and see if any bug in there.

*TD;LR Separate update password into a new method instead of putting it in the pre-save since you may accidentally update a new password along with other fields

*Update: Thanks #imns for suggesting a better SALT_FACTOR number.




回答2:


I think the issue could be in the hash function. Tried duplicating you code into a simpler but similar experiment on my computer.

As the bcrypt docs state here https://www.npmjs.com/package/bcrypt#to-hash-a-password

The hash function only takes in 3 arguments, you send in 4. Whereas the third argument in your case is null.

Here is some code to illustrate the issue and the, hopefully, solution

Inside the salting callback

bcrypt.hash(user.password, salt, null, function(err, hash) {
  if (err) return next(err);
  user.password = hash;
  next();
});

But change the third argument to be the callback function instead.

bcrypt.hash(user.password, salt, function(err, hash) {
  if (err) return next(err);
  user.password = hash;
  next();
});



回答3:


I had the same issue just delete the null parameter from this line:

bcrypt.hash(user.password, salt, null, function(err, hash) {




回答4:


I already used this code in my current project, and its working fine, I saw a small error in your code in function UserSchema.pre('save', function(next). when you hash the password with bcrypt.hash then it took four arguments but there are only three argument in my code like

schema.pre('save', function(next) {
    var user = this;
    var SALT_FACTOR = 5;

    if(!user.isModified('password')){
        return next();
    }

    bcrypt.genSalt(SALT_FACTOR, function(err, salt) {
        if(err){
            return next(err);
        }
        bcrypt.hash(user.password, salt, function(err, hash) {
            if(err){
                return next(err);
            }
            user.password = hash;
            next();
        });
    });
});

Third argument must be callback function see document for bcrypt



来源:https://stackoverflow.com/questions/42682923/password-reset-in-nodejs

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!