{select: false} works differently on mongoose queries - node.js

I am trying to create a user in mongoose and return it after User.create query without password field. I set "select: false" on password field in model schema but it keeps returning me password in response after User.create.
// models/user.js
const userSchema = new mongoose.Schema({
// ...
password: {
type: String,
required: true,
minlength: 5,
select: false,
},
});
// routes/index.js
routes.post(
"/sign-up",
celebrate({
body: Joi.object().keys({
name: Joi.string().min(2).max(30),
about: Joi.string().min(2).max(30),
avatar: Joi.string().pattern(RegExp(urlChecker)),
email: Joi.string().required().email(),
password: Joi.string().required().min(5),
}),
}),
usersController.createUser,
);
// controllers/user.js
const User = require("../models/user");
exports.createUser = (req, res, next) => {
const {
name,
about,
avatar,
email,
password,
} = req.body;
bcrypt
.hash(password, 10)
.then((hash) => User.create({
name,
about,
avatar,
email,
password: hash,
}))
.then((user) => {
if (!user) {
throw new InvalidInputError("Invalid data");
}
res.send(user); // response includes password field
})
.catch((err) => next(err));
};
However, if I add User.findById query after User.create, I get a response without password field.
// controllers/user.js
// ...
.then((user) => {
if (!user) {
throw new InvalidInputError("Invalid data");
}
return User.findById(user._id);
})
.then((user) => {
if (!user) {
throw new NotFoundError("User not found");
}
res.send(user); // works fine!
})
Am I right that {select: false} works only on find queries in mongoose? Are there any other workarounds for not returning password field after User.create method?

the result of save is a object model, you should convert it to a object and delete password key, like this:
user = user.toObject()
delete user.password
res.send(user); // response includes password field

Related

How to exclude password from user object sent in response in express.js?

I am returning a response object with the user and jwt token whenever a user logs in. But I want to exclude the password field when the user is sent in the response.
Controller:
const loginUser = asyncHandler(async (req, res) => {
const { email, password } = req.body;
// Check for user email
const user = await User.findOne({ email });
if (user && (await bcrypt.compare(password, user.password))) {
res.json({
user: user,
token: generateToken(user._id),
});
} else {
res.status(400);
throw new Error("Invalid credentials");
}
});
if I exclude the password when finding the user like this:
const user = await User.findById(decoded.id).select("-password");
Then bcrypt's compare method will not work as it needs user.password.
Please tell me how I can exclude password from user object in JSON response?
You can set undefined to the user before returning the user.
const loginUser = asyncHandler(async (req, res) => {
const { email, password } = req.body;
// Check for user email
const user = await User.findOne({ email });
if (user && (await bcrypt.compare(password, user.password))) {
user.password = undefined;
res.json({
user: user,
token: generateToken(user._id),
});
} else {
res.status(400);
throw new Error('Invalid credentials');
}
});
Or you can use the toJSON method in the user schema to exclude password:
const userSchema = new mongoose.Schema(
{
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
},
{
toJSON: {
transform(doc, ret) {
delete ret.password;
},
},
},
);

Error: Illegal arguments: string, undefined

Matching User while logging in
const LocalStrategy = require('passport-local').Strategy;
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
// Load User Model
const User = require('../models/User');
module.exports = function(passport) {
passport.use(
new LocalStrategy({ username: 'username' }, (username,
password, done) => {
// Match User
User.findOne({ username: username })
.then(user => {
if (!user) {
return done(null, false, { message: 'username is
not registered' });
}
// Match Password
bcrypt.compare(password, user.hashedPassword, (err,
isMatch) => {
if (err) throw err;
if (isMatch) {
return done(null, user);
} else {
return done(null, false, { message:
'Password incorrect' });
}
});
})
.catch(err => console.error(err))
})
);
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(err, user);
});
});
}
Validation pass while registering user
User.findOne({ username: username })
.then(async user => {
if (user) {
//User exists
errors.push({ msg: "username has already taken" });
res.render('register', {
errors,
username,
password
});
} else {
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password,
salt);
const newUser = new User({
username: username,
password: hashedPassword,
});
newUser.save()
.then(user => {
// res.status(201).json(user);
req.flash('success_msg', 'You are now
registered')
res.redirect('/users/login');
})
.catch(err => console.log(err));
}
})
.catch(err => {
console.log(err);
})
}
../models/User
// Importing modules
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const UserSchema = new mongoose.Schema({
username: {
type: String,
lowercase: true,
unique: true,
// required: [true, "can't be blank"],
match: [/^[a-zA-Z0-9]+$/, 'is invalid'],
index: true
},
password: {
type: String,
required: true
},
Date: {
type: Date,
default: Date.now
}
}, { timestamps: true });
// export userschema
module.exports = mongoose.model('User', UserSchema);
why is it showing the error "Error: Illegal arguments: string, undefined". The same error was throwing when saving the password as hash in DB and now the same error throwing while comparing the hashed password. I checked the documentation, the format is the same but I think there is a syntactical error.
In the definition of the User data type, only the fields username and password exist. There is no field hashedPassword. However, you're trying to access this field in the line saying bcrypt.compare(password, user.hashedPassword ....
You're stroring the hased password in the password field here:
const newUser = new User({
username: username,
password: hashedPassword,
});
So later, you also need to read it from the password field:
bcrypt.compare(password, user.password ...

FindOneAndUpdate MongoDB with Mongoose updating user's profile not working with email?

I'm building a REST API in Express JS and Mongo DB. I'm utilising the Mongoose package to interface with the database and am trying to implement a robust endpoint for allowing a user to update details about their account, for instance, their name.
My endpoint is setup, and I can send a request, however, it only seems to work if I don't pass through the email and password of the user I want to update (bearing in mind I need to check that the email they're changing it to doesn't already exist)
router.patch('/update', verify, async (req, res) => {
// validate incoming data
const { error } = registerValidation(req.body)
if (error) return res.status(400).send(error.details[0].message)
// check if user already exists
const emailExists = await User.findOne({ email: req.body.email })
if (emailExists) return res.status(400).send('Email already exists')
// hash password
const salt = await bcrypt.genSalt(10)
const hashPassword = await bcrypt.hash(req.body.password, salt)
// update user
const user = new User({
name: req.body.name,
email: req.body.email,
password: hashPassword
})
User.findOneAndUpdate({ _id: req.user.id }, user, { runValidators: true, useFindAndModify: false, new: true }, (error, result) => {
if (error) {
console.log('error')
return res.status(400).send(error)
}
res.status(200).send(result)
});
})
For a starters, I'm getting a returned 1 instead of an object, but earlier I was getting a Mongo DB error with the code of 66 for some reason...
What's going on here?
UPDATE
When sending my body to update a user, I get a response of: {"operationTime":"6845592053464694785","ok":0,"code":66,"codeName":"ImmutableField","$clusterTime":{"clusterTime":"6845592053464694785","signature":{"hash":"+uHFhNsV5B60qR/Yhd2qg8Cd6jA=","keyId":"6843771360993345539"}},"name":"MongoError"}, even though I've only edited the name out of the three fields
First, you should get a field to make a query. You should know the _id or you should know the previous email or any unique field value.
You created a new user by the following code. But this created user also will have a unique id.
const user = new User({
name: req.body.name,
email: req.body.email,
password: hashPassword
})
But if you need to update, you should not create a new user.
A user never knows his id. So you should query your data by using any other field like email. You may be in need of this code
await User.findByIdAndUpdate({email:req.body.existingemail},{
name: req.body.name,
email: req.body.email,
password: hashPassword
})
So the code which will help you may be like this,
router.patch('/update', verify, async (req, res) => {
// validate incoming data
const { error } = registerValidation(req.body)
if (error) return res.status(400).send(error.details[0].message)
// check if user already exists
const emailExists = await User.findOne({ email: req.body.email })
if (emailExists) return res.status(400).send('Email already exists')
// hash password
const salt = await bcrypt.genSalt(10)
const hashPassword = await bcrypt.hash(req.body.password, salt)
// update user
const user ={
name: req.body.name,
email: req.body.email,
password: hashPassword
}
//make sure you pass existingemail in postman
User.findOneAndUpdate({ email:req.body.existingemail }, user, { runValidators: true, useFindAndModify: false, new: true }, (error, result) => {
if (error) {
console.log('error')
return res.status(400).send(error)
}
res.status(200).send(result)
});
})

How to check if username already exist in database collection MongoDB

I'm making post request on registration,But I want error to pop up if username is already taken.
Any suggestions?
Here is my post route:
app.post('/addUser', (req,res) => {
const addUser = new User({username: req.body.username, password: req.body.password})
addUser.save().then(result => res.status(200).json(result)).catch((err) => console.log(err))
})
Alternate method, depending on the error style you want.
const users = new mongoose.Schema(
{
username: {type: String, unique: 'That username is already taken'}
},
{ timestamps: true }
)
Now mongo will index usernames and check it before the insertion. An error will be thrown if it's not unique.
You can use findOne method of mongoose
app.post('/addUser', async (req,res) => {
//validation
var { username, password } = req.body;
//checking username exists
const existUsername = await User.findOne({ username: req.body.username});
if (existUsername) {
console.log('username taken');
}
});

Finding _id of user just created in a route

I am making a rapid prototype of a MERN application, I have a backend question: I have a User model and a Category model, when a user sign up, I need to fill the category model with some basic informations exported from an object the user can edit later. I would like to assign every category a ref to the just created account id. The problem is I don't understand how I can retrieve the just created user id.
Here is my route (yes is not refactored, sorry):
// #route POST api/users/register
// #desc Register user
// #access Public
router.post('/register', (req, res)=>{
//Validate req.body
const {errors, isValid} = validateRegisterInput(req.body);
//Check validation
if(!isValid){
return res.status(400).json(errors); 
}
User.findOne({ email: req.body.email })
.then(user => {
if(user){
errors.email = 'Email already exists';
return res.status(400).json(errors)
} else {
const avatar = gravatar.url(req.body.email, {
s: '200', //Size
r: 'pg', //Rating
d: 'mm' //Default
});
const newUser = new User({
name: req.body.name,
email: req.body.email,
avatar,
password: req.body.password
});
//Hash the password and save
bcrypt.genSalt(10, (err, salt)=>{
bcrypt.hash(newUser.password, salt, (err, hash)=>{
if(err) throw err;
newUser.password = hash;
newUser.save()
.then(user => res.json(user))
.catch(err => console.log(err))
})
});
//Fill user categories with default categories
defaultIcons.map((icon) => {
const newCategory = new Category ({
name: icon.name,
type: icon.type,
icon: icon.icon
})
newCategory.save();
});
}
})
});
And this is the Category Schema:
//Create Schema
const CategorySchema = new Schema({
//Every account is associated with actual User schema by id
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
name: {
type: String,
required: true
},
type: {
type: String,
required: true
},
icon: {
type: String,
required: true
}
})
What would be the best solution for this? Is it better to have a separated schema for the categories like I am doing or I can implement an array of objects field in the User schema?
The part where you do
if(err) throw err;
newUser.password = hash;
newUser.save()
.then(user => res.json(user))
.catch(err => console.log(err))
you have the user in your resolved promise. you can just
const newCreatedUserID = user._id
to get the just created user ID.

Resources