multiple responses Cannot set headers after they are sent to the client - node.js

I wrote the following signup function.
It works fine with Postman but when I've added the code between "//send the cookie with the token" and "// end", I got this error message : "(node:11748) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client".
From what I saw here in stackoverflow, this error occurs because of multilple res. but I can't find how to rearrange the code so that I avoid this error.
exports.signup = async(req, res) => {
const { firstName, familyName, email, password, role } = req.body;
console.log("image", req.file);
try {
const user = await User.findOne({ attributes: ['email'], where: { email: email } });
if (user) {
fs.unlinkSync(req.file.path);
return res.status(409).send('This email already exists!');
} else {
const hashPass = await bcrypt.hash(password, 10);
const userObject = {
firstName: firstName,
familyName: familyName,
email: email,
password: hashPass,
role: role,
photoUrl: req.file ? `${req.protocol}://${req.get('host')}/images/${req.file.filename}` : null,
};
console.log("photo", userObject.photoUrl);
console.log("userObject", userObject);
const createdUser = await User.create(userObject);
const newToken = jwt.sign({ userId: user.id },
process.env.COOKIE_KEY, { expiresIn: "24h" }
);
const newCookie = { token: newToken, userId: createdUser.id };
const cryptedToken = cryptojs.AES.encrypt(JSON.stringify(newCookie), process.env.COOKIE_KEY).toString();
res.cookie('snToken', cryptedToken, {
httpOnly: true,
maxAge: 86400000 // 24h
});
res.status(200).send({ message: 'The user is successfully connected!', data: createdUser, cryptedToken: cryptedToken });
}
} catch (error) {
return res.status(500).send({ error: 'An error has occured while trying to sign up!' });
}
}

It's not related to "multiple request". In a successful case (also on error), you wrote status more than once.
for example in a successful case:
first you returned 201 after creating the user (which returned a response to the client)
res.status(201).send({message: User ${req.userObject.firstName} ${req.userObject.familyName} was registred }, createdUser);
and then
and here you tried to send another response at the end of the request
res.status(200).send({ message: 'The user is successfully connected!', data: user, cryptedToken: cryptedToken });
and you got the right error Cannot set headers after they are sent to the client, since it already returned 201 after Creation.
try to do something like:
try {
const hashPass = await bcrypt.hash(password, 10);
const userObject = { firstName: firstName,
familyName: familyName,
email: email,
password: hashPass,
role: role,
photoUrl: req.file ? `${req.protocol}://${req.get('host')}/images/${req.file.filename}` : null,
};
console.log("photo", userObject.photoUrl);
console.log("userObject", userObject);
const createdUser = await User.create(userObject);
//send the cookie with the token
const newToken = jwt.sign({ userId: user.id },
process.env.COOKIE_KEY, { expiresIn: "24h" }
);
const newCookie = { token: newToken, userId: user.id };
const cryptedToken = cryptojs.AES.encrypt(JSON.stringify(newCookie), process.env.COOKIE_KEY).toString();
res.cookie('snToken', cryptedToken, {
httpOnly: true,
maxAge: 86400000 // 24h
});
res.status(200).send({ message: 'The user is successfully connected!', data: user, cryptedToken: cryptedToken });
}
catch (error) {
return res.status(500).send({ error: 'An error has occured while trying to sign up!' });
}

Related

While Verifying email and reseting password, only the first created/registered user is being valid

Scenario : When I create/register user1 ,the verification mail is sent to that email id and he(user1) is being verified successfully and I am able to change password for user1.
After creating user1 , I am creating/registering user2 ,where the verification email is sent to the account .After clicking the link , it's becomes INVALID
Overall , I am only able to create one user
Languages used : MERN stack
Backend => route.js :
const express = require("express");
const router = express.Router();
const User = require("../models/userModel");
const Doctor = require("../models/doctorModel");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const authMiddleware = require("../middlewares/authMiddleware");
const sendEmail = require("../utils/sendMail");
const Token = require("../models/tokenModel");
const Appointment = require("../models/appointmentModel");
const moment = require("moment");
router.post("/register", async (req, res) => {
try {
const userExists = await User.findOne({ email: req.body.email });
if (userExists) {
return res
.status(200)
.send({ message: "User already exists", success: false });
}
const password = req.body.password;
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password, salt);
req.body.password = hashedPassword;
const newuser = new User(req.body);
const result = await newuser.save();
await sendEmail(result, "verifyemail");
res
.status(200)
.send({ message: "User created successfully", success: true });
} catch (error) {
console.log(error);
res
.status(500)
.send({ message: "Error creating user", success: false, error });
}
});
router.post("/login", async (req, res) => {
try {
const result = await User.findOne({ data: req.body.userId });
console.log(result);
const user = await User.findOne({ email: req.body.email });
if (!user) {
return res
.status(200)
.send({ message: "User does not exist", success: false });
}
if (user.isVerified === false) {
return res
.status(200)
.send({ message: "User not Verified", success: false });
}
const isMatch = await bcrypt.compare(req.body.password, user.password);
if (!isMatch) {
return res
.status(200)
.send({ message: "Password is incorrect", success: false });
} else {
const dataToBeSentToFrontend = {
id: user._id,
email: user.email,
name: user.name,
};
const token = jwt.sign(dataToBeSentToFrontend, process.env.JWT_SECRET, {
expiresIn: "1d",
});
res
.status(200)
.send({ message: "Login successful", success: true, data: token });
}
} catch (error) {
console.log(error);
res
.status(500)
.send({ message: "Error logging in", success: false, error });
}
});
router.post("/get-user-info-by-id", authMiddleware, async (req, res) => {
try {
const user = await User.findOne({ _id: req.body.userId });
user.password = undefined;
if (!user) {
return res
.status(200)
.send({ message: "User does not exist", success: false });
} else {
res.status(200).send({
success: true,
data: user,
});
}
} catch (error) {
res
.status(500)
.send({ message: "Error getting user info", success: false, error });
}
});
router.post("/send-password-reset-link", async (req, res) => {
try {
const result = await User.findOne({ email: req.body.email });
await sendEmail(result, "resetpassword");
res.send({
success: true,
message: "Password reset link sent to your email successfully",
});
} catch (error) {
res.status(500).send(error);
}
});
router.post("/resetpassword", async (req, res) => {
try {
const tokenData = await Token.findOne({ token: req.body.token });
if (tokenData) {
const password = req.body.password;
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password, salt);
await User.findOneAndUpdate({
_id: tokenData.userid,
password: hashedPassword,
});
await Token.findOneAndDelete({ token: req.body.token });
res.send({ success: true, message: "Password reset successfull" });
} else {
res.send({ success: false, message: "Invalid token" });
}
} catch (error) {
res.status(500).send(error);
}
});
router.post("/verifyemail", async (req, res) => {
try {
const tokenData = await Token.findOne({ token: req.body.token });
if (tokenData) {
await User.findOneAndUpdate({ _id: tokenData.userid, isVerified: true });
await Token.findOneAndDelete({ token: req.body.token });
res.send({ success: true, message: "Email Verified Successlly" });
} else {
res.send({ success: false, message: "Invalid token" });
}
} catch (error) {
res.status(500).send(error);
}
});
Backend => sendEmail.js :
const nodemailer = require("nodemailer");
const bcrypt = require("bcrypt");
const Token = require("../models/tokenModel");
module.exports = async (user, mailType) => {
try {
const transporter = nodemailer.createTransport({
service: "gmail",
host: "smtp.gmail.com",
port: 587,
secure: true,
auth: {
user: "sh***********th#gmail.com",
pass: "e**************l",
},
});
const encryptedToken = bcrypt
.hashSync(user._id.toString(), 10)
.replaceAll("/", "");
const token = new Token({
userid: user._id,
token: encryptedToken,
});
await token.save();
let mailOptions, emailContent;
if (mailType === "verifyemail") {
emailContent = `<div><h1>Please click on the below link to verify your email address</h1> ${encryptedToken} </div>`;
mailOptions = {
from: "sh************th#gmail.com",
to: user.email,
subject: "Verify Email For MERN Auth",
html: emailContent,
};
} else {
emailContent = `<div><h1>Please click on the below link to reset your password</h1> ${encryptedToken} </div>`;
mailOptions = {
from: "shanshangeeth#gmail.com",
to: user.email,
subject: "Reset Password",
html: emailContent,
};
}
await transporter.sendMail(mailOptions);
} catch (error) {
console.log(error);
}
};
// auth: {
// user: "shanshangeeth#gmail.com",
// pass: "erwsvgtamrplzssl",
// },
Backend => authMiddleware.js :
const jwt = require("jsonwebtoken");
module.exports = async (req, res, next) => {
try {
const token = req.headers["authorization"].split(" ")[1];
jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
if (err) {
return res.status(401).send({
message: "Auth failed",
success: false,
});
} else {
req.body.userId = decoded.id;
next();
}
});
} catch (error) {
return res.status(401).send({
message: "Auth failed",
success: false,
});
}
};
Backend => tokenmodel.js :
const mongoose = require("mongoose");
const tokenSchema = new mongoose.Schema(
{
userid: {
type: String,
required: true,
},
token: {
type: String,
required: true,
},
},
{ timestamps: true }
);
const tokenModel = mongoose.model("tokens", tokenSchema);
module.exports = tokenModel;
When I create/register user1 , the verification mail is sent to that email id and he(user1) is being verified successfully and I am able to change password for user1.
After creating user1 , I am creating/registering user2 ,where the verification email is sent to the account .After clicking the link , it's becomes INVALID
Overall , I am only able to create one user who's being verified
In the "verifyemail" route handler is you are trying to access the body of the req which is null, remember that when a user clicks on that URL in the email, a get request is send. The token will then exist in the req.params object, Not req.body.
Try the changes below.
router.get("/verifyemail/:token", async (req, res) => {
try {
const tokenData = await Token.findOne({ token: req.params.token });
if (tokenData) {
await User.findOneAndUpdate({ _id: tokenData.userid, isVerified: true });
await Token.findOneAndDelete({ token: req.params.token });
res.send({ success: true, message: "Email Verified Successlly" });
}

Cannot set headers to pass token in node js

node js
This is my register method to register a user. I am trying to pass token in headers when a user is registered which will be used in the front end to access the token and store it in the local storage.
module.exports.register = async function (req, res, next) {
try {
const { username, email, password } = req.body;
const profileImage = req.file.path;
const usernameCheck = await User.findOne({ username });
if (usernameCheck)
return res.json({ msg: "Username already used", status: false });
const emailCheck = await User.findOne({ email });
if (emailCheck)
return res.json({ msg: "Email already exists", status: false });
const hashedPassword = await bcrypt.hash(password, 10);
const user = await User.create({
_id: new mongoose.Types.ObjectId(),
username,
email,
profileImage,
password: hashedPassword,
});
delete user.password;
//create jwt token
const token = jwt.sign(
{
username: user.username,
email: user.email,
userId: user._id,
},
process.env.JWT_KEY,
{
expiresIn: "1h",
}
);
res.header("x-auth-token", token); //This is not setting the token in headers
return res.json({
message: "User Created Successfully",
status: true,
user,
});
} catch (ex) {
next(ex);
}
};
react js
This is my front-end react code to register a user. I want to login the user with the jwt token stored in localStorage once the user is registered.
const handleSubmit = async (values) => {
try {
const { username, email, profileImage, password } = values;
const formData = new FormData();
formData.append("username", username);
formData.append("email", email);
formData.append("profileImage", profileImage);
formData.append("password", password);
const response = await register(formData);
console.log(response);
if (response.status === false) return;
else {
loginWithJwt(response.headers["x-auth-token"]);// log the user in using jwt token
console.log(response.headers["x-auth-token"]);
navigate("/chatroom");
}
} catch (ex) {
console.log(ex.message);
}
};

Why is Chrome not setting cookie in production

I have an app in production, that sometimes sets a cookie in Chrome, and sometimes doesnt. Im using nodejs on backend. This doesnt happen in firefox, only Chrome. Am I doing something wrong?
Recently I added
this.app.use(cors({ origin: true, credentials: true }));
but it didnt help at all.
Code for setting cookie:
exports.oauthLogin = async (req, res) => {
if (!req.query.token) {
return res.status(400).send({ error: "No token provided" });
}
const settings = await AppSettings.findOne();
const response = await checkToken({
token: req.query.token,
serverUrl: settings.oauthServerUrl,
clientId: settings.oauthClientId,
clientSecret: settings.oauthClientSecret,
});
if (response.error) {
return res.redirect("/");
}
const { _id, displayName, email } = response.user;
let user = await User.findOne({ email: email });
if (!user) {
user = new User({
displayName,
email,
oauthId: _id,
accessLevel: "User",
});
await user.save();
}
const jwtUserData = {
userId: user._id,
userAccessLevel: user.accessLevel,
};
const token = jwt.sign(jwtUserData, process.env.JWT_SECRET);
res.cookie("token", token);
return res.redirect(req.query.redirectUrl ? req.query.redirectUrl : "/");
};

Cannot Save and Activate the Registered Email Account

I have successfully send a token in my Email upon Registration via Send Grid
router.post(
'/',
[
check('lastname', 'Lastname is required').not().isEmpty(),
check('firstname', 'Firstname is required').not().isEmpty(),
check('email', 'Please include a valid email').isEmail(),
check(
'password',
'Please enter a password with 6 or more characters'
).isLength({ min: 6 }),
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { lastname, firstname, email, password } = req.body;
try {
// Identify if users exists
let user = await User.findOne({ email });
if (user) {
return res.status(400).json({
errors: [{ msg: 'User already exists' }],
});
}
// Get users avatar
const avatar = gravatar.url(email, {
// size
s: '200',
// rating
r: 'pg',
// default (mm = default image)
d: 'mm',
});
// create a new instance of a user
user = new User({
lastname,
firstname,
email,
avatar,
password,
});
// // Encrypt password
// // salt to do the hashing
const salt = await bcrypt.genSalt(10);
// // creates a hash and put to the user.password
user.password = await bcrypt.hash(password, salt);
const token = jwt.sign(
{
user,
},
accountActivation,
{
expiresIn: 360000,
}
);
const emailData = {
from: emailFrom,
to: user.email,
subject: 'Account Activation',
html: `
<h1>Please use the following to activate your account</h1>
<p>${PORT}/activeprofile/${token}</p>
<hr />
<p>This email may contain sensetive information</p>
<p>${PORT}</p>
`,
};
sgMail
.send(emailData)
.then(() => {
return res.json({
message: `Email has been sent to ${email}`,
});
})
.catch((err) => {
return res.status(400).json({
msg: 'Unable to send',
});
});
// await user.save()
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
});
and I have successfully get an Email with it's token.
whenever I am trying to verify it in my postman.
router.post('/activeprofile', (req, res) => {
const { token } = req.body;
if (token) {
jwt.verify(token, accountActivation, (err) => {
if (err) {
console.log('Activation error');
return res.status(401).json({
errors: 'Expired link. Signup again',
});
} else {
const { lastname, firstname, email, password } = jwt.decode(
token
);
// create a new instance of a user
const user = new User({
lastname: req.body.lastname,
firstname,
email,
password,
});
user.save((err, user) => {
if (err) {
console.log('Error Saving the User', err.message);
return res.status(401).json({ msg: 'Unable to save' });
} else {
return res.status(200).json({
success: true,
message: user,
message: 'Signup Success',
});
}
});
}
});
} else {
return res.json({
message: 'Error happened, Please try again later',
});
}});
I always get this Error.
I even tried doing
const user = new User({
lastname: req.body.lastname,
firstname: req.body.firstname,
email: req.body.email,
password: req.body.passsword,
});
still I ended up with all the same errors in the picture posted.
Btw. this is my User Schema
const UserSchema = new mongoose.Schema({
lastname: {
type: String,
required: true,
},
firstname: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
avatar: {
type: String,
},
date: {
type: Date,
default: Date.now,
},}); module.exports = Use = mongoose.model('user', UserSchema);
my postman error:
I manage to figure out the problem. It's because I have created a double instance of the user
so I removed the user instance from the registration
router.post(
'/',
[
check('lastname', 'Lastname is required').not().isEmpty(),
check('firstname', 'Firstname is required').not().isEmpty(),
check('email', 'Please include a valid email').isEmail(),
check(
'password',
'Please enter a password with 6 or more characters'
).isLength({ min: 6 }),
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { lastname, firstname, email, password } = req.body;
try {
// Identify if users exists
let data = await User.findOne({ email });
if (data) {
return res.status(400).json({
errors: [{ msg: 'User already exists' }],
});
}
const token = jwt.sign(
{
lastname,
firstname,
email,
password,
},
accountActivation,
{
expiresIn: 360000,
}
);
const emailData = {
from: emailFrom,
to: email,
subject: 'Account Activation',
html: `
<h1>Please use the following to activate your account</h1>
<p>${PORT}/activeprofile/${token}</p>
<hr />
<p>This email may contain sensetive information</p>
<p>${PORT}</p>
`,
};
sgMail
.send(emailData)
.then(() => {
return res.json({
message: `Email has been sent to ${email}`,
});
})
.catch((err) => {
return res.status(400).json({
msg: 'Unable to send',
});
});
// await user.save()
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
});
and put it in the account activation together with the gravatar and the hashing of the passwords
router.post('/activeprofile', (req, res) => {
const { token } = req.body;
if (token) {
jwt.verify(token, accountActivation, async (err, decoded) => {
if (err) {
console.log('Activation error');
return res.status(401).json({
errors: 'Expired link. Signup again',
});
}
const { lastname, firstname, email, password } = jwt.decode(token);
// // Get users avatar
const avatar = gravatar.url(email, {
// size
s: '200',
// rating
r: 'pg',
// default (mm = default image)
d: 'mm',
});
// create a new instance of a user
let user = new User({
lastname,
firstname,
email,
avatar,
password,
});
// Encrypt password
// salt to do the hashing
const salt = await bcrypt.genSalt(10);
// creates a hash and put to the user.password
user.password = await bcrypt.hash(password, salt);
user.save((err, user) => {
if (err) {
console.log('Error Saving the User', err.message);
return res.status(401).json({ msg: 'Unable to save' });
} else {
return res.status(200).json({
success: true,
message: user,
message: 'Signup Success',
});
}
});
});
} else {
return res.json({
message: 'Error happened, Please try again later',
});
}});
and it's successful, the user is saved in the database.

Can't update document data with mongoose, node.js, express and async function

I'm trying to make a login function that receives and email and password from user.
checks if email exists - works
checks if password match to encrypted password in db - works
if 1+2 is true -> generates a token and set the token into the user document. But it doesn't work, the action user.token = token is invalid, and postman yields no response.
what am I doing wrong?
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const User = require('../models/user');
exports.login = async (req, res, next) => {
const email = req.body.email;
const password = req.body.password;
let emailExist = await User.findOne({ email: email }).then(user => {
if (!user) {
return res.json({ isAuth: false, message: 'Login failed, email not found' });
}
return user;
});
let isPasswordMatch = await bcrypt.compare(password, emailExist.password);
if (!isPasswordMatch) {
return res.json({ isAuth: false, message: 'Login failed, wrong password' });
}
let loadedUser = await User.findOne({ email: email, password: emailExist.password })
.then(user => {
if (!user) {
return res.json({ isAuth: false, message: 'Login failed' });
}
return user;
})
.then(user => {
const token = jwt.sign({ role: user.role, email: user.email, userId: user._id.toString() }, 'secret');
console.log(user);
user.token = token;
return user.save();
});
res.status(200)
.cookie(('auth', token))
.json({ isAuth: true, token: token, user: loadedUser });
};
updated version: (still doesn't work)
now it gives me the following error:
(node:11336) UnhandledPromiseRejectionWarning: CastError: Cast to
number failed for value
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjowLCJlbWFpbCI6ImFoQGdtYWlsLmNvbSIsInVzZXJJZCI6IjVjMTc4NDc3Mzg5MWI5MjY5MGNkMzgwNiIsImlhdCI6MTU0NTA1MTY5OX0.8GWuV82A7yOvKKkXeOjIeYve5aH0YwBEK_RuH0NVfYA"
at path "token"
exports.login = async (req, res, next) => {
const email = req.body.email;
const password = req.body.password;
let emailExist = await User.findOne({ email: email });
if (!emailExist) {
return res.json({ isAuth: false, message: 'Login failed, email not found' });
}
let isPasswordMatch = await bcrypt.compare(password, emailExist.password);
if (!isPasswordMatch) {
return res.json({ isAuth: false, message: 'Login failed, wrong password' });
}
let loadedUser = await User.findOne({ email: email, password: emailExist.password });
let token = jwt.sign(
{
role: loadedUser.role,
email: loadedUser.email,
userId: loadedUser._id.toString()
},
'secret'
);
console.log('token === ', token);
console.log('user before token === ', loadedUser);
updateUserToken = await loadedUser.update({ $set: { token: token } });
console.log('user === ', loadedUser);
res.status(200)
.cookie(('auth', token))
.json({ isAuth: true, token: token, user: updateUserToken });
};
check your user model file and see whether you have add the token as a number. if so this might be the issue. Try changing it to string

Resources