web-dev-qa-db-ja.com

NodeJSでのパスワードリセット

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

私はあなたのコードに問題を見つけませんでした(または見つけていませんでした)が、バグを追跡するための提案があります。

このコードブロックは危険です。誤ってパスワードフィールドを更新し、パスワードの再ハッシュプロセスをトリガーする可能性があります。

_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他のフィールドと一緒に誤って新しいパスワードを更新する可能性があるため、更新パスワードを事前保存に入れるのではなく、新しいメソッドに分離します

*更新:より良いSALT_FACTOR番号を提案してくれてありがとう #imns .

8
Anh Cao

問題はハッシュ関数にあると思います。あなたのコードを私のコンピューター上でよりシンプルだが似たような実験に複製しようとしました。

Bcrypt docsがここに述べているように https://www.npmjs.com/package/bcrypt#to-hash-a-password

ハッシュ関数は3つの引数のみを受け取り、4を送信します。一方、ケースの3番目の引数はnullです。

この問題と、できれば解決策を説明するコードを次に示します。

塩漬けコールバック内

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

ただし、代わりに3番目の引数をコールバック関数に変更します。

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

私は同じ問題を抱えていましたが、この行からnullパラメータを削除するだけです:

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

1
user9348495

私はすでに現在のプロジェクトでこのコードを使用しており、正常に機能しています。関数UserSchema.pre('save', function(next)のコードに小さなエラーがあります。 bcrypt.hashでパスワードをハッシュすると、4つの引数を取りますが、私のコードには3つの引数しかありません。

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

3番目の引数はコールバック関数でなければなりません bcrypt のドキュメントを参照してください

0
Gaurav