I want my pre('save') mongoose function to operate only once - node.js

I do not know if the exact request in title is possible, but if not; i would really appreciate an alternate solution.
I have this pre save method of mongoose
ownerSchema.pre("save", function(next) {
const owner = this;
bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash(owner.password, salt, function(err, hash) {
// Store hash in your password DB.
owner.password = hash;
next();
});
});
});
When i save new user(owner) a hash is created successfully and all is good>
The problem occurs when i login. when i login i generate jwt with a mongoose custom method as below
ownerSchema.methods.generateToken = function(cb) {
var owner = this;
var token = jwt.sign(
{
_id: owner._id,
username: owner.username,
email: owner.email,
category: owner.category === 0 ? false : true,
phones: owner.phones,
address: owner.address
},
config.SECRET,
{ expiresIn: "1h" }
);
owner.token= token;
owner.save(function(err,owner){
if(err) return cb(err);
cb(null,owner);
})
};
as you see i generate token to send it in "res" and at the same time i add the new token to the record in the data base. all working fine till now and the response is returned successfully>
BUT!! while i performed save() in the generate token function to save the token>> the previous pre(save) function ran again, so that a new hash is generated for the password feild.
when i try to login again, the password had already changed from calling the pre save hashing function when generating the token in the first login.
Any workaround for solving this issue?

You could use isModified method on your 'password' field.
I use it in this way, only run bcrypt if the password property was changed:
UserSchema.pre('save', function (next) {
var user = this;
if (user.isModified('password')) {
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(user.password, salt, (err, hash) => {
user.password = hash;
next();
});
});
} else {
next();
}
});

Related

How to update salt and hash value in mongoDB when user changes the password in nodejs?

I have to implement a functionality for the user who can after login change the password to new one.
For that I want to update the hash and salt value for new password set. How can I do that?
Since on registration I am saving the password first time in hash and salt form in mongoDB.
How can I update that now?
Here is the code I am trying to use for password change:
router.get('/api/changePassword', function(req,res,next){
req.checkBody('oldPass', 'Empty Password').notEmpty();
req.checkBody('newPass', 'Password do not match').equals(req.body.confirmPassword).notEmpty();
var user = new User();
user.setPassword(req.body.newPass);
user.save(function (err,data) {
if (err) {
res.render('register', {errorMessages: err});
} else {
console.log("password set successfully");
}
})
})
But here I doubt that it will get updated into the existing user's database since I am creating the object user again here and saving it. Will it create again a new user in Collections and update the hash and salt value for that? How to then update the existing user password hash and salt value?
Below is the hash and salt model User schema code:
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);
And this is the route code of registartion page, first time when user registers and put password:
router.route('/register')
.get(function(req, res, next) {
res.render('register', { title: 'Register a new account'});
})
.post(function(req, res, next) {
req.checkBody('name', 'Empty Name').notEmpty();
req.checkBody('email', 'Invalid Email').isEmail();
req.checkBody('location', 'Empty Location').notEmpty();
req.checkBody('password', 'Empty Password').notEmpty();
req.checkBody('password', 'Password do not match').equals(req.body.confirmPassword).notEmpty();
var errors = req.validationErrors();
if (errors) {
res.render('register', {
name: req.body.name,
email: req.body.email,
location:req.body.location,
errorMessages: errors
});
} else {
var user = new User();
user.name = req.body.name;
user.email = req.body.email;
user.location = req.body.location;
user.setPassword(req.body.password);
user.save(function (err,data) {
if (err) {
res.render('register', {errorMessages: err});
} else {
console.log("user saved successfully");
}
})
Sorry, my previous explanation was incorrect, so I am going to write this as an answer. You won't actually be able to access the User object in the method alone, so what you will have to do is something like the following:
create a function with User.find() that returns the user.
Then pass in this user as a parameter to the new method you created as I described in the comment above, and continue to .update() this user that was passed into the setPassword() method as a parameter.
So your methods for the schema should be:
createPassword()
validPassword()
setPassword()
and the part of your code where you actually have the function that uses these methods to set this data should look something like:
function findUser() {
return User;
}
function setPassword(password) {
User.setPassword(User, password, passwordValid /*This is a boolean*/)
}

Hash password before mongodb update

So i've spent a while trying to solve this. Basically I have a user profile update page, when the user inputs the new credentials I want to update my mongo db. When I update it everything does through normally and my mongo server gets updated but when I log in, I use bcrypt to match the hashed password and unhashed password and this is what is giving me my error because the updated password isn't hashed.
Update mongo:
const { email, password, password2 } = req.body;
const _id = ObjectID(req.user);
User.updateOne(
{ _id },
{ $set: { email: email, password: password } },
(err) => {
if (err) {
throw err;
} else {
req.flash('success_msg', 'profile updated');
res.redirect('profile');
}
}
);
This is my try at hashing the password. It gives an error in the console(Error: Argument passed in must be a single String of 12 bytes or a string of 24 hex characters). I tried to solve this by making the object id .toString but it still gave an error:
const { email, password, password2 } = req.body;
const _id = ObjectID(req.user);
User.updateOne(
{ _id },
{ $set: { email: email, password: password } },
(err, user) => {
const updatedPassword = password;
if (err) {
throw err;
} else {
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(updatedPassword.password, salt, (err, hash) => {
if (err) {
throw err;
}
updatedPassword.password = hash;
updatedPassword.save();
});
});
}
req.flash('success_msg', 'profile updated');
res.redirect('profile');
}
);
I'm a kinda new to express so sorry if this is messy. Also if you find a solution please explain it and if you need more code I'll post it.
You likely deserialize the user in a middleware not shown and so the following line should change from:
const _id = ObjectID(req.user);
to
const _id = new ObjectID(req.user._id);

Bcrypt is always false after the first login [duplicate]

I do not know if the exact request in title is possible, but if not; i would really appreciate an alternate solution.
I have this pre save method of mongoose
ownerSchema.pre("save", function(next) {
const owner = this;
bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash(owner.password, salt, function(err, hash) {
// Store hash in your password DB.
owner.password = hash;
next();
});
});
});
When i save new user(owner) a hash is created successfully and all is good>
The problem occurs when i login. when i login i generate jwt with a mongoose custom method as below
ownerSchema.methods.generateToken = function(cb) {
var owner = this;
var token = jwt.sign(
{
_id: owner._id,
username: owner.username,
email: owner.email,
category: owner.category === 0 ? false : true,
phones: owner.phones,
address: owner.address
},
config.SECRET,
{ expiresIn: "1h" }
);
owner.token= token;
owner.save(function(err,owner){
if(err) return cb(err);
cb(null,owner);
})
};
as you see i generate token to send it in "res" and at the same time i add the new token to the record in the data base. all working fine till now and the response is returned successfully>
BUT!! while i performed save() in the generate token function to save the token>> the previous pre(save) function ran again, so that a new hash is generated for the password feild.
when i try to login again, the password had already changed from calling the pre save hashing function when generating the token in the first login.
Any workaround for solving this issue?
You could use isModified method on your 'password' field.
I use it in this way, only run bcrypt if the password property was changed:
UserSchema.pre('save', function (next) {
var user = this;
if (user.isModified('password')) {
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(user.password, salt, (err, hash) => {
user.password = hash;
next();
});
});
} else {
next();
}
});

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

BCrypt hash error

this is my code for hashing password and for compare existing password into existing module with a password that has been sended on body request:
//hash password of document that use this schema
bcrypt.hash(user.password, null, null, function (err, hashed) {
if (err) {
throw err;
} else {
user.password = hashed;
//next api
next();
}
})
});
userSchema.methods.comparePassword = function (password) {
//refer at userSchema
var user = this;
//return method of bcryot library that compare two string: original password and password hashed
return bcrypt.compareSync(password, user.password);
};
But compare this error message:
Uncaught, unspecified "error" event. (Not a valid BCrypt hash.)
Resolved !!! Into the database i have a lot of user's password not hashed and when i try to login, with bcrypt.compareSync (password, user.password); it expected that has been hashed password.
You're using null twice. I'd wager that you've wrapped this function inside the bcrypt.genSalt function(if you haven't , do so). You need to pass it the bcrypt salt where the first null is written.
Here's a full example:
userSchema.pre('save', function (next) {
const SALTROUNDS = 10; // or another integer in that ballpark
const user = this;
if(!user.isModified('password')) {
return next();
}
bcrypt.genSalt(SALTROUNDS, (err, salt) => {
if (err) { return next(err); }
bcrypt.hash(user.password, salt, null, (error, hash) => {
if (error) { return next(error); }
user.password = hash;
next();
});
});
});

Resources