Strange async behavior in Node with Mongoose and promises - javascript

So I'm using Node.js + Mongoose + Lie as a promise library. The code is as follows:
var User = require('../models/user'),
Promise = require('lie'),
jwt = require('jsonwebtoken'),
config = require('../../config');
module.exports = function(express) {
var router = express.Router();
router.post('/', function(req, res) {
if (!req.body.username) return res.json({ success: false, reason: 'Username not supplied.' });
if (!req.body.password) return res.json({ success: false, reason: 'Password not supplied.' });
var findUser = new Promise(function(resolve, reject) {
User.findOne({ username: req.body.username }, function(err, user) {
if (err) reject (err);
if (!user) reject({ success: false, reason: 'User not found or password is incorrect.' });
if (!user.validPassword(req.body.password)) reject({ success: false, reason: 'User not found or password is incorrect.' });
resolve(user);
});
});
var sendToken = function(user) {
var token = jwt.sign({ username: user.username }, config.secret, { expiresIn: 2 * 60 * 60 });
res.json({ success: true, token: token });
};
findUser.then(function(value) {
sendToken(value);
}).catch(function(reason) {
res.send(reason);
});
return router;
};
So basically it's an auth route, which sends a signed jwt if everything is good. The strange behavior is that if I send a wrong username, the server throws an error saying
TypeError: Cannot read property 'validPassword' of null
So, it says that the user === null when it reaches the password validity check, HOWEVER, before this validity check, there is a check that a user is found, mainly
if (!user) reject({ success: false, reason: 'User not found or password is incorrect.' });
And here the server already knows that user === null, hence it should reject this promise with success: false, and this piece of code should be thrown into catch part, but it doesn't happen.
Any ideas, please?
I should add that if I change that if (!user.validPassword... part to else if instead of if, it works like it should. However I cannot understand why any code is still executed after the promise gets rejected.
EDIT
While learning node, I mainly used the MEAN Machine book, which is a great help, and there they use this syntax:
if (!user) {
//
} else if (user) {
//
}
But I guess it should work my way as well.

You are not stopping execution of your function on reject, so if findOne results in empty result validPassword is still get called.
Probably you need to add return:
User.findOne({ username: req.body.username }, function(err, user) {
if (err)
return reject (err);
if (!user)
return reject({ success: false, reason: 'User not found or password is incorrect.' });
if (!user.validPassword(req.body.password))
return reject({ success: false, reason: 'User not found or password is incorrect.' });
resolve(user);
});
Or use else if:
User.findOne({ username: req.body.username }, function(err, user) {
if (err) {
reject (err);
} else if (!user) {
reject({ success: false, reason: 'User not found or password is incorrect.' });
} else if (!user.validPassword(req.body.password))
reject({ success: false, reason: 'User not found or password is incorrect.' });
} else {
resolve(user);
}
});

Related

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

Why is my promise not resolving correctly?

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

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

Javascript - Giving the wrong status Code during tests

I am running tests for signin controller, it keeps giving the wrong status code (401) instead of 200 as i programmed it to be.
I expect it to use the data stored when a user signs up and return it if the given input is correct.
It works perfectly in postman but as i am writing tests, it throws the 401 error.
It is like it does not find the user
This is test block for the sign in:
it('it should signin a new user', (done) => {
request(app)
.post('/api/users/signin')
.send({
username: "Charles",
password: "challenger",
})
.expect(200)
.end((err, res) => {
if (err) {
return done(err);
}
done()
});
});
This is my controller for Logging in:
signin(req, res) {
const username = req.body.username.toLowerCase().trim();
// const email = req.body.email.trim();
if(!username) {
return res.status(401)
.send(
{status: false,
message: "Username cannot be empty"
});
}
else if (!req.body.password) {
return res.status(401)
.send({
status: false,
message: "Password field cannot be empty"
});
}
return User.findOne({
where: {
username,
}
})
.then((user) =>{
if(!user) {
return res.status(401).send({message: "User is not registered"})
}
else if(!user.validPassword(req.body.password)){
return res.status(401)
.send({
message: "The password is incorrect"
})
}
const token = user.generateAuthToken();
res.header('x-auth', token).status(200).send({
statusCode: 200,
message: `Welcome back, ${user.username}`,
user
});
})
.catch(error => {return res.status(400).send(error)})
},
This is the error i get:
1) Testing API routes POST /api/users/ it should signin a new user:
Error: expected 200 "OK", got 401 "Unauthorized"
at Test._assertStatus (node_modules\supertest\lib\test.js:266:12)
at Test._assertFunction (node_modules\supertest\lib\test.js:281:11)
at Test.assert (node_modules\supertest\lib\test.js:171:18)
at Server.assert (node_modules\supertest\lib\test.js:131:12)
at emitCloseNT (net.js:1552:8)
at _combinedTickCallback (internal/process/next_tick.js:77:11)
at process._tickCallback (internal/process/next_tick.js:104:9)
I would put a bunch of console.log() in there to see exactly which code is firing since you have 4 chances to fire the 401.
Here is some code for you to examine:
// I don't understand enough context, so I have to re-write this
// to show you how it could be an async function which will
// return a promise, but will also allow you to await.
// async function sign(req, res) {
const sign = async (req, res) => { // This is same as above line
const username = req.body.username.toLowerCase().trim();
// const email = req.body.email.trim();
if (!username) {
console.log('username was empty')
return res.status(401).send({
status: false,
message: "Username cannot be empty"
});
}
if (!req.body.password) {
console.log('password was empty')
return res.status(401).send({
status: false,
message: "Password field cannot be empty"
});
}
return await User.findOne({ where: { username } })
// I'm making this one async also to ensure user.generateAuthToken()
// has a value before it proceeds to res.send()
.then(async (user) => {
if (!user) {
console.log('couldnt find user')
return res.status(401).send({
message: "User is not registered"
})
}
else if (!user.validPassword(req.body.password)){
console.log('password was incorrect')
return res.status(401).send({
message: "The password is incorrect"
})
}
const token = await user.generateAuthToken();
// I added a return here
return res.header('x-auth', token).status(200).send({
statusCode: 200,
message: `Welcome back, ${user.username}`,
user
});
})
.catch((error) => {
console.log('lets put data in here: ' + error)
return res.status(400).send(error)
})
},
I notice the MongoDB search User.findOne({ where: { username } }). I can't remember if it needs to be $where. I think MongoDB syntax uses a $. That could be your problem, and it will fire that console.log('couldnt find user') if so. That might be only for native MongoDB driver. I just Googled and found that the syntax could also be: User.findOne({ username }) which is shorthand for User.findOne({ username: username }).
Some people will tell you it is redundant to do return await fn() and omit the await, but it will throw an unhandled promise rejection if the promise is rejected. It will be caught if await is there. This is part of the upper scope's error handling architecture.
I recommend watching some async/await tutorials, because I see you mixing in a little bit of callback sauce. Your code is pretty good, but I think you can take it to the next level. It looks like you're ready.
Fun fact, you can also omit { and } if your if statement only has one expression, ie:
if (err) {
throw err;
}
can be shorthand:
if (err) throw err;
This can go a long way to help clean up the code, but async/await syntax with try/catch blocks used properly along with throw will do incredible improvements to synchronous looking code with minimal nesting.
Here is how you could re-write some of this, because I want to show you how we can get rid of nesting that adds confusion to your flow control:
const sign = async (req, res) => {
try {
const username = req.body.username.toLowerCase().trim()
if (!username) throw 'noUsername'
if (!req.body.password) throw 'noPassword'
const foundUser = await User.findOne({ username })
if (!foundUser.username) throw 'notFound'
// I assume this returns Boolean
const validPassword = await user.validPassword(req.body.password)
if (!validPassword) throw 'invalidPassword'
// Alter generateAuthToken() to throw 'badToken' if it fails
const token = await user.generateAuthToken()
return res.header('x-auth', token).status(200).send({
statusCode: 200,
message: `Welcome back, ${user.username}`,
user
})
} catch (error) {
// errors are manually thrown into here, and rejected promises
// are automatically thrown into here
if (error === 'noUsername') return res.status(401).send({
status: false,
message: 'Username cannot be empty'
})
if (error === 'noPassword') return res.status(401).send({
status: false,
message: 'Password field cannot be empty'
})
if (error === 'notFound') return res.status(401).send({
message: 'User is not registered'
})
if (error === 'invalidPassword') return res.status(401).send({
message: 'The password is incorrect'
})
if (error === 'badToken') return res.status(403).send({
message: 'User is not authorized'
})
return res.status(400).send(error)
}
}
sign(req, res).then((response) => console.log(response))
Hopefully, this has been helpful :) and sorry I don't use semi-colons.

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