How to use methods on schema in mongoose + express - node.js

I'm getting the following error when I try to run user.comparePassword from exports.index.post (see below) -- I pasted all code to help narrow down the problem. The UserSchema.pre('save') method works fine, but not the one in ./routes/account.js (I'm using mongoose 3)
Here is the error I get.
Caught exception: [TypeError: Object { username: 'test4',
email: 'test4#test.com',
password: '$2a$10$Ix5vCuVYGIU7AmXglmfIxOyYnF6CiPJfw9HLSAGcRDxMJEttud/F6',
_id: 505fee7ce28f10711e000002,
__v: 0 } has no method 'comparePassword']
## ./app.js
app.post('/account', routes.account.index.post);
## ./models/user.js
var mongoose = require('mongoose')
, bcrypt = require('bcrypt')
, Schema = mongoose.Schema
, db = mongoose.createConnection('localhost', 'mydb');
var UserSchema = new Schema({
username : { type: String, required: true, index: { unique: true }, trim: true }
, email : { type: String, required: true, index: { unique: true }, trim: true, lowercase: true }
, password : { type: String, required: true, trim: true }
});
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(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();
});
});
});
//compare supplied password
UserSchema.methods.comparePassword = function(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
if (err) return cb(err);
cb(null, isMatch);
});
};
module.exports = db.model('User', UserSchema);
##./routes/account.js
/*
* GET account home page.
*/
exports.index = {};
exports.index.get = function(req, res){
var d = { title: 'Edit account' };
res.render('account', { d: d } );
};
exports.index.post = function(req, res){
req.assert('email', 'Enter email').notEmpty().isEmail();
req.assert('password', 'Enter password').notEmpty().isAlphanumeric().len(5,20);
//user must confirm password
if ( req.body.password_new ) {
req.assert('password_new', 'Enter password').notEmpty().isAlphanumeric().len(5,20);
req.assert('password_new_confirm', 'Passwords must match').equals(req.body.password);
}
res.locals.err = req.validationErrors(true);
if ( res.locals.err ) {
var d = { title: 'Edit account' };
res.render('account', { d: d } );
return;
}
var User = require('../models/user')
, mongoose = require('mongoose')
, db = mongoose.createConnection('localhost', 'mydb');
var user = db.model('User', User.UserSchema);
user.find({username: req.session.user.username }, function(err, user){
if ( err ) return next(err);
/*********** THIS IS WHERE THE ERROR OCCURS **************/
user.comparePassword(req.body.password, function(err, isMatch) {
console.log("isMatch", isMatch);
if (err) next(err);
if (!isMatch) {
req.flash('error', 'Woops, looks like you mistyped your password.');
req.session.user = user;
res.locals.user = user;
res.redirect('/account');
return;
}
//user is authenticated
//session length
console.log(req.body);
});
});
};

user.find queries for 0 or more docs, so the second parameter to its callback is an array of docs, not a single doc. user.findOne queries for 0 or 1 docs, so the second parameter to its callback is either null or that single doc. So you're trying to call your schema's method on a JavaScript Array which of course won't work. Change that find call to a findOne and it should work.

Related

How to fix "User.findOne()" returning null for user

I'm currently working on a new project and I'm trying to get the login route working. But the login always fails because the User.findOne() method is always returning as null.
I've tried changing the export from the usermodel to
var User = mongoose.model('User', UserSchema, 'User');
But it hasn't change anything.
I know the connection to the database is fine because the register route works fine and saves correctly.
Login Route
router.post('/login', function (req, res) {
User.findOne({ username: req.body.username, password: req.body.password }, function (err, user) {
if (err || user == null) {
res.redirect('/login');
console.log(user);
} else {
req.session.userId = user._id;
res.redirect('/');
}
});
});
User Schema
var mongoose = require('mongoose');
var bcrypt = require('bcrypt');
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();
}
});
});
};
//hashing a password before saving it to the database
UserSchema.pre('save', function (next) {
var user = this;
bcrypt.hash(user.password, 10, function (err, hash) {
if (err) {
return next(err);
}
user.password = hash;
next();
});
});
var User = mongoose.model('users', UserSchema);
module.exports = User;
Database Connection
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/wowDb', { useNewUrlParser: true });
var db = mongoose.connection;
mongoose.set('useCreateIndex', true);
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function () { });
I used
db.users.findOne({username: "Imortalshard"});
and got the output
{
"_id" : ObjectId("5cc10cd13361880abc767d78"),
"email" : "admin#wowdb.com",
"username" : "Imortalshard",
"password" : "$2b$10$7Ln5yHFqzPw/Xz6bAW84SOVhw7.c0A1mve7Y00tTdaKzxzTph5IWS",
"__v" : 0
}
Console output from console.log(req.body) and console.log(user)
I'm waiting the user that i registered to be able to successfully login, but currently it is just redirecting me back to the login page and giving me a null reading for user in the console.

access the user._id in nodejs

I'm pretty new with node.js and I'm trying to implement simple user registration and login form using Node.js, Express, bcrypt, express-session and mongoose.
Whenever the user log in, I want to set the value of req.session.userID to user's id. When I trace the code I can't find the problem. I followed up the tutorial in this link and everything seems to be similar.
Schema:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var bcrypt = require('bcrypt');
var userSchema = new Schema({
teamName: {
type: String,
unique: true,
trim: true,
required: true
},
faculty: {
type: String,
required: true
},
email: {
required: true,
unique: true,
trim: true,
type: String
},
password: {
required: true,
type: String
},
score: {
type: Number,
default: 0
}
});
userSchema.pre('save', function(next) {
var user = this;
bcrypt.hash(user.password, 10, function(err, hash) {
if (err) return next(err)
user.password = hash;
next();
});
});
userSchema.statics.authenticate = (email, password, callback) => {
userModel.findOne({email: email}, (err, user) => {
if (err) return callback(err);
else if (!user) {
console.log('User not found!')
}
else {
bcrypt.compare(password, user.password, (err, result) => {
if (result) {
callback(null, true)
}
else {
return callback()
}
})
}
})
}
var userModel = mongoose.model('User', userSchema);
module.exports = userModel;
server:
var userModel = require('./../models/users');
router.post('/login', (req, res) => {
var email = req.body.email;
var password = req.body.password;
userModel.authenticate(email, password, (err, user) => {
console.log(user)
if (err) {
console.log(err)
}
else if (!user) {
console.log('Wrong Password')
}
else {
req.session.userId = user._id;
console.log(req.session.userId);
}
})
});
Where I have logged the value of req.session.userId it returns undefined! Where is the problem?
The problem is that the callback is returning TRUE. the callback should be returning the user data. callback(null, user)
bcrypt.compare(password, user.password, (err, result) => {
if (result) {
callback(null, true)
}
Should be
bcrypt.compare(password, user.password, (err, result) => {
if (result) {
callback(null, user)
}

Keep hashed password on update using NodeJs Mongo and Express

I am trying to build an application using MEAN. On register, everything works fine, user will be introduced into database with the fields password and verify hashed. But on update, the password and verify won't be hashed anymore and they will be added into database as a plain text. How can I resolve this? (I don't have the frontend code yet, I used Postman to send the request)
This is what I have by now:
model.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var bcrypt = require('bcrypt');
var schema = new Schema({
firstname: { type: String, required: true },
lastname: { type: String, required: true },
email: { type: String, required: true },
password: { type: String, required: true },
verify: { type: String, required: true },
});
schema.pre('save', function (next) {
var user = this;
bcrypt.hash(user.password, 10, function (err, hash) {
if (err) {
return next(err);
}
user.password = hash;
user.verify = hash;
next();
});
});
module.exports = mongoose.model('User', schema);
controller.js
var router = express.Router();
// register user
router.post('/register', function (req, res, next) {
addToDB(req, res);
});
async function addToDB(req, res) {
var user = new User({
firstname: req.body.firstname,
lastname: req.body.lastname,
email: req.body.email,
password: req.body.password,
verify: req.body.verify
});
try {
doc = await user.save();
return res.status(201).json(doc);
}
catch (err) {
return res.status(501).json(err);
}
}
// update user
router.put('/:id', function (req, res, next) {
User.findByIdAndUpdate(req.params.id, req.body, function (err, post) {
if (err) {
console.log('Error in user update: ' + JSON.stringify(err, undefined, 2));
return next(err);
}
res.json(post);
});
});
Update your Mongoose middleware to only hash the password if it has been modified (or is new) e.g.
schema.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(10, 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;
user.verify = hash
next();
});
});
});
Because findByIdAndUpdate is a wrapper around findOneAndUpdate, better to use save so that the pre save hook is invoked
var _ = require('lodash');
// update user
router.put('/:id', function (req, res, next) {
// fetch user
User.findById(req.params.id, function(err, post) {
if (err) return next(err);
_.assign(post, req.body); // update user
post.save(function(err) {
if (err) return next(err);
return res.json(200, post);
})
});
});

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.

Mongoose password hashing

I am looking for a good way to save an Account to MongoDB using mongoose.
My problem is: The password is hashed asynchronously. A setter wont work here because it only works synchronous.
I thought about 2 ways:
Create an instance of the model and save it in the callback of the
hash function.
Creating a pre hook on 'save'
Is there any good solution on this problem?
The mongodb blog has an excellent post detailing how to implement user authentication.
http://blog.mongodb.org/post/32866457221/password-authentication-with-mongoose-part-1
The following is copied directly from the link above:
User Model
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
bcrypt = require('bcrypt'),
SALT_WORK_FACTOR = 10;
var UserSchema = new Schema({
username: { type: String, required: true, index: { unique: true } },
password: { type: String, required: true }
});
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 using 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(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
if (err) return cb(err);
cb(null, isMatch);
});
};
module.exports = mongoose.model('User', UserSchema);
Usage
var mongoose = require(mongoose),
User = require('./user-model');
var connStr = 'mongodb://localhost:27017/mongoose-bcrypt-test';
mongoose.connect(connStr, function(err) {
if (err) throw err;
console.log('Successfully connected to MongoDB');
});
// create a user a new user
var testUser = new User({
username: 'jmar777',
password: 'Password123'
});
// save the user to database
testUser.save(function(err) {
if (err) throw err;
});
// fetch the user and test password verification
User.findOne({ username: 'jmar777' }, function(err, user) {
if (err) throw err;
// test a matching password
user.comparePassword('Password123', function(err, isMatch) {
if (err) throw err;
console.log('Password123:', isMatch); // -> Password123: true
});
// test a failing password
user.comparePassword('123Password', function(err, isMatch) {
if (err) throw err;
console.log('123Password:', isMatch); // -> 123Password: false
});
});
For those who are willing to use ES6+ syntax can use this -
const bcrypt = require('bcryptjs');
const mongoose = require('mongoose');
const { isEmail } = require('validator');
const { Schema } = mongoose;
const SALT_WORK_FACTOR = 10;
const schema = new Schema({
email: {
type: String,
required: true,
validate: [isEmail, 'invalid email'],
createIndexes: { unique: true },
},
password: { type: String, required: true },
});
schema.pre('save', async function save(next) {
if (!this.isModified('password')) return next();
try {
const salt = await bcrypt.genSalt(SALT_WORK_FACTOR);
this.password = await bcrypt.hash(this.password, salt);
return next();
} catch (err) {
return next(err);
}
});
schema.methods.validatePassword = async function validatePassword(data) {
return bcrypt.compare(data, this.password);
};
const Model = mongoose.model('User', schema);
module.exports = Model;
TL;DR - Typescript solution
I have arrived here when I was looking for the same solution but using typescript. So for anyone interested in TS solution to the above problem, here is an example of what I ended up using.
imports && contants:
import mongoose, { Document, Schema, HookNextFunction } from 'mongoose';
import bcrypt from 'bcryptjs';
const HASH_ROUNDS = 10;
simple user interface and schema definition:
export interface IUser extends Document {
name: string;
email: string;
password: string;
validatePassword(password: string): boolean;
}
const userSchema = new Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
});
user schema pre-save hook implementation
userSchema.pre('save', async function (next: HookNextFunction) {
// here we need to retype 'this' because by default it is
// of type Document from which the 'IUser' interface is inheriting
// but the Document does not know about our password property
const thisObj = this as IUser;
if (!this.isModified('password')) {
return next();
}
try {
const salt = await bcrypt.genSalt(HASH_ROUNDS);
thisObj.password = await bcrypt.hash(thisObj.password, salt);
return next();
} catch (e) {
return next(e);
}
});
password validation method
userSchema.methods.validatePassword = async function (pass: string) {
return bcrypt.compare(pass, this.password);
};
and the default export
export default mongoose.model<IUser>('User', userSchema);
note: don't forget to install type packages (#types/mongoose, #types/bcryptjs)
I think this is a good way by user Mongoose and bcrypt!
User Model
/**
* Module dependences
*/
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const bcrypt = require('bcrypt');
const SALT_WORK_FACTOR = 10;
// define User Schema
const UserSchema = new Schema({
username: {
type: String,
unique: true,
index: {
unique: true
}
},
hashed_password: {
type: String,
default: ''
}
});
// Virtuals
UserSchema
.virtual('password')
// set methods
.set(function (password) {
this._password = password;
});
UserSchema.pre("save", function (next) {
// store reference
const user = this;
if (user._password === undefined) {
return next();
}
bcrypt.genSalt(SALT_WORK_FACTOR, function (err, salt) {
if (err) console.log(err);
// hash the password using our new salt
bcrypt.hash(user._password, salt, function (err, hash) {
if (err) console.log(err);
user.hashed_password = hash;
next();
});
});
});
/**
* Methods
*/
UserSchema.methods = {
comparePassword: function(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
if (err) return cb(err);
cb(null, isMatch);
});
};
}
module.exports = mongoose.model('User', UserSchema);
Usage
signup: (req, res) => {
let newUser = new User({
username: req.body.username,
password: req.body.password
});
// save user
newUser.save((err, user) => {
if (err) throw err;
res.json(user);
});
}
Result
Result
The Mongoose official solution requires the model to be saved before using the verifyPass method, which can cause confusion. Would the following work for you? (I am using scrypt instead of bcrypt).
userSchema.virtual('pass').set(function(password) {
this._password = password;
});
userSchema.pre('save', function(next) {
if (this._password === undefined)
return next();
var pwBuf = new Buffer(this._password);
var params = scrypt.params(0.1);
scrypt.hash(pwBuf, params, function(err, hash) {
if (err)
return next(err);
this.pwHash = hash;
next();
});
});
userSchema.methods.verifyPass = function(password, cb) {
if (this._password !== undefined)
return cb(null, this._password === password);
var pwBuf = new Buffer(password);
scrypt.verify(this.pwHash, pwBuf, function(err, isMatch) {
return cb(null, !err && isMatch);
});
};
Another way to do this using virtuals and instance methods:
/**
* Virtuals
*/
schema.virtual('clean_password')
.set(function(clean_password) {
this._password = clean_password;
this.password = this.encryptPassword(clean_password);
})
.get(function() {
return this._password;
});
schema.methods = {
/**
* Authenticate - check if the passwords are the same
*
* #param {String} plainText
* #return {Boolean}
* #api public
*/
authenticate: function(plainPassword) {
return bcrypt.compareSync(plainPassword, this.password);
},
/**
* Encrypt password
*
* #param {String} password
* #return {String}
* #api public
*/
encryptPassword: function(password) {
if (!password)
return '';
return bcrypt.hashSync(password, 10);
}
};
Just save your model like, the virtual will do its job.
var user = {
username: "admin",
clean_password: "qwerty"
}
User.create(user, function(err,doc){});
const bcrypt = require('bcrypt');
const saltRounds = 5;
const salt = bcrypt.genSaltSync(saltRounds);
module.exports = (password) => {
return bcrypt.hashSync(password, salt);
}
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const hashPassword = require('../helpers/hashPassword')
const userSchema = new Schema({
name: String,
email: {
type: String,
match: [/^(([^<>()[\]\\.,;:\s#\"]+(\.[^<>()[\]\\.,;:\s#\"]+)*)|(\".+\"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, `Please fill valid email address`],
validate: {
validator: function() {
return new Promise((res, rej) =>{
User.findOne({email: this.email, _id: {$ne: this._id}})
.then(data => {
if(data) {
res(false)
} else {
res(true)
}
})
.catch(err => {
res(false)
})
})
}, message: 'Email Already Taken'
}
},
password: {
type: String,
required: [true, 'Password required']
}
});
userSchema.pre('save', function (next) {
if (this.password) {
this.password = hashPassword(this.password)
}
next()
})
const User = mongoose.model('User', userSchema)
module.exports = User
const mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');
SALT_WORK_FACTOR = 10;
const userDataModal = mongoose.Schema({
username: {
type: String,
required : true,
unique:true
},
password: {
type: String,
required : true
}
});
userDataModal.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 using our new salt
bcrypt.hash(user.password, salt, null, function(err, hash) {
if (err) return next(err);
// override the cleartext password with the hashed one
user.password = hash;
next();
});
});
});
userDataModal.methods.comparePassword = function(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
if (err) return cb(err);
cb(null, isMatch);
});
};
// Users.index({ emaiId: "emaiId", fname : "fname", lname: "lname" });
const userDatamodal = module.exports = mongoose.model("usertemplates" , userDataModal)
//inserting document
userDataModel.findOne({ username: reqData.username }).then(doc => {
console.log(doc)
if (doc == null) {
let userDataMode = new userDataModel(reqData);
// userDataMode.password = userDataMode.generateHash(reqData.password);
userDataMode.save({new:true}).then(data=>{
let obj={
success:true,
message: "New user registered successfully",
data:data
}
resolve(obj)
}).catch(err=>{
reject(err)
})
}
else {
resolve({
success: true,
docExists: true,
message: "already user registered",
data: doc
}
)
}
}).catch(err => {
console.log(err)
reject(err)
})
//retriving and checking
// test a matching password
user.comparePassword(requestData.password, function(err, isMatch) {
if (err){
reject({
'status': 'Error',
'data': err
});
throw err;
} else {
if(isMatch){
resolve({
'status': true,
'data': user,
'loginStatus' : "successfully Login"
});
console.log('Password123:', isMatch); // -> Password123: true
}
I guess it would be better to use the hook, after some research i found
http://mongoosejs.com/docs/middleware.html
where it says:
Use Cases:
asynchronous defaults
I prefer this solution because i can encapsulate this and ensure that an account can only be saved with a password.
I used .find({email}) instead of .findOne({email}).
Make sure to use .findOne(...) to get a user.
Example:
const user = await <user>.findOne({ email });

Resources