Search code examples
passport.jspassport-local

Trouble with password reset with passport-local-mongoose


I'm trying to add a forgot your password feature to my site. I want the typical flow: I want users to submit their email address, receive an email with a reset link, enter a new password which replaces their current password and logs them in. I have it almost working but I get this error when I try to log the user in after changing their password.

What am I missing?

This is the error I'm getting:

error null
user false
info { AuthenticationError
    at /Users/Tom/Heroku/airbnb-toolkit/node_modules/passport-local-mongoose/lib/authenticate.js:50:36
    at /Users/Tom/Heroku/airbnb-toolkit/node_modules/mongoose/lib/model.js:3431:16
    at /Users/Tom/Heroku/airbnb-toolkit/node_modules/mongoose/lib/services/model/applyHooks.js:144:20
    at _combinedTickCallback (internal/process/next_tick.js:73:7)
    at process._tickCallback (internal/process/next_tick.js:104:9)
  name: 'IncorrectPasswordError',
  message: 'signin.errors.incorrect-password' }

This is my node.js code:

app.post('/forgot', async function(req, res, next) {
    try {
        var buf = await crypto.randomBytes(20);
        var token = buf.toString('hex');
        var username = req.body.username;
        var user = await User.findOneAndUpdate(
            {username},
            {
                resetPasswordToken: token,
                resetPasswordExpires: moment().add(1, 'hour').toDate()
            }
        );
        if (!user) {
            handleError(res, 'No account with that email address exists.', 'No account with that email address exists.', 421);
        }
        sendEmail(username, "Superhost Tools Password Reset", "You are receiving this because you have requested the reset of the password for your account. Please click on the following link, or paste this into your browser to complete the process: http://example.com/#!/reset/" + token + "  If you did not request this, please ignore this email and your password will remain unchanged.");
        res.status(200).json("success");
    } catch(error) {
        console.error(error);
        handleError(res, error.message, "/forgot");
    }
});

app.post('/reset', async function(req, res, next) {
    try {
        var resetPasswordToken = req.body.resetPasswordToken;
        var username = req.body.username;
        var password = req.body.password;
        var user = await User.findOneAndUpdate({
            username,
            resetPasswordToken, 
            resetPasswordExpires: {$gt: Date.now()},
        }, {
            // resetPasswordToken: undefined,
            // resetPasswordExpires: undefined,
            password
        });
        passport.authenticate('local', function(error, user, info) {
            console.log("error", error);
            console.log("user", user);
            console.log("info", info);
            if (error) {
                return next(error);
            }
            if (!user) {
                return handleError(res, "There was a problem resetting your password.  Please try again.", {error_code: 401, error_message: "There was a problem resetting your password.  Please try again."}, 401);
            }
            req.logIn(user, function(error) {
                if (error) {
                    return next(error);
                }
                return res.redirect('/admin/#/');
            });
        })(req, res, next);
    } catch(error) {
        console.error(error);
        handleError(res, error.message, "/reset");
    }
});

I'm running:

"mongoose": "^4.8.4",
"express": "~3.3.4",
"passport": "^0.3.2",
"passport-local": "^1.0.0",
"passport-local-mongoose": "^4.1.0",

"node": "7.9.0"

Solution

  • For others that come across this issue. I ended up finding the fix on my own. I needed to use setPassword():

    app.post('/reset', async function(req, res, next) {
        try {
            var resetPasswordToken = req.body.resetPasswordToken;
            var username = req.body.username;
            var password = req.body.password;
            var user = await User.findOneAndUpdate({
                username,
                resetPasswordToken, 
                resetPasswordExpires: {$gt: Date.now()},
            }, {
                resetPasswordToken: undefined,
                resetPasswordExpires: undefined,
            });
            user.setPassword(password, (error, user) => {
                if (error) {
                    return next(error);
                }
                user.save((err, user) => {
                    if (error) {
                        return next(error);
                    }
                    passport.authenticate('local', function(error, user, info) {
                        console.log("error", error);
                        console.log("user", user);
                        console.log("info", info);
                        if (error) {
                            return next(error);
                        }
                        if (!user) {
                            return handleError(res, "There was a problem resetting your password.  Please try again.", {error_code: 401, error_message: "There was a problem resetting your password.  Please try again."}, 401);
                        }
                        req.logIn(user, function(error) {
                            if (error) {
                                return next(error);
                            }
                            return res.redirect('/admin/#/');
                        });
                    })(req, res, next);
                });
            });
        } catch(error) {
            console.error(error);
            handleError(res, error.message, "/reset");
        }
    });