Mongoose update found document [duplicate] - node.js

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

Related

Updated certain field of a document and do not effect the rest

I have below code.
User.findOneAndUpdate({ _id: request.body.id }, record, {new:true})
.exec((error, doc) => {
if (error) return response.json(error);
return response.json(doc);
});
Above code when I enter all the fields such as name, email and password then it correctly update the User Schema however, when I provide name and email but not password then it again updates all the fields including the password to null value. But in second scenario I do not want to update the password because I am not providing the password so it should not change. How can I make it work as I intend?
UPDATE
I ended up using below method but for save password is required and it has a pre hook that which encrypts password before saving. That means password is necessary for this method.
User.findOne({ _id: request.body.id })
.exec((error, user) => {
if (error) return response.json(error);
user.name = request.body.name;
user.email = request.body.email;
user.password = request.body.password;
user.save();
return response.json(user);
});
Use $set for partial update as following:
Users.findOneAndUpdate(
{ _id: request.body.id }, // query
{
"$set": { // doc
"name": req.body.name,
"email": req.body.password
}
},
{ upsert: true, new: true }, // options
function(err, doc){ // callback
if (err){
throw err;
} else if(doc){
console.log('Existing document updated successfully');
console.log(doc);
}
}
);
Have you try with new updateOne(). You can do like this:
const updateObj = {
"$set": {
"name": req.body.name,
"email": req.body.password
}
};
const updateQuery = User.updateOne({
_id: request.body.id
}, updateObj, {
new: true
}).exec();
updateQuery
.then((updateResponse) => {
console.log("Record update successfully");
response.status(200).json(updateResponse);
})
.catch((err) => {
console.log("Error while updating record: ", err);
return response.status(500).json({
error: err
});
});
You can read more on: https://docs.mongodb.com/manual/reference/method/db.collection.updateOne/
Hope this helps :)

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

isModified and pre-save mongoose...Nodejs

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')
})
})

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);
});
});

Cannot update MongoDB using mongoose

I am trying to update a collection from my database using de node module mongoose. The problem is with $set updates. Here is my code:
// Update a user
app.patch('/user/:user_id', passport.authenticate('bearer', { session: false }),
function (req, res) {
var conditions = { _id: new ObjectId(req.params.user_id)},
updateObj = { $set: req.body }; // {email : "bob#example.com", username: "bob"}
User.update(conditions, updateObj, function callback (err, numAffected, rawResponse) {
if (err) {
res.send(err);
return;
}
// numAffected is the number of updated documents
if (numAffected == 0) {
res.json({ message: 'No user affected'});
return;
}
res.json({ message: 'User updated'});
});
});
If I update an existing key like email, it is updated. But if I want to add a new key, numAffected is always 0 and the rawResponse is undefined.
Any idea of what happens?
Edit
Here is my Schema:
var userSchema = mongoose.Schema({
email : String,
username : String,
password : String
});
In order to set multiple fields in a document, you must set the Multi option in your config, otherwise Mongoose will ignore the continuation, and only update the first doc.
From the docs:
var conditions = { name: 'borne' }
, update = { $inc: { visits: 1 }}
, options = { multi: true };
Model.update(conditions, update, options, callback);
function callback (err, numAffected) {
// numAffected is the number of updated documents
});
Another note here: The numAffected should return as expected, but I can't find any documentation on their site about the raw response, but it should return as expected as well. Do you know of any documentation for this?
I think this is what you really want to do with mongoose to update email and username of a user.
app.patch('/user/:user_id', passport.authenticate('bearer', { session: false }),
function (req, res) {
User.findOneAndUpdate({_id: req.params.user_id},
{
$set: {
username: req.body.username,
email: req.body.email
}
}, function(err, user) {
if (err)
res.send(err);
if (user) {
res.json({message: 'User updated'});
} else {
res.json({message: 'User does not exist'});
}
});
});

Resources