Node.js, Mongoose: Cannot read properties of null (reading 'comparePassword') - node.js

I was attempting to updateUserPassword in my userController.js and came across the following error. Unsure what kind of error this could be. Any input and knowledge would be appreciated.
Getting this error on postman
{
"msg": "Cannot read properties of null (reading 'comparePassword')"
}
Body
{
"oldPassword":"secret",
"newPassword":"newsecret"
}
userController.js
const updateUserPassword = async (req, res) => {
const { oldPassword, newPassword } = req.body;
if (!oldPassword || !newPassword) {
throw new CustomError.BadRequestError('Please provide both values');
}
const user = await User.findOne({ _id: req.user.userId });
const isPasswordValid = await user.comparePassword(oldPassword);
if (!isPasswordValid) {
throw new CustomError.UnauthenticatedError('Invalid Credentials');
}
user.password = newPassword;
await user.save();
res.status(StatusCodes.OK).json({ msg: 'Success! Password Updated.' });
};
Github: https://github.com/k4u5hik/node-express-course

You need to check whether the user is exists or not before calling user.camparePassword, for example:
const user = await User.findOne({ _id: req.user.userId });
if (!user) {
throw new CustomError.UserNotFound();
}
const isPasswordValid = await user.comparePassword(oldPassword);

Related

SyntaxError: await is only valid in async functions and the top level bodies of modules - NodeJS

I am trying to create user login and sign up in NodeJS with mongoDB, but in login module i am getting this error -
existingUser = await User.findOne({email: email});
^^^^^
SyntaxError: await is only valid in async functions and the top level
bodies of modules.
Here is my code of "user-controller.js" file code.
const User = require('../model/User');
const bcrypt = require('bcryptjs');
// next is used to move to the next middleware task
const signup = async (req, res, next) => {
const { name, email, password } = req.body;
let existingUser;
try {
existingUser = await User.findOne({ email: email });
} catch (err) {
console.log(err);
}
if (existingUser) {
return res.status(400).jason({ message: 'User already exists! Login Instead' })
}
const hashedPassword = bcrypt.hashSync(password);
const user = new User({
name,
email,
password: hashedPassword,
});
try {
await user.save();
} catch (err) {
console.log(err);
}
return res.status(201).json({ message: user })
};
const login = (req, res, next) => {
const { email, password } = req.body;
let existingUser;
try {
existingUser = await User.findOne({ email: email });
} catch (err) {
return new Error(err);
}
if (!existingUser) {
return res.status(400).json({ message: "User not found. Signup Please" })
}
const isPasswordCorrect = bcrypt.compareSync(password, existingUser.password);
if (!isPasswordCorrect) {
return res.status(400).json({ message: "Invalid Email / Password" })
}
return res.status(200).json({ message: "Successfully logged in" })
}
exports.signup = signup;
exports.login = login;
How to resolve it?
We can only use await inside an async function, in your case
const login = async (req, res, next) => {
// We can use await here
}
Instead of try catch we can do something like this
try {
User.findOne({ email: email }).then((response)=>{
//do something
});
} catch (err) {
//do something
}

Error: Invalid email/password combination with Node.js and MongoDB

Am trying to login my admin , i defined the login credentials both in the mongodb and in the .env so here is the code which has a problem.
const Admin = require('../models/admin');
const Voters = require('../models/voters');
const bcrypt = require('bcrypt');
exports.checkCredentials = async (req, res, next) => {
const email = req.body.email;
const password = req.body.password;
Admin.findOne({ email: email }).exec(async (error, adminData) => {
if (error) {
// some error occured
return res.status(400).json({ error });
}
if (adminData) {
// email is correct checking for password
const match = await bcrypt.compare(password, adminData.password);
if (match) {
req.adminID = adminData._id;
next();
} else {
return res.status(200).json({
msg: 'Invalid email/password combination yyy',
});
}
} else {
// no data found for given email
return res.status(200).json({
msg: 'Invalid email/password combination !!!!',
});
}
});
};
exports.verifyVoter = async (req, res, next) => {
let query;
if (req.query.voterID) {
query = {
voterID: req.query.voterID,
};
} else {
query = {
phone: req.body.phone,
};
}
console.log(query);
Voters.findOne(query).exec(async (error, voterData) => {
if (error) {
// some error occured
return res.status(400).json({ error });
}
if (voterData) {
// Voter found
if (voterData.hasRegistered === true) {
return res.status(200).json({
msg: 'Voter already registered',
});
} else {
req.phone = voterData.phone;
req.district = voterData.pinCode;
req._id = voterData._id;
next();
}
} else {
// no data found for given Voter
return res.status(200).json({
msg: 'Invalid VoterID',
});
}
});
};
that code above brings an error but this is how i defined my admin credentials in the .env
ADMIN_EMAIL = bkroland19#gmail.com
ADMIN_PASSWORD =felinho/013
and this is how i defined them in mongodb
{
"email": "bkroland19#gmail.com",
"password": "felinho/013"
}
and this is the resulting error i get yet the email am entering matches those two emails.
Any help please
Am expecting to be allowed to login in when i enter the credentials as they are in the mongodb database
If you store the password in cleartext you don't need bcrypt.compare:
const match = password === adminData.password;
if (match) {
req.adminID = adminData._id;
next();
}
Anyway, it is strongly suggested to encrypt it, you can do it with:
const salt = await bcrypt.genSalt(12);
const encryptedPassword = await bcrypt.hash(password, salt);
const user = await Admin.create({ email, password: encryptedPassword });

Mongoose return "new ObjectId("//id")" instead of just the id

I am trying to do my login function (I am using bcrypt and jsonwebtoken) the problem is that console.log (user._id) returns me "new ObjectId (" 6148f043ebbaa0ab41ac8499 ")" instead of just "6148f043ebbaa0ab41ac8499" , which would be easier for the creation of the token.
module.exports.login = async (req, res) => {
const { email, password } = req.body;
// Compare the req.body.password to the hashed password in DB
const user = await UserModel.findOne({ email: email });
const match = await bcrypt.compare(password, user.password);
if (match) {
try {
const user = await UserModel.findOne({ email: email });
console.log(user._id);
// Assign a token
const token = jwt.sign({ userId: user._id }, process.env.LOGIN_TOKEN, {
expiresIn: "1h",
});
console.log(token);
res.cookie("jwt", token, { httpOnly: true});
res.status(200).json({ user: user._id });
} catch (err) {
res.status(500).json(err);
}
} else {
res.status(500).json({ message: "error!!!" });
}
};
How to fix this please?
That is a normal behaviour. Since you got an ObjectId, you can convert it to a string by calling the toHexString() method on it. I have also modified the code to check for an undefined user, and removed the extra call to find a user since you already did in the previous line. Please see updated code:
module.exports.login = async (req, res) => {
const { email, password } = req.body;
const user = await UserModel.findOne({ email: email });
if (!user) {
return res.status(400).json({ message: "Unauthorised"});
}
// Compare the req.body.password to the hashed password in DB
const match = await bcrypt.compare(password, user.password);
if (match) {
try {
// Convert user id (ObjectId) to a string
const userId = user._id.toHexString();
// Now user id is a string
console.log(userId);
// Assign a token
const token = jwt.sign({ userId }, process.env.LOGIN_TOKEN, {
expiresIn: "1h",
});
console.log(token);
res.cookie("jwt", token, { httpOnly: true});
res.status(200).json({ user });
} catch (err) {
res.status(500).json(err);
}
} else {
res.status(400).json({ message: "Unauthorised" });
}
};
use the following
const id = _id.toHexString();

got `{}` when set a field in mongodb to `undefined` using mongoose

I'm facing a weird problem
I'm using mongoose 5.12.8, I try to delete a user's image with this route
router.delete('/users/me/avatar', auth, async(req, res) => {
try {
req.user.avatarObj = undefined
await req.user.save()
res.send({ message: "Deleted user's avatar" })
} catch (error) {
res.status(500).send(error.message)
}
});
but when I use get route, i got req.user.avatarObj is still an empty object {}
//get user avatar
router.get('/users/:id/avatar', async(req, res) => {
try {
const user = await User.findById(req.params.id);
console.log(user.avatarObj) // print {} ?????????
console.log(user) //user has no avatarObj field
if (user && user.avatarObj) {
res.set('Content-Type', user.avatarObj.contentType);
res.send(user.avatarObj.data);
} else {
throw new Error('Not found user or image');
}
} catch (err) {
res.status(404).send({ error: err.message });
}
});
this is my userSchema
avatarObj: {
data: Buffer,
contentType: String
}
and some middleware
//hash plain text password before saving
userSchema.pre('save', async function(next) {
const user = this
if (user.isModified('password')) {
user.password = await bcrypt.hash(user.password, 10)
}
next()
});
//remove all tasks before remove user
userSchema.pre('remove', async function(next) {
await Task.deleteMany({ owner: this._id })
next()
});
My question is: Why user.avatarObj is still an empty object?
Thank you guys!

How can I implement transaction concept in mongoose model?

I have three models "userLogin.js","userDetail.js",and "userAddress.js".I want data should be stored simultaneously, if any error occurs it should rolback all the insert actions.this what I have tried. I gives me the error user is not defined . when try to fix them it gives the error "schema is not registered"
const UserLogin=require("../models/userLogin");
const UserDeatil=require("../models/userDetail");
var myModelSchema1 = require('mongoose').model('UserLogin').schema;
var myModelSchema2 = require('mongoose').model('UserDeatils').schema;
exports.user_signup = (req, res, next) => {
UserLogin.find({ email: req.body.email })
.exec()
.then(user => {
if (user.length >= 1) {
return res.status(409).json({
message: "Mail exists"
});
} else {
bcrypt.hash(req.body.password, 10, (err, hash) => {
if (err) {
return res.status(500).json({
error: err
});
} else {
const user = new UserLogin({
_id: new mongoose.Types.ObjectId(),
email: req.body.email,
password: hash,
loginDate:req.body.logindate,
});
const userdetils = new UserDeatil({
_id: new mongoose.Types.ObjectId(),
userId:result.userID,
userName:req.body.username,
dob:req.body.dob,
gender:req.body.gender,
photo: req.file? req.file.path : null,
imei:req.body.imei,
});
insertUsers();
}
});
}
});
};
async function insertUsers(){
try{
const id= transaction.insert(myModelSchema1, user);
const id1= transaction.insert(myModelSchema2, userdetils);
const final = await transaction.run();
}
catch(error){
console.error(error);
const rollbackObj = await transaction.rollback().catch(console.error);
transaction.clean();
c
}
}
first when you define your users schema the email must be uniqe wich when fails when you tries to create anothe user document with the same email,
and with this convention you can move forward like this:
const UserLogin=require("../models/userLogin");
const UserDeatil=require("../models/userDetail");
cosnt signup = async (req ,res)=>{
const { email , password ,...details} = req.body
const createdDocs = []
const hashedPwd = hash(password);
try{
const user = new UserLogin({ email , password: hashedPwd });
await user.save()
createdDocs.push(user)
const userDetails = new UserDetails({...details,userId:user._id});
await userDetails.save()
createdDocs.push(userDetails)
catch(err){
res.json({ status:false, message:err.message})
//emulates the rollback when any thing fails on the try flow
if(createdDocs.length){
const operationsToRollBack = createdDocs.map(doc=>doc.remove)
await Promise.all(operationsToRollBack)
}
}
MongoDB supports multi-document transactions starting from version 4.0.
Ideally, if you need a transactional database you would use an SQL type db.
But if you would still like to enjoy MongoDB while needing transactions, they have introduced an API for this - https://docs.mongodb.com/manual/core/transactions/

Resources