Form validation and Node JS - avoiding pyramid of doom - javascript

I am new to Node and am trying to do some user registration form validation. I would like to refactor the following code to get rid of the pyramid if possible. Im using Express Validator within the userService module to check against empty fields and for email validation, but the isValueUnique function is just a Mongoose findOne query. Im using Graphics Magick to resize the image. Any suggestions most welcome:
router.post('/register', function(req, res, next){
userCheck = userService.checkRegistration(req)
if(userCheck.errors){
return res.render('user/register', {
errors: userCheck.errors,
message: req.flash('error'),
title: 'People Power | Register'
})
}
User.isValueUnique({username: req.body.username}, function(err, user){
if(user){
return res.render('user/register', { message: 'Username already taken. Please choose another.', title: 'People Power | Register' });
}else{
User.isValueUnique({ email: req.body.email }, function(err, user){
if(user){
return res.render('user/register', { message: 'Email already registered. Please try again.', title: 'People Power | Register' });
} else{
User.createUser(userCheck, function(err, user){
if(err) throw err;
userService.resizeImage(userCheck, function(){
req.login(user, function(err){
req.flash('success', 'You have registered successfully, and are now logged in!')
res.redirect('/')
})
});
});
}
})
}
})
});

You could use async#waterfall to improve "callback hell", so your code might looks like the following:
async.waterfall([
function (callback) {
User.isValueUnique({username: req.body.username}, callback);
},
function (user, callback) {
if (user) {
return res.render('user/register', { message: 'Username already taken. Please choose another.', title: 'People Power | Register' });
}
User.isValueUnique({ email: req.body.email}, callback);
}
function (user, callback) {
if (user) {
return res.render('user/register', { message: 'Email already registered. Please try again.', title: 'People Power | Register'};
}
User.createUser(userCheck, callback);
},
function (user, callback) {
userService.resizeImage(userCheck, callback);
},
function (callback) {
req.login(user, function(err) {
req.flash('success', 'You have registered successfully, and are now logged in!')
res.redirect('/')
});
}
], function (err) {
if (err) throw err;
});
Also I suggest to look at PassportJS module that already has suite of patterns for implementation of users registration feature.

Related

How to use access messages from your passportjs strategy in React

If you have a react application and don't wanna use connect-flash to access the messages generated by the local strategy you can use the following:
My passport local strategy, I am using a sqlite db:
passport.use(
new LocalStrategy({ usernameField: 'name' },
(name, password, done) => {
//Match user
db.get(`SELECT * FROM Users WHERE name = '${name}'`, (err, user) => {
if(err) throw err;
if(!user) {
return done(null, false, { message : 'Username not registered' });
}
//Match password
bcrypt.compare(password, user.password, (err, isMatch) => {
if(err) throw Error;
if(isMatch) {
done(null, user, { message : 'Log in successful' });
} else { done(null, false, { message : 'Password incorrect' })
}
});
})
})
);
You can use a custom call back function for your /login route:
app.post('/login', (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if (err) { return next(err); }
if (!user) { return res.json(info); }
req.logIn(user, (err) => {
if (err) { return next(err); }
return res.json(info);
});
})(req, res, next);
});
The info argument contains the object passed as a third argument in done() from the strategy and can be added to the body of res and therefore used by react.

Auth0: Not authorized for query on db.collection

I'm trying to connect my Mongodb with Auth0 authorization. I'm using Mlabs to host my db. Inside this db, I have a "users" collection, and the documents have "username", "password", "email, and "_id" keys. When I try to use the Auth0 login script, I get the following error: Not authorized for query on mydb.users. Below is the script:
function login(email, password, callback) {
mongo('<mongodb uri>', function (db) {
var users = db.collection('users');
console.log(users);
users.findOne({ email: email }, function (err, user) {
if (err) return callback(err);
if (!user) return callback(new WrongUsernameOrPasswordError(email));
bcrypt.compare(password, user.password, function (err, isValid) {
if (err) {
callback(err);
} else if (!isValid) {
callback(new WrongUsernameOrPasswordError(email));
} else {
callback(null, {
user_id: user._id.toString(),
nickname: user.nickname,
email: user.email
});
}
});
});
});
}
Any ideas why I might be getting this error? Thanks in advance!
You probably have to check for a connection to the db.
The callback should have an error parameter
mongo('<mongodb uri>', function (err,db) {
if(err){
console.log(err)
}
})
if that doesn't fix it then you have to assign read and write permissions to perform the intended operations (in your case read) on the specified database

API Route Testing Unexpected Results

I am writing a MEAN API at the minute and I have a route called /authenticate where users can log in. Below I have shown the route it's self.
// User Login Route - http://localhost:8080/api/authenticate
router.post('/authenticate', function(req, res) {
User.findOne({ username: req.body.username }).select('email username password').exec(function(err, user) {
if (err) throw err;
if (!user) {
res.json({ success: false, message: 'Could not authenticate user.'});
} else if (user) {
//Using comparePassword Method
if (req.body.password) {
var validatePassword = user.comparePassword(req.body.password);
if (!validatePassword) {
res.json({success : false, message : 'Could not authenticate password!'});
}
else{
res.status(200).json({success : true, message : 'User authenticated!'});
}
}
else{
res.json({success : false, message : 'Not password provided!'});
}
}
});
});
I am now trying to test the route using the mocha and chai libraries. Below I have shown the describe block that I am using to test this function.
describe('POST /authenticate', function() {
it('should return an error', function (done) {
var newUser = {
"username" : "testetet",
"password" : "hgfhghg"
};
chai.request(server)
.post('/api/authenticate')
.send(newUser)
.end(function (err, res) {
expect(res).to.have.status(201);
expect(res.body).to.have.property('message').equal('User Created.');
});
done();
});
});
As you can see from the test above the test should fail because none of the response criteria in the test matches the response that should be coming back from the API, but it still continues to pass every time I run it.
Does anyone have any ideas why this would be happening?
Thanks for any help in advance.
Hi guys found the problem the done() in the test code was in the wrong place. It should look like the code below.
it('should return a user authenticated message', function (done) {
var user = {
"username": "testuser1",
"password": "testPassword1"
};
chai.request(server)
.post('/api/authenticate')
.send(user)
.end(function (err, res) {
expect(res).to.have.status(200);
expect(res.body).to.have.property('message').equal('User Authenticated!');
done();
});
});

Combine two functions into one Stripe and Passport

I currently have it set so a user signs up with just their email and password. Simple and easy, I want them to be able to select a plan from a select element. I know how to access the part of the request I want using req.body.plan if the name for the select is plan.
I have two separate controller functions currently to do this in a bit different way. I have it so the postSignup signs the user up using passport.js and everything is all good. I also have postPlan that when the user is on /billing and selects a plan then submits the form that they have that plan assigned to them. The part I am stuck on is the User.findById(req.user.id, function(err, user) { part, more specifically the req.user.id. How can I take the two functions below and have them combined so that is sets the user's plan based off the select element in the signup form.
postPlan
exports.postPlan = function(req, res, next){
var plan = req.body.plan;
console.log("plan: ",req.body.plan);
var stripeToken = null;
if(plan){
plan = plan.toLowerCase();
}
if(req.user.stripe.plan == plan){
req.flash('info', {msg: 'The selected plan is the same as the current plan.'});
return res.redirect(req.redirect.success);
}
if(req.body.stripeToken){
stripeToken = req.body.stripeToken;
}
if(!req.user.stripe.last4 && !req.body.stripeToken){
req.flash('errors', {msg: 'Please add a card to your account before choosing a plan.'});
return res.redirect(req.redirect.failure);
}
User.findById(req.user.id, function(err, user) {
if (err) return next(err);
user.setPlan(plan, 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: msg});
return res.redirect(req.redirect.failure);
}
req.flash('success', { msg: 'Plan has been updated.' });
res.redirect(req.redirect.success);
});
});
};
postSignup
exports.postSignup = function(req, res, next){
req.assert('email', 'Please sign up with a valid email.').isEmail();
req.assert('password', 'Password must be at least 6 characters long').len(6);
var errors = req.validationErrors();
if (errors) {
req.flash('errors', errors);
req.flash('form', {
email: req.body.email
});
return res.redirect('/signup');
}
// calls next middleware to authenticate with passport
passport.authenticate('signup', {
successRedirect: '/dashboard', // Select redirect for post signup
failureRedirect: '/signup',
failureFlash : true
})(req, res, next);
next();
};
Current Idea
exports.postSignup = function(req, res, next){
req.assert('email', 'Please sign up with a valid email.').isEmail();
req.assert('password', 'Password must be at least 6 characters long').len(6);
var errors = req.validationErrors();
if (errors) {
req.flash('errors', errors);
req.flash('form', {
email: req.body.email
});
return res.redirect('/signup');
}
// calls next middleware to authenticate with passport
passport.authenticate('signup', {
successRedirect: '/dashboard', // Select redirect for post signup
failureRedirect: '/signup',
failureFlash : true
});
// var plan = req.body.plan;
var plan = 'silver';
var stripeToken = null;
if(req.body.stripeToken){
stripeToken = req.body.stripeToken;
}
User.findById({"email":req.body.email}, function(err, user) {
if (err) return next(err);
console.log("Ran here");
user.setPlan(plan, stripeToken, function (err) {
var msg;
if (err) {
if(err && err.message) {
msg = err.message;
} else {
msg = 'An unexpected error occurred.';
}
req.flash('errors', { msg: msg});
return res.redirect(req.redirect.failure);
}
req.flash('success', { msg: 'Thanks for signing up! ' });
res.redirect(req.redirect.success);
});
});
(req, res, next);
next();
};
Error message for current attempt above
{
"message": "Cast to ObjectId failed for value \"{ email: 'snappierjaguar#gmail.com' }\" at path \"_id\" for model \"User\"",
"error": {
"message": "Cast to ObjectId failed for value \"{ email: 'snappierjaguar#gmail.com' }\" at path \"_id\" for model \"User\"",
"name": "CastError",
"stringValue": "\"{ email: 'snappierjaguar#gmail.com' }\"",
"kind": "ObjectId",
"value": {
"email": "snappierjaguar#gmail.com"
},
"path": "_id"
}
}

Nodejs/Express - Error: Can't set headers after they are sent

Pretty new to node/express. I'm checking to see if the user (via the username) already exists in the database that one wants to register to, giving an error if they do already exist.
When I use curl to try to set it off intentionally, I get the following error:
Error: Can't set headers after they are sent.
I know already that the first check I do to ensure that all the fields are filled in works correctly, and provides no issues with headers being set multiple times.
Any help would be greatly appreciated.
(My relevant code is below. If you need anything else, feel free to say so!)
router.post('/register', function(req, res, next) {
if(!req.body.username || !req.body.password){
return res.status(400).json({ message: 'Please fill out all fields.' });
}
User.count({ username: req.body.username}, function(err, count){
console.log(count);
if(count > 0) {
return res.status(400).json({message: 'This user already exists!' });
}
});
var user = new User();
user.username = req.body.username;
user.setPassword(req.body.password);
user.save(function(err) {
if(err) { return next(err); }
return res.json({ token: user.generateJWT()});
});
});
When you are returning inside User.count and user.save, you are returning only from inside the callbacks but not the entire method.
Its a good practice to send a response in just one place. At the end of the method. Before that evaluate your conditions and set the response code and response message in some variable. Which you can use to send the response as a final step.
Try this as a workaround for now:
router.post('/register', function(req, res, next)
{
if(!req.body.username || !req.body.password)
{
return res.status(400).json({ message: 'Please fill out all fields.' });
}
User.count({ username: req.body.username}, function(err, count)
{
console.log(count);
if(count > 0)
{
return res.status(400).json({message: 'This user already exists!' });
}
else
{
var user = new User();
user.username = req.body.username;
user.setPassword(req.body.password);
user.save(function(err)
{
if(err)
{
return next(err);
}
return res.json({ token: user.generateJWT()});
});
}
});
});
Put all your code in the callback function of User.count, otherwise the two part of code are executed
router.post('/register', function(req, res, next) {
if(!req.body.username || !req.body.password){
return res.status(400).json({ message: 'Please fill out all fields.' });
}
User.count({ username: req.body.username}, function(err, count){
console.log(count);
if(count > 0) {
return res.status(400).json({message: 'This user already exists!' });
}
var user = new User();
user.username = req.body.username;
user.setPassword(req.body.password);
user.save(function(err) {
if(err) { return next(err); }
return res.json({ token: user.generateJWT()});
});
});
});

Categories