isModified and pre-save mongoose...Nodejs - node.js

Hi i want save with hashed password only if password is change, so i used isModified function in pre-save, but its always return false even i changed the password. The reason that i am trying to do this is because i dont want to change and save my password when i change other properties.
router.post('/changepw', isAuthenticated, function (req, res, next) {
User.findOneAndUpdate({_id: req.user._id}, {$set: req.body},{ new: true }, function (err, user){
if (err) {
return err;
}
else {
if (req.body.password) {
user.password = req.body.password;
user.save();
} else {
}
}
res.redirect('/profile');
});
});
like here i dont want to change my password when i change my graduated value.
router.post('/edit', isAuthenticated, function (req, res, next) {
User.findOneAndUpdate({
_id: req.user._id
}, {
$set: {
name: req.body.name,
phone: req.body.phone,
classc: req.body.classc,
major: req.body.major,
minor: req.body.minor,
linkedin: req.body.linkedin,
bio: req.body.bio
}
}, {
new: true
}, function (err, user, done) {
if (err) {
return err;
} else {
if (typeof req.body.graduated == 'undefined') {
user.graduated = false;
} else if (typeof req.body.graduated == 'string') {
user.graduated = true;
}
user.save();
}
res.redirect('/profile');
});
});
userSchema.pre('save', function(next) {
console.log(this.isModified('password'));
if(this.password && this.isModified('password')){
this.password = bcrypt.hashSync(this.password, bcrypt.genSaltSync(8),null);
}
next()
});
any suggestions?

note that when you're using findAndUpdate() method, the pre-save hook is not triggered. Check the Mongoose documentation by using new hooks: http://mongoosejs.com/docs/middleware.html#notes.

Try this
userSchema.pre('save', async function (next) {
// Only run this function if password was moddified (not on other update functions)
if (!this.isModified('password')) return next();
// Hash password with strength of 12
this.password = await bcrypt.hash(this.password, 12);
//remove the confirm field
this.passwordConfirm = undefined;
});

findOneAndUpdate()already updates user, so in the callback function you provided user is already up to date. When you call user.save() in that callback, the pre save() hook will be called, but isModified('password') will be false.
If you provide a new password with req.body, the password will land in the database unhashed, since "Pre and post save() hooks are not executed on update(), findOneAndUpdate(), etc." (see documentation). If you then compare the password in req.body and user, they will be the same and you cannot decide whether to trigger the hash function with a save() or not.
Leave your pre save() hook as is and do the update as a combination of findById() and save():
User.findById(req.user._id, (err, user) => {
if (err)
handleYourErrorAndLeave(err)
// Update all user attributes which are different or missing from user with values from req.body
Object.assign(user, req.body)
// Save the updated user object.
// pre save() hook will be triggered and isModified('password') should be correct
user.save()
.then(savedUser => {
res.redirect('/profile')
})
})

Related

Hashing password before update a user in mongoose

I create the user, hash his password and save on mongo. My problem begins when I try to update that user. For now, when I update the hash isn't generated, cause I really don't know how to do it.
The middleware to get the user that I'm talking about:
exports.userByID = function(req, res, next, id) {
User.findOne(
{
_id: id
},
function(err, user) {
if (err) {
return next(err);
} else {
req.user = user;
next();
}
}
);
};
The user controller, to update an user:
exports.update = async function(req, res, next) {
User.findByIdAndUpdate(req.user.id, req.body, function(err, user) {
if (err) {
return next(err);
} else {
res.json(user);
}
});
};
The pre 'save' on User's model:
UserSchema.pre("save", function(next) {
var user = this;
if (user.password) {
var md5 = crypto.createHash("md5");
user.password = md5.update(user.password).digest("hex");
console.log("Password após o save (hasheando):" + user.password);
}
next();
});
I'm using passport authentication ('local'). Already tried user.save() on the controller update:
user.save();
res.json(user);
But, without success.
This is may be because you are not storing the new_password in the mongo.
In update controller you have to do like this:
User.findByIdAndUpdate(req.user.id, req.body, function (err, user) {
if (err) {
return next(err);
} else {
user.password = req.body.new_password;
user.save(function (err, user) {
if (err) {
res.send("Error: ", err);
} else {
res.send("password updated successfully!");
}
})
}
});
Before saving the password just hash it and update it in DB. it will be something like below.
exports.update = async function(req, res, next) {
let { body} = req;
if(body['password']){
var md5 = crypto.createHash("md5");
body['password']= md5.update(body['password']).digest("hex");
}
let updateUser = await User.findByIdAndUpdate(req.user.id, body)
};

passport-local-mongoose set new password after checking for old password

I am using passportjs to handle auth of my app.
Once the user is logged in, I want to add the possibility to change the password from inside the app.
this is in my controller:
$http.post('/change-my-password',{oldPassword: $scope.user.oldpassword, newPassword: $scope.user.newpassword})
.then(function (res) {
if (res.data.success) {
// password has been changed.
} else {
// old password was wrong.
}
});
and this is my route handler in express nodejs in backend:
router.post('/change-my-password', function (req, res) {
if (!req.isAuthenticated()) {
return res.status(403).json({
success: false
});
}
UserSchema.findById(req.user._id, function(err, user){
if (err) return res.status(200).json({success: false});
user.validatePassword(req.body.oldPassword, function(err) {
if (err){
return res.status(200).json({
success: false
});
}
user.setPassword(req.body.newPassword, function() {
if (err || !user) {
return res.status(200).json(
{
success: false
}
)
}
user.save(function(err) {
if (err) return res.status(200).json({success: false});
req.login(user, function (err) {
if (err) return res.status(200).json({success: false});
return res.status(200).json({success: true});
});
});
});
});
});
});
here is my user schema model:
// user model
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var passportLocalMongoose = require('passport-local-mongoose');
var bcrypt = require('bcrypt-nodejs');
var UserSchema = new Schema({
email: String,
password: String,
confirmStatus: Boolean,
token: String,
registerAt: Number
});
UserSchema.methods.validatePassword = function (password, callback) {
this.authenticate(password, callback);
};
UserSchema.plugin(passportLocalMongoose,
{
usernameField: 'email'
});
module.exports = mongoose.model('users', UserSchema);
the problem:
I find my user by Id in my mongoose schema UserSchema then I should check if the oldPassword is valid or not, and then I set the new password.
I successfully find the user and the set the new password. But the part that should check for comparison of the old password field, doesn't work at all. Whatever I enter in the old password field gets accepts as OK and that step is skipped. Whereas, it should throws an error saying that the old password is wrong.
I am also advised to use sanitizedUser in order not to show my salt and etc.
Question is: how can I first do the comparison check of the old password and then do the set new password step? If possible, how can I use the sanitize? And how can I check if the user is not entering the same password as the new password? or if possible, saying that the new password is very similar to the old one?
You can implement the it using the new feature added 3 days ago:
just use the changePassword method, and it handles it through this:
schema.methods.changePassword = function(oldPassword, newPassword, cb) {
if (!oldPassword || !newPassword) {
return cb(new errors.MissingPasswordError(options.errorMessages.MissingPasswordError));
}
var self = this;
this.authenticate(oldPassword, function(err, authenticated) {
if (err) { return cb(err); }
if (!authenticated) {
return cb(new errors.IncorrectPasswordError(options.errorMessages.IncorrectPasswordError));
}
self.setPassword(newPassword, function(setPasswordErr, user) {
if (setPasswordErr) { return cb(setPasswordErr); }
self.save(function(saveErr) {
if (saveErr) { return cb(saveErr); }
cb(null, user);
});
});
});
};
so in your code, you need to replace the validatePassword method by this:
user.changePassword(req.body.oldPassword,req.body.newPassword, function(err) {
if (err){
return res.status(200).json({
success: false
});
}
hope this works for you.

Unable to verify hashed password

Hi All,
I am authenticating my user using bcrypt module.
I am able to do perform the Registration process, but facing problem during Login process.
User Model:
var userSchema = new Schema({
email: {type: String, required: true},
password: {type: String,
});
Hashing methods:
userSchema.methods.encryptPassword = function (password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(5), null)
};
userSchema.methods.validPassword = function (password) {
return bcrypt.compareSync(password, this.password);
};
Sign in:
module.exports.login = function (user, callback) {
User.findOne({'email': user.email, 'password': user.validPassword(this.password)}, callback);
};
Login Route
router.post('/login', function (req, res) {
var user = req.body;
User.login(user, function (err, user) {
if (err) {
throw err;
}
if (!user) {
res.sendStatus(404);
return;
}
res.json(user.id);
});
});
While executing am getting this error: TypeError:user.validPassword is not a function
Please Help.
Your mistake is that the user being provided to your login method is not a Mongoose DB object. Instead, your login function should look something like this:
module.exports.login = function (request, callback) {
User.findOne({'email': request.email }, function(err, user) {
if (err) return callback(err);
if(!user || !user.validPassword(request.password)) return callback();
return callback(null, user);
});
};
This will ensure that user is a valid Mongoose object before you attempt to verify the password.
One other possible solution, if you'd prefer to avoid checking that the password is valid in your data layer, is to simply fetch the user document based on its email and then check the password in the login route.
router.post('/login', function (req, res) {
var user = req.body;
User.findOne(user, function (err, user) {
if (err) {
throw err;
}
if (!user) {
res.sendStatus(404);
return;
}
if (!user.validPassword(req.body.password)) {
res.sendStatus(401);
return;
}
res.json(user.id);
});
});
In Login Route, you need to instantiate the Schema:
router.post('/login', function (req, res) {
var user = new User(req.body);
User.login(user, function (err, user) {
if (err) {
throw err;
}
if (!user) {
res.sendStatus(404);
return;
}
res.json(user.id);
});
});

Mongoose update found document [duplicate]

I have a Mongoose User model:
var User = mongoose.model('Users',
mongoose.Schema({
username: 'string',
password: 'string',
rights: 'string'
})
);
I want to find one instance of the User model, modify it's properties, and save the changes. This is what I have tried (it's wrong!):
User.find({username: oldUsername}, function (err, user) {
user.username = newUser.username;
user.password = newUser.password;
user.rights = newUser.rights;
user.save(function (err) {
if(err) {
console.error('ERROR!');
}
});
});
What is the syntax to find, modify and save an instance of the User model?
The user parameter of your callback is an array with find. Use findOne instead of find when querying for a single instance.
User.findOne({username: oldUsername}, function (err, user) {
user.username = newUser.username;
user.password = newUser.password;
user.rights = newUser.rights;
user.save(function (err) {
if(err) {
console.error('ERROR!');
}
});
});
Why not use Model.update? After all you're not using the found user for anything else than to update it's properties:
User.update({username: oldUsername}, {
username: newUser.username,
password: newUser.password,
rights: newUser.rights
}, function(err, numberAffected, rawResponse) {
//handle it
})
findOne, modify fields & save
User.findOne({username: oldUsername})
.then(user => {
user.username = newUser.username;
user.password = newUser.password;
user.rights = newUser.rights;
user.markModified('username');
user.markModified('password');
user.markModified('rights');
user.save(err => console.log(err));
});
OR findOneAndUpdate
User.findOneAndUpdate({username: oldUsername}, {$set: { username: newUser.username, user: newUser.password, user:newUser.rights;}}, {new: true}, (err, doc) => {
if (err) {
console.log("Something wrong when updating data!");
}
console.log(doc);
});
Also see updateOne
I wanted to add something very important. I use JohnnyHK method a lot but I noticed sometimes the changes didn't persist to the database. When I used .markModified it worked.
User.findOne({username: oldUsername}, function (err, user) {
user.username = newUser.username;
user.password = newUser.password;
user.rights = newUser.rights;
user.markModified(username)
user.markModified(password)
user.markModified(rights)
user.save(function (err) {
if(err) {
console.error('ERROR!');
}
});
});
tell mongoose about the change with doc.markModified('pathToYourDate') before saving.
If you want to use find, like I would for any validation you want to do on the client side.
find returns an ARRAY of objects
findOne returns only an object
Adding user = user[0] made the save method work for me.
Here is where you put it.
User.find({username: oldUsername}, function (err, user) {
user = user[0];
user.username = newUser.username;
user.password = newUser.password;
user.rights = newUser.rights;
user.save(function (err) {
if(err) {
console.error('ERROR!');
}
});
});
You could also write it a little more cleaner using updateOne & $set, plus async/await.
const updateUser = async (newUser) => {
try {
await User.updateOne({ username: oldUsername }, {
$set: {
username: newUser.username,
password: newUser.password,
rights: newUser.rights
}
})
} catch (err) {
console.log(err)
}
}
Since you don't need the resulting document, you can just use updateOne instead of findOneAndUpdate.
Here's a good discussion about the difference: MongoDB 3.2 - Use cases for updateOne over findOneAndUpdate

hashed passwords are not being saved with pre save hook in mongoose

I am using Postman to test an API that I am working on for a project. I am sending
{
"fullName": "Smellydog",
"emailAddress": "SmellydogCoding#gmail.com",
"password": "password",
"confirmPassword": "password"
}
as the request body to this API route
users.post('/', (req, res, next) => {
let user = new Users(req.body);
user.save((error,user) => {
if (error) {
return next(error);
} else {
res.status = 201;
res.location('/');
res.end();
}
});
});
This is my Users Schema
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const emailRegex = /^(([^<>()\[\]\\.,;:\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,}))$/;
const UserSchema = new mongoose.Schema({
fullName: {
type: String,
required: true,
trim: true,
},
emailAddress: {
type: String,
required: true,
unique: true,
match: [emailRegex, "Please enter a valid email address"],
trim: true
},
password: {
type: String,
required: true
},
confirmPassword: {
type: String,
required: true
}
});
// hash password before saving to database
UserSchema.pre('save', function(next) {
bcrypt.hash(this.password, 10, function(error, hash) {
if (error) {
return next(error);
} else {
this.password = hash;
this.confirmPassword = hash;
next();
}
});
});
UserSchema.pre('validate', function(next) {
let user = this;
if (user.password !== user.confirmPassword) {
return next('Passwords must match');
} else {
next();
}
});
const User = mongoose.model('User', UserSchema);
module.exports = User;
The pre save hook is supposed to encrypt the password and then save it to the password and confirmPassword fields. If I use Chrome debugger to set a breakpoint at the end of the pre save hook (where next() is called), and inspect this.password and this.confirmPassword, I see that they are set to the newly created hash, but when I check the database afterwards both of those fields are set to the original password string
{
"_id": "58d835f0d026194610578c74",
"fullName": "Smellydog",
"emailAddress": "SmellydogCoding#gmail.com",
"password": "password",
"confirmPassword": "password",
"__v": 0
}
I agree with the previous answer made by smellyDogCoding. The reason why the solution works is because of the context of this. When the thread of execution hits the line:
let user =this
The context of 'this' is referring to an instance of the document being created. However, once we enter the execution context of the bcrypt hash method, the 'this' context no longer refers to the instance of the document we're creating in the database, but instead refers to the scope of the function.
By saving a reference to the instance of the document object we are creating in a variable, user, we retain a reference to the this we want to update after hashing. And since we're only making a shallow copy--really a reference to the original this, when we update the property 'password' on the user object, we're also updating the property 'password' on the 'this' document object.
Its because you are using
bcrypt.hash(this.password, 10, function(error, hash)
the .hash method asynchronous, which means you are running the next() function before the password is hashed.
use async/await instead
UserSchema.pre('save', async function (next) {
// check if password is present and is modified.
try {
if (this.password && this.isModified('password')) {
this.password = await bcrypt.hash(this.password, passwordSaltRound);
}
next();
} catch (err) {
next(err);
}
});
with your method you can use bcrypt.hashSync(this.password, 10, function(error, hash) but it will create blocking code!
so just use the async/await method <3
I added
let user = this;
to the first line of the pre save hook and then referred to the fields as
user.password
user.confirmPassword
like so
// hash password before saving to database
UserSchema.pre('save', function(next) {
let user = this;
bcrypt.hash(user.password, 10, function(error, hash) {
if (error) {
return next(error);
} else {
user.password = hash;
user.confirmPassword = hash;
next();
}
});
});
It seems that this.password was being updated while inside the pre save hook but it was not being persisted to the database at the end. Setting this to a variable seems to change the context as Paolo has suggested. I would really love to know the how and why of this.
I know that this is an old question but I would like to say that another solution for this is to use arrow function expressions introduced in ES6.
UserSchema.pre('save', function(next) {
bcrypt.hash(this.password, 10, (error, hash) => {
if (error) {
return next(error);
} else {
this.password = hash;
this.confirmPassword = hash;
next();
}
});
});
the reason why this works is because an arrow function expression does not have its own bindings to this, so it takes the context from where the function is defined; which is the user object's context in this case.
you can read more about arrow function on MDN Web Docs here

Resources