【问题标题】:Password Reset In NodeJSNodeJS中的密码重置
【发布时间】:2017-07-29 16:55:15
【问题描述】:

我已经设置了使用 NodeJS/Passport 更新用户密码。 我遵循了这个很棒的指南:http://sahatyalkabov.com/how-to-implement-password-reset-in-nodejs/

其中 99% 是有效的。我不得不对其进行一些修改以包含一些条带功能。恐怕我在某处有一个严重错误,但我找不到它。用户目前能够一路完成向他们发送电子邮件、输入新密码并登录的过程。随后的另一封电子邮件表明他们的密码已成功更新。都完美。然而。因为某些原因。未保存新密码。用户只能使用旧密码登录。我已经尝试了我能想到的一切来解决这个问题。

我已经让其他几个程序员研究过这个问题,但他们都无法弄清楚它到底是如何不工作的。

当前的想法是会话可能无法正确结束,但我们尝试销毁会话,但仍然无法正常工作。

非常感谢任何帮助。

完整设置:

用户模型:

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();
    });
  });
});

注册新帐户 (这也包含无关的条带信息,但可能会导致问题。)

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)

          });
          }
      });
});

设置忘记密码发布

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
  });
});

设置更改密码帖子

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('/');
  });
});

【问题讨论】:

  • 我试过你发布的代码没有条纹部分,它对我来说工作正常。当发生这么多事情时,要查明问题要困难得多。隔离电话并一次接一个电话可能会对您有更多帮助,我认为如果您可以提供您认为可能出错的区域,这将有助于有人接听电话。
  • 我不确定您是否已经找到解决问题的方法。有几个会话设置可能会导致您的配置中缺少此问题。如果你还没有,我认为它值得研究。考虑用app.use(session({ secret: 'session secret key', resave: false, saveUninitialized: false })); app.use(passport.initialize()); app.use(passport.session()); 替换app.use(session({ secret: 'session secret key' })); 并验证订单。你可以在这里查看更多关于这些标志的信息github.com/expressjs/session#options
  • 预存中间件中的所有行都执行了吗?那是我首先开始调试的地方。
  • 通常不建议告诉用户不存在具有指定电子邮件地址的用户 - 即使是这样。您应该只说“如果存在使用此电子邮件地址的用户,我们已发送密码重置链接”。没有理由向潜在的攻击者提供更多信息。他们以你的方式拥有它,你让他们能够准确地找出你的用户是谁。

标签: node.js mongodb express passwords passport.js


【解决方案1】:

我没有(或没有)发现您的代码有任何问题,但我有一个跟踪错误的建议。

这段代码有风险。您可能会不小心更新密码字段并触发重新哈希密码过程。

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();
      });
   });
});

if (!user.isModified('password')) 之后放置一个console.log 以检查意外的密码更新。现在重试忘记密码,看看是否有任何错误。

*TD;LR 将更新密码分离到新方法中,而不是将其放入预保存中,因为您可能会不小心将新密码与其他字段一起更新

*更新:感谢 #imns 提出更好的 SALT_FACTOR 号码。

【讨论】:

【解决方案2】:

我认为问题可能出在哈希函数中。尝试在我的计算机上将您的代码复制到一个更简单但类似的实验中。

正如 bcrypt 文档在这里所说的 https://www.npmjs.com/package/bcrypt#to-hash-a-password

散列函数只接受 3 个参数,您发送 4 个。而在您的情况下,第三个参数是 null

这里有一些代码来说明问题和希望的解决方案

加盐回调内部

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

但是将第三个参数改为回调函数。

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

【讨论】:

  • 感谢您试一试!我同意问题一定出在这一步的某个地方。我尝试实施您建议的更改,我得到的错误是:“没有给出回调函数。”
  • 奇怪!如 package.json 中所述,您使用哪个版本和包的 bcrypt。如果您创建一个新用户,密码是否会在第一次被散列?
  • @mertje OP 使用的是bcrypt-nodejs 而不是bcrypt
【解决方案3】:

我遇到了同样的问题,只是从这一行中删除了 null 参数:

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

【讨论】:

    【解决方案4】:

    如果您不想自己实现“忘记密码”流程,请考虑使用authentication-flows-js。 (当然是available on npm

    它是一个可以回答大多数流程的模块 - 身份验证、注册、忘记密码、更改密码等,并且它足够安全,因此应用程序可以使用它而不必担心它会被轻易入侵。

    Read the full article with more explanations here.

    【讨论】:

      【解决方案5】:

      我已经在我当前的项目中使用了这个代码,并且它工作正常,我在函数 UserSchema.pre('save', function(next) 中看到你的代码中有一个小错误。当您使用 bcrypt.hash 对密码进行哈希处理时,它需要四个参数,但我的代码中只有三个参数,例如

      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();
              });
          });
      });
      

      第三个参数必须是回调函数参见bcrypt的文档

      【讨论】:

      • 作者使用的不是bcrypt,而是bcrypt-nodejs。有关更多信息,请查看他提到的文章。
      猜你喜欢
      • 1970-01-01
      • 2017-07-29
      • 2018-11-12
      • 2022-01-26
      • 1970-01-01
      • 2021-03-23
      • 1970-01-01
      • 2016-10-28
      • 2017-12-30
      相关资源
      最近更新 更多