Reputation: 5914
I created added some password reset functionality to my sign up flow that allows users to change their password, but when a user resets their password, they are unable to login with the new password and I receive a consoled Server Error
. I'm wondering if this has to do with updating the user password outside of the passportjs logic or maybe because my password isn't hashed on the update?
Here is the console log:
GET /login 200 9.507 ms - 1793
GET /stylesheets/styles.css 304 1.093 ms - -
Database query triggered
Executing (default): SELECT `user_id`, `first_name` AS `firstName`, `last_name` AS `lastName`, `email`, `password`, `organization_id` AS `organizationId`, `reset_password_token` AS `resetPasswordToken`, `reset_password_expires` AS `resetPasswordExpires`, `createdAt`, `updatedAt` FROM `user` AS `user` WHERE `user`.`email` = '[email protected]' LIMIT 1;
Server Error
POST /login 302 16.169 ms - 56
GET /login 200 10.926 ms - 1862
Here is my password update route:
var express = require('express');
var siteRoutes = express.Router();
var path = require('path');
var async = require('async');
var crypto = require('crypto');
var nodemailer = require('nodemailer');
var sgTransport = require('nodemailer-sendgrid-transport');
var moment = require('moment');
var url = require('url');
var passport = require(path.resolve(__dirname, '..', '..','./config/passport.js'));
var models = require('../models/db-index');
/*==== /RESET ====*/
siteRoutes.route('/reset/:token')
.get(function(req, res){
var urlPath = url.parse(req.url).pathname.split('/');
var urlToken = urlPath[2];
console.log(urlToken);
models.User.findOne({
where: {
resetPasswordToken: req.params.token,
resetPasswordExpires: {
$gt: moment().format('YYYY-MM-DD HH:mm:ss')
}
}
}).then(function(){
res.render('pages/app/reset-password.hbs',{
urlToken: urlToken
});
})
})
.post(function(req, res){
async.waterfall([
function(done){
models.User.update({
password: req.body.password,
resetPasswordToken: null,
resetPasswordExpires: null
}, { where: {
resetPasswordToken: req.body.token,
resetPasswordExpires: {
$gt: moment().format('YYYY-MM-DD HH:mm:ss')
}
}})
// Nodemailer
var transporter = nodemailer.createTransport(sgTransport(options));
var mailOptions = {
from: '"Tester" <[email protected]',
to: '[email protected]', //Replace with Email
subject: 'Your password has been changed',
text: 'Hello,\n\n' +
'This is a confirmation that the password for your account ' + '[email protected]' + ' has just been changed.\n'
};
transporter.sendMail(mailOptions, function(error, info){
if(error){
return console.log(error + 'During Post');
}
console.log('Message sent: ' + info.response);
})
}
])
res.redirect('/login');
});
Here is my login routing:
/*==== Login ====*/
siteRoutes.route('/login')
.get(function(req, res){
res.render('pages/site/login.hbs',{
error: req.flash('error')
});
})
.post(passport.authenticate('local', {
successRedirect: '/app',
failureRedirect: '/login',
failureFlash: 'Invalid email or password.'
}));
Here is the user model:
var bcrypt = require('bcrypt-nodejs');
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define('user', {
user_id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
firstName: {
type: DataTypes.STRING,
field: 'first_name'
},
lastName: {
type: DataTypes.STRING,
field: 'last_name'
},
email: {
type: DataTypes.STRING,
isEmail: true,
unique: true,
set: function(val) {
this.setDataValue('email', val.toLowerCase());
}
},
password: DataTypes.STRING,
organizationId: {
type: DataTypes.INTEGER,
field: 'organization_id',
allowNull: true
},
resetPasswordToken: {
type: DataTypes.STRING,
field: 'reset_password_token'
},
resetPasswordExpires: {
type: DataTypes.DATE,
field: 'reset_password_expires'
}
}, {
freezeTableName: true,
classMethods: {
associate: function(db) {
User.belongsToMany(db.Organization, { through: 'member', foreignKey: 'organizationId'})
},
generateHash: function(password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
},
},
instanceMethods: {
validPassword: function(password) {
return bcrypt.compareSync(password, this.password);
},
},
});
return User;
}
Here is the passportjs logic:
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var models = require('../app/models/db-index');
/*==== Passport Configuration ====*/
// Serialize sessions
passport.serializeUser(function(user, done) {
console.log("User ID: " + user.user_id + " is serializing");
done(null, user.user_id);
});
passport.deserializeUser(function(user_id, done) {
models.User.find({where: {user_id: user_id}}).then(function(user){
console.log("User ID: " + user.user_id + " is deserializing");
done(null, user);
}).error(function(err){
done(err, null);
});
});
//Login logic
passport.use('local', new LocalStrategy({
passReqToCallback: true,
usernameField: 'email'
}, function(req, email, password, done) {
console.log("Database query triggered");
//Find user by email
models.User.findOne({
where: {
email: req.body.email
}
}).then(function(user) {
if (!user) {
done(null, false, { message: 'The email you entered is incorrect' }, console.log("Unknown User"));
} else if (!user.validPassword(password)){
done(null, false, console.log("Incorrect Password"));
} else {
console.log("User match");
done(null, user);
}
}).catch(function(err) {
console.log("Server Error");
return done(null, false);
});
}));
//Sign Up Logic
passport.use('local-signup', new LocalStrategy({
passReqToCallback: true,
usernameField: 'email'
}, function(req, email, password, done){
models.User.findOne({
where: {
email: email
}
}).then(function(existingUser){
if (existingUser)
return done(null, false, req.flash('error', 'Email already exists.'));
if (req.user) {
var user = req.user;
user.firstName = firstName;
user.lastName = lastName;
user.email = email;
user.password = models.User.generateHash(password);
user.save().catch(function(err){
throw err;
}).then(function(){
done(null, user, req.flash('error', 'All fields need to be filled in'));
});
} else {
var newUser = models.User.build({
firstName: req.body.firstName,
lastName: req.body.lastName,
email: req.body.email,
password: models.User.generateHash(password)
});
newUser.save().then(function(){
done(null, newUser);
}).catch(function(err){
done(null, false, console.log(err));
});
}
}).catch(function(e){
done(null, false, req.flash('error', 'All fields need to be filled in'),console.log(e.email + e.message));
})
}));
module.exports = passport;
Upvotes: 0
Views: 366
Reputation: 870
Looks like you forget to generate your password again.
.post(function(req, res){
async.waterfall([
function(done){
models.User.update({
password: models.User.generateHash(req.body.password)
Upvotes: 1