Nodejs, bcrypt async, mongoose login - javascript

I have the following code for the user model
const userSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
username: {
type: String,
required: true,
trim: true,
minlength: 3,
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
}
})
Then for hashing the password i have this code
UserSchema.pre('save', function (next) {
if (this.isModified('password')){
bcrypt.genSalt(10, (err, salt)=>{
bcrypt.hash(this.password, salt, (err, hash)=>{
this.password = hash;
next();
})
});
}
next();
})
Then for checking the hash against the input value from user i have the following code
userSchema.statics.comparePassword = function(password){
let user = this;
return bcrypt.compareAsync(password, user.password)
}
So when it comes to the usage of all of these pieces of code i have the following
async loginUser(req, res) {
try{
const {email, password} = req.body
const user = await User.findOne({
email: req.body.email
})
if(!user){
return res.status(403).send({
error: "Incorrect details email"
})
}
const isPassValid = await user.comparePassword(password)
}catch(err){
res.status(403).send({
error: "The bid deal happened"
})
}
}
So I've tried searching on google and on this forum to find the answers but everything seems to be outdated or not working particularly for my situation. This code always sends "The bid deal happened", i've tried to debug it from all sides but its still unsuccessful.
The question is how to make it work? so that i can compare the passwords in the right way
P.S I've tried changing from compareAsync to compareSync, shows no effect

You encrypt the password when the value is changed, but not when you insert a new mongo document, you can check this with document.isNew.
I have updated your save method to the follow.
UsersSchema.pre('save', function (next) {
let user = this;
if (this.isModified('password') || this.isNew) {
bcrypt.genSalt(10, (err, salt) => {
if (err) {
return next(err);
}
bcrypt.hash(user.password, salt, (err, hash) => {
if (err) {
return next(err);
}
user.password = hash;
next();
});
});
} else {
next();
}
});
Also, Schema.statics is used to serve static methods. The this context will not return the user, thus making this.password undefined. To populate the instances of your schema with methods, you have to append them to the Schema.methods object.
I have used bcrypt.compare in the past, I dont know if bcrypt.compareAsync is a valid method because the first one is already async. And if it was async, it wouldnt directly return a value. Compare requires a callback.
UsersSchema.methods.comparePassword = function (password, callback) {
bcrypt.compare(password, this.password, (err, isMatch) => callback(err, isMatch));
};
To compare the password, u can do something like the following:
const { email, password } = req.body
User.findOne({
email: email,
}, (err, user) => {
if (err) throw err;
if (user) {
user.comparePassword(password, (err, match) => {
if (match && !err) {
// match
}
});
}
});

Related

How do i Validate old password while updating user password?

What's the proper way to validate old user password while updating new password?
So far i have tried and always get error: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
What i did:
I tried using bcrypt to compare the old password from req.body with user existing password and then hash with bcrypt before saving. Comparing the password using bcrypt gave the error above. Not comparing the old password at all and just saving new password works properly.
My code:
exports.updatePassword = async (req, res) => {
try {
const { oldPassword, password } = req.body;
let updatedPassword = {
password: password,
};
const user = await User.findOneAndUpdate(
{ _id: req.params.userId },
{ $set: updatedPassword },
{ new: true, useFindAndModify: false }
);
// validate old password
bcrypt.compare(oldPassword, user.password, function (err, match) {
if (!match || err)
return res.status(400).send('Please enter correct old password');
});
//hash password and save user
bcrypt.genSalt(12, function (err, salt) {
bcrypt.hash(user.password, salt, (err, hash) => {
user.password = hash;
user.save();
return res.json({user});
});
});
} catch (err) {
console.log(err);
return res.status(400).send('Something went wrong. Try again');
}
};
The issue is that the updatePassword function is ending before you actually process everything. To avoid nested function calls and returns, use the async methods provided by bcrypt (also check their recomendation on using async vs sync).
Regarding the code itself, you are updating the user's password before checking if the password is valid. You should get the user from the db, check if the current password matches, and only then insert the new hashed password into the db.
exports.updatePassword = async (req, res) => {
const { oldPassword, password } = req.body;
try {
// get user
const user = await User.findById(req.params.userId);
if (!user) {
return res.status(400).send('User not found');
}
// validate old password
const isValidPassword = await bcrypt.compare(oldPassword, user.password);
if (!isValidPassword) {
return res.status(400).send('Please enter correct old password');
}
// hash new password
const hashedPassword = await bcrypt.hash(password, 12);
// update user's password
user.password = hashedPassword;
const updatedUser = await user.save();
return res.json({ user: updatedUser });
} catch (err) {
console.log(err);
return res.status(500).send('Something went wrong. Try again');
}
};

Mongoose | Object changes in pre-save hook are not saved to db

I've run into a problem, that I'm not able to solve.
I'll try to describe it as meaningful and simple as possible.
This is my method, that handles post request and saves data:
app.post('/users/', (req, res) => {
let body = _.pick(req.body, ["email", "password"]);
let user = new User(body);
user.save().then(
user => res.json(user),
err => res.send(err)
)
});
When I save new user to database, this pre-save hook fires:
userSchema.pre('save', function(next) {
var user = this;
if(user.isNew){
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(user.password, salt, (err, hash) => {
user.password = hash;
console.log(user);
next();
})
})
}
next();
})
For this input in POST body:
{
"email": "example#example.com",
"password": "somepass"
}
console.log from pre-save hook logs:
{ __v: 0,
email: 'example#example.com',
password: '$2a$10$tWuuvw.wGicr/BTzHaa7k.TdyZRc5ADDV0X1aKnItvVm6JYVe5dsa',
_id: 59482e8136fd8d2bf41e24b7
}
however in db I've got:
{
"_id" : ObjectId("59482e8136fd8d2bf41e24b7"),
"email" : "example#example.com",
"password" : "somepass",
"__v" : 0
}
Clearly changes on user object are not saved and in save() method I still use old values with unhashed password. Why is that? And how can I make changes from pre-save hook to be stored?
The problem is that you're always calling next() after the if block, even when the password needs async encrypting.
Change your code to only do that for existing user docs:
if(user.isNew){
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(user.password, salt, (err, hash) => {
user.password = hash;
console.log(user);
next();
})
})
}
else {
next();
}

"Error: Illegal arguments: string, undefined" and stop server in node JS

I'm trying to build logging application in node JS. in here password authentication app do not work properly. when i enter username and password it occur following error and stop server.
this is the error.
Here is the code for authentication part
passport.use(new LocalStrategy(
function(username, password, done) {
User.getUserByUsername(username, function(err, user){
if(err) throw err;
if (!user) {
return done(null, false, {message: 'Unknown user'});
}
User.comparePassword(password, user.password, function(err, isMatch){
if(err) throw err;
if (isMatch) {
return done(null, user);
} else {
return done(null, false, {message: 'Invalid password'});
}
});
});
}));
This code work for Unknown user.
but it is not working for comparing username and password. i cannot see any bug in here. i want a help for solve this.
In the name of the universe programmer
in my case i forgot to select the password
because in database the password was ((select: false))
this code for app
const user = await User.findOne({email}).select("+password")
i forgot to append the ((.select("+password")))to the findOne
and I received this error ;
Error: Illegal arguments: string, undefined
and this code for database
const User = new mongoose.Schema({
username:{
type:String,
required: [true,"نام کاربری ضروری است"]
},
email:{
type:String,
required: [true,"رایانامه ضروری است"],
unique: true,
match:[
/^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{1,3})$/,
"لطفا یک رایانامه صحیح وارد کنید"
]
},
password:{
type:String,
required:[true,"رمز ضروری است"],
minlegth: 5,
select: false
}
})
I found the problem in here. it is not things regarding the code.
The thing is i had registered two users with same user name and different password. then when i tried to login with the user name and one password it occurred this error and stop the server.
Because there is embarrassing situation with find password to username that user entered. because there are two password with same username.
In my case, I was using arrow function
userSchema.methods.comparePassword = async (enterdPassword) => {
return await bcrypt.compare(enterdPassword, this.password);
};
which I converted to normal function
userSchema.methods.comparePassword = async function (enterdPassword) {
return await bcrypt.compare(enterdPassword, this.password);
};
that solved the problem
In my case, I'm using social signin/signup. When the user is signing up using a social login option, the value of the password stored is "NULL".
So I just added this little check :
comparePassword: function(password, user){
if (!user.password)
return false;
return bcrypt.compareSync(password, user.password);
}
At
"models/user.js"
Inside comparePassword
module.exports.comparePassword = (candidatePassword, hash, callback) => {...)
Add this code:
bcrypt.hash(candidatePassword, 10, (err, hash) => {
if(err) {
throw err;
}
bcrypt.compare(candidatePassword, hash, (err, isMatch) => {
if(err) {
throw err;
}
callback(null, isMatch);
});
});
Here We are grabbing username and password from the sign in page AND
finding our user by the username from the database and then
Matching its encrypted password with an entered password by the user
passport.use(new LocalStrategy(
(username,password,done)=> {
db.users.findOne({username: username},(err, user)=> {
if(err) return done(err);
if(!user) {
return done(null,false,{message: 'Incorrect Username'});
}
bcrypt.compare(password, user.password,(err,isMatch)=> {
if(err) return done(err);
if(isMatch) {
return done(null, user);
} else {
return done(null, false,{message: 'Incorrect Password'});
}
});
});
}
));
You need to apply await to your salt and password assignments too.
Like this,
const salt = await bcrypt.genSaltSync(10);
const password = await req.body.password;
You can write a code like this: After this.findOne({ select: [] ........}) ... I hope this is helpful
async validateUserPassword(loginDto: AuthLoginDto): Promise<User> {
const { mobile, email, password } = loginDto;
const user = await this.findOne({
select: ['id', 'email', 'mobile', 'password', 'salt', 'status', 'logged_at'],
where: [
{ mobile: mobile },
{ email: email }
]
});
if (user && await user.validatePassword(password)) {
const logged_at = {
logged_at: new Date()
}
await this.update({ id: user.id }, logged_at)
return user;
} else {
return null;
}
}
async validatePassword(password: string): Promise<boolean> {
const hash = await bcrypt.hash(password, this.salt);
return hash === this.password;
}
In my own case , I just want to check if the old password matches the password in Db but got the error , here is my code below:
changePassword = asyncHandler (async (req: IGetUserAuthInfoRequest, res: Response) => {
const user = await User.findById(req.user._id)
const {oldPassword, password} = req.body
if(!user) {
res.status(400)
throw new Error("User not found, please signup")
}
// Validate
if(!oldPassword || !password) {
res.status(400)
throw new Error("Please add old and new password")
}
// Check if old password matches password in DB
const passwordIsCorrect = await bcrypt.compare(oldPassword, user.password)
// Save new password
if(user && passwordIsCorrect) {
user.password = password
await user.save()
res.status(200).send("Password change successful")
} else {
res.status(400)
throw new Error("Old password is incorrect")
}
});

bcrypt compare returning false when password contains numbers

I'm using node-bcrypt along with PostgreSQL (via Sequelizejs to hash and save passwords.
The user's password is hashed in a beforeValidate hook like so:
beforeValidate: function(user, model, cb) {
bcrypt.hash(user.password, 10, function(err, hash) {
if ( err ) { throw err; }
user.password = hash;
cb(null, user);
});
}
The column on the User model in which the hash is stored is defined as:
password: { type: DataTypes.STRING, allowNull: false }
When a user is logging in (I'm using Passport for authentication), the function looks like this:
passport.use(new LocalStrategy(function(username, password, done) {
models.User.find({ username: username }).then(function(retrievedUser) {
if ( !_.isEmpty(retrievedUser) ) {
retrievedUser.verifyPassword(password, function(err, result) {
if ( err || !result ) {
return done(null, false, { message: 'Incorrect password.' });
} else {
return done(null, retrievedUser);
}
});
} else {
return done(null, false, { message: 'User could not be found at that username.' });
}
}).catch(function(err) {
return done(err);
});
}));
Which retrieves the user correctly.
And the comparison, defined here on the User model:
instanceMethods: {
verifyPassword: function(password, cb) {
bcrypt.compare(password, this.password, cb);
}
}
The verification passes just fine if the password only contains letters and/or symbols. However, any passwords with numbers never pass the comparison. Any ideas?
In your beforeValidate function you are passing in 10 as the salt you need to generate your salt, change that function to this
beforeValidate: function(user, model, cb) {
var salt = bcrypt.genSalt(10);
bcrypt.hash(user.password, salt, function(err, hash) {
if ( err ) { throw err; }
user.password = hash;
cb(null, user);
});
}
Well, embarrassingly (and as some people suspected), this was completely unrelated to Bcrypt.
In my authentication function, I was attempting to fetch the appropriate user with the following:
models.User.find({ username: username })
However, the query should have been specified in a where field, like so:
models.User.find({
where: { username: username }
})
This was causing the authentication method to always be comparing to the password for the first user in the database (returned by default when no where field is specified). Thus, it SEEMED to work because the first user's letters-only password always worked but no following passwords worked (in reality, even a letters-only password would have failed for other users).

Mongoose: Find, modify, save

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

Categories

Resources