How to do simple mongoose findOne with multiple conditions? - node.js

How can I perform this findOne query with multiple conditions? Or a substitute method (preferrably an equally simple one) if it is not possible...
User.findOne({ 'email': email }, function (err, user) {
if (err) {
request.session.flash = {
type: "danger",
message: "Failed to log you in, error occurred."
};
response.redirect("/snippet/");
}
I have tried
User.findOne({ 'email': email, 'pwd': pwd }, function (err, user) {
and
User.findOne({ 'email': email $and 'pwd': pwd }, function (err, user) {
and
User.findOne({ 'email': email}, $and: {'pwd': pwd}, function (err, user) {

Try using the mongoose promise system (.exec()). The query inside .findOne() should be a single object.
User
.findOne({email: email, pwd: pwd})
.exec(function(err, user){
...
});
Sidenote - it looks like this is for authenticating a a user login. It might be a better strategy to query just on the email, then try to match the passwords to give more depth to the error responses than a blanket 'Invalid login' type response.
User
.findOne({email: email}) //If not found, no user with that email exists
.exec(function(err, user){
var hashed_pwd = hashPassword(pwd);
if(!hashed_pwd === user.pwd){
//If they don't match, user entered wrong password
}
...
});

User.findOne({ 'email': email, 'pwd': pwd }, function (err, user) {
this syntax is correct. if this isn't returning any results, it means you didn't match any records.
my guess is that your pwd field is hashed / encrypted and that you need to run the same hash / encryption on your pwd variable before doing this findOne

When the mongoDB is returning your docs, if it has not found Data then it will return null .
You can check your docs if it is null then response with some other page.
On the other hand if docs have data respond with the next page.
if(docs)
respond("your content");
else
respond("error page incorrect credentials")
I was doing the mistake thinking that it will create an exception err, but it's not an error it is simply a null data.

Related

Looking up a mongoDB database

so I have implemented a register page that sends the inputs into a mongoDB atlas. However, I am trying to implement a new page where users are able to login. I am having trouble trying to find the specific key: value pair in order to check my inputs.
My mondoDB atlas shows the following:
_id: 62da20a99df697486c4b12cc
username: "hello"
password: "hello"
__v: 0
My function for logging in is the below:
exports.login = async (req, res) => {
if (await User.find({ username: req.body.username })) {
console.log("do smth");
}}
For now, lets just ignore the console.log() as I just want to see whether its going through or not. It's always evaluating to true and I don't know why. I can type any input and it always goes through but I'm not sure why its always evaluating to true so I know for sure I'm not looking up at the database correctly.
The
User.find()
where
User
is the name of the variable I defined when requiring the file I defined my schema. Any help would be appreciated in terms of how I should look up a key which in this case is username and I want to look up the req.params.username to see if that actually exists in my database.
Updated CODE
exports.login = async (req, res) => {
if (
User.findOne({ username: req.body.username }, function (err, user) {
console.log(user);
})
) {
res.send("YES");
} else {
res.send("NO");
}
If the input is in the database, it will log the user which in this case is
{
_id: new ObjectId("62daeb740c2c6e2b61325151"),
username: '123',
password: '123',
__v: 0
}
However, if its not in the database, it will log null but the if statement still evaluates to true.
User.find returns an array, which may be empty or not, but is never false in the Javascript sense. By contrast User.findOne returns an object, or null if nothing is found, and null does not satisfy the if condition. Probably that's what you want.
If User.findOne returns a Query, you can try invoking it with a callback function:
User.findOne({username: req.body.username}, function(err, user) {
if (err)
console.error(err);
else if (user) {
console.log("do smth");
}
});

When I get dublicate key error for email (unique) , I want to check another field in mongodb findoneandupdate

I am using mongodb with nodejs . I want to update user , if dublicate key error happens for email (unique) then check isdelete(not unique) field .If isdelete equals to true , update user otherwise not.
//user is a model
User.findOne({ 'email': req.body.email, 'isDeleted': true}, function (err, result) {
if(result) { //if user isdeleted true then update user
//write here your update code
}
});
User.findOneAndUpdate({ _id: userFindByid }, {
$set: {
"email": req.body.email,
"username": req.body.username,
"phone_number": req.body.phone_number,
"address": req.body.address,
"isBenefactor": req.body.isBenefactor,
"location": req.body.location
}
}, { runValidators: true, context: 'query' }, (err, doc) => {
if (err) {
// if request email has already exist in db I want to check that emails isDeleted field in here . if isDeleted is true I want to update .
return res.status(500).json({ message: err.message });
}
else {
return res.status(200).json({ message: 'Your account was updated' });
}
})
//
Let me explain scenario clearly,
I registered with an email address(first#gmail.com) then I deleted my account =>(first#gmail.com)=>isDeleted=true
After that I again registered with another email address(second#gmail.com)=>isDeleted=false
Now I want to update my second email address with first one I will get an unique key error because (first#gmail.com) is in mydb ,but I have to da update process because (first#gmail.com)=>IsDelete=true
If I use { 'email': req.body.email, 'isDeleted': true} I can not update (second#gmail.com)=>isDeleted=false
I can fix the problem by using too much if statements , but I dont want to use if statements too much. I am looking for best practice for that problem.
I hope I could explain

nodejs passport - use same routes in api but return different sets of data based on permissions

Not sure of a clean way to go about his. Let's say I have this endpoint:
GET /api/Books/
For the user on the webservice, this will return only the user's resources. It might look a little something like this:
exports.getBooks = function(req, res) {
// find all books for user
BookModel.find({ userId: req.user._id }, function(err, books) {
if (err)
res.send(err);
res.json(books);
});
};
The web service using the api needs the user to be logged in first. I can achieve this using a basic passport strategy to ensure authentication. But let's say I have an admin account that needs to see ALL books ever recorded. What's more is that the admin account and user accounts have completely different properties so assigning a Boolean for permissions is not enough. Using the same endpoint:
GET /api/Books
I see no reason to write another endpoint to achieve this. However the difference would look like this:
exports.getBooks = function(req, res) {
// find all books in the database
BookModel.find({}, function(err, books) {
if (err)
res.send(err);
res.json(books);
});
};
However I cannot come up with a clean way to achieve this while also using the passport middlewear as it is intended like so:
router.route('/books')
.post(authController.isAuthenticated, bookController.postBooks)
.get(authController.isAuthenticated, bookController.getBooks);
The function isAuthenticated will will only verify whether or not the user requesting resources has permission and does not change the way the controller behaves. I'm open to ideas.
ANSWER
The user #ZeroCho suggested to check user properties in req.user object to determine what should be sent back. This was more simple than I expected. In my implementation for passport.BasicAuth strategy, I check which table has a matching doc. Once the user is found in the common user or Admin user table all you do is add a property in the isMatch return object.
// Basic strategy for users
passport.use('basic', new BasicStrategy(
function(email, password, done) {
verifyUserPassword(email, password,
function(err, isMatch) {
if(err) { return done(err); }
// Password did not match
if(!isMatch) { return done(null, false); }
// Success
var userInfo = {
email: email,
isAdmin: isMatch.isAdmin || false,
businessID: isMatch.businessID || false
};
return done(null, userInfo);
});
})
);
Then you can check if .isAdmin or .businessID is valid in your requests.
Just separate your controller by if statement
exports.getBooks = function(req, res) {
if (req.user.isAdmin) { // or some other code to check user is admin
// find all books in the database
BookModel.find({}, function(err, books) {
if (err)
res.send(err);
res.json(books);
});
} else {
BookModel.find({ userId: req.user._id }, function(err, books) {
if (err)
res.send(err);
res.json(books);
});
}
};

Bcrypt-NodeJS compare() returns false whatever the password

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

How to access Mongoose Schema Attributes?

I am fairly new to using Mongoose and MongoDB. I am working on a register / login page. The register function works fine, storing the user account onto the database, but my problem is with logging in. What I am trying to do is get the 'password' attribute from the matching user off of the database, to match against the password that the user enters. This is my login function.
router.post('/logSubmit', function(req, res, next) {
var gusername = req.body.username;
var gpassword = req.body.password;
User.count({
'credentials.username': gusername
}, function(err, count) {
if (err) throw err;
console.log(count);
if (count > 0) {
// Where I need to pull password attribute from the database
} else {
// Wrong username or password
}
});
});
I have looked all over the internet on how to read an attribute from a database entry, but I can't find anything. I feel like it is something very simple, but I guess I don't know the syntax. The name of my model is User. I figured it would be something like:
User.find({ username: gusername }, function(err, user) {
if (err) throw err;
var getpassword = user.password;
console.log(getpassword);
});
I half-thought that would work, but it didn't. How do I access the password attribute from the database?? Thanks.
EDIT:
This is what my user accounts look like stored in my database:
{
"_id": {
"$oid": "569e5344d4355010b63734b7"
},
"credentials": {
"username": "testuser",
"password": "password1234"
},
"__v": 0
}
A find query is sufficient for your purposes. If a non-null user object is retrieved from the find query, you have a guarantee that it is a user with a password.
User.find({ 'credentials.username': gusername }, function(err, users) {
if (err) throw err;
// 'users' is an array of the User objects retrieved.
users.forEach(function(user) {
// Do something with the password.
// The password is stored in user.credentials.password
console.log(user.credentials.password);
});
});

Resources