Hi so I'm trying to create my first login with sequelize and I'm struggling with hashing and comparing hashes and it always returns false.
I figured I am doing something wrong on the hashes or comparing since I'm learning. I'm using SQL database
Here's my login code, I'm using express session and sequelize:
'processLogin': (req, res) => {
db.User.findOne({
where: {
email: req.body.email
}
})
.then(async user => {
var eSession = req.session.userLogin
let checkPass = await bcrypt.compare(req.body.password, user.password)
console.log(checkPass);
if(checkPass){
eSession = user;
res.send("Success");
}else{
res.render('login', {errors: [{
msg: "Incorrect password"
}]});
}
if(user == undefined){
res.render('login', {errors: [{
msg: "User not found, please Register"
}]});}
})
}
And here is where I actually hashed the passwords on my register:
'save': async (req, res) => {
var a = [req.body.fullname, req.body.email, req.body.number, bcrypt.hashSync(req.body.password, 10), bcrypt.hashSync(req.body.repassword, 10)];
let errors = validationResult(req);
if(errors.isEmpty()){
db.User.create({
full_name: a[0],
email: a[1],
phone_number: a[2],
password: await bcrypt.hash(a[3], 10),
confirm_password: await bcrypt.hash(a[4], 10)
})
.then(users => {
res.send("succes!!");
})
}else{
res.render('register', { errors: errors.errors })
}
}
}
insted of sync why don't you try async and wait unitll it get's hashed or decrypt.
inside async function to hash password.
let hashedPassword = await hash(password, 10);
and inside async function for comparing password
let checkPass = await compare(password, foundUser.password);
Related
I'm trying to let the user sign in using either email or username, I'm trying this code in Postman and each time I try it gives me this error:
"success":false,"status":500,"message":"data and hash arguments
required","stack":"Error: data and hash arguments required\n at
Object.compare
Auth.js Configurations:
const emailRegex = "/^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/";
router.post("/login", async (req, res, next) => {
const isEmail = String(req.body.emailOrUsername).match(emailRegex);
try {
if (!req.body.password) {
return next(createError(400, "Invalid password"));
}
let user;
if (isEmail) {
user = await User.findOne({ where: { email: req.body.emailOrUsername } });
} else {
user = await User.findOne({ where: { username: req.body.emailOrUsername } });
};
if (!user) return next(createError(404, "User not found!"));
const isPasswordCorrect = await bcrypt.compare(
req.body.password,
user.password
);
if (!isPasswordCorrect) return next(createError(400, "Wrong password!"));
const { password, ...others } = user._doc;
res.status(200).json(others);
} catch (err) {
next(err);
}
});
I'm not sure what I'm missing here!
I don't know what I'm missing but in the block catch session.abortTransaction() not rolling bak anything. I purposely used throw new Error("Error message") after the creation of a user to land in the catch block to trigger the session.abortTransaction() but when I look in the database I see that the user is still created despite the abortion.
Here is my source code
export const signUp = async (req, res) => {
const { email, password, fullname } = req.body;
// Init session
const session = await mongoose.startSession();
// Begin transaction
session.startTransaction();
try {
// Check if user exist in database using his email
const dbUser = await UserModel.findOne({ email }, null, session);
// Return a conflit code if user already exist
if (dbUser) return res.status(409).json({ message: "User already exists" });
// Create tenant
const tenantResult = await TenantModel.create([{ createdAt: new Date() }], null, session);
// Get admin role
const roleResult = await RoleModel.findById('626db17b8645717d657fc4c8', null, session);
//Hash password using jwt
const hashedPassword = await bcrypt.hash(password, 12);
// Store user in db
const userResult = await UserModel.create([
{
email: email,
password: hashedPassword,
fullname: fullname,
tenant: tenantResult[0],
role: roleResult
}
], null, session);
throw new Error("Error message");
if (userResult){
// Create subscription && currency
// Create currency for user
await CurrencyModel.create([
{
name: 'Dollar',
createdAt: new Date(),
symbol: '$',
isoCode: 'USD',
default: true,
userRole: 'setting-list',
createdBy: userResult[0],
}
], null, session);
}
// Generate token using jwt, secret, and user data
const token = jwt.sign( { email: userResult[0].email, id: userResult[0]._id }, secret, { expiresIn: "24h" } );
// Send welcome email to registered user
await sendWelcomeEmail(userResult[0].email, userResult[0].fullname, `Welcome ${userResult[0].fullname} - light-speed.dev`, 'welcome-email.html');
//Commit transaction
await session.commitTransaction();
// Return 201 Created http code with user and token
return res.status(201).json({ userResult, token });
} catch (error) {
// Abort transaction if one of these request throw error
await session.abortTransaction();
console.log(error);
return res.status(400).json({ message: 'Something went wrong!' });
} finally {
// close session
session.endSession();
}
};
Thank you for your help!
I finally figured out why the session doesn't roll back transactions. It's because of the way I specify the session option.
I change these lines
const dbUser = await UserModel.findOne({ email }).session(session);
const tenantResult = await TenantModel.create([{ createdAt: new Date() }], { session: session });
const roleResult = await RoleModel.findById('626db17b8645717d657fc4c8').session(session);
const userResult = await UserModel.create([
{
email: email,
password: hashedPassword,
fullname: fullname,
tenant: tenantResult[0],
role: roleResult
}
], { session: session });
await CurrencyModel.create([
{
name: 'Dollar',
createdAt: new Date(),
symbol: '$',
isoCode: 'USD',
default: true,
userRole: 'setting-list',
createdBy: userResult[0],
}
], { session: session });
I just learned Nodejs, now I'm having a problem that I don't know how to add async/await in function service login. I researched today without finding a solution. Please help me. Thank you very much!
userService.js
exports.findUser = async (email, password) => {
var result = null;
User.findOne({
email: email,
password: password,
})
.then((data) => {
if (data) {
result = data;
}
})
.catch((err) => {});
return result;
};
userController.js
exports.login = (req, res, next) => {
var email = req.body.email;
var password = req.body.password;
var data = userService.findUser(email, password);
console.log(data);
if (data !== null) {
res.status(200).json({ message: "null" });
} else {
res.status(401).json({ message: "Incorrect email or password!" });
}
};
Solution
From userService.js it is clear you are using mongoose. to use async and await in nodejs you can edit your code like this...
exports.findUser = async (email, password) => {
try{
var result = await User.findOne({
email: email,
password: password,
})
return result
}catch(e){
// catch errors and do something
}
}
Notice i used try and catch block so that i can catch error if anything goes wrong in the await. after the await resolves you get back a mongodb document same as you will get in the parameter passed to .then in a promise callback function.
The thing with async functions is it allows you to await a promise, basicallly suspending the execution of the function until the promise resolves. So, your code could be rewritten as:
//userService.js
exports.findUser = async (email, password) => {
var result = null;
try{
result = await User.findOne({
email: email,
password: password,
})
} catch(e){
\\handle error
}
return result;
};
//userController.js
exports.login = async (req, res, next) => {
var email = req.body.email;
var password = req.body.password;
var data = await userService.findUser(email, password);
console.log(data);
if (data !== null) {
res.status(200).json({ message: "null" });
} else {
res.status(401).json({ message: "Email hoặc mật khẩu không đúng!" });
}
};
By making both functions async, you can take advantage of the await keyword and promises without the 'callback hell' that appears with .then() and .catch() on promises.
MDN Docs
I am trying to get the return value from the below function, but I can't because return value gets executed before the result being returned.
Could someone point me to the right direction?
exports.addUser = async (data) => {
const updateduser = await db.User.findOne({
where: {
email: data.email,
},
}).then(user => {
if (user) {
return null
} else {
bcrypt.hash(data.password, 10).then(
hashedPassword => {
const newUser = {
firstName: data.firstName,
lastName: data.lastName,
email: data.email,
password: hashedPassword
}
db.User.create(newUser).then(insertedUser => {
return insertedUser
})
}
)
}
});
return updateduser
}
The controller where response should be based on the returned value from addUser functions above..
exports.signUp = async (req, res, next) => {
const userData = {
firstName: req.body.firstName,
lastName: req.body.lastName,
email: req.body.email,
password: req.body.password,
};
const {
error
} = signUpValidation(userData);
if (error) return res.status(400).json({
message: error.details[0].message
});
try {
const user = await addUser(userData);
if (!user) {
res
.status(403)
.json({
message: "This email is already exits, Please login 🔁 "
});
} else {
res.status(200).json({
id: user.id,
email: user.email,
status: true,
message: " You're now registered!✅Please check your Email!✅ ",
});
}
} catch (error) {
next(error);
}
}
You are mixing async/await, then, and nested then calls to a point where it gets really hard to follow your code. You should always try to stick to either async/await or then's (there are a couple of exceptions, though). Also try to avoid nesting whenever possible. Your code can be simplified to the following:
exports.addUser = async (data) => {
const user = await db.User.findOne({
where: {
email: data.email,
},
});
if (user) {
return null;
}
const hashedPassword = await bcrypt.hash(data.password, 10);
const newUser = {
firstName: data.firstName,
lastName: data.lastName,
email: data.email,
password: hashedPassword,
};
return db.User.create(newUser);
};
You should probably read a little more about await.
Note that the main problem in your code is that you are returning something in a nested then callback. But you neither return the inner nor outer then block itself.
The Code in Users.js gets an error in the snippet at: qrcode.toDataURL(secret.otpauth_url, (err, data_url) => {.
I've tried adding return statement to make sure I'm not sending the response multiple times. I can see that the data_url when converted to image online shows me a QR code but I'm unable to see that when I'm using Postman.
router.post(
"/",
[
check("name", "Name is required")
.not().isEmpty(),
check("email", "Please include a valid email").isEmail(),
check(
"password",
"Please enter a password with 6 or more characters"
).isLength({ min: 6 })
],
async (req, res) => {
console.log("hi");
console.log(JSON.stringify(req.body));
const errors = validationResult(req);
if (!errors.isEmpty()) {
// return res.status(400).json({ errors: errors.array() });
}
const {
name,
email,
password,
type_of_user,
question1,
answer1,
question2,
answer2
} = req.body;
try {
let user = await User.findOne({ email }); // await User.findOne({ email });
user = new User({
name,
email,
avatar,
password,
type_of_user,
question1,
answer1,
question2,
answer2
});
const salt = await bcrypt.genSalt(10); //await
user.password = await bcrypt.hash(password, salt); // await
user
.save()
.then(result => {
// MFAOptions & secret will generate a secret
const MFAOptions = {
issuer: "xyz",
user: req.body.email,
length: 64
};
const secret = speakEasy.generateSecret(MFAOptions);
const token = jwt.sign(
{
name: user.name,
email: user.email,
twofactor: false
},
config.get("jwtSecret"), // chnaged from process env jwt
{
expiresIn: "1h"
}
);
// update the user that is just created:
user
.update(
{ email: req.body.email },
{
$set: { twoFASecret: secret.base32 }
}
)
.exec()
.then(result => {
console.log(result);
qrcode.toDataURL(secret.otpauth_url, (err, data_url) => {
console.log(data_url);
res.status(200).json({
img: data_url,
token: token
});
});
return;
})
//if anything wrong, throws an error
.catch(err => {
console.log(err);
// res.status(500).json({ error: err });
});
})
// originaly this will end here, but now it should redirect to twoFA route,
// if something wrong, shows an error
.catch(err => {
console.log(err);
// res.status(500).json({ error: err });
});
// user with an id, primise which returns an id
const payload = {
user: {
id: user.id
}
};
jwt.sign(
payload,
config.get("jwtSecret"),
{ expiresIn: 3600 },
(err, token) => {
if (err) throw err;
res.json({ token });
}
);
// } //else end
} catch (err) {
console.error(err.message);
res.status(500).send("Server error");
}
}
);
module.exports = router;
I think your problem with executing this line qrcode.toDataURL(secret.otpauth_url, (err, data_url) => {
this calling has callback which means that you will continue in executing the rest of the code and send a response using res.json then after qrcode finish it executes will enter the callback and send another response which is not allowed.
you have multi execution for res.json you need to remove one of them and refactor your code
I tried to refactor your code :
const validation = [check('name', 'Name is required').not().isEmpty(),
check('email', 'Please include a valid email').isEmail(),
check('password', 'Please enter a password with 6 or more characters').isLength({ min: 6 })]
const toDataURL = (otpauth_url) => new Promise((resolve, reject) => {
qrcode.toDataURL(secret.otpauth_url, (err, data_url) => {
if(err)reject(err)
resolve(data_url)
res.status(200).json({
img: data_url,
token,
})
})
});
const signJwt = (payload)=>new Promise((resolve,reject)=>{
return jwt.sign(
payload,
config.get('jwtSecret'),
{ expiresIn: 3600 },
(err, token) => {
if (err) reject(err)
resolve(token)
}
)
})
const postRequest = async (req, res) => {
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() })
}
const { name, email, password, type_of_user, question1, answer1, question2, answer2, } = req.body
try {
let user = await User.findOne({ email })
user = new User({
name,
email,
avatar,
password,
type_of_user,
question1,
answer1,
question2,
answer2,
})
const salt = await bcrypt.genSalt(10) // await
user.password = await bcrypt.hash(password, salt) // await
await user.save()
// MFAOptions & secret will generate a secret
const MFAOptions = {
issuer: 'xyz', user: req.body.email, length: 64,
}
const secret = speakEasy.generateSecret(MFAOptions)
const token = jwt.sign(
{
name: user.name,
email: user.email,
twofactor: false,
},
config.get('jwtSecret'), { expiresIn: '1h', })
// update the user that is just created:
await user.update({ email: req.body.email },
{ $set: { twoFASecret: secret.base32 }, }).exec()
const data_url= await toDataURL(secret.otpauth_url)
if(data_url) return res.status(200).json({
img: data_url,
token,
})
const payload = {
user: {
id: user.id,
},
}
const token= await signJwt(payload)
return res.json({token})
} catch (err) {
console.error(err.message)
return res.status(500).send('Server error')
}
}
router.post('/', validation, postRequest)
module.exports = router