Q-promises and error handling - node.js

I am trying to understand Q Promises and how to handle two different errors thrown from two different then blocks.
Here is the function I would like to "Promisfy":
router.post('/user', function(req, res) {
var user = new User(req.body);
User.findOne({ email: req.body.email }, function(error, foundUser) {
if(foundUser) {
res.send("Error: User with email " + foundUser.email + " already exists.", 409);
} else {
User.create(user, function(err, createdUser) {
if (err) {
res.send(err, 400);
} else {
res.json({ id: createdUser.id }, 201);
}
});
}
});
});
It takes some User details, and tries to create a new User if one doesn't exist already with the same email. If one does, send a 409. I also handle the normal mongoose error with a 400.
I've tried using mongoose-q to convert it over, and I end up with this:
router.post('/user', function(req, res) {
var user = new User(req.body);
User.findOneQ({email : req.body.email})
.then(function(existingUser) {
if (existingUser) {
res.send("Error: User with email " + existingUser.email + " already exists.", 409);
}
return User.create(user);
})
.then(function(createdUser) {
res.json({ id: createdUser.id }, 201);
})
.fail(function(err) {
res.send(err, 400)
})
});
Is this correct? Is there anyway to push that existing user check into a fail block? I.e. throw an Error, and then catch it and deal with it?
Something like this perhaps:
router.post('/user', function(req, res) {
var user = new User(req.body);
User.findOneQ({email : req.body.email})
.then(function(existingUser) {
if (existingUser) {
throw new Error("Error: User with email " + existingUser.email + " already exists.");
}
return User.create(user);
})
.then(function(createdUser) {
res.json({ id: createdUser.id }, 201);
})
.fail(function(duplicateEmailError) {
res.send(duplicateEmailError.message)
})
.fail(function(mongoError) {
res.send(mongoError, 400)
})
});

I'm not experienced enough with Q. However, I can answer with bluebird.
bluebird supports typed catches. Which means you can create new Error subclasses, throw them, and handle them accordingly.
I've used newerror in my example to simplify the creation of Error subclasses.
var newerror = require('newerror');
var DuplicateEmailError = newerror('DuplicateEmailError');
router.post('/user', function(req, res) {
var user = new User(req.body);
User.findOneAsync({ email: req.body.email }).then(function(existingUser) {
if (existingUser) {
throw new DuplicateEmailError('Error: User with email ' + existingUser.email + ' already exists.');
}
return User.createAsync(user);
}).then(function(createdUser) {
res.json({ id: createdUser.id }, 201);
}).catch(DuplicateEmailError, function(err) {
res.send(err.message, 409);
}).catch(function(err) {
res.send(err.message, 500);
});
});

Related

keeps getting "Illegal arguments: undefined, string at Object.bcrypt.hashSync"

I've been struggling with Bcrypt on my MERN project I'm trying to create an authentication system I'm trying to run tests on Postman and I'm not sure why do I keep getting the error: "Illegal arguments: undefined, string at Object.bcrypt.hashSync"
this is my postman request:
this is the Controller Code:
const config = require("../config/auth.config");
const db = require("../models");
const User = db.user;
const Role = db.role;
var jwt = require("jsonwebtoken");
var bcrypt = require("bcryptjs");
exports.signup = (req, res) => {
const user = new User({
username: req.body.username,
email: req.body.email,
password: bcrypt.hashSync(req.body.password, 8),
});
user.save((err, user) => {
if (err) {
res.status(500).send({ message: err });
return;
}
if (req.body.roles) {
Role.find(
{
name: { $in: req.body.roles },
},
(err, roles) => {
if (err) {
res.status(500).send({ message: err });
return;
}
user.roles = roles.map((role) => role._id);
user.save((err) => {
if (err) {
res.status(500).send({ message: err });
return;
}
res.send({ message: "User was registered successfully!" });
});
}
);
} else {
Role.findOne({ name: "user" }, (err, role) => {
if (err) {
res.status(500).send({ message: err });
return;
}
user.roles = [role._id];
user.save((err) => {
if (err) {
res.status(500).send({ message: err });
return;
}
res.send({ message: "User was registered successfully!" });
});
});
}
});
};
exports.signin = (req, res) => {
User.findOne({
username: req.body.username,
})
.populate("roles", "-__v")
.exec((err, user) => {
if (err) {
res.status(500).send({ message: err });
return;
}
if (!user) {
return res.status(404).send({ message: "User Not found." });
}
var passwordIsValid = bcrypt.compareSync(
req.body.password,
user.password
);
if (!passwordIsValid) {
return res.status(401).send({ message: "Invalid Password!" });
}
var token = jwt.sign({ id: user.id }, config.secret, {
expiresIn: 86400, // 24 hours
});
var authorities = [];
for (let i = 0; i < user.roles.length; i++) {
authorities.push("ROLE_" + user.roles[i].name.toUpperCase());
}
req.session.token = token;
res.status(200).send({
id: user._id,
username: user.username,
email: user.email,
roles: authorities,
});
});
};
exports.signout = async (req, res) => {
try {
req.session = null;
return res.status(200).send({ message: "You've been signed out!" });
} catch (err) {
this.next(err);
}
};
The error message:
Illegal arguments: undefined, string at Object.bcrypt.hashSync wants to say that you're passing undefined as an argument to the hashSync function. We need to fix this error.
Take a closer look at this line where the error occurs:
password: bcrypt.hashSync(req.body.password, 8),
req.body.password is undefined, you can verify it by console.log(req.body.password). What's wrong is that you are sending data as URL parameters. So req.body is an empty object and req.body.password is undefined.
In Postman, select the Body tab, choose JSON format, then type your data as a JSON object. Then, in your code, use express.json() middleware to parse requests in JSON format. You'll have the desired output.
You can see my example request in Postman below:

MongoDB find always returning true when something doesnt exist (even on an empty database)

app.post('/sign-up', function (req, res) {
let emailValid = validator.validate(req.body.email);
let consent = req.body.consent ? true:false
if(emailValid && consent) {
const user = new UserModel({
name: req.body.firstName,
surname: req.body.surname,
email: req.body.email
})
UserModel.find({'email': req.body.email}, function(notFound, found) {
if(notFound) {
user.save().then(item => {
console.log('Saved successfully!');
res.render('submitSuccess', {data: req.body});
}).catch(err => {
res.status(400).render('404');
})
} else if(found) {
console.log('Exists');
res.status(404).render('submitSuccess', {data:req.body});
}
else {
res.status(404).render('404');
}
});
}
});
The intended functionality here is that if someone submits an email to the database that already exists, it does not then save a duplicate. However, it seems that found is returning true everytime, therefore nothing is getting saved.
Run this code:
app.post('/sign-up', function (req, res) {
let emailValid = validator.validate(req.body.email);
let consent = req.body.consent ? true : false
if (emailValid && consent) {
const user = new UserModel({
name: req.body.firstName,
surname: req.body.surname,
email: req.body.email
})
UserModel.find({ 'email': req.body.email }, function (err, found) {
if (err) {
console.error(err);
res.status(500).end();
return;
}
if (found.length == 0) {
user.save().then(item => {
console.log('Saved successfully!');
res.render('submitSuccess', { data: req.body });
}).catch(err => {
res.status(400).render('404');
})
} else {
console.log('Exists');
res.status(404).render('submitSuccess', { data: req.body });
}
});
}
});
What's in the err?

Stripe subscription not updating quantity

I have a fairly simple application that just allows you to create a single admin user that can create sub users under their account. When an admin creates another user I want them to update their subscription on stripe with their current number of users which is gets stored at user.company.subUserCount. But when I do this the user model is not updated with the correct value and stripe will not update at all.
Was hoping someone could take a look at my code snippets and see what is wrong with it that is causing it not to update Stripe.
Route
// POST USER/NEW
app.post('/user/new',
isAuthenticated,
company.postNewUserPlan,
sessions.postSignupSub,
(req, res) => {
User.findById(req.user.id, function(err, user) {
user.company.subUserCount = req.user.company.subUserCount + 1;
user.save();
});
});
company.postNewUserPlan
exports.postNewUserPlan = function(req, res, next){
var plan = req.user.company.stripe.plan;
var coupon = null;
var stripeToken = null;
plan = plan.toLowerCase();
if(req.body.stripeToken){
stripeToken = req.body.stripeToken;
}
User.findById(req.user.id, function(err, user) {
if (err) return next(err);
var quantity = user.company.subUserCount + 1;
user.setPlan(plan, coupon, quantity, stripeToken, function (err) {
var msg;
if (err) {
if(err.code && err.code == 'card_declined'){
msg = 'Your card was declined. Please provide a valid card.';
} else if(err && err.message) {
msg = err.message;
} else {
msg = 'An unexpected error occurred.';
}
req.flash('errors', msg);
return res.redirect('/user/create');
}
});
});
next();
};
sessions.postSignupSub passport code
passport.use('signup-sub', new LocalStrategy({
usernameField: 'email',
passReqToCallback : true
},
function(req, email, password, done) {
User.findOne({ email: req.body.email }, function(err, existingUser) {
if(err){
console.log(err);
}
if (existingUser) {
req.flash('form', {
email: req.body.email
});
return done(null, false, req.flash('error', 'An account with that email address already exists.'));
}
var preRole = req.body.role;
var role = ''
if (preRole === undefined) {
role = 'manager';
} else if (preRole === 'on') {
role = 'employee';
}
// edit this portion to accept other properties when creating a user.
var user = new User({
email: req.body.email,
password: req.body.password, // user schema pre save task hashes this password
role: role,
companyID: req.user.companyID,
isVerified: true
});
user.save(function(err) {
if (err) return done(err, false, req.flash('error', 'Error saving user.'));
var time = 14 * 24 * 3600000;
req.session.cookie.maxAge = time; //2 weeks
req.session.cookie.expires = new Date(Date.now() + time);
req.session.touch();
return done(null, user, req.flash('success', `Your new ${role} has been created`));
});
});
})
);
I managed to solve the issue it was related to code that was not included in the OP after a bit of testing I figured out the subscription was not being updated with a quantity but instead was only being created with it.
Code
schema.methods.setPlan = function(plan, coupon, quantity, stripe_token, cb) {
var user = this;
var subscriptionHandler = function(err, subscription) {
if(err) return cb(err);
user.company.stripe.plan = plan;
user.company.stripe.subscriptionId = subscription.id;
user.company.subUserCount = quantity;
user.save(function(err){
if (err) return cb(err);
return cb(null);
});
};
var createSubscription = function(){
stripe.customers.createSubscription(
user.company.stripe.customerId,
{plan: plan, coupon: coupon, quantity: quantity},
subscriptionHandler
);
};
if(stripe_token) {
user.setCard(stripe_token, function(err){
if (err) return cb(err);
createSubscription();
});
} else {
if (user.company.stripe.subscriptionId){
// update subscription
stripe.customers.updateSubscription(
user.company.stripe.customerId,
user.company.stripe.subscriptionId,
{ plan: plan, coupon: coupon, ***quantity: quantity*** }, <-- Part I had to change
subscriptionHandler
);
} else {
createSubscription();
}
}
};

How to Async Mongoose Controller

Trying to configure a SignUp() controller that can update multiple (separate) user accounts when a referral code is provided by the user.
Basic Flow:
Verify email doesn't already exist in system
Find the driver w/ userID matching the rider's refCode (FindOneAndUpdate)
If Found: Add the userID of each user to the other users [clients] list
Only need to do a refCode match if isRider
If any of those fail... Return the specific error to the client/user
This does not work. But essentially, this is what I'm trying to accomplish...
// POST `/signup` (Create a new local user)
export function signUp(req, res, next) {
const newUser = new User({
email: req.body.email,
password: req.body.password,
profile: {
userID: req.body.userID,
refCode: req.body.refCode,
isRider: req.body.isRider
}
});
User.findOne({ email: req.body.email }, (findErr, foundUser) => {
if (foundUser) {
return res.status(409).send('This e-mail address already exists');
}
// riders must link to a driver
if (req.body.isRider) {
// find driver + add rider ID to clients
return User.findOneAndUpdate({ 'profile.userID': req.body.refCode }, { $push: { clients: newUser.profile.userID }}).exec()
.then((err, foundDriver) => {
if (err) {
return res.status(409).send('Error searching for driver');
} else if (!foundDriver) {
return res.status(409).send(`We can't find your driver (${req.body.refCode})!`);
}
// add driver ID to rider's clients
newUser.clients = [req.body.refCode];
return newUser.save((saveErr) => {
if (saveErr) return next(saveErr);
return req.logIn(newUser, (loginErr) => {
if (loginErr) return res.sendStatus(401);
return res.json(newUser.profile);
});
});
});
}
return newUser.save((saveErr) => {
if (saveErr) return next(saveErr);
return req.logIn(newUser, (loginErr) => {
if (loginErr) return res.sendStatus(401);
return res.json(newUser.profile);
});
});
});
}
Tried to configure it as a pure promise but no luck. Most of the examples out there all seem different to me... Also could not figure out how to handle/throw specific errors using the mongoose docs.
Greatly appreciated if anyone can lend a hand, Thx!
UPDATE:
Ippi's answer helped a ton - Thx!
This does the trick. Remember to return null from .then() after the req.login stuff to avoid warnings - Any tips on how to improve this are appreciated - Thx!
const createUser = (foundUser) => {
if (foundUser) { throw new Error('This e-mail address already exist.'); }
if (!req.body.isRider) { return newUser.save(); }
return User.findOneAndUpdate({ 'profile.userID': req.body.refCode.toLowerCase() }, { $push: { clients: newUser.profile.userID }}).exec()
.then((driver) => {
if (!driver) { throw new Error('We can\'t find your driver.'); }
newUser.clients = [req.body.refCode];
return newUser.save();
})
.catch(() => { throw new Error('There was a database error.'); });
};
User.findOne({ email: req.body.email }).exec()
.then(createUser)
.then((user) => {
if (user.profile) {
req.logIn(user, (loginErr) => {
if (loginErr) return res.sendStatus(401);
return res.status(200).send({ profile: user.profile, clients: user.clients });
});
} else { res.status(409); }
return null;
})
.catch((err) => { return res.status(409).send(err.message); });
function signUp(req, res, next) {
return new Promise((resolve, reject) => {
const newUser = new User({
email: req.body.email,
password: req.body.password,
profile: {
userID: req.body.userID,
refCode: req.body.refCode,
isRider: req.body.isRider
}
});
User.findOne({ email: req.body.email }, (findErr, foundUser) => {
if (foundUser) {
// return res.status(409).send('This e-mail address already exists');
reject('This e-mail address already exists');
}
// riders must link to a driver
if (req.body.isRider) {
// find driver + add rider ID clients
return User.findOneAndUpdate({ 'profile.userID': req.body.refCode }, { $push: { clients: newUser.profile.userID } }).exec()
.then((err, foundDriver) => {
if (err) {
// return res.status(409).send('Error searching for driver');
reject('Error searching for driver');
} else if (!foundDriver) {
// return res.status(409).send(`We can't find your driver (${req.body.refCode})!`);
reject(`We can't find your driver (${req.body.refCode})!`);
}
// add driver ID to rider's clients
newUser.clients = [req.body.refCode];
newUser.save((saveErr) => {
if (saveErr)
// next(saveErr);
reject(saveErr);
req.logIn(newUser, (loginErr) => {
if (loginErr)
// return res.sendStatus(401);
reject('401');
// return res.json(newUser.profile);
resolve(newUser.profile);
});
});
});
}
newUser.save((saveErr) => {
if (saveErr)
// return next(saveErr);
reject(saveErr);
req.logIn(newUser, (loginErr) => {
if (loginErr)
// return res.sendStatus(401);
reject(loginErr);
// return res.json(newUser.profile);
resolve(newUser.profile);
});
});
});
});}
This is how I would do it. I couldn't be bothered to try with express or the login (you need to replace console.log with res.status().json()) and I might have done some other blunder in the logic with the driver. But other than that I tested it with local mongo and it probably works and if nothing else it's a little bit more concise.
let updateUser = user => {
if (user){ throw new Error("USER_EXIST"); }
if (!req.body.isRider) { return newUser.save() }
return User.findOneAndUpdate({ 'profile.userID': req.body.refCode },{ $push: { clients: newUser.profile.userID }}).exec()
.then(driver => {
if (!driver) { throw new Error("NO_DRIVER");}
newUser.clients.push(req.body.refCode);
return newUser.save();
});
}
User.findOne({ email: req.body.email }).exec()
.then(updateUser)
.then(req.logIn) // newUser.save() response is passed in as is (I have not tested this line.)
.then( ()=>{ return console.log('profile', newUser.profile); })
.catch( Error, err => {
if (err.message == "USER_EXISTS") return console.log ("This e-mail address already exist." );
if (err.message == "NO_DRIVER") return console.log ("We can't find your driver." );
throw err;
});
Something worth remembering:
Callback calls or res.send should always go in the last then / catch. Calling res.send in middle of chains leads to trouble.

how to use Promise with express in node.js?

I am using Promise with Express.
router.post('/Registration', function(req, res) {
var Promise = require('promise');
var errorsArr = [];
function username() {
console.log("1");
return new Promise(function(resolve, reject) {
User.findOne({ username: req.body.username }, function(err, user) {
if(err) {
reject(err)
} else {
console.log("2");
errorsArr.push({ msg: "Username already been taken." });
resolve(errorsArr);
}
});
});
}
var username = username();
console.log(errorsArr);
});
When I log errorsArray, it is empty and I don't know why. I am new in node.js. Thanks in advance.
Try the following, and after please read the following document https://www.promisejs.org/ to understand how the promises work.
var Promise = require('promise');
router.post('/Registration',function(req,res,next) {
function username() {
console.log("agyaaa");
return new Promise(function(resolve,reject) {
User.findOne({"username":req.body.username}, function(err,user) {
if (err) {
reject(err)
} else {
console.log("yaha b agyaaa");
var errorsArr = [];
errorsArr.push({"msg":"Username already been taken."});
resolve(errorsArr);
}
});
});
}
username().then(function(data) {
console.log(data);
next();
});
});
You can have other errors also (or things that shouldn't be done that way). I'm only showing you the basic use of a Promise.
router.post('/Registration', function(req, res) {
return User
.findOne({ username: req.body.username })
.then((user) => {
if (user) {
return console.log({ msg:"Username already been taken" });
}
return console.log({ msg: "Username available." });
})
.catch((err)=>{
return console.error(err);
});
});
you can write a clean code like this.
Promise is a global variable available you don't need to require it.

Resources