Mongoose bcryptjs compare password doesn't refer to the document (this) - node.js

I have mongoose schema like so
var mongoose = require ('mongoose');
var bcrypt = require('bcryptjs');
var Schema = mongoose.Schema;
var SALT_WORK_FACTOR = 10;
var touristSchema = new Schema ({
local: {
email: String,
password: String
},
facebook: {
id: String,
token: String,
email: String,
name: String,
}
});
touristSchema.pre('save', function(next) {
var user = this;
console.log('bcrypt called by strategy', user);
// if user is facebook user skip the pasword thing.
if (user.facebook.token) {
next();
}
// only hash the password if it has been modified (or is new)
if (!user.isModified('password') && !user.isNew){
console.log('I am in here', user.isNew);
return next();
}
// generate a salt
bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
console.log('I am in genSalt');
if (err) return next(err);
// hash the password using our new salt
bcrypt.hash(user.local.password, salt, function(err, hash) {
if (err) return next(err);
// override the cleartext password with the hashed one
user.local.password = hash;
next();
});
});
});
touristSchema.methods.comparePassword = function(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.local.password, function(err, isMatch) {
// console.log(this.local.password);
if (err) return cb(err);
cb(null, isMatch);
});
};
module.exports = mongoose.model('users', touristSchema);
and login strategy like this
passport.use('local-login', new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
},
function(req, email, password, done) {
process.nextTick(function() {
User.findOne({ 'local.email': email }, function(err, user) {
if(err)
return done(err);
if(!user)
return done(null, false, req.flash('loginMessage', 'No User Found'));
user.comparePassword(password, function(err, isMatch) {
if (err) throw err;
if (isMatch) {
done(null, user);
}
else
done(null, false, req.flash('loginMessage', 'Incorrect password'));
});
});
});
}
));
everything is working as expected but when I try to log in it gives me error:
TypeError: Cannot read property 'password' of undefined.
Turns out that this.local is undefined.
This is very strange as in touristSchema.pre hook we assigned var user = this and on logging this returned user document. When in compare method we are using touristSchema.methods.compare then this should must refer to the document as written in mongoose middleware docs too. I am beating my head over it for a couple of days now and have consumed every available help I could. Any help is greatly appreciated in advance. Thanks
and yes my
mongnodb version is v3.2.15
mongoose version is 4.9.7
which looks compatible to me according to mongoose docs

Related

How can I add the feature of 'Change Password' in nodejs with passport authentication and passport-local package?

This is the authentication passport code I have put it in passport.js:
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
passport.serializeUser(function (user, done) {
done(null, user._id);
});
passport.deserializeUser(function (id, done) {
User.findOne({_id: id}, function (err, user) {
done(err, user);
})
});
passport.use(new LocalStrategy({
usernameField: 'email'
},
function (username, password, done) {
User.findOne({email: username}, function (err, user) {
if (err) return done(err);
if (!user) {
return done(null, false, {
message: 'Incorrect username or password'
});
}
if (!user.validPassword(password)) {
return done(null, false, {
message: 'Incorrect username or password'
});
}
return done(null, user);
})
}
));
And this is the Schema code I am storing password in the form of salt and hash in mongoDB using mongoose queries:
var mongoose = require('mongoose');
var crypto = require('crypto');
var userSchema = new mongoose.Schema({
email: {
type: String,
unique: true,
required: true
},
name: {
type: String,
required: true
},
hash: String,
salt: String
});
userSchema.methods.setPassword = function(password) {
this.salt = crypto.randomBytes(16).toString('hex');
this.hash = crypto.pbkdf2Sync(password, this.salt, 1000, 64, 'sha1').toString('hex');
};
userSchema.methods.validPassword = function(password) {
var hash = crypto.pbkdf2Sync(password, this.salt, 1000, 64, 'sha1').toString('hex');
return this.hash === hash;
};
module.exports = mongoose.model('User', userSchema);
This is the auth.js code for Login route:
var express = require('express');
var router = express.Router();
var passport = require('passport');
var mkdirp = require('mkdirp');
var nodemailer = require('nodemailer');
var config = require('../config');
var transporter = nodemailer.createTransport(config.mailer);
router.route('/login')
.get(function(req, res, next) {
res.render('login', { title: 'Login your account'});
})
.post(passport.authenticate('local', {
failureRedirect: '/login'
}), function (req, res) {
res.redirect('/profile');
})
And below is the password change route I am trying to execute which is possibly not working. What I think is I need to update the hash and salt value into the database while user changes password successfully which I am unable to figure out how can I do it. Please help!!!!!!!!!!!!!!!
router.post('/api/changePassword', function(req,res,next){
req.checkBody('oldPass', 'Empty Password').notEmpty();
req.checkBody('newPass', 'Password do not match').equals(req.body.confirmPass).notEmpty();
var errors = req.validationErrors();
if (errors) {
console.log("Errors hain bhai");
console.log(errors);
res.render('settingsClient', {
oldPass: req.body.oldPass,
newPass: req.body.newPass,
confirmPass:req.body.confirmPass,
errorMessages: errors
});
}
else {
User.findOne({id: req.user.id}, function (err, data) {
console.log("came inside api changePassword else condition inside User.findOne");
if (err) {
console.log(err);
}
else {
data.setPassword(req.body.newPass, function(err,datas){
if(datas) {
data.save(function (err,datass) {
if (err) {
res.render('settingsClient', {errorMessages: err});
} else {
console.log("Hash and Salt saved");
}
});
}
else {
console.log("setPassword error"+ err);
}
});
}
})
}
})
Its not working/not updating the password values into the database. What could be the possible reason and mistake I might be doing?
Yeah! So I removed the callback funtion from the setPassword and tried with the promises way/route to solve this query:
Here I am posting the solution which is working fine now.
User.findOne(userObj).then(function(sanitizedUser) {
if (sanitizedUser) {
console.log("sanitizedUser.hash = "+ sanitizedUser.hash);
sanitizedUser.setPassword(req.body.newPass);
console.log("Password going to be changed successfully now")
sanitizedUser.save(function (err,data) {
if (err) {
res.render('register', {errorMessages: err});
} else {
console.log("Password changed successfully");
res.send('Password reset successful');
}
});
} else {
res.send('user does not exist');
}
}, function(err) {
console.error(err);
});

How to fix "TypeError: cb is not a function" error for comparing passwords

I've been starting to add user authentication into my app and when adding the login route, i've been getting the error "TypeError: cb is not a function". I know it is coming from my login route as all my other routes work fine.
I have tried researching the issue and trying a few fixes that i've found but none have worked. So i'm starting to believe i've messed up somewhere and I can't find where.
Login Route:
router.post('/login', function (req, res) {
User.findOne({ username: req.body.username }, function (err, user) {
if (err || user == null) {
console.log(err);
res.redirect('/login');
}
if (!user.comparePassword(req.body.password)) {
req.flash('invalidDetails', 'Wrong username or password!');
res.redirect('/login');
} else {
req.session.userId = user._id;
req.flash('loggedIn', 'User ' + req.body.username + ' has been logged in!');
return res.redirect('/');
}
});
});
User Model:
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
bcrypt = require('bcrypt'),
SALT_WORK_FACTOR = 10;
var UserSchema = new mongoose.Schema({
email: {
type: String,
unique: true,
required: true,
trim: true
},
username: {
type: String,
unique: true,
required: true,
trim: true
},
password: {
type: String,
required: true
}
});
UserSchema.statics.authenticate = function (email, password, callback) {
User.findOne({ email: email }).exec(function (err, user) {
if (err) {
return callback(err);
} else if (!user) {
var err = new Error('User not found.');
err.status = 401;
return callback(err);
}
bcrypt.compare(password, hash, function (err, result) {
if (result === true) {
return callback(null, user);
} else {
return callback();
}
});
});
};
UserSchema.pre('save', function (next) {
var user = this;
// only hash the password if it has been modified (or is new)
if (!user.isModified('password')) return next();
// generate a salt
bcrypt.genSalt(SALT_WORK_FACTOR, function (err, salt) {
if (err) return next(err);
// hash the password along with our new salt
bcrypt.hash(user.password, salt, function (err, hash) {
if (err) return next(err);
// override the cleartext password with the hashed one
user.password = hash;
next();
});
});
});
UserSchema.methods.comparePassword = function comparePassword(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, function (err, isMatch) {
if (err) {
return cb(err);
}
cb(null, isMatch);
});
};
var User = mongoose.model('User', UserSchema);
module.exports = User;
I'm expecting it to compare the password with the password that has been entered into the login form with the password that is stored in the database and log in the user if the password is correct or redirect back to the login page with the invalidDetails flash message if the password is incorrect.
But what i'm actually getting is the "TypeError: cb is not a function" error when trying to login.
You are not passing a callback that's why.
If you want a promise based method you can write something like this
AuthSchema.methods.comparePassword = function(candidatePassword) {
const currentPassword = this.password;
return new Promise((resolve, reject) => {
bcrypt.compare(candidatePassword, currentPassword, function(err, isMatch) {
if (err) return reject(err);
resolve(isMatch);
});
})
};
And now you can call that with await

pre function not called in mongoose

I am trying to encrypt password on registration using mongoose and mongodb but pre function is not called at all.
var mongoose = require('mongoose');
var Schema = mongoose.Schema,
bcrypt = require('bcrypt'),
SALT_WORK_FACTOR = 10;
var patientSchema = new Schema({
username: {type: String, trim: true, index: { unique: true }},
password: {type: String, required: true}
});
//====================== Middleware:Start==========================//
patientSchema.pre('save', function(next) {
console.log('pre called'); //This is not printed at all
var user = this;
// only hash the password if it has been modified (or is new)
if (!user.isModified('password')) return next();
// generate a salt
bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
if (err) return next(err);
// hash the password along with our new salt
bcrypt.hash(user.password, salt, function(err, hash) {
if (err) return next(err);
// override the cleartext password with the hashed one
user.password = hash;
next();
});
});
});
//======================Middleware:End===========================//
//======================API Routes:Start===========================//
router.route('/signup')
.post(function (req, res) {
console.log('post signup called', req.body);
var patients = new Patients({
username: req.body.username,
password: req.body.password
});
Patients.findOne({username: req.body.username}, function (err, user) {
if (err) {
console.log('user not found');
}
if (user) {
console.log("patient already exists");
res.json({message: 'patient already exists'});
} else {
//Saving the model instance to the DB
patients.save(function (err) {
if (err)
throw err;
console.log("user Saved Successfully");
res.json({message: 'user Saved Successfully'});
});
}
});
});
module.exports = router;
//======================API Routes:End===========================//
Inside the pre function, console.log('pre called'); is not printed at all. What am I missing here?
it might be solve your error.
const patients = new Patients({
username: req.body.username,
password: req.body.password
})
if(!patients) return res.json(patients)
patients.save((err,patients) => {
if(err) return res.json({status: 500, message: err})
return res.json({status: 200, user: patients})
})
Thank you.

User Authentication on MEAN stack application

I am building an API using the MEAN stack and am working on the login and signup functions.
And I want to respond with a json string as follows
{
success: 0,
message: ""
}
success:1 for successful login and 0 otherwise.
My authenticate.js is as follows
module.exports = function(passport){
//log in
router.post('/login', passport.authenticate('login', {
//success
//failure
}));
//sign up
router.post('/signup', passport.authenticate('signup', {
//success
//failure
}));
//log out
router.get('/signout', function(req, res) {
req.logout();
res.redirect('/');
});
return router;
}
My passport.init.js middleware is as follows
var mongoose = require('mongoose');
var User = mongoose.model('User');
var LocalStrategy = require('passport-local').Strategy;
var bCrypt = require('bcrypt-nodejs');
module.exports = function(passport){
// Passport needs to be able to serialize and deserialize users to support persistent login sessions
passport.serializeUser(function(user, done) {
console.log('serializing user:',user.username);
done(null, user._id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
console.log('deserializing user:',user.username);
done(err, user);
});
});
passport.use('login', new LocalStrategy({
passReqToCallback : true
},
function(req, username, password, done) {
// check in mongo if a user with username exists or not
User.findOne({ 'username' : username },
function(err, user) {
// In case of any error, return using the done method
if (err)
return done(err);
// Username does not exist, log the error and redirect back
if (!user){
console.log('User Not Found with username '+username);
return done(null, false);
}
// User exists but wrong password, log the error
if (!isValidPassword(user, password)){
console.log('Invalid Password');
return done(null, false); // redirect back to login page
}
// User and password both match, return user from done method
// which will be treated like success
return done(null, user);
}
);
}
));
passport.use('signup', new LocalStrategy({
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, username, password, done) {
// find a user in mongo with provided username
User.findOne({ 'username' : username }, function(err, user) {
// In case of any error, return using the done method
if (err){
console.log('Error in SignUp: '+err);
return done(err);
}
// already exists
if (user) {
console.log('User already exists with username: '+username);
return done(null, false);
} else {
// if there is no user, create the user
var newUser = new User();
// set the user's local credentials
newUser.username = username;
newUser.password = createHash(password);
// save the user
newUser.save(function(err) {
if (err){
console.log('Error in Saving user: '+err);
throw err;
}
console.log(newUser.username + ' Registration succesful');
return done(null, newUser);
});
}
});
})
);
var isValidPassword = function(user, password){
return bCrypt.compareSync(password, user.password);
};
// Generates hash using bCrypt
var createHash = function(password){
return bCrypt.hashSync(password, bCrypt.genSaltSync(10), null);
};
};
Please help me out in passing the JSON string accordingly
Using Express, all you have to do is to execute res.json inside any controller, passing it any JavaScript object. Express will automatically convert it to JSON and return it to the user.
return res.json({ success: 0, message: '' }

Unable to authenticate using email with passport-local nodejs..tried several examples

I have been trying to use email and password to authenticate using passport-local. I had similar code when I was using username and it worked fine. With email, I made some changes however nothing is working. Right at the endpoint '/login' of type 'post' the condition !user condition in users.js (shown below as 2nd code snippet) is somehow executing. Its not even going inside passport.use. Following is the code:-
In user.js(model file),
var mongoose=require('mongoose');
var bcrypt=require('bcryptjs');
//user schema
var UserSchema=mongoose.Schema({
phone: {
type:String,
},
email:{
type: String,
index:{unique:true}
},
password:{
type: String
},
firstname:{
type: String
},
lastname:{
type: String
}
});
var User=module.exports = mongoose.model('User',UserSchema);
module.exports.getUserByUsername=function(email,callback){
var query={email:email};
User.findOne(query, callback);
}
module.exports.comparePassword=function(candidatePassword, hash, callback){
bcrypt.compare(candidatePassword, hash, function(err, isMatch) {
callback(null,isMatch);
});
}
}
In users.js(where i specify routes):
var express = require('express');
var router = express.Router();
var bodyParser=require('body-parser');
var User=require('../models/user');
var passport=require('passport');
var localStrategy=require('passport-local').Strategy;
router.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) {
return next(err);
}
if (!user) { /*this is where the problem is this code executes everytime*/
return res.send('User not found');
}
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.json(user);
});
})(req, res, next);
});
passport.serializeUser(function(user, done) {
done(null, user.id);
});
//for sessions
passport.deserializeUser(function(id, done) {
User.getUserById(id, function(err, user) {
done(err, user);
});
});
//this doesnt seem to work
passport.use(new localStrategy({usernameField:'email', passwordField:'password'},function(email,password,done){
User.getUserByUsername(email, function(err,user){
if(err) throw err;
if(!user){
return done(null,false,{message: 'User not found'});
}
User.comparePassword(password, user.password, function(err, isMatch){
if(err) return done(err);
if(isMatch){
return done(null, user);
}
else{
return done(null,false,{message: 'Password doesnt match our records'});
}
});
});
}));
Note that there is no front end on this. I am just using postman to test my apis.
This code works fine with emails. The issue was I also have another file called admin.js and admins.js which do the same task as user and users. However, admin makes use of username. I had the same passport.use code for admin however, users was trying to access that file instead of the current one. Once I removed the admin code it all worked.

Resources