Session not updating after redirect - node.js

I'm trying to create a login system and having some very strange issues.
Page code:
const { email, password } = req.body;
if(!email ||!password) return res.status(400).send("Missing required fields");
let user = await client.fetchUser({ username: email });
if(!user) user = await client.fetchUser({ email });
if(!user) return res.render("login.ejs", {bot:client,message:"Invalid email or password",type:"error"});
const valid = await bcrypt.compare(password, user.account.password);
if(!valid) return res.render("login.ejs", {bot:client,message:"Invalid email or password",type:"error"});
req.session.user = {
username: user.account.username,
password: user.account.password
}
return res.redirect('/dashboard');
When doing
return res.status(200).send("Success");
It works.
I've tried doing await req.session.save() but that does not work either.
Also did some extensive googling and couldn't find an answer.
Please note: this is a POST request.

Sessions are not automatically saved by res.redirect. And req.session.save() does not return a promise but requires a callback function:
req.session.save(function(err) {
if (err) res.status(500).send(err);
else res.redirect("/dashboard");
});

Related

Can't set headers after they are sent to the client Node.js ERROR

Here is my code:
try {
const user = await User.findOne({ email: req.body.email });
!user && res.status(404).json("user not found");
const validPassword = await bcrypt.compare(req.body.password, user.password)
!validPassword && res.status(400).json("wrong password")
res.status(200).json(user)
} catch (err) {
res.status(500).json(err)
}
});
If I use the right credentials there is no problem but
if I type in wrong password or email I get an error stating:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
and the app crashes, im following a YouTube tutorial by Lama Dev on Node.js Social media API and have the code copied one by one.
You can only send one response for a request.
Here is an example of what you are actually trying to do:
try {
const user = await User.findOne({ email: req.body.email });
if (!user) {
return res.status(404).json("user not found");
}
const validPassword = await bcrypt.compare(req.body.password, user.password)
if(!validPassword) {
return res.status(400).json("wrong password")
}
res.status(200).json(user)
} catch (err) {
res.status(500).json(err)
}
});
Note that the response is returned, which allows exits the function when a response should be sent. You are checking to see IF a user does not exists, and IF they have an invalid password.

Mongoose static method call doesn't work, console logging it does

I'm creating an authentication system using Node and Mongoose. I have a login user function here:
export const loginUser = async (req, res) => {
try {
const email = req.body.email;
const password = req.body.password;
//const workingUser = await User.findById("xxxxxxxxxxxx");
console.log(await User.findByCredentials(email, password));
const user = await User.findbyCredentials(email, password);
console.log(user);
if (!user) {
return res.status(401).json({ error: "Login failed! Check authentication credentials." });
}
const token = await user.generateAuthToken();
res.status(201).json({ user, token });
} catch (error) {
res.status(400).json({ error: error });
}
};
I always get a 400 error. Console logging 'user' shows nothing. When I substitute my 'findByCredentials' function for the commented out 'findById', the code works perfectly. Also, where I console log 'await User.findByCredentials(email, password)' the user I want is console logged, which makes me think the findByCredentials code is implemented correctly.
Here is the code for that:
// this method searches for a user by email and password
userSchema.statics.findByCredentials = async (email, password) => {
console.log(email);
const user = await User.findOne({ email });
if (!user) {
throw new Error({ error: "Invalid login details" });
}
const isPasswordMatch = await bcrypt.compare(password, user.password);
if (!isPasswordMatch) {
throw new Error({ error: "Invalid login details"});
}
return user;
}
const User = mongoose.model("User", userSchema);
export default User;
Not exactly sure what I'm doing wrong. Thank you
There's a typo in your code (in second line)
const user = await User.findByCredentials(email, password);
findByCredentials not findbyCredentials. See the capital B

Nodejs error when making a POST request [ERR_HTTP_HEADERS_SENT]

I am new to NODEJS / express , and I was following a tutorial to build a small MERN app.
In the initial steps, while setting up a POST route, when sending a POST request with Postman, I am getting this Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
I´ve reading about it, and I somehow understand is because I am calling res.json two times in the same response.
My router is this :
router.post('/login',
async (req, res) => {
try {
const user = await userModel.findOne({email:req.body.email});
!user && res.status(404).send("user not found");
const correctPassword = await bcrypt.compare(req.body.password, user.password);
!correctPassword && res.status(400).json('incorrect password')
res.status(200).json(user); // if not invalid return user
} catch (err) {
console.log(err)
}
})
I tried different solutions i found here (IF & if Else statements, using return ...) without any success.
This very same code (with different variable names) is working flawless in mentioned tutorial.
Any idea how could I solve it?
Thanks in advance.
EDIT : I add the resto of my user Route for completion
´´´
import express from 'express';
const router = express.Router();
import bcrypt from 'bcrypt';
import userModel from '../models/userModel.js'
router.post('/register',
async (req, res) => {
try {
// hash password
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(req.body.password, salt)
// Create New User
const newUser = new userModel({
userName: req.body.userName,
email: req.body.email,
password: hashedPassword,
});
// Save New User and return response
const user = await newUser.save();
res.status(200).json(user);
} catch(err){
console.log(err)
}
});
Likely user is falsy or correctPassword evaluates to false, which will send a response of "user not found" or "incorrect password".
However, it continues to execute the rest of your function and eventually executes this line
res.status(200).json(user);
This line will cause the error you've mentioned, as you have already sent a response to the client previously and cannot send another response in the same request-response-cycle.
This should fix the issue:
router.post("/login", async (req, res) => {
try {
const user = await userModel.findOne({ email: req.body.email });
if (!user) {
return res.status(404).send("user not found");
}
const correctPassword = await bcrypt.compare(req.body.password, user.password);
if (!correctPassword) {
return res.status(400).json("incorrect password");
}
return res.status(200).json(user); // if not invalid return user
} catch (err) {
console.log(err);
return res.status(500).send(err.message);
}
});
I'd recommend using ifs here, as it makes the code easier to read. I'd also recommend using return anytime you send a response as it will avoid the issue you ran into.

deleting key from object but still showing in response

I am experimenting with node authentication, I have managed to store a username and a hashed password into my database, but I want to return the json back without the hashed password.
I am deleting the password key before sending the JSON back but the password still shows in the returned result.
router.post("/signup", async (req, res, next) => {
const user = await User.exists({ username: req.body.username });
if (user) {
const error = new Error("Username already exists");
next(error);
} else {
const newUser = new User({
username: req.body.username,
password: req.body.password,
});
try {
const result = await newUser.save();
delete result.password;
res.json(result);
} catch (err) {
res.json(err.errors);
}
}
});
the User model has a pre hook to hash the password before save:
userSchema.pre("save", async function save(next) {
const user = this;
if (!user.isModified("password")) return next();
try {
user.password = await bcrypt.hash(user.password, 12);
return next();
} catch (err) {
return next(err);
}
});
Here is the solution thanks to Mahan for pointing it out.
result returns a Mongoose object so needs turning into a normal Javascript object first.
try {
let result = await newUser.save();
result = result.toObject();
delete result.password;
res.json(result);
} catch (err) {
res.json(err.errors);
}

Unable to return a thrown error to calling API

I have an API that simply logs in a user. I am testing out certain test cases for when the username or password are invalid. For some reason, I can't detect the thrown error is not being returned to the API.
The API looks like this:
// routes/users.js
router.post('/users/login', async (req, res) => {
//Login a registered user
try {
const { email, password } = req.body
const user = await User.findByCredentials(email, password)
if (!user) {
return res.status(401).send({ error: 'Login failed! Check authentication credentials' })
}
const token = await user.generateAuthToken()
res.send({ user, token })
} catch (error) {
res.status(400).send(error)
}
})
And here is the method in the model that should return the error. Using, the debugger I can step through the code and it looks like all the statements are being executed but the error is returned as an empty object as such, Error: [object Object]
// models/user.models.js
userSchema.statics.findByCredentials = async (email, password) => {
// Search for a user by email and password.
const user = await User.findOne({ email} )
if (!user) {
throw new Error({ error: 'Invalid user name' })
}
const isPasswordMatch = await bcrypt.compare(password, user.password)
if (!isPasswordMatch) {
throw new Error({ error: 'Invalid password' })
}
return user
}
Looking at the code, I don't think (!user) should be considered an error, as the query just simply found no user record that matches the query condition. As far as handling the real error, try below:
If you want to test this, try:
User.findOne({email}, function(err, user) {
if (err) {
//true error
} else {
if (!user) {
//this is when no user matching the email was found
} else {
//user matching email was found
}
}
}
Because there was no runtime error, there would be no error object in the case the no user was found:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
So this seems to do the trick. Not sure why this works and the original approach did not.
The difference is basically a user defined function handling the error message.
userSchema.statics.findByCredentials = async (email, password) => {
// Search for a user by email and password.
const user = await User.findOne({ email })
function myError(message) {
this.message = message
}
myError.prototype = new Error()
if (!user) {
throw new myError('Username provided is incorrect, please try again')
}
const isPasswordMatch = await bcrypt.compare(password, user.password)
if (!isPasswordMatch) {
throw new myError('Password provided is incorrect, please try again')
}
return user
}

Resources