Using async await with mongoose and express - express

in this piece of code whenever someone enters a username that already exists i get this error:
'Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client'
anybody know how to fix this? thanks.
const express = require('express')
const router = express.Router()
const bcrypt = require('bcryptjs')
const users= require('./users')
router.post('/reg', async (req, res, next) => {
let { firstname, lastname, username, email, password, confirm_password } = req.body
if (password !== confirm_password) {
res.status(400).json({
msg: 'passwords dont match'
})
return
}
await (users.findOne({username: username})).then(user => {
if(user) {
res.status(400).json({
msg: `username already exists`
})
return
}
})
await users.findOne({email: email}).then(user => {
if(user) {
res.status(400).json({
msg: 'email alraedy exists'
})
return
}
})
let newUser = new users({
firstname,
lastname,
username,
password,
email
})
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(newUser.password, salt, (err, hash) => {
if (err) throw err
newUser.password = hash
newUser.save().then(user => {
return res.status(201).json({
success: true,
msg: 'you successfully signed up'
})
})
})
})
})```

Related

Getting error in MERN stack Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client

I am new to MERN. I was using this code below . I followed a youtube tutorial and it was working fine for me for 4 to 5 days but now suddenly it has stopped working. I didn't change anything. I am not able to login, logout or even fetch data. My postman is giving positive results using these api but it won't work on my code. I want to remind you guys again, it was working fine for 4 to 5 days.
const User = require("../model/user");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const signup = async (req, res, next) => {
const { fname, lname, email, password, role, phone } = req.body;
let existingUser;
try {
existingUser = await User.findOne({ email: email });
} catch (err) {
console.log(err);
}
if (existingUser) {
return res.status(400).json({ message: "user already exists" });
}
const hashedPassword = bcrypt.hashSync(password);
const user = new User({
fname,
lname,
email,
password: hashedPassword,
phone,
role,
});
try {
await user.save();
} catch (err) {
console.log(err);
}
return res.status(201).json({ message: user });
};
const login = async (req, res, next) => {
const { email, password } = req.body;
let existingUser;
try {
existingUser = await User.findOne({ email: email });
} catch (err) {
console.log(err);
}
if (!existingUser) {
return res
.status(400)
.json({ message: "user doesn't exist. Please signup" });
}
const isPasswordCorrect = bcrypt.compareSync(password, existingUser.password);
if (!isPasswordCorrect) {
return res.status(401).json({ message: "invalid email or password" });
}
const token = jwt.sign({ id: existingUser._id }, "change1122", {
expiresIn: "1h",
});
res.cookie(String(existingUser._id), token, {
path: "/",
expires: new Date(Date.now() + 1000 * 3600),
httpOnly: true,
sameSite: "lax",
});
return res
.status(200)
.json({ message: "user logged in sucessfully", user: existingUser, token });
};
const verifyToken = (req, res, next) => {
const cookies = req.headers.cookie;
const token = cookies.split("=")[1];
if (!token) {
res.status(404).json({ message: "no token found" });
}
jwt.verify(String(token), "change1122", (err, user) => {
if (err) {
return res.status(404).json({ message: "invalid token" });
}
req.id = user.id;
});
next();
};
const getUser = async (req, res, next) => {
const id = req.id;
let user;
try {
user = await User.findById(id, "-password");
} catch (err) {
console.log(err);
}
if (!user) {
res.status(404).json({ message: "user not found with the id" });
}
return res.status(200).json({ user });
};
const logout = async (req, res, next) => {
const cookies = req.headers.cookie;
console.log(cookies);
const token = cookies.split("=")[1];
if (!token) {
res.status(404).json({ message: "no token found" });
}
const user = req.id;
res.clearCookie(`${user}`);
req.cookies[`${user}`] = "";
return res.status(200).json({ message: "successfully logged out" });
};
exports.signup = signup;
exports.login = login;
exports.verifyToken = verifyToken;
exports.getUser = getUser;
exports.logout = logout;
Here is the error
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at new NodeError (node:internal/errors:372:5) at ServerResponse.setHeader (node:_http_outgoing:576:11)
at ServerResponse.header (E:\Reacct\pos\server\node_modules\express\lib\response.js:794:10)
at ServerResponse.send (E:\Reacct\pos\server\node_modules\express\lib\response.js:174:12)
at ServerResponse.json (E:\Reacct\pos\server\node_modules\express\lib\response.js:278:15)
at getUser (E:\Reacct\pos\server\controller\user-controller.js:86:25)
at processTicksAndRejections (node:internal/process/task_queues:96:5) {
code: 'ERR_HTTP_HEADERS_SENT'
}
[nodemon] app crashed - waiting for file changes before starting...
I think i have an issue with cookies or token, I am new so i don't understand it properly.

How to inform single page application that email was verified?

I have vue js application running on localhost:3000 and express js restful api running on localhost:4000.
Whenever new user registers, the request is sent to /api/v1/signup endpoint and after successfull registration verification email is sent with url including
localhost:4000/api/v1/verify-email/:token
After user click link in the email how to inform vue js application that email validated? use socket or ...?
Register controller
async store(req, res) {
let errors = {}
let valid = false
await signupSchema.validate({
first_name: req.body.first_name?.trim(),
last_name: req.body.last_name?.trim(),
email: req.body.email?.trim(),
password: req.body.password?.trim(),
password_confirmation: req.body.password_confirmation?.trim(),
}, { abortEarly: false })
.then(() => valid = true)
.catch((err) => {
err.inner.map((inn) => errors[inn.path] = inn.errors)
})
if (!valid) return res.status(400).json({ errors: errors, message: 'Validation errors' })
const data = {
first_name: req.body.first_name.trim(),
last_name: req.body.last_name?.trim(),
email: req.body.email.trim(),
password: bcrypt.hashSync(req.body.password, 12)
}
let id = null
// create user
await knex('users').insert(data)
.then((res) => id = res[0])
.catch((err) => {
logger.log(err)
return res.status(500).json({ error: err.message })
})
// create verification token
const verificationToken = jwt.sign({ id: id }, process.env.ACCESS_TOKEN_SECRET, { expiresIn: 3600 })
await (new VerificationEmail(verificationToken, { id })).queue(data.email)
const user = await knex('users')
.select([ 'id', 'email', 'first_name', 'last_name', 'verified_at', 'deleted_at', 'created_at', 'updated_at' ])
.where('id', id)
.first()
.then((res) => res)
// sign jwt tokens
const accessToken = jwt.sign(user, process.env.ACCESS_TOKEN_SECRET, { expiresIn: 60 })
const refreshToken = jwt.sign(user, process.env.REFRESH_TOKEN_SECRET, { expiresIn: '1h' })
return res.json({ id: id, message: 'Successfully registered', accessToken, refreshToken })
}
VerifyEmailController
class VerifyEmailController {
/**
*
* #param {*} req
* #param {*} res
*/
async store(req, res) {
const user = await knex('users').where('id', req.params.id).first().then(res)
if (!user) res.status(404).json({ message: 'User not found' })
// validate token
jwt.verify(req.params.token, process.env.ACCESS_TOKEN_SECRET, async (err, data) => {
if (err) return res.status(401).json({ message: 'Invalid token' })
if (data.id !== user.id) res.status(401).json({ message: 'Invalid token' })
await knex('users').where('id', req.params.id)
.update({ verified_at: new Date() })
return res.json({ message: 'Your account verified. Thanks' })
})
}
}
VerificationEmail
class VerificationEmail {
token;
user;
constructor(token, user) {
this.token = token
this.user = user
}
layout(content) {
const str = fs.readFileSync(path.join(__dirname, '../../views/vendor/mail/default.ejs'), 'utf-8')
return ejs.render(str, { content })
}
build() {
const url = `${process.env.APP_URL}/api/v1/verify-email/${this.user.id}/${this.token}`
const str = fs.readFileSync(path.join(__dirname, '../../views/email/verification.ejs'), 'utf-8')
const content = ejs.render(str, { url })
return this.layout(content)
}
async queue(to) {
const html = this.build()
return emailQueue.add({ to, subject: 'Verify email address', html })
}
}
Signup page

How can I make an async authenticate calls with passport-local-mongoose?

I have a working implementation with passport-local-mongoose but would like to use async/await to keep things consistent, avoid nested callbacks and be comfortable that all errors are caught and handled.
I have sorted out the register method, but am stuck on the others. For example I've found that req.login() requires a callback so I can't await that one.
Here's my controller:
const User = require('../models/User')
const { populateModel } = require('../helpers/modelHelper')
const responseHelper = require('../helpers/responseHelper')
const { body, validationResult } = require('express-validator')
const passport = require('passport')
const jwt = require('jsonwebtoken')
const JwtStrategy = require('passport-jwt').Strategy
const ExtractJwt = require('passport-jwt').ExtractJwt
const options = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.AUTH_SECRET
}
// register working as expected async
module.exports.register = async (req, res, next) => {
const user = new User(),
fillable = [
'title',
'firstName',
'lastName',
'practice',
'addressLine1',
'addressLine2',
'city',
'state',
'postcode',
'jobTitle',
'country',
'locale',
'userRole',
'termsAccepted',
'consentOptIn',
'username',
'password',
'legacyUserId',
'authType'
]
populateModel(user, fillable, req.body)
try {
const result = await User.register(user, req.body.password)
return responseHelper.handleSuccess(res, {
user: result
})
} catch(err) {
next(err)
}
}
// login - would like to conver to async
module.exports.login = [
body('username', 'Email is required').not().isEmpty().isEmail(),
body('password', 'Password is required').not().isEmpty(),
(req, res) => {
const errors = validationResult(req)
if ( ! errors.isEmpty()) return responseHelper.handleValidationError(res, errors)
passport.authenticate('local', {}, (err, user, info) => {
if (err) return responseHelper.handleOperationError(res, err)
if ( ! user) return responseHelper.handleAuthError(res, 'Username/password not matched.')
req.logIn(user, (err) => {
if (err) return responseHelper.handleAuthError(res, err)
const token = jwt.sign({ _id: user._id }, process.env.AUTH_SECRET, { expiresIn: 1800 })
return res.json({
message: "Authentication successful.",
user: user,
token: token
})
})
})(req, res)
}
]
// token strategy - would like to conver to async
passport.use(new JwtStrategy(options, (payload, callback) => {
User.findById(payload, (err, user) => {
if (err) return callback(err, false)
if (user) return callback(null, user)
return callback(null, false)
})
}))
// get user - would like to conver to async
module.exports.user = [
(req, res, next) => {
passport.authenticate('jwt', { session: false }, (err, user) => {
if (err) return responseHelper.handleOperationError(res, err)
if ( ! user) return responseHelper.handleAuthError(res, 'User not authenticated.')
return res.json({
message: "Authentication successful.",
user: user
})
})(req, res, next)
}
]
In my app file I'm using the defaults for passport-local-mongoose:
passport.use(User.createStrategy())
passport.serializeUser(User.serializeUser())
passport.deserializeUser(User.deserializeUser())
Any my router calls my controller:
router.post('/auth/register', authController.register)
router.post('/auth/login', authController.login)
router.get('/auth/user', authController.user)
Finally I have an error handler set as middleware and a helper module for responses:
app.use((err, req, res, next) => responseHelper.handleUnexpectedError(res, err))
module.exports.handleSuccess = (res, data) => {
return res.json({
message: 'Success.',
data: data
})
}
module.exports.handleUnexpectedError = (res, err) => {
const validationErrors = [
'ValidationError',
'UserExistsError',
'MissingUsernameError',
'MissingPasswordError'
]
let code = err.status || 500
if (code === 500 && err.name && validationErrors.includes(err.name)) code = 422
return res.status(code).json({
message: err.message || 'Internal Server Error.',
error: err
})
}

Can someone please tell me where can i write my bcrypt-hashing function?

exports.postLogin = (req, res) => {
let { email, pass } = req.body;
console.log(email);
User.findOne({ email }, (err, result) => {
console.log(email, pass, result.pass);
if (err) {
res.json({ status: 'failed', message: err });
} else if (!result) {
res.json({ status: 'failed', message: 'email or password are wrong' });
} else {
bcrypt.compare(pass, result.pass).then(async (isPassCorrect) => {
if (isPassCorrect) {
const token = await signToken(result.id);
res.json({
status: 'success',
message: 'you logged in !!',
token,
});
} else res.json({ status: 'failed', message: 'email or password are wrong!' });
});
}
});
};
Your quetsion is not really clear about what you are trying to do, so I'm guessing into the black.
If I get you right, you are looking for the right place in order to hash a password before it gets saved, so you can use bcrypt.compare() on the encrypted password, is that right?
If yes, you could use mongooses pre-hook to hash the password before mongoose actually saves a document. To accomplish this add this to your model file
User.pre('save', async function (next) {
await bcrypt.genSalt(12).then(async salt => {
this.password = await bcrypt.hash(this.password, salt).catch(err => {
return next(err)
})
}).catch(err => {
return next(err)
})
})

ForbiddenError: invalid csrf token

I am trying to use csrf in add employee function. However, whenever I hit submit I alway get ForbiddenError: invalid csrf token. I am not sure the way I did csrf correctly.
On the other hand, I have a login and register form. It works fine. And I did the same steps for add employee.
I've been reading some other posts but I didn't understand them. Can anyone point me to the correct way? I am really appreciate.
const express = require('express');
const router = express.Router();
const {uploadProduct, uploadForEmployee, uploadForVendor} = require('../upload');
const validation = require('validator');
const bcrypt = require('bcryptjs');
const csrf = require('csurf');
const csrfProtection = csrf();
//Load User model
const User = require('../../models/User');
router.get('/viewEmployee', (req, res) => {
User.find({$or: [{role: 'Admin'}, {role: 'Employee'}] }, (err, doc) => {
res.status(200).render('./admin/viewEmployee', {
user: req.user,
doc: doc,
link: "/admin/employees/"
})
})
});
router.delete('/:email', (req, res) => {
User.deleteOne({email: req.params.email}, (err, user) => {
if(err) {
console.log('Error while deleting product');
} else {
console.log(req.params.email + ' deleted');
res.redirect('/admin/employees/viewEmployee');
}
});
});
router.use(csrfProtection);
router.get('/addEmployee', (req, res) => {
res.render("./admin/employeeForm", {csrfToken: req.csrfToken()});
});
router.post('/addEmployee', uploadForEmployee.single('image'), (req, res) => {
let {name, email, password, phone, address, role} = req.body;
let image = req.file;
let errors = [];
if(!name || !email || ! password || !phone || !address || !role) {
errors.push({msg: 'Please enter all fields.'});
}
if(!image) {
errors.push({msg: 'Please upload a photo of yourself.'});
}
//sanitize input
name = validation.escape(name);
email = validation.escape(email);
password = validation.escape(password);
phone = validation.escape(phone);
address = validation.escape(address);
//Validate email
if(!validation.isEmail(email)){
errors.push({msg: 'Bad email.'})
} else {
email = validation.normalizeEmail(email, [
'all_lowercase', 'gmail_remove_dots','gmail_remove_subaddress', 'gmail_convert_googlemaildotcom',
'outlookdotcom_remove_subaddress', 'yahoo_remove_subaddress', 'icloud_remove_subaddress'
])
}
if (password.length < 6) {
errors.push({ msg: 'Password must be at least 6 characters.' });
}
if (errors.length > 0) {
res.render('./admin/employeeForm', {
errors,
name,
email,
password,
phone,
address,
role,
image,
csrfToken: req.csrfToken()
});
} else {
User.findOne({ email: email }).then(user => {
if (user) {
errors.push({ msg: 'Email already exists.' });
res.render('./admin/employeeForm', {
errors,
name,
email,
password,
phone,
address,
role,
image,
csrfToken: req.csrfToken()
});
} else {
const newUser = new User({
name: name,
email: email,
password: password,
phone: phone,
address: address,
role: role,
imageUrl: '/images/employees/' + image.filename
});
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(newUser.password, salt, (err, hash) => {
if (err) throw err;
newUser.password = hash;
newUser
.save()
.then(user => {
req.flash(
'success_msg',
'You are now registered and can log in.'
);
res.redirect('/admin/employees/viewEmployee');
})
.catch(err => console.log(err));
});
});
}
});
}
});
module.exports = router;
This is my login and register functions. These ones worked with csrf just fine.
const express = require('express');
const router = express.Router();
const bcrypt = require('bcryptjs');
const passport = require('passport');
const {ensureLoggedIn, ensureLoggedOut} = require('connect-ensure-login');
const validation = require('validator');
const csrf = require('csurf');
const csrfProtection = csrf();
// Load User model
const User = require('../models/User');
//Profile
router.get('/profile', ensureLoggedIn('/users/login'), (req, res, next) => {
res.render('profile')
});
// Logout
router.get('/logout', (req, res) => {
req.logout();
req.flash('success_msg', 'You are logged out.');
res.redirect('/users/login');
});
// Register
router.post('/register', (req, res) => {
let { name, email, password, password2 } = req.body;
let errors = [];
if (!name || !email || !password || !password2) {
errors.push({ msg: 'Please enter all fields' });
}
//sanitize input
name = validation.escape(name);
email = validation.escape(email);
password = validation.escape(password);
password2 = validation.escape(password2);
//Validate email
if(!validation.isEmail(email)){
errors.push({msg: 'Bad email.'})
} else {
email = validation.normalizeEmail(email, [
'all_lowercase', 'gmail_remove_dots','gmail_remove_subaddress', 'gmail_convert_googlemaildotcom',
'outlookdotcom_remove_subaddress', 'yahoo_remove_subaddress', 'icloud_remove_subaddress'
])
}
if (password != password2) {
errors.push({ msg: 'Invalid Password.' });
}
if (password.length < 6) {
errors.push({ msg: 'Password must be at least 6 characters.' });
}
if (errors.length > 0) {
res.render('./home/register', {
errors,
name,
email,
password,
password2,
csrfToken: req.csrfToken()
});
} else {
User.findOne({ email: email }).then(user => {
if (user) {
errors.push({ msg: 'Email already exists.' });
res.render('./home/register', {
errors,
name,
email,
password,
password2,
csrfToken: req.csrfToken()
});
} else {
const newUser = new User({
name,
email,
password
});
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(newUser.password, salt, (err, hash) => {
if (err) throw err;
newUser.password = hash;
newUser
.save()
.then(user => {
req.flash(
'success_msg',
'You are now registered and can log in.'
);
res.redirect('/users/login');
})
.catch(err => console.log(err));
});
});
}
});
}
});
// Login
router.post('/login', (req, res, next) => {
passport.authenticate('local', {
successReturnToOrRedirect: '/',
failureRedirect: '/users/login',
failureFlash: true
})(req, res, next);
});
router.use(csrfProtection);
// Login Page
router.get('/login', ensureLoggedOut('/'), (req, res, next) => {
res.render('./home/login', {
csrfToken: req.csrfToken()
})
});
// Register Page
router.get('/register', ensureLoggedOut('/'), (req, res, next) => {
res.render('./home/register', {
csrfToken: req.csrfToken()
})
});
module.exports = router;