Mongoose not saving an updated document to the database - javascript

EDIT: This question was asked earlier, but I didn't do a good job of asking it. I've rewritten the question. Thanks in advance for your help!
I'm in the process of writing a simple messaging server for a school project. Among its other functionalities, the server allows the user to update the information stored in their account. When the user does update their account, an authentication token is generated for them. Here's the schema that defines all of that. Note, header and body are parts of the user input:
UserSchema = new Schema({
_id: {type: ObjectId, select: false},
username: {type: String, required: true, index: {unique: true} },
password: {type: String, required: true, select: false},
email: {type: String},
token: {type: String, select: false}
}, {
autoIndex: false
});
UserSchema.pre("save", function(next) {
// Create a new token for the user
var self = this;
bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
if (err) {
next(err);
} else {
crypto.randomBytes(256, function(err, bytes) {
if (err) {
next(err);
} else {
bytes = bytes.toString("hex");
bcrypt.hash((new Date() + bytes), salt, function(err, tokenHash) {
if (err) {
next(err);
} else {
self.token = tokenHash;
next();
}
});
}
});
}
});
});
UserSchema.pre("save", function(next) {
// Hash the password before saving
var self = this;
if (!self.isModified("password")) {
next();
} else {
bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
if (err) {
next(err);
} else {
bcrypt.hash(self.password, salt, function(err, passwordHash) {
if (err) {
next(err);
} else {
self.password = passwordHash;
next();
}
});
}
});
}
});
I'm running into an issue when updating a particular user. Because I want to use the Model middleware, the way I'm updating a user is by using Model#findOne() followed by Model#save(). Here's the code I have to do that:
// Make sure user provided all necessary information.
if (!header.token) {
return callback(new errors.MissingHeaderDataError("Missing 'token' parameter in the header."));
} else {
// Update the user account based on what's in the envelope's body.
User.findOne({"token": header.token}, "+token +password", function (err, user) {
if (err) {
return callback(err);
} else {
// Get a list of all parameters the user wants to change.
var paramsToChange = Object.keys(body);
// Now update the parameters
paramsToChange.forEach(function(param) {
user[param] = body[param];
});
console.log("Updated user:");
console.dir(user);
user.save(function(err, user) {
if (err) {
return callback(err);
} else {
console.log("Returned user:");
console.dir(user);
User.find({}, "+token +password", function(err, foundUser) {
if (err) {
throw err;
} else {
console.log(JSON.stringify(foundUser));
}
});
callback(null, new SuccessEnvelope(user));
}
});
}
});
}
When I run my tests and come to the last bit of code (after save() is returned), I get this output:
Updated user:
{ token: '$2a$10$5VWWqjJ52aGbS4xc6NDKjuGPv8brX7pRmwiKyYjP8VHoTKCtYZiTu',
username: 'jim_bob',
password: '$2a$10$ue08HUsunzzzcbZURzXF7uaH1dZxF3SwkwadC6D1JsIC9xAUhTbCC',
email: 'joe_bob#email.com',
__v: 0 }
Returned user:
{ token: '$2a$10$fRwED..7fFFhN46Vn.ZJW..xYql5t5P39LHddjFS4kl/pmhwfT.tO',
username: 'jim_bob',
password: '$2a$10$ue08HUsunzzzcbZURzXF7uaH1dZxF3SwkwadC6D1JsIC9xAUhTbCC',
email: 'joe_bob#email.com',
__v: 0 }
[{"token":"$2a$10$5VWWqjJ52aGbS4xc6NDKjuGPv8brX7pRmwiKyYjP8VHoTKCtYZiTu","username":"joe_bob","password":"$2a$10$ue08HUsunzzzcbZURzXF7uaH1dZ
xF3SwkwadC6D1JsIC9xAUhTbCC","email":"joe_bob#email.com","__v":0}]
As you can see, the document is not properly saved to the database, as the previous data is still there. My question is: why? Why is the user not being updated when calling save? I think I'm doing everything properly, but obviously I'm not. Any help with this would be great since I'm going mad!

Apparently, in order to save a document to the database, it needs an _id. Kinda silly that Mongoose doesn't give an error when it doesn't find a document. Alas...
I updated my code to reflect the change:
// Make sure user provided all necessary information.
if (!header.token) {
return callback(new errors.MissingHeaderDataError("Missing 'token' parameter in the header."));
} else {
// Update the user account based on what's in the envelope's body.
User.findOne({"token": header.token}, "+_id +token +password", function (err, user) {
if (err) {
return callback(err);
} else {
console.log("Found user:");
console.dir(user);
// Get a list of all parameters the user wants to change.
var paramsToChange = Object.keys(body);
// Now update the parameters
paramsToChange.forEach(function(param) {
user[param] = body[param];
});
console.log("Updated user:");
console.dir(user);
user.save(function(err, user, numberTouched) {
if (err) {
return callback(err);
} else {
console.log("Returned user:");
console.dir(user);
console.log(numberTouched);
User.find({}, "+token +password", function(err, foundUser) {
if (err) {
throw err;
} else {
console.dir(foundUser);
}
});
callback(null, new SuccessEnvelope(user));
}
});
}
});
}

Related

Can I pass my own data (object) back from passport.use('local') to passport.authenticate?

I want to pass my own data from passport.use to passport.authenticate.
I thought that the info parameter in
passport.authenticate('local', function(err, user, info)
could be used for that.
So is there a way to doing this ?
My auth route
passport.authenticate('local-register', (err, user, info) => {
if (err) {
return next(err); // 500 status
}
console.log(info);
if (info) {
console.log('rendering info ' + info);
return res.render('auth/register', { info });
} else {
if (!user) {
return res.status(409).render('auth/register');
}
req.login(user, err => {
if (err) {
console.log(err);
return next(err);
}
return res.redirect('auth/profile');
});
}
})(req, res, next);
});
My config file
module.exports = passport => {
passport.use(
'local-register',
new LocalStrategy(
{
...
},
(req, email, password, done) => {
// Check if form is filled out correctly
let errors = [];
//check for same email
SCUser.findOne({ 'local.email': req.body.email }, (err, local) => {
if (local) errors.push({ text: 'Email already in use.' });
//check for same passwords
...
//check for password length
...
//abort if errors are found
if (errors.length > 0) {
const info = {
errors: errors,
...,
};
console.log(`returning info ${info}`);
return done(null, info);
}
//form is filled in correctly create a user
else {
...
}
...
Random things I've tried so far:
Adding , form_validate behind info & changing the required variables to form_validate doesn't pass it through to the auth file.
There are probably better ways to handle form validation, haven't looked that up yet, if you have any suggestions please tell me, but I still kind of want to know if it would be possible to pass custom objects through passports methods.
Take a look at the example from the documentation:
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({ username: username }, function (err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
if (!user.validPassword(password)) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
});
}
));
If you determine that the username/pass are not correct, you can call done with the last parameter being any object that you wish
return done(null, false, { message: "Incorrect password", otherData: "my other data"});
And, if it is a success, you can pass the user, but there is no reason you can't add more data to the user, or pass something completely different.
return done(null, {username: "user123", otherData: myData, customString: "myString"});

Using Multiple FindOne in Mongodb

I am trying to extend the amount of fields that our API is returning. Right now the API is returning the student info by using find, as well as adding some information of the projects by getting the student info and using findOne to get the info about the project that the student is currently registered to.
I am trying to add some information about the course by using the same logic that I used to get the project information.
So I used the same findOne function that I was using for Projects and my logic is the following.
I created a variable where I can save the courseID and then I will put the contents of that variable in the temp object that sending in a json file.
If I comment out the what I added, the code works perfectly and it returns all the students that I require. However, when I make the additional findOne to get information about the course, it stops returning anything but "{}"
I am going to put a comment on the lines of code that I added, to make it easier to find.
Any sort of help will be highly appreciated!
User.find({
isEnrolled: true,
course: {
$ne: null
}
},
'email pantherID firstName lastName project course',
function(err, users) {
console.log("err, users", err, users);
if (err) {
return res.send(err);
} else if (users) {
var userPromises = [];
users.map(function(user) {
userPromises.push(new Promise(function(resolve, reject) {
///////// Added Code START///////
var courseID;
Course.findOne({
fullName: user.course
}, function(err, course) {
console.log("err, course", err, course);
if (err) {
reject('')
}
courseID = course ? course._id : null
//console.log(tempObj)
resolve(tempObj)
}),
///// ADDED CODE END //////
Project.findOne({
title: user.project
}, function(err, proj) {
console.log("err, proj", err, proj);
if (err) {
reject('')
}
//Course ID, Semester, Semester ID
//map to custom object for MJ
var tempObj = {
email: user.email,
id: user.pantherID,
firstName: user.firstName,
lastName: user.lastName,
middle: null,
valid: true,
projectTitle: user.project,
projectId: proj ? proj._id : null,
course: user.course,
courseId: courseID
}
//console.log(tempObj)
resolve(tempObj)
})
}))
})
//async wait and set
Promise.all(userPromises).then(function(results) {
res.json(results)
}).catch(function(err) {
res.send(err)
})
}
})
using promise could be bit tedious, try using async, this is how i would have done it.
// Make sure User, Course & Project models are required.
const async = require('async');
let getUsers = (cb) => {
Users.find({
isEnrolled: true,
course: {
$ne: null
}
}, 'email pantherID firstName lastName project course', (err, users) => {
if (!err) {
cb(null, users);
} else {
cb(err);
}
});
};
let findCourse = (users, cb) => {
async.each(users, (user, ecb) => {
Project.findOne({title: user.project})
.exec((err, project) => {
if (!err) {
users[users.indexOf(user)].projectId = project._id;
ecb();
} else {
ecb(err);
}
});
}, (err) => {
if (!err) {
cb(null, users);
} else {
cb(err);
}
});
};
let findProject = (users, cb) => {
async.each(users, (user, ecb) => {
Course.findOne({fullName: user.course})
.exec((err, course) => {
if (!err) {
users[users.indexOf(user)].courseId = course._id;
ecb();
} else {
ecb(err);
}
});
}, (err) => {
if (!err) {
cb(null, users);
} else {
cb(err);
}
});
};
// This part of the code belongs at the route scope
async.waterfall([
getUsers,
findCourse,
findProject
], (err, result) => {
if (!err) {
res.send(result);
} else {
return res.send(err);
}
});
Hope this gives better insight on how you could go about with multiple IO transactions on the same request.

Getting a variable to outer scope

I'm confused by this one. First of all this is my code:
router.post('/update', (req, res, next) => {
// Todo legit credit card holding
Account.findOneAndUpdate(
{ _id: req.user._id },
{
$set: {
// username: req.body.username,
creditCardNo: req.body.cardNo,
isPremium: true,
},
},
{ upsert: true },
(err, doc) => {
if (err) {
console.log(err);
}
}
);
var newUser;
Account.findById(req.user._id,(err, doc)=>{
if(err){
console.log(err);
}
else{
newUser = doc;
}
});
console.log(newUser);
res.render('user-pannel/pannel', {
title: 'User pannel',
user: newUser,
});
});
What it does is: It gets the POST call and updates a record in the db. Now I want to basically reload the the page (res.render part) and send the new user object.
I need to send the new one, because the one in req.user is now outdated (was updated before and I'm just printing the old version).
I tried getting around the problem by doing this newUser = doc;, but for some reason the newUservariable is undefined when logged outside of the findById method. Why? If I console log the doc inside of the findById method, it returns the changed object.
I turned it into an async function and awaited its resolution with the desired value.
router.post('/update', async (req, res, next) => {
// Todo legit credit card holding
Account.findOneAndUpdate(
{ _id: req.user._id },
{
$set: {
// username: req.body.username,
creditCardNo: req.body.cardNo,
isPremium: true,
},
},
{ upsert: true },
(err, doc) => {
if (err) {
console.log(err);
}
}
);
const newUser = await new Promise((resolve, reject) => {
Account.findById(req.user._id,(err, doc) => {
if(err) reject(err);
else resolve(doc);
});
});
console.log(newUser);
res.render('user-pannel/pannel', {
title: 'User pannel',
user: newUser,
});
});

Nodejs, bcrypt async, mongoose login

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

findByIdAndUpdate $set does not check for unique?

I have a schema and specify login is unique is true. When I use findByIdAndUpdate and pass query $set to update an user object, it did not throw back error when login is dup. Does anyone know why and how I should update an object and force schema validation?
Thanks!
// UserSchema.js
var schema = new Schema({
login: {
required: true,
unique: true
},
password: {
index: true,
type: String
}
});
// Update
UserSchema.findByIdAndUpdate('someID', { '$set': { login: 'abc' } }, function (error, user) {
callback(error, user);
});
You simply need to set runValidators to true:
findByIdAndUpdate(req.params.id, {$set: data}, {runValidators: true}, function (err, doc) {
if (err) {
// Handle validation errors
}
})
More here: http://mongoosejs.com/docs/api.html#findOneAndUpdate
Using the shorthand helper methods in Mongoose bypasses validation, so you need to use a 3 step approach:
Find
Edit
Save
For example:
// 1: Find
UserSchema.findById( 'someID',
function (err, user) {
if(!err){
// 2: Edit
user.login = 'abc';
// 3: Save
user.save(function (err, user) {
if (err) {
console.log(err);
return;
}
console.log('User saved: ' + user);
});
}
}
);

Categories

Resources