I'm trying to implement a simple login function.
module.exports.login = (req, res, next) => {
let loggedin_user;
User.findOne({email: req.body.email.toLowerCase()})
.then(user => {
if(!user){
throw ('Invalid e-mail or password');
}
loggedin_user = user;
return bcryptjs.compare(req.body.password, user.password)
})
.then(res => {
if(!res){
return res.status(401).json('Invalid e-mail or password')
}
const token = jwt.sign({
id: loggedin_user._id,
role: loggedin_user.role
}, process.env.JWT_KEY, { expiresIn: '24h' });
return res.status(200).json({
token: token,
role: loggedin_user.role,
expires_in: 24*60*60})
})
.catch(err => {
return res.status(401).json(err);
})
}
My code works great until it reaches the last return part, this part:
return res.status(200).json({
token: token,
role: loggedin_user.role,
expires_in: 24*60*60,
})
It doesn't return anything instead it jumps to the catch block, although it console logs that javascript object that I need to return, it logs it right before the return statement.
What's the problem?
You should log the error message in the catch to see what the error is.
I'd suspect req.body or user may be undefined, and checking the properties email and password could result in an error.
At first glance, I don't catch any error to your code. Maybe issue is with your password, but I am not sure.
Any way to simplify things and check the same, I modified your code as below. Try this and let me know the output. To deal with Promises, Async/Await is better.
module.exports.login = async (req, res, next) => {
try {
const user = await User.findOne({ email: req.body.email.toLowerCase() });
if (!user) {
res.status(401).json('Invalid e-mail');
}
const checkPass = await bcryptjs.compare(req.body.password, user.password);
if (!checkPass) {
res.status(401).json('Invalid password');
}
const token = jwt.sign(
{
id: user._id,
role: user.role,
},
process.env.JWT_KEY,
{ expiresIn: '24h' }
);
res.status(200).json({
token: token,
role: user.role,
expires_in: 24 * 60 * 60,
});
} catch (err) {
console.error(err.message);
}
};
Related
I am new to Passport and to TSOA. I am trying to migrate an old Express route to TSOA controllers but I have not any idea how to achieve this. I did not find any documentation regarding this, in fact, I found this issue but I think I need a workaround.
router.post(
'/register',
(req: Request, res: Response, next) => {
const user: IUserData = req.body
if (validator.isEmpty(email))
return res.status(422)
.json({ error: 'Email is mandatory' })
if (validator.isEmpty(password.trim()))
return res.status(422)
.json({ error: 'Password is mandatory' })
next()
},
passport.authenticate('local'),
async (req: Request, res: Response) => {
if (req.user)
return res.json({ message: 'Already logged in' })
const existingUser: IUser = await UserModel.findOne({ email: req.body.email })
if (existingUser)
return res.status(400)
.json({ error: 'User exists' })
const data: IUserData = req.body
try {
const hashedPassword = await bcrypt.hashSync(data.password, parseInt(process.env.SALT_ROUNDS) || 10)
data.password = hashedPassword
} catch(error) {
return res.status(500)
.json({ error: error.message })
}
try {
const user: IUser = await new User(data)
await user.save()
res.json(user);
} catch(error) {
return res.status(500)
.json({ error: error.message });
}
return;
}
);
I have tried to "replicate" this code into a TSOA controller but I don't know how to apply the middleware stuff:
#Route('/auth')
export class AuthController extends Controller {
#Post()
public async register(#BodyProp() user: IUserData) {
const errors: Array<IAuthError> = []
if (validator.isEmpty(user.emailAddress)) {
this.setStatus(422)
return { error: 'Email is mandatory' }
}
if (validator.isEmpty(user.password.trim())) {
this.setStatus(422)
return { error: 'Password is mandatory' }
}
}
}
// to be continued ...
I could also separate this certain route in a different file (without any controller) but I don't know if that would work at all. I guess not.
How should I manage this?
Edit:
I just have read this article regarding TSOA authentication and it seems to handle authentication by using middlewares.
I have a sign-in logic with Express and Mongo. I need to put them into one promise chain and avoid nesting promises.
Here is my code:
const foundUser = User.findOne({ email: email }).exec();
const hashedPassword = bcrypt.hash(password, 10);
return Promise.all([foundUser, hashedPassword])
.then(arr => {
const [ user, hashedPassword ] = arr;
if (user) {
return res.status(400).json({
message: "This email has already occupied."
});
};
const newUser = new User({
fullName,
email,
password: hashedPassword
});
newUser.save()
.then(user => {
return res.status(200).json({
message: `${user.fullName}, you are successfully registered.`
});
})
.catch(err => {
console.log(err);
return res.status(500);
})
})
.catch(err => {
console.log(err);
return res.status(500);
});
I tried to use to combine them with Promise.all, but one of my promises depends on the previous one, so I added newUser.save() in my then.
I achieved that async/awaits like this:
const foundUser = await User.findOne({ email: email }).exec();
if (foundUser) {
return res.status(400).json({
message: "This email has already occupied."
});
}
const hashedPassword = await bcrypt.hash(password, 10);
const newUser = new User({
fullName,
email,
password: hashedPassword
});
const user = await newUser.save();
return res.status(200).json({
message: `${user.fullName}, you are successfully registered.`
});
} catch (err) {
console.log( err);
return res.status(500).json({
message: 'Error'
})
}
But I wonder is there a way to do it with promises and without nesting.
I need to put them on one chain so I can avoid using multiply catches
As #Bergi mentioned in the comment, you can return a promise from your .then callback. Depending on if this returned promise gets resolved or rejected, your .then or .catch next in the chain is called. You can do something like this:
const foundUser = User.findOne({ email: email }).exec();
const hashedPassword = bcrypt.hash(password, 10);
return Promise.all([foundUser, hashedPassword])
.then(arr => {
const [ user, hashedPassword ] = arr;
if (user) {
return res.status(400).json({
message: "This email has already occupied."
});
};
const newUser = new User({
fullName,
email,
password: hashedPassword
});
return newUser.save()
})
.then(user => {
return res.status(200).json({
message: `${user.fullName}, you are successfully registered.`
});
})
.catch(err => {
console.log(err);
return res.status(500);
});
Note: this is my first time posting, if you have feedback please let me know
Goal: I am building some endpoints that let a user reset their password if they forgot it. Flow would look like this:
User doesn't know password so they click on forgot password.
User types in email and clicks send
User receives email with link to reset password. Clicks on link and is redirected to type in their new password.
They click 'save' and they are redirected to login to sign in with their new password
I am using Insomnia to hit the endpoints for testing.
Things that are working:
When providing an email to reset password, Nodemailer does send out an email.
When updating the password it does show 'password updated' and gives a 200 status.
Bugs:
After trying to log in with that new password, it is not saving to the database. Only the old password will allow you to log back in.
Things I have tried:
I tried changing my user.model to use my findByEmail function and ran into some weird bugs, which then led me down a rabbit hold of issues.
I tried console logging quite a few things to see if I could trace the path.
I tried changing the user.update function but was not able to get it to work.
Here is my code:
Any guidance would be appreciated. If you need to look at any other files please let me know.
Forgot.password.js
const router = require('express').Router();
const crypto = require('crypto')
const User = require('../models/users.model')
const nodemailer = require('nodemailer')
router.post('/forgotpassword', (req, res) => {
let {
email
} = req.body
console.log(req.body)
// if (req.body.email === '') {
// res.status(400).json({ message: 'Email is required'})
// } console.error(req.body.email)
User.findBy({
email
})
.first()
.then(user => {
if (user === null) {
res.status(403).json({
message: 'Email not in db'
})
} else {
const token = crypto.randomBytes(20).toString('hex')
User.update({
resetPasswordToken: token,
resetPasswordExpires: Date.now() + 3600000,
})
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: `${process.env.EMAIL_USER}`,
pass: `${process.env.EMAIL_PASS}`
}
})
const mailOptions = {
from: `${process.env.EMAIL_USER}`,
to: `${user.email}`,
subject: '[Promoquo] Reset Password Link',
text: 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' +
'Please click on the following link, or paste this into your browser to complete the process within one hour of receiving it:\n\n' +
`http://localhost:5000/reset/${token}\n\n` +
'If you did not request this, please ignore this email and your password will remain unchanged.\n',
}
transporter.sendMail(mailOptions, (err, res) => {
if (err) {
console.log('ERROR coming from forgot.password js and it sucks', err)
} else {
console.log('here is the res', res)
res.status(200).json({
message: 'recovery email sent hell yes'
})
}
})
}
res.status(200).json({
message: 'Reset password email has been sent WOOHOO 🎉'
})
})
.catch(error => {
res.status(500).json({
message: 'ERROR on last catch forgotpassword.js, likely no user exists',
error
})
console.log(error)
})
})
module.exports = router
Update.password.js
const router = require('express').Router();
const passport = require('passport')
const bcrypt = require('bcrypt')
const User = require('../models/users.model')
const BCRYPT_SALT_ROUNDS = 12
router.put('/updatePasswordViaEmail', (req, res) => {
User.find({
where: {
username: req.body.username,
resetPasswordToken: req.body.resetPasswordToken,
resetPasswordExpires: Date.now() + 3600000,
}
})
.then(user => {
if (user == null) {
console.error('password reset link has expired')
res.status(403).json({ message: 'Password reset link is invalid or has expired' })
} else if (user != null) {
console.log('user exists in db')
bcrypt.hash(req.body.password, BCRYPT_SALT_ROUNDS)
.then(hashedPassword => {
User.update({
password: hashedPassword,
resetPasswordToken: null,
resetPasswordExpires: null,
})
})
.then(() => {
console.log('log for THEN updating password')
res.status(200).json({ message: 'password updated' })
})
} else {
console.error('no user exists in db to update')
res.status(401).json({ message: 'no user exists in db to update'})
}
})
})
module.exports = router
Users.model.js
const db = require('../dbConfig')
module.exports = {
add,
find,
findBy,
findById,
findByEmail,
findByType,
update
};
function find() {
return db('users').select('id', 'username', 'email', 'password');
}
function findBy(filter) {
return db('users').where(filter);
}
async function add(user) {
const [id] = await db('users').insert(user);
return findById(id);
}
function findById(id) {
return db('users').where({ id }).first();
}
function findByEmail(email) {
return db('users').where({ email }).first();
}
function findByType(type) {
return db('users').where({ type }).first();
}
function update(changes, id) {
return db('users').where({ id }).update(changes)
}
20200913211559_users.js (this is the table)
exports.up = function(knex) {
return knex.schema.createTable('users', tbl => {
tbl.increments();
tbl.string('firstname', 30).notNullable();
tbl.string('lastname', 30).notNullable();
tbl.string('username', 30).notNullable()
tbl.string('email', 50).notNullable()
tbl.string('password', 128).notNullable();
tbl.string('type').notNullable();
tbl.boolean('confirmed').defaultTo('false');
tbl.string('resetPasswordToken');
tbl.date('resetPasswordExpires');
})
};
exports.down = function(knex) {
return knex.schema.dropTableIfExists('users')
};
Your User.update() lines aren't running (you either need to return their promises into the chains of promises, or hook into their callbacks). async/await is your friend here to avoid "callback hell."
const user = await User.find({
where: {
username: req.body.username,
resetPasswordToken: req.body.resetPasswordToken,
resetPasswordExpires: Date.now() + 3600000,
}
})
if (!user) { /* ... */ }
const token = crypto.randomBytes(20).toString('hex')
await User.update({ // await here!
resetPasswordToken: token,
resetPasswordExpires: Date.now() + 3600000,
})
I have a repository where I connect directly to my model to insert some data, it creates the data successfully but when I connect my controller to this repository, I get a nulled response, if I log it in the repository itself I get Promise . Please checkout my code below:-
Repository.js
exports.register = (request) => {
const data = UserModel.findOne({email: request.email})
.then(user => {
if(user)
{
return {status: 400, message: 'Email Already exist'}
} else {
return bcrypt.genSalt(10, (err, salt) => {
const newUser = new UserModel({
username: request.username,
email: request.email,
password: request.password
});
return bcrypt.hash(newUser.password, salt, async (err, hash) => {
if(err) throw err;
newUser.password = hash;
return newUser.save()
.then(user => {
const token = jwt.sign({id: user._id}, process.env.JWT_SECRET, {
expiresIn: 86400 // expires in 24 hours
});
return {status: 200, message: 'Successfully Registered', auth: true, token: token, user: user}
})
.catch(err => {
return {status: 400, message: err}
})
})
})
}
})
console.log(data) // This part is return Promise <pending>
return data;
};
Controller.js
exports.SeedRegisteration = async (req, res, next) => {
try {
let element = await userRepo.register({username: "Testin", email: "Testin#test.com", "password":
"joe" });
return await res.status(200).json({ status: 200, data: element })
} catch (e) {
return res.status(400).json({ status: 400, message: e.message });
}
};
Works fine but does not return data
Here's the register function using the Promise version of bcrypt (if you don't supply a callback, the bcrypt functions return a Promise
exports.register = (request) =>
UserModel.findOne({
email: request.email
})
.then(user => {
if (user) {
throw 'Email Already exist'
}
})
.then(() => bcrypt.genSalt(10))
.then(salt => {
const newUser = new UserModel({
username: request.username,
email: request.email,
password: request.password
});
return bcrypt.hash(newUser.password, salt)
.then((hash) => {
newUser.password = hash;
return newUser.save();
})
}).then(user => {
const token = jwt.sign({
id: user._id
}, process.env.JWT_SECRET, {
expiresIn: 86400 // expires in 24 hours
});
return {
status: 200,
message: 'Successfully Registered',
auth: true,
token: token,
user: user
}
}).catch(err => {
return {
status: 400,
message: err
}
});
Note: there is ONE nested .then - this code could be perfectly flat if you used async/await in register - however I was not prepared to perform such a big rewrite for the answer. Now that the code is in a nice almost flat promise chain, it's relatively simple to convert the whole thing into async/await style
There are too many return statements which return promise. Please update your code in to the following:
exports.register = (request) => {
return new Promise((resolve, reject) => {
try {
UserModel.findOne({ email: request.email })
.then(user => {
if (user) {
return reject({ status: 400, message: 'Email Already exist' })
} else {
bcrypt.genSalt(10, (err, salt) => {
const newUser = new UserModel({
username: request.username,
email: request.email,
password: request.password
});
bcrypt.hash(newUser.password, salt, async (err, hash) => {
if (err) return reject(err);
newUser.password = hash;
newUser.save()
.then(user => {
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, {
expiresIn: 86400 // expires in 24 hours
});
return resolve({ status: 200, message: 'Successfully Registered', auth: true, token: token, user: user })
})
.catch(err => {
return reject({ status: 400, message: err })
})
})
})
}
}).catch(err => {
return reject(err)
})
} catch (error) {
return reject(error)
}
});
};
I have a function that look like this, for now at least it's working.
exports.changePassword = (req, res) => {
const { token, password, confirmPassword } = req.body
User.findOne({resetPasswordToken: token}, (err, user)=>{
if(!user){
return res.status(400).send({
msg: 'Invalid token or token has been used!'
})
}
const hash_password = bcrypt.hashSync(password, 10)
User.findOneAndUpdate({_id: user._id},
{hash_password},
(err, result)=>{
if(err){
return res.status(400).send({
msg: err
})
}
User.findOneAndUpdate({_id: user._id},
{resetPasswordToken: ''},
(err, result)=>{
if(err){
return res.status(400).send({
msg: err
})
}
res.status(200).json({
status: 1,
data: 'Your password has been changed.'
})
}
)
})
})
}
I just felt bad writing this block of code, because I think it has several problems:
callback hell
duplication of error handling code
For first problem maybe I can use done argument? and do some chaining? And also sometime I doubt I need to handle every single err callback. How would you rewrite above function to become more elegant?
You can use promises with Mongoose, which will help with your callback hell:
exports.changePassword = (req, res) => {
const { token, password, confirmPassword } = req.body
User.findOne({resetPasswordToken: token}).then((user)=>{
// do stuff with user here
const hash_password = bcrypt.hashSync(password, 10)
// Now chain the next promise by returning it
return User.findOneAndUpdate({_id: user._id}, {hash_password});
}).then((result)=>{
// Now you have the result from the next promise, carry on...
res.status(200).json({
status: 1,
data: 'Your password has been changed.'
})
}).catch(err => {
// Handle any errors caught along the way
});
}
Since these are promises, you can actually make this even neater by using the ES6 async/await syntax:
// Note this now has the async keyword to make it an async function
exports.changePassword = async (req, res) => {
const { token, password, confirmPassword } = req.body
try {
// Here is the await keyword
const user = await User.findOne({resetPasswordToken: token});
// do stuff with user here
const hash_password = bcrypt.hashSync(password, 10)
// Now the next promise can also be awaited
const result = await User.findOneAndUpdate({_id: user._id}, {hash_password});
// Finally send the status
res.status(200).json({
status: 1,
data: 'Your password has been changed.'
});
} catch (err) {
// Any promise rejections along the way will be caught here
});
}
To avoid this ugly Promise hell we have
ES2017 async/await syntax
You should change your whole code for something like this
exports.changePassword = async function (req, res){
try {
const { token, password, confirmPassword } = req.body
var user = await User.findOne({resetPasswordToken: token}).exec()
const hash_password = bcrypt.hashSync(password, 10)
var result = await User.findOneAndUpdate({_id: user._id}, {hash_password}).exec()
var result2 = await User.findOneAndUpdate({_id: user._id}, {resetPasswordToken: ''}).exec()
res.status(200).json({status: 1, data: 'Your password has been changed.'})
} catch (err) {
res.status(400).send({msg: err }) //If some await reject, you catch it here
}
}