Why is my promise not resolving correctly? - javascript

exports.addUser = async(req, res) => {
const {
username,
email,
password
} = req.body;
//hash password
const password_hash = await hashPassword(password);
//check whitelist
this.checkWhitelist(email).then(function(response) {
if (response) {
console.log("RESOLVED TRUE")
//POST user to Airtable
new Promise(function(resolve, reject) {
return usersTable.create({
email,
username,
password_hash,
"email_verified": "false"
},
function(err) {
if (err) {
resolve(false);
console.error(err);
res.send({
"Success": false,
"responseCode": 502,
})
}
resolve(true);
res.send({
"Success": true,
"responseCode": 200,
});
}
).then(function(response) {
if (response) {
const EMAIL_SECRET = "xxxxxxxxxxx";
jwt.sign({
'username': username,
},
EMAIL_SECRET, {
expiresIn: '1d',
},
(err, emailToken) => {
const url = `http://localhost:3000/confirmation/${emailToken}`;
transporter.sendMail({
to: args.email,
subject: 'Confirm Email',
html: `Please click this email to confirm your email: ${url}`,
});
}
)
}
})
})
} else {
console.log('RESOLVED FALSE')
res.send({
"Success": false,
"responseCode": 403
})
}
})
}
For some reason, the promise I created at usersTable.create is not resolving correctly. When I call .then() after, I get the error: UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'then' of undefined.
For context, this is the user registration flow for a webapp. First, the pass is hashed, then the email is check against a whitelist (so far this logic is working). Now I just need to verify the email, but can't get the .then() to call correctly.
What's going on?

In your first then where you return createTable you'll need to return new Promise so it can be chained to the next then.
If createTable returns a promise, you can simply write return createTable and get rid of the new Promise that wraps it.
Since you are using async-await earlier in the code, I'd recommend you moving to that completely as it makes the code much easier to read.
I took a stab at that,
exports.addUser = async(req, res) => {
const {
username,
email,
password
} = req.body;
//hash password
const password_hash = await hashPassword(password);
//check whitelist
try {
const whitelist = await this.checkWhiteList(email)
if (whitelist) {
await usersTable.create() // shortened for readability sake.
const EMAIL_SECRET = 'xxxxxxxxxxx';
jwt.sign(
{
'username': username,
},
EMAIL_SECRET,
{
expiresIn: '1d',
},
(err, emailToken) => {
const url = `http://localhost:3000/confirmation/${emailToken}`;
transporter.sendMail({
to: args.email,
subject: 'Confirm Email',
html: `Please click this email to confirm your email: ${url}`,
});
}
);
}
res.send({
'Success': true,
'responseCode': 200,
});
} catch (error) {
res.send({
'Success': false,
'responseCode': 403,
});
}
}

Related

Forgot password functionality using NodeJs/Knex/Nodemailer and it is not working properly

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

Getting error while saving documents using mongoose in node express.js project

I am getting error even after saving document to the mongodb using mongoose in node express.js project.
Here's my code:
exports.storeJob = async (req, res, next) => {
const { name, email, password, title, location, descriptionUrl, tags, company, companyLogo, coupon, showLogo, highlightWithColor, customColor, makeSticky } = req.body;
const { error } = userRegisterValidation(req.body);
if (error) return res.status(400).json({ success: false, message: error.details[0].message });
const emailExists = await User.findOne({ email: email });
if (emailExists) return res.status(400).json({ success: false, message: "User already exits. Please Login" });
const salt = await bcrypt.genSalt(10);
const hashPassword = await bcrypt.hash(password, salt);
const user = new User({
name: name,
email: email,
password: hashPassword
});
// try{
const savedUser = await user.save();
const job = new Job({
title,
location,
descriptionUrl,
tags,
company,
companyLogo,
coupon,
showLogo,
highlightWithColor,
customColor,
makeSticky,
status: 'open',
user: savedUser
});
try {
const createdJob = await job.save();
// try {
user.jobs.push(createdJob);
user.save();
res.status(201).json({ success: true, data: savedUser });
// } catch {
// res.status(400).json({ success: false, message: "Some error occured" });
// }
} catch (err) {
res.status(400).json({ success: false, message: "Error while creating job.", error: err });
}
// } catch(err) {
// res.status(400).json({ success: false, message: "Error while creating user" });
// }
}
I have 2 questions:
I have register method in userController. Is there any way to use that method inside storeJob method?
In the above code even after saving user and job to the database and linking them api response is
{ success: false, message: "Error while creating job.", error: {} }
user.jobs.push(createdJob);
user.save();
In that case, this two lines creates a new user, because user defines the User schema.
Instead of these two lines try this
var push = {
jobs:createdJob
}
var update = {
"$addToSet":push
}
await User.findOneAndUpdate({ "_id": savedUser._id },update).exec();
Hope it will work fine to you. Thanks

Object is returned the same although I'm mutating it

The upcoming code snippet is removing the password attribute from the user JSON object and return it in response. what is happening is that the password attribute is still returning!
const signin = (req, res, next) => {
let requestBody = req.body;
userModel.findUserByEmail(requestBody.email).then(user => {
bcrypt.compare(requestBody.password, user.password, (error, result) => {
if (!result) {
return res.status(500).json({
status: false,
message: 'Auth Failed!',
error
});
}
if (error) {
return res.status(500).json({
error
});
}
let token = jwt.sign({
email: user.email,
userId: user._id
},
process.env.JWT_KEY,
{
expiresIn: "2h"
});
// remonve password key
delete user.password
res.status(200).json({
status: true,
message: 'Authenticated!',
data: {
token,
user
}
});
});
}).catch(error => {
return res.status(500).json({
status: false,
message: 'Auth Failed!',
error
});
});
}
not sure the problem is related to async compilation or not
You could create a new object without the password and use that in your response:
const { password, ...restOfUser } = user
res.status(200).json({
status: true,
message: 'Authenticated!',
data: {
token
user: restOfUser
}
})

How to migrate my mongoose PROMISE chain transactions to ASYNC / AWAIT flow?

I created an API that integrate database responses in a promise flow, but I think the interpretation of the code is complex and I believe that async / await approach could improve both understanding and the code itself.
The API is built in NodeJS using mongoose 5.6.1 and express 4.17.1.
Can you help me in improve this?
Below is the API that I want to improve:
/** New employee */
router.post('/', (req, res) => {
let { idCompany, name, departament } = req.body;
let _id = mongoose.Types.ObjectId(); // Generating new MongoDB _ID
let employeeCreated;
const promise1 = new Promise((resolve, reject) => {
// Querying by document '$oid'
Companies.findOne({ _id: idCompany }, (err, company) => {
// Error returned
if (err) reject({ error: "Invalid request, something went wrong!" });
// Invalid data received
if (!company) reject({ error: "Unauthorized action!" });
// Everything OK
resolve(company);
});
})
.then(company => {
if(company) {
const promise2 = new Promise((resolve, reject) => {
Employees.create({ _id, idCompany, name, departament }, (err, employee) => {
// Error returned
if (err) reject({ error: "Invalid request, something went wrong!", err });
// Everything OK
employeeCreated = employee;
resolve(company);
});
})
return promise2;
}else reject({ error: "Company not found!" });
})
.then(company => {
let { name: companyName, address, email, tel, employees } = company;
employees.push(_id);
const promise3 = new Promise((resolve, reject) => {
Companies.findByIdAndUpdate(
{ _id: idCompany },
{ $set: { _id: idCompany, name: companyName, address, email, tel, employees } }, // spotlight
{ new: true },
(err, company) => {
// Something wrong happens
if (err) reject({ success: false, error: "Can't update company!" });
// Everything OK
resolve(company);
}
);
});
return promise3;
});
promise1
.then(() => res.json({ success: true, employeeCreated }))
.catch(err => res.status(400).json({ error: "Invalid request, something went wrong!", err }));
});
Regards.
One key to using promises with mongoose, is using the exec method:
Your code could then look something like this (not tested):
router.post('/', async (req, res) => {
try {
const { idCompany, name, departament } = req.body;
const _id = mongoose.Types.ObjectId();
const company = await Companies.findOne({ _id: idCompany }).exec();
const employeeCreated = await Employees.create({ _id, idCompany, name, departament });
const { name: companyName, address, email, tel, employees } = company;
employees.push(_id);
await Companies.findByIdAndUpdate(
{ _id: idCompany },
{ $set: { _id: idCompany, name: companyName, address, email, tel, employees } }, // spotlight
{ new: true }).exec();
res.json({ success: true, employeeCreated });
} catch(err) {
res.status(400).json({ error: "Invalid request, something went wrong!", err });
}
});
You could throw some specific custom errors in the try block if you find that necessary.
You could simply make the functions where your promises are running async and so, you could await for the promises to resolve.
For example, in your route use this:
router.post('/', async (req, res) => {
and then when performing an async operation, use this:
const company = await Companies.findOne({ _id: idCompany }).exec();
Also, I would suggest you to wrap this with try and catch statments
Hope it helps!

Mongoose query promise in function make code became ugly

I was creating my users API, I want to check if username had been used.
So I wrote a static function
static findByName(name) {
const query = User.where({
username: name,
});
query.findOne((queryErr, user) => {
if (queryErr) {
console.log(queryErr);
return false;
}
return user;
});
}
when I called it in signUp
signup(req, res) {
if (!req.body.username || !req.body.password || !req.body.email) {
return res.status(400).json({ success: false, message: 'Bad Request' });
}
if (!Users.findByName(req.body.username)) {
return res.status(409).json({ success: false, message: 'Username has been used' });
}
const hashedPassword = this.genHash(req.body.password);
const newUser = User({
username: req.body.username,
});
}
findByName return Undefined.
Finally I use promise.
signup(req, res) {
if (!req.body.username || !req.body.password || !req.body.email) {
return res.status(400).json({ success: false, message: 'Bad Request' });
}
return Users.findByName(req.body.username).then((existingUser) => {
if (existingUser) {
return res.status(409).json({ success: false, message: 'Username has been used' });
}
const hashedPassword = this.genHash(req.body.password);
const newUser = User({
username: req.body.username,
password: hashedPassword,
email: req.body.email,
});
return newUser.save().then((user) => {
res.json({ success: true, user });
}).catch((err) => {
res.status(500).json({ success: false, message: 'Internal Server Error' });
});
}).catch((err) => {
res.status(500).json({ success: false, message: 'Internal Server Error' });
});
}
That is really horrible code.
Is there better way to clean the code?
Is there better way to clean the code
Yes. I am going to assume /signup is defined as a POST route on the usual Express app instance
With that said you, since you are already using promises, you can go a step further and use async/await which is enabled by default in Node.js v7.6+.
This will make your code read more synchronously:
async signup(req, res) {
if (!req.body.username || !req.body.password || !req.body.email) {
return res.status(400).json({ success: false, message: 'Bad Request' });
}
try {
const existingUser = await Users.findByName(req.body.username)
if (existingUser) {
return res.status(409).json({ success: false, message: 'Username has been used' })
}
const hashedPassword = this.genHash(req.body.password);
const newUser = await User({
username: req.body.username,
password: hashedPassword,
email: req.body.email,
}).save()
res.json({ success: true, newUser });
} catch (error) {
res.status(500).json({ success: false, message: 'Internal Server Error' });
}
}
You may have noticed the use of try/catch. This is because since we are not using .catch() and we still have to handle any error that occurs. To further clean up to code, we can write an error handler middleware that will take care of errors for us:
src/middleware/error-handlers.js
// Wraps the router handler, catches any errors, and forwards to the next middleware that handles errors
exports.catchErrors = action => (req, res, next) => action(req, res).catch(next);
// Notice the first parameter is `error`, which means it handles errors.
exports.displayErrors = (error, req, res, next) => {
const err = error;
const status = err.status || 500;
delete err.status;
err.message = err.message || 'Something went wrong.';
if (process.env.NODE_ENV === 'production') {
delete err.stack;
} else {
err.stack = err.stack || '';
}
res.status(status).json({
status,
error: {
message: err.message,
},
});
};
Now we just need to use our error handlers:
app.js
const { catchErrors, displayErrors } = require('./middleware/error-handlers')
// Whenever you defined the function, needs to have the `async` keyword
async function signup(req, res) { ... }
// Wrap the function call
app.post('/signup', catchErrors(signUp))
// Handle any errors
app.use(displayErrors)
Using the above middleware transforms our code to:
async signup(req, res) {
const error = new Error()
if (!req.body.username || !req.body.password || !req.body.email) {
error.status = 400
error.message = 'Bad Request'
throw error
}
const existingUser = await Users.findByName(req.body.username)
if (existingUser) {
error.status = 409
error.message = 'Username has been used'
throw error
}
const hashedPassword = this.genHash(req.body.password);
const newUser = await User({
username: req.body.username,
password: hashedPassword,
email: req.body.email,
}).save()
res.json({ success: true, newUser });
}
You can see how the code is much easier to read without all the noise.
Be sure to read up on:
Writing middleware for use in Express apps
Express error handling
I can't test that my solution actually works (probably not) or that I'm covering every functionality of your original code. What I tried to do is to make functions that handle specific things, then chain them together, so that you can see a more clear flow of the program.
signup(req, res) {
if (!req.body.username || !req.body.password || !req.body.email) {
return res.status(400).json({ success: false, message: 'Bad Request' });
}
const handleError = error => res.status(500).json({ success: false, message: 'Internal Server Error' });
const handleExistingUser = existingUser => {
if (existingUser) {
return res.status(409).json({ success: false, message: 'Username has been used' });
} else {
return Promise.resolve();
}
}
const handleCreateUser = () => {
const hashedPassword = this.genHash(req.body.password);
const newUser = User({
username: req.body.username,
password: hashedPassword,
email: req.body.email,
});
return newUser.save().then((user) => {
res.json({ success: true, user });
});
};
// the flow of the program is hopefully more clear here
return Users.findByName(req.body.username)
.then(handleExistingUser)
.then(handleCreateUser)
.catch(handleError);
}
If you handle the error of the inner and outer promise the same, then I think it's enough to have the error handling in the outer layer. (In your last example.) But I'm not 100% certain.
Your function returns undefined, because it does not have a return statement. The return user statement, is the (useless) return value for the findOne callback function, not for findByName.
If you go for promises, then define the function as follows:
static findByName(name) {
return User.where({ username: name }).findOne().exec();
}
And your promise chain can be simplified a bit, like this:
signup(req, res) {
function report(message, status = 200) {
res.status(status).json({ success: status === 200, message });
}
if (!req.body.username || !req.body.password || !req.body.email) {
return report('Bad Request', 400);
}
Users.findByName(req.body.username).then((existingUser) => {
return existingUser ? null // treat this condition in the next `then`
: User({
username: req.body.username,
password: this.genHash(req.body.password),
email: req.body.email,
}).save().exec();
}).then((user) => {
return existingUser ? report(user)
: report('Username has been used', 409);
}).catch((err) => {
report('Internal Server Error', 500);
});
}

Categories

Resources