I am using Node.js and MongoDB. I want to make sure that if the user writes in the specific field, the database should be updated with that value, else it should be null.
Following is the code I am using:
exports.updatingUser = async (user_,request,res)=> {
let result = "";
const updateUserInfo = {
fullName: request.fullName,
userName: request.userName,
email :request.email,
password : request.password,
profileImage:request.profileImage,
backgroundImage:request.backgroundImage
};
await User.updateOne({_id:request._id},{$set:updateUserInfo})
.exec()
.then(docs => {
result = docs;
})
.catch(err => {
res.status(500).json({
error: err
});
});
return result;
}
This works when the user writes in the specific fields but not when any of the field is empty.
Try like this
fullName: request.fullName ? request.fullName : '', // You can put null if you want
Note: Need to put in all
I know that question has already been asked a few times (like here, here or there, or even on Github, but none of the answers actually worked for me...
I am trying to develop authentication for a NodeJS app using Mongoose and Passport, and using Bcrypt-NodeJS to hash the users' passwords.
Everything was working without any problem before I decided to refactor the User schema and to use the async methods of bcrypt. The hashing still works while creating a new user but I am now unable to verify a password against its hash stored in MongoDB.
What do I know?
bcrypt.compare() always returns false whatever the password is correct or not, and whatever the password (I tried several strings).
The password is only hashed once (so the hash is not re-hashed) on user's creation.
The password and the hash given to the compare method are the right ones, in the right order.
The password and the hash are of type "String".
The hash isn't truncated when stored in the database (60 characters long string).
The hash fetched in the database is the same as the one stored on user's creation.
Code
User schema
Some fields have been stripped to keep it clear, but I kept the relevant parts.
var userSchema = mongoose.Schema({
// Local authentication
password: {
hash: {
type: String,
select: false
},
modified: {
type: Date,
default: Date.now
}
},
// User data
profile: {
email: {
type: String,
required: true,
unique: true
}
},
// Dates
lastSignedIn: {
type: Date,
default: Date.now
}
});
Password hashing
userSchema.statics.hashPassword = function(password, callback) {
bcrypt.hash(password, bcrypt.genSaltSync(12), null, function(err, hash) {
if (err) return callback(err);
callback(null, hash);
});
}
Password comparison
userSchema.methods.comparePassword = function(password, callback) {
// Here, `password` is the string entered in the login form
// and `this.password.hash` is the hash stored in the database
// No problem so far
bcrypt.compare(password, this.password.hash, function(err, match) {
// Here, `err == null` and `match == false` whatever the password
if (err) return callback(err);
callback(null, match);
});
}
User authentication
userSchema.statics.authenticate = function(email, password, callback) {
this.findOne({ 'profile.email': email })
.select('+password.hash')
.exec(function(err, user) {
if (err) return callback(err);
if (!user) return callback(null, false);
user.comparePassword(password, function(err, match) {
// Here, `err == null` and `match == false`
if (err) return callback(err);
if (!match) return callback(null, false);
// Update the user
user.lastSignedIn = Date.now();
user.save(function(err) {
if (err) return callback(err);
user.password.hash = undefined;
callback(null, user);
});
});
});
}
It may be a "simple" mistake I made but I wasn't able to find anything wrong in a few hours... May you have any idea to make that method work, I would be glad to read it.
Thank you guys.
Edit:
When running this bit of code, match is actually equal to true. So I know my methods are correct. I suspect this has something to do with the storage of the hash in the database, but I really have no idea of what can cause this error to occur.
var pwd = 'TestingPwd01!';
mongoose.model('User').hashPassword(pwd, function(err, hash) {
console.log('Password: ' + pwd);
console.log('Hash: ' + hash);
user.password.hash = hash;
user.comparePassword(pwd, function(err, match) {
console.log('Match: ' + match);
});
});
Edit 2 (and solution) :
I put it there in case it could be helpful to someone one day...
I found the error in my code, which was occurring during the user's registration (and actually the only piece of code I didn't post here). I was hashing the user.password object instead of user.password.plaintext...
It's only by changing my dependencies from "brcypt-nodejs" to "bcryptjs" that I was able to find the error because bcryptjs throws an error when asked to hash an object, while brcypt-nodejs just hashes the object as if it were a string.
I know the solution has been found but just in case you are landing here out of google search and have the same issue, especially if you are using a schema.pre("save") function, sometimes there is a tendency of saving the same model several times, hence re-hashing the password each time. This is especially true if you are using references in mongoDB to create schema relationship. Here is what my registration function looked like:
Signup Code
User.create(newUser, (err, user) => {
if (err || !user) {
console.warn("Error at stage 1");
return res.json(transformedApiRes(err, "Signup error", false)).status(400);
}
let personData: PersonInterface = <PersonInterface>{};
personData.firstName = req.body.first_name;
personData.lastName = req.body.last_name;
personData.user = user._id;
Person.create(personData, function (err1: Error, person: any): any {
if (err1 || !person) {
return res.json(transformedApiRes(err1, "Error while saving to Persons", false));
}
/* One-to-One relationship */
user.person = person;
user.save(function (err, user) {
if (err || !user) {
return res.json({error: err}, "Error while linking user and person models", false);
}
emitter.emit("userRegistered", user);
return res.json(transformedApiRes(user, `Signup Successful`, true));
});
});
});
As you can see there is a nested save on User because I had to link the User model with Person model (one-to-one). As a result, I had the mismatch error because I was using a pre-save function and every time I triggered User.create or User.save, the function would be called and it would re-hash the existing password. A console statement inside pre-save gave me the following, showing that indeed that password was re-hashed:
Console debug after a single signup call
{ plain: 'passwd',
hash: '$2b$10$S2g9jIcmjGxE0aT1ASd6lujHqT87kijqXTss1XtUHJCIkAlk0Vi0S' }
{ plain: '$2b$10$S2g9jIcmjGxE0aT1ASd6lujHqT87kijqXTss1XtUHJCIkAlk0Vi0S',
hash: '$2b$10$KRkVY3M8a8KX9FcZRX.l8.oTSupI/Fg0xij9lezgOxN8Lld7RCHXm' }
The Fix, The Solution
To fix this, you have to modify your pre("save") code to ensure the password is only hashed if it is the first time it is being saved to the db or if it has been modified. To do this, surround your pre-save code in these blocks:
if (user.isModified("password") || user.isNew) {
//Perform password hashing here
} else {
return next();
}
Here is how the whole of my pre-save function looks like
UsersSchema.pre("save", function (next: NextFunction): any {
let user: any = this;
if (user.isModified("password") || user.isNew) {
bcrypt.genSalt(10, function (err: Error, salt: string): any {
if (err) {
return next(err);
}
bcrypt.hash(user.password, salt, function (err: Error, hash: string) {
if (err) {
console.log(err);
return next(err);
}
console.warn({plain: user.password, hash: hash});
user.password = hash;
next();
});
});
} else {
return next();
}
});
Hopefully this helps someone.
I am dropping this here because it might help someone someday.
In my own case, the reason why I was having bcrypt.compare as false even when I supplied the right authentication details was because of the constraints on the datatype in the model. So each time the hash was saved in the DB, it was truncated in order to fit into the 50 characters constraints.
I had
'password': {
type: DataTypes.STRING(50),
allowNull: false,
comment: "null"
},
The string could only contain 50 characters but the result of bcrypt.hash was more than that.
FIX
I modified the model thus DataTypes.STRING(255)
bcrypt.hash() has 3 arguments... you have 4 for some reason.
Instead of
bcrypt.hash(password, bcrypt.genSaltSync(12), null, function(err, hash) {
it should be
bcrypt.hash(password, bcrypt.genSaltSync(12), function(err, hash) {
Since you were hashing only during user creation, then you might not have been hashing properly. You may need to re-create the users.
Tip: If you are switching
then().then()
Block always check return value.
You can always check the max length for the password field in the database. Make sure it is large. In my case, I have set it to 500. And then the code worked flawlessly!
TS version
const { phone, password } = loginDto;
const user = await this.usersService.findUserByPhone(phone);
const match = await compare(password, user.password);
if (user && match){
return user
}else{
throw new UnauthorizedException();
}
JS version
const { phone, password } = loginDto;
const user = await this.usersService.findUserByPhone(phone);
const match = await bcrypt.compare(password, user.password);
if (user && match){
return user
}else{
throw new UnauthorizedException();
}
I am getting data like this:
This is the code :
User.find({ Username: user }, function(err, found_user) {
console.log('user data'+ found_user );
if(found_user.length > 0){
console.log('inside found user');
var recordings = found_user.recordings;
console.log(recordings)
for (var singleRecords in recordings){
console.log("Single record :"+singleRecords);
if(!singleRecords.isPlayed){
console.log(singleRecords.playingUrl);
twiml.play(singleRecords.playingUrl);
found_user.recordings[singleRecords].isPlayed = true;
found_user.save(function (err) {
if(err)
throw err
});
}
}
}
And this is the value of found User :
user data { Username: 'B',
__v: 2,
_id: 58ac15e4b4e1232f6f118ba3,
recordings:
[ { isPlayed: false,
playingUrl: 'http://localhost:8000/public/toplay/playing_file_1487672817599.mp3' },
{ isPlayed: false,
playingUrl: 'http://localhost:8000/public/toplay/playing_file_1487672827411.mp3' } ]
}
inside found user
in variable found_user. But it is not giving me any data inside it. Like found_user.Username gives undefined value.
I want to store that recordings array inside a variable. Any idea how to do it ?
find() returns an array of docs that match the criteria in the callback hence the line
var recordings = found_user.recordings;
will not work as it's expecting a Document not an array.
You could use findOne() method which returns a document as:
User.findOne({ Username: user }.exec(function(err, found_user) {
console.log('user data'+ found_user );
if (found_user) {
console.log('inside found user');
var recordings = found_user.recordings;
console.log(recordings);
}
});
I am writing an API that takes data and stores it into DB, also edits it and deletes it, the add/delete works fine, but when I update I want to be able to update only certain attributes, and if the user doesn't send an attribute I want the code to keep the data already in the DB. Instead if I don't send an attribute it's overwritten by empty data.
Here's an example:
router.post('/update', function (req, res) {
var first_name = req.body.first_name,
last_name = req.body.last_name,
email = req.body.email,
phone_number = req.body.phone_number,
clas = req.body.clas,
subject = req.body.subject,
teacher_id = req.body.teacher_id;
req.assert('teacher_id', 'Invalid teacher_id').notEmpty();
var errors = req.validationErrors();
if (errors) {
res.json(400, {success: false, message: "please enter your teacher_id "});
return;
}
Teacher.findOne({_id: teacher_id}, function (err, teacher) {
if (err) {
console.log(err);
} else {
teacher.first_name = first_name != null || first_name
!= undefined ? first_name : teacher.first_name;
teacher.last_name = last_name != null || last_name
!= undefined ? last_name : teacher.last_name;
teacher.email = email != null || email
!= undefined ? email : teacher.email;
teacher.phone_number = phone_number != null || phone_number
!= undefined ? phone_number : teacher.pickup_points;
teacher.clas = clas != null || clas
!= undefined ? clas : teacher.clas;
teacher.subject = subject != null && subject
!= undefined ? subject : teacher.subject;
teacher.save(function (err, teacher) {
if (err) {
console.log(err);
} else {
res.json({success: true, message: "teacher successfully updated"});
}
});
}
});
});
This may not be the only way, but I use the Lodash library to make this kind of thing easy. I also use the 'param' route to populate the request with my object for me to save callbacks.
const _= require("lodash ")
router.param("teacherId", function(id, req,res,next){
Teacher.findOne({_id : id}, function(e, teacher){
//add error handling of your choice
req.teacher = teacher;
next();
})
})
router.put("teacher/:teacherId", function(req,res){
var updates = _.pick(req.body, ["name","age"]; // whitelist allowed attribute names to change
var teacher = _.merge(req.teacher, updates);
teacher.save(function(e){
// handle your response.
})
})
/* Edit */
Also, please note that in this solution, I'm assuming the use of (reasonably standard) REST-formatted routes. If you want to keep using router.post('/update'... that's fine, but you won't be able to separate out the teacher query like I did.
The advantage of the way I did it is that anytime you want to find a thing first (e.g. viewing a teacher, deleting a teacher, etc), you have the logic for doing so in one place, and don't have to repeat it in other handlers.
This method is a little different from what you are doing but I'd recommend this way to you so that you can keep your document models in one place and call them whenever you need them. Validations part can be handled using a separate module which is for mongoose.
The way you are updating it expects other variables to be assigned as it is, like I am fetching the document using findone then objectname.value = newobjectname.value
A simple way to solve this would be using findoneandupdate.
modelname.findOneAndUpdate({
primarykey: primarykey
}, {
$set: {
nameindocument: valuetobereplace
//you can add here values that you would like to change
}
}, {
new: true //new:true here helps to get updated object in return
}, function(err, doc) {
if (err) {
console.error("Error:" + err);
} else {
}
});
I am trying to create a nodejs application using mongodb as database. I need to check whether a username exist in mongodb or not. If username present, it will output "username not available", else will list all the user details with that username.
var userSchema = require('../schemas/user');
exports.collect = function(req,res) {
userSchema.find({ username: "bob" }).exec(function(err,display) {
if(err){
console.log(err);
}
else
{
if(display=='[]'){
res.send("username not available");
}
else{
res.send(display)
}
}
});
};
Is there any alternative or simple way for performing this operation?
I would decouple the whole logic from the controller if you ask me, but I'm not going to lecture you on that. I would use findOne as that will find just one record. If the user is not available, you will know that this username is not available. Don't forget that even when an error occurs, you still want to output "some" data, as you don't want the client to wait until it times out.
var userSchema = require('../schemas/user');
exports.collect = function(req,res) {
userSchema.findOne({username: "bob"}).exec(function(err, user) {
if (err) {
console.log(err);
// Handle the error properly here, we should not continue!
return res.sendStatus(500);
}
if (!user) {
return res.send("username not available");
}
// Don't know what you want to do with it, I just display it like this
return res.json(user);
});
};