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
Related
I'm using Node, Express, & Mongoose trying to get this POST request to work using Postman but it keeps giving me the 500 status error. I also tried posting with just the username & instead of giving me the expected 400 status error it just gave me a 500 error again.
const jwt = require('jsonwebtoken')
const bcrypt = require('bcrypt')
const User = require('../models/userModel');
const registerUser = async (req, res) => {
try {
//get the username & password from the req.body
const { username, password } = req.body;
//check if the username is unique
const uniqueCheck = await User.findOne(username);
if (uniqueCheck) {
res.status(403).json('Username already exists');
}
//hash password
const salt = await bcrypt.genSalt(10);
const hash = await bcrypt.hash(password, salt);
//check all fields are filled
if (!username || !password) {
res.status(400).json('Please fill in all fields')
} else {
//create user with username & password that is assigned to the hash version of it
const user = await User.create(username, { password: hash });
res.status(201).json(user);
}
} catch (error) {
res.status(500).json({ error: 'Problem registering user' });
}
}
As already I told you in the comment, you should ad a console.error statement in the catch block to better understand where is the problem.
Also, if the first if is matched, a response is sent to the client but the code execution will countinue, triyng to repliyng again to the client and giving you another error. You should return in the first if block to avoid it.
Check the following solution with comments on relevant edits
const jwt = require('jsonwebtoken')
const bcrypt = require('bcrypt')
const User = require('../models/userModel');
const registerUser = async (req, res) => {
try {
//get the username & password from the req.body
const { username, password } = req.body;
//check if the username is unique
const uniqueCheck = await User.findOne(username);
if (uniqueCheck) {
return res.status(403).json('Username already exists'); // --> !!! add a return statement here
}
//hash password
const salt = await bcrypt.genSalt(10);
const hash = await bcrypt.hash(password, salt);
//check all fields are filled
if (!username || !password) {
res.status(400).json('Please fill in all fields')
} else {
//create user with username & password that is assigned to the hash version of it
const user = await User.create(username, { password: hash });
res.status(201).json(user);
}
} catch (error) {
console.error(error) // --> !!! log errors here
res.status(500).json({ error: 'Problem registering user' });
}
}
Okay i am fairly new to node js and i am learning user authentication. I keep getting the 'Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client' error. can someone just tell me what is wrong with my code and how to fix it?. When i test it in postman, The register route works, its the login route that gives me this problem. Here is my code:
const User = require('../models/User')
const CryptoJS = require("crypto-js");
const jwt = require("jsonwebtoken");
const {BadRequestError, UnauthenticatedError} = require('../errors')
const Register = async (req, res)=>{
const newUser = new User({
username: req.body.username,
email: req.body.email,
password: CryptoJS.AES.encrypt(req.body.password, process.env.pass_secret ).toString(),
});
if(newUser){
const savedUser = await newUser.save();
res.status(201).json(savedUser);
}
}
const Login = async (req, res) =>{
const {username} = req.body
//checking if both the email and password are provided
if(!username){
throw new BadRequestError('please provide a username and password')
}
//finding a user with the email, if the user doesnt exist, return an error
const user = await User.findOne({username: req.body.username});
if(!user){
throw new UnauthenticatedError('Invalid username or password')
}
//checking if the passwords match
const hashedPassword = CryptoJS.AES.decrypt( user.password, process.env.pass_secret);
const originalPassword = hashedPassword.toString(CryptoJS.enc.Utf8);
if(originalPassword !== req.body.password){
throw new UnauthenticatedError('Invalid email or password')
}
const accessToken = jwt.sign( { id: user._id, isAdmin: user.isAdmin}, process.env.jwt_secret, {expiresIn:"30d"});
const { password, ...others } = user._doc;
res.status(200).json({...others, accessToken});
}
module.exports = {Register, Login}
Wherever you have this:
if(newUser){
const savedUser = await newUser.save();
res.status(201).json(savedUser);
}
You need to change it to:
if(newUser){
const savedUser = await newUser.save();
res.status(201).json(savedUser);
return;
}
You don't want the code to continue after you've done
res.status(201).json(savedUser);
because it will then try to send another response.
In addition, everywhere you have this:
if (err) throw err;
inside an async callback, you need to replace that with something that actually sends an error response such as:
if (err) {
console.log(err);
res.sendStatus(500);
return;
}
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.
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
}
Working with http end points and trying to login user by searching database with his email.user password is hashed.when email provided is not in database it is returning 200 status code with no info.
I've tried self defined functions which checks if user obj is empty. even here the obj is not empty. Catch block is executing even if correct details are provided.
Router.post("/users/login", async (req, res) => {
try {
const user = await User.findByCredentials(req.body.email, req.body.password)
res.send(user)
} catch (error) {
res.status(400).send(error)
}
})
userSchema.statics.findByCredentials = async (email, password) => {
const user = User.findOne({ email: email })
if (!user) {
throw new Error("Unable to login")
}
const isMatch = await bcrypt.compare(password, user.password)
if (!isMatch) {
throw new Error("Unable to login")
}
return user
}
Expected output is user. Actual output is empty with 400 status code.
Your problem looks to be caused at the line:
const user = User.findOne({email : email})
and because you are not awaiting it or passing a callback it returns a Mongoose promise.
Then
const isMatch = await bcrypt.compare(password, user.password)
always evaluate to false since a you are comparing a function and a string.
!isMatch
evaluates to true which means you will always be throwing the Error('Unable to login').