Below is my rest API endpoint /signup. The problem I'm having now is that the endpoint does not stop after validateEmail. Even after it failed email form-validation and res.send() is done, the endpoint continues. So I'm keep getting the error 'Error: Can't set headers after they are sent.'. I would like to be able to finish the endpoint inside its functions like validateEmail , checkEmailInUse, makeUser, and so on.
router.post("/signup", async (req, res, next) => {
const { email, password } = req.body;
const users = req.app.get("users");
validateEmail(res, email);
await checkEmailInUse(res, users, email);
const user = await makeUser(res, users, email, password);
res.send({ message: "POST signup request OK", user });
});
function validateEmail(res, email) {
const isEmail = emailFilter.test(email);
if (!isEmail) {
res.status(400).send({
error: {
message: "Requested email is not email type",
type: "FormatValidation",
location: "validateEmail"
}
});
return;
}
}
async function checkEmailInUse(res, users, email) {
const query = { email };
try {
const user = await users.findOne(query);
if (user) {
res.send({ message: "The email is already used" });
}
} catch (err) {
res.status(400).send({
error: {
message: "Failed to find user",
type: "DatabaseError",
location: "checkEmailInUse"
}
});
return;
}
}
The code keeps going after a failed validate because you call:
validateEmail(res, email);
and then your code just keeps going. This is normal flow of control in Javascript. Your function keeps executing lines of code until you return in the function. The same issue is true for checkEmailInUse(). If you want to sometimes send the response inside those functions and be done, then you need a return value from those functions that you can check and then use if statements to determine whether your code should do more or not.
Following your style of sending the error response inside the validation functions (which is not how I would probably structure things), you could return values from those functions and test those return values in the request handler like this:
router.post("/signup", async (req, res, next) => {
const { email, password } = req.body;
const users = req.app.get("users");
if (validateEmail(res, email)) {
if (await checkEmailInUse(res, users, email)) {
const user = await makeUser(res, users, email, password);
res.send({ message: "POST signup request OK", user });
}
}
});
function validateEmail(res, email) {
const isEmail = emailFilter.test(email);
if (!isEmail) {
res.status(400).send({
error: {
message: "Requested email is not email type",
type: "FormatValidation",
location: "validateEmail"
}
});
return false;
}
return true;
}
async function checkEmailInUse(res, users, email) {
const query = { email };
try {
const user = await users.findOne(query);
if (user) {
res.send({ message: "The email is already used" });
return false;
} else {
return true;
}
} catch (err) {
res.status(400).send({
error: {
message: "Failed to find user",
type: "DatabaseError",
location: "checkEmailInUse"
}
});
return false;
}
}
}
But, I think you might find this is simpler if you get rid of the local functions because then when you send a response, you can just directly return from the main function and be done. Here's how that could look:
router.post("/signup", async (req, res, next) => {
function err(res, message, type, location) {
res.status(400).send({error: {message, type, location}});
}
const { email, password } = req.body;
if (!emailFilter.test(email)) {
err(res, "Requested email is not email type", "FormatValidation", "validateEmail");
return;
}
const users = req.app.get("users");
try {
const user = await users.findOne({email});
if (user) {
res.send({ message: "The email is already used" });
return;
}
} catch(e) {
err(res, "Failed to find user", "DatabaseError", "checkEmailInUse");
return;
}
try {
const user = await makeUser(res, users, email, password);
res.send({ message: "POST signup request OK", user });
} catch(e) {
err(res, "Failed to make user", "DatabaseError", "makeUser");
}
}
Related
i am trying to get roles from user, but got empty object instead
export async function resetPassword(req, res) {
const { id } = req.user;
try {
const loggedInUser = await User.where('id', id).fetch({withRelated: ['roles']});
if (!loggedInUser) {
return res.status(HttpStatus.NOT_FOUND).json({
message: 'User not found',
});
}
const usersRoles = loggedInUser.roles;
return res.json({
test: usersRoles
});
} catch (err) {
Logger.error(err.message);
return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({
message: err.message,
});
}
}
this is the user object
how to access the roles data, please help?
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.
Sequelize + Bcrypt not storing passwords in DB as hash
As the title says, whenever I attempt to store a user into my SQLite DB the console outputs the password as a hash but when I look into the DB with DBbrowser I can see the plaintext password.
Model
// const Promise = require('bluebird')
const bcrypt = require('bcrypt')
async function hashPassword (user, options) {
if (!user.changed('password')) {
return 0
}
const SALT_FACTOR = 8
await bcrypt.hash(user.password, SALT_FACTOR, (err, hash) => {
if (err) {
console.log(err)
}
// user.setDataValue('password', hash)
user.password = hash
console.log(user)
})
}
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
email: {
type: DataTypes.STRING,
unique: true
},
password: DataTypes.STRING
}, {
hooks: {
beforeSave: hashPassword,
beforeCreate: hashPassword
}
})
User.prototype.comparePassword = function (password) {
bcrypt.compare(password, this.password, function (res, err) {
if (res) {
console.log(res)
} else {
console.log(err)
}
})
return bcrypt.compare(password, this.password)
}
return User
}
Controllers
module.exports = {
async register (req, res) {
try {
const user = await User.create(req.body)
const userJson = user.toJSON()
res.send({
user: userJson,
token: jwtSignUser(userJson)
})
} catch (err) {
// e-mail already exists or such
res.status(400).send({
error: 'This email address is already in use'
})
}
},
async login (req, res) {
try {
// Grab user input
const { email, password } = req.body
const user = await User.findOne({
where: {
email: email
}
})
// Check to see if user is in db
if (!user) {
res.status(403).send({
error: 'the login information was incorrect / Not Found'
})
}
// Check to see if password is valid
const isPasswordValid = await user.comparePassword(password)
if (!isPasswordValid) {
return res.status(403).send({
error: 'The login information was incorrect'
})
}
// return user using toJSON()
const userJson = user.toJSON()
res.send({
user: userJson,
token: jwtSignUser(userJson)
})
} catch (e) {
res.status(500).send({ error: 'An error occured attempting to login' })
console.log(e)
}
}
}
To elaborate a little more, whenever I create a user, I receive the following:
{
"user": {
"id": 1,
"email": 'test#test.com",
"password": "$2b$08$SYYXU/GDSCFsp3MVeuqrduI0lOLHeeub7whXiaMMoVxO53YJry.1i",
"updatedAt": "2018-09-07T22:44:12.944Z",
"createdAt": "2018-09-07T22:44:12.944Z"
},
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiZW1haWwiOiJTVVBCUkhVQGxvbC5jb20iLCJwYXNzd29yZCI6IiQyYiQwOCRTWVlYVS9HRFNDRnNwM01WZXVxcmR1STBsT0xIZWV1Yjd3aFhpYU1Nb1Z4TzUzWUpyeS4xaSIsInVwZGF0ZWRBdCI6IjIwMTgtMDktMDdUMjI6NDQ6MTIuOTQ0WiIsImNyZWF0ZWRBdCI6IjIwMTgtMDktMDdUMjI6NDQ6MTIuOTQ0WiIsImlhdCI6MTUzNjM2MDI1MywiZXhwIjoxNTM2OTY1MDUzfQ.mDaeIikzUcV_AGTuklnLucx9mVyeScGpMym1y0kJnsg"
}
Which to me says the DB successfully hashed my password, and stored it. The overhanging issue for me with this is the fact that I believe it's causing the bcrypt.compare function to spit out 'false'. As always, any insight or help would be greatly appreciated!
I'm pretty sure that this answer is too late for you, but might help others landing on this same question.
The main issue I can see is how you are using the async/await pattern. Changing this:
async function hashPassword (user, options) {
if (!user.changed('password')) {
return 0
}
const SALT_FACTOR = 8
await bcrypt.hash(user.password, SALT_FACTOR, (err, hash) => {
if (err) {
console.log(err)
}
// user.setDataValue('password', hash)
user.password = hash
console.log(user)
})
}
to this, worked for me:
async function hashPassword(user, options) {
if (!user.changed("password")) {
return 0;
}
user.password = await bcrypt.hash(user.password, SALT_FACTOR);
}
Can you please try to add only one hook
hooks: {
beforeSave: hashPassword,
}
Because I think your password is getting hashed two times. as beforeSave and beforeCreate both hooks get executed.
Hope it helps
I am trying to reset the user password using the following code and Postman.
But what I realised is that there is no user after I generate the token. the console is saying null;
// Reset User Password
exports.resetPassword = function (req, res) {
User.findOne({
reset_password_token: req.body.token,
reset_password_expires: {
$gt: Date.now()
}
}).exec(function (err, user) {
console.log('this user: ' + user)
if (!err && user) {
if (req.body.newPassword === req.body.verifyPassword) {
user.hash_password = bcrypt.hashSync(req.body.newPassword, 10);
user.reset_password_token = undefined;
user.reset_password_expires = undefined;
user.save(function (err) {
if (err) {
return res.status(422).send({
message: err
});
} else {
console.log(user.hash_password)
}
});
} else {
return res.status(422).send({
message: 'Passwords do not match'
});
}
} else {
return res.status(400).send({
message: 'Password reset token is invalid or has expired.'
});
}
});
};
This is how I use it in Postman
{
"newPassword": "cocacola",
"verifyPassword": "cocacola",
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI1YjI3NjAyNDAwOWI1NDA5ZjMwNzAzZWYiLCJpYXQiOjE1MzA5NjA2NDEwOTN9.1LjroayiTWDNevShnH30n3LxUGCrazmTaJlHgOUNvJ0"
}
and the response in Postman is message from status 400
I try to test my signup router post function which use email-verification framework,but mocha shows me the error message below :
TypeError: Cannot read property 'findOne' of null
This my code :
function that invoke the error:
nev.resendVerificationEmail(email,(err,userExist) =>{
if(err){
return res.status(404).send('Error : resending verification email failed');}
if(userExist){
res.json({message : 'An email has been sent to you , again. Please check it to verify your account'});
}else{
res.json({message : 'Your verification code has expired . Please sign up again'});
and this is the implementation of resendVerificationEmail function
var resendVerificationEmail = function(email, callback) {
var query = {};
query[options.emailFieldName] = email;
options.tempUserModel.findOne(query, function(err, tempUser) { //this the error handler I guess
if (err) {
return callback(err, null);
}
// user found (i.e. user re-requested verification email before expiration)
if (tempUser) {
// generate new user token
tempUser[options.URLFieldName] = randtoken.generate(options.URLLength);
tempUser.save(function(err) {
if (err) {
return callback(err, null);
}
sendVerificationEmail(getNestedValue(tempUser, options.emailFieldName), tempUser[options.URLFieldName], function(err) {
if (err) {
return callback(err, null);
}
return callback(null, true);
});
});
} else {
return callback(null, false);
}
});
};
and this is my spec
describe(' SignUp : /POST Test with fake client request : ', () => {
let req, res, statusCode, sendData,user;
beforeEach((done) => {
SignupModal.remove({}, (err) => {
done();
});
user = {
firstName : 'ben',
secondName : 'wissem',
username : 'wiss',
email : 'xy#zt.sq',
password : 'wissem'
};
res = {
json: function (code, data) {
statusCode = code;
sendData = data;
}
};
});
it('should send 200 code', () => {
chai.request(server)
.post('/user/signup')
.send(user)
.end((err, res) => {
res.statusCode.should.equal(200);
});
});
Can any one help me please ?!
if you want to end-to-end test email verification try EmailE2E.com, it's a free API that let's you send and receive emails from randomly generated inboxes. It's perfect for Firebase, Amazon Cognito, or other OAuth providers that use email verification codes during sign up. It also has a Javascript client you can use with Mocha.