Using bcrypt on Node.js results in validation failed - node.js

I am writing code in Node.js to encrypt passwords using bcrypt.
However, if you use bcrypt, you will get an ValidationError: User validation failed: password: Cast to String failed for value "Promise { <pending> }" at path "password"
I do not get this error if I save it as plain text without encryption.
Is there a secret of bcrypt I do not know?
bcrypt (not working)
const bcrypt = require('bcrypt');
sign_up = (req, res, next) => {
const { email, password } = req.body;
const User = User.findOne({ email: email });
if (exUser) {
return res.send('exist user');
}
const hash = bcrypt.hash(password, 8);
const user = new User({
email: email,
password: hash
});
user.save((err) => {
if (err) {
return next(err);
}
res.send('signup success');
});
};
no bcrypt (working)
sign_up = (req, res, next) => {
const { email, password } = req.body;
const User = User.findOne({ email: email });
if (exUser) {
return res.send('exist user');
}
const user = new User({
email: email,
password: password
});
user.save((err) => {
if (err) {
return next(err);
}
res.send('signup success');
});
};

To elaborate on Chris's comment:
It appears that bcrypt.hash is asynchronous, and is returning a Promise.
To fix this, I would recommend using an async function and awaiting the result. MDN page
This may require a newer version of NodeJS than what you are running.
const bcrypt = require('bcrypt');
// Async function allows us to use await
sign_up = async (req, res, next) => {
const { email, password } = req.body;
const User = User.findOne({ email: email });
if (exUser) {
return res.send('exist user');
}
// We await the result of the hash function
const hash = await bcrypt.hash(password, 8);
const user = new User({
email: email,
password: hash
});
user.save((err) => {
if (err) {
return next(err);
}
res.send('signup success');
});
};
Do not use the bcrypt.hashSync function, as while it is running your server will not be able to do anything else.

Related

Not getting req.body results in login function despite it working in the registration function

I'm currently in the process of adding authentication to my app using the MERN stack.
I've managed to add the register functionality for the backend, but I'm struggling with the login function. Despite the user existing in the database, when I test it out in Postman I get an error every time. I tried to just find the user and not validate anything, but that also throws an error, which is confusing since the email definitely exists in the database.
Here's the loginUser and registerUser functions:
const jwt = require('jsonwebtoken');
const bcrypt = require ('bcryptjs')
const asyncHandler = require('express-async-handler')
let User = require('../models/user.model')
const registerUser = asyncHandler(async (req, res) => {
const {name, email, password} = req.body
//validate
if(!name || !email || !password) {
res.status(400)
throw new Error("Please add all fields")
}
const userExists = await User.findOne({email})
if(userExists){
res.status(400)
throw new Error("User already exists")
}
//hash password
const salt = await bcrypt.genSalt(10)
const hashedPassword = await bcrypt.hash(password, salt)
//create new user
const user = await User.create({
name : name,
email : email,
password : hashedPassword
})
if (user){
res.status(201).json({
_id: user.id,
name: user.name,
email: user.email
})
} else {
res.status(400)
throw new Error("Invalid user data")
}
})
const loginUser = asyncHandler(async (req, res) => {
const { email, password } = req.body
// Check for user email
const user = await User.findOne({ email })
if (user && (await bcrypt.compare(password, user.password))) {
res.json({
_id: user.id,
name: user.name,
email: user.email
})
} else {
res.status(400)
throw new Error('Invalid credentials')
}
})
const getUser = asyncHandler(async (req, res) => {
res.json("getUser")
})
router.route('/').post(registerUser)
router.route('/login').post(loginUser)
module.exports = router
And the Postman request:
I double checked the spellings and routes, which are all working fine, and I simply can't put my finger on why it isn't finding the User.
Any direction would be appreciated!

await bcrypt.compare(req.body.password, user.password) return false

The bcrypt.compare returns false always when it compares the result from the DB with the string password.
Hi Dear, I am trying to create a login authentication for my form. I create the user with a hash password and then I am trying to log in but during to compare plain text password and hashed password bcrypt.compare return false.
When I create a hash password and compare it in the same function it works well but if I take the hash password from the DB again it returns false.
const myFunction = async ()=>{
const passwordText = 'abcd123'
const hashedPassword = await bcrypt.hash(passwordText, 10)
console.log(passwordText)
console.log(hashedPassword)
const isMatch = await bcrypt.compare(passwordText, hashedPassword)
console.log(isMatch)
}
myFunction()
Out Put
abcd123
$2b$10$yNuWJBqlV8NjHrmqOfwaSuKDk.rSB9O6KstAmUpS2770GC1Nlyjw.
true
But when I create user with a hash password like this
router.post('/user/signup', async (req, res)=>{
try{
const user = new User(req.body)
const salt = await bcrypt.genSalt(10)
user.password = await bcrypt.hash(user.password, salt)
await user.save().then((user)=>{
res.status(201).send(user)
}).catch((e)=>{
res.send(e)
})
} catch(e){
res.status(500).send()
}
})
and when I compare it in log in route it returns false
router.post("/user/login", async (req, res) => {
const body = req.body;
const user = await User.findOne({ email: body.email });
if (user) {
// check user password with hashed password stored in the database
const validPassword = await bcrypt.compare(body.password, user.password);
if (validPassword) {
res.status(200).json({ message: "Valid password" });
} else {
res.status(400).json({ error: "Invalid Password" });
}
} else {
res.status(401).json({ error: "User does not exist" });
}
});
I tried to create a hash password in user schema like this
userSchema.pre('save', async function(next){
const user = this
if(user.isModified('password')){
user.password = await bcrypt.hash(user.password, 10)
}
console.log('Befor saveing')
next()
})
again it returns false.
I will appreciate any help, thanks.
Update and Solution
Finally, this post solve my problem, everything is working.
When I create a user in the password field I used lowercase: true, and after that, I remove this now bcrypt compare is working I got True return.
I will share my solution, I hope to help you.
LOGIN
exports.ValidateUser = async (req, res, next) => {
const user = await User.findOne({email:req.query.email});
if(user !== null){
const verify_password = await bcrypt.compare(req.query.password,user.password);
if(verify_password){
const token = await generateToken(user);
res.header("x-auth-token",token).send({
token:token
});
}else{
res.status(400).send({message:'Wrong email or password.'});
}
}else{
res.status(404).send({message:"User not found."})
}
}
Create User
exports.createUser = async (req,res,next) => {
const user = new User({first_name: req.body.first_name,
last_name: req.body.last_name,
email: req.body.email,
password: await bcrypt.hash(req.body.password, 10),
roles:req.body.roles});
user.save().then(()=>{
res.status(201).send({message:"User created."})
}).catch((error)=>{
res.status(400).send({error:error});
});
}

Why my data cannot be saved into MongoDB?

I try to sign up, i submit the form then it logs me undefined, and it doesn't save the user into the database, even the code from the tutorial works perfectly.
Here's my code
exports.postSignup = (req, res, next) => {
const email = req.body.email;
const password = req.body.password;
const confirmPassword = req.body.confirmPassword;
User.findOne({ email: email })
.then(userDoc => {
if (userDoc) {
return res.redirect('/signup');
}
const user = new User({
email: email,
password: password,
cart: { items: [] }
});
return user.save();
})
.then(result => {
res.redirect('/login');
})
.catch(err => {
console.log(err);
});
};
I have to mention that the user model is correct.
Im not sure, but in my own code I dont save an object within the return line.
Maybe try this
exports.postSignup = (req, res, next) => {
const email = req.body.email;
const password = req.body.password;
const confirmPassword = req.body.confirmPassword;
User.findOne({ email: email })
.then(userDoc => {
if (userDoc) {
return res.redirect('/signup');
}
const user = new User({
email: email,
password: password,
cart: { items: [] }
});
try{
user.save();
}catch(err){
res.send(err);
}
return user; //only if you want to return a user ofc
})
Other things that maybe going on:
Is your ip whitelisted?
Did you include the connection string?
Do you return a jason object? then make sure to use a parser in your middleware.

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);
}

error: data and hash arguments required for bcrypt. Incorrect MongoDB setup

I am trying to set up Passport with Express and MongoDB. At the moment I am able to register users in the database. But whenever I try to login, I get an error saying that data and hash arguments are required. Right now I have my Server.js file like this
const mongoose = require('mongoose');
const User = require('./models/users')
const initializePassport = require('./passport-config')
initializePassport(
passport,
email => User.find({email: email}),
id => User.find({id: id})
)
app.post('/register', checkNotAuthenticated, async (req, res) => {
try {
const hashedPassword = await bcrypt.hash(req.body.password, 10)
const newUser = new User({
id: Date.now().toString(),
name: req.body.name,
email: req.body.email,
password: hashedPassword
})
res.redirect('/login')
console.log(newUser)
} catch {
res.redirect('/register')
}
And my Passport-Config.js file like this `
const LocalStrategy = require('passport-local').Strategy
const bcrypt = require('bcrypt');
const User = require('./models/users')
function initialize(passport, getUserByEmail, getUserById) {
const authenticateUser = async (email, password, done) => {
const user = getUserByEmail(email)
if (user === null) {
return done(null, false, { message: 'No user with that email' })
}
try {
if (await bcrypt.compare(password, user.password)) {
return done(null, user)
} else {
return done(null, false, { message: 'Password incorrect' })
}
} catch (e) {
return done(e)
}
}
passport.use(new LocalStrategy({ usernameField: 'email' }, authenticateUser))
passport.serializeUser((user, done) => done(null, user.id))
passport.deserializeUser((id, done) => {
return done(null, User.findById({user: id}))
})
}
`
I've done some investigation using console.log() statements (not proud of it) but I think I've managed to find out the issue. If we add in the the first console log statement here:
app.post('/register', checkNotAuthenticated, async (req, res) => {
try {
console.log("BCRYPT COMPARE RUNS HERE")
const hashedPassword = await bcrypt.hash(req.body.password, 10)
const newUser = new User({
id: Date.now().toString(),
name: req.body.name,
email: req.body.email,
password: hashedPassword
})
res.redirect('/login')
console.log(newUser)
} catch {
res.redirect('/register')
}
and the second one here:
const initializePassport = require('./passport-config')
initializePassport(
passport,
email => User.find({email: email}).then((result) => { console.log("USER DATA EXTRACTED HERE") }).catch((err) => { console.log(err) }),
id => User.find({id: id})
)
The next time you click on login, you should see an output like:
Listening on port 3000
BCRYPT COMPARE HAPPENING
Error: data and hash arguments required
...
...
...
USER DATA EXTRACTED HERE
Notice that bcrypt.compare is being run before we are actually able to grab the user information from the DB? This means that all the arguments into that function are null, which is what is returning that error. Now, I'm no JS expert, but this can be fixed with an await statement added here:
function initialize(passport, getUserByEmail, getUserById) {
const authenticateUser = async (email, password, done) => {
const user = await getUserByEmail(email)
if (user === null) {
return done(null, false, { message: 'No user with that email' })
}
Which makes sure that the user info is queried from the DB before moving along in the script.

Resources