I'm using the async.series() control flow from caolan's async module. U
nlike the doc's explanation that it should excute all functions in sequence, one after the other and stop when one calls its callback with an error; mine actually calls the main callback if one of the functions gets an erro but then happily continues to execute the rest of the functions in sequence.
async.series([
function (cb) {
if (!req.body.join_firstname || !req.body.join_lastname || !req.body.join_email || !req.body.join_password) {
req.flash('error', 'Please enter a name, email and password.');
cb(true);
}
if (req.body.join_password !== req.body.join_passwordConfirm) {
req.flash('error', 'Passwords must match.');
cb(true);
}
if (req.body.join_email !== req.body.join_emailConfirm) {
req.flash('error', 'Emails must match.');
cb(true);
}
cb(null);
},
function (cb) {
keystone.list('User').model.findOne({
email: req.body.join_email
}, function (err, user) {
if (err || user) {
req.flash('error', 'User already exists with that email address.');
cb(true);
}
cb(null);
});
},
function (cb) {
var userData = {
name: {
first: req.body.join_firstname,
last: req.body.join_lastname
},
email: req.body.join_email,
password: req.body.join_password
};
var User = keystone.list('User').model,
newUser = new User(userData);
newUser.save(function (err) {
if (err) {
//if there's an error, don't send activation mail
cb(err);
} else {
newUser.activationEmail(function (err) {
if (err) {
//if we can't send activation email,
//delete user from db to prevent re-registration failing because of non-unique email
keystone.list('User').model.findOne({
email: req.body.join_email
}).remove(function (err) {
req.flash('error', "Couldn't send an activation email. Contact support if this problem persists.");
cb(true);
});
} else {
cb(err);
}
});
}
});
}
], function (err) {
if (err) return next();
req.flash('success', "Hi, " + req.body.join_firstname + "! We've sent you an activation email. Please check your inbox and spam folder.");
return res.redirect('/');
});
For example, when I purposely enter the wrong password confirm value, it throws an error, executes the callback and return next(); and then just continues, even saving the user in the db. Obviously that was not the intended result.
Anyone got an idea what I'm doing wrong here?
If you want execution of a current function to stop, it's not enough to call a callback. For instance:
function(cb) {
if (!req.body.join_firstname || !req.body.join_lastname || !req.body.join_email || !req.body.join_password) {
req.flash('error', 'Please enter a name, email and password.');
cb(true); // will add this callback to the stack
}
// continuing here
// ...
}
Either change your if-then-construct:
function(cb) {
if (!req.body.join_firstname || !req.body.join_lastname || !req.body.join_email || !req.body.join_password) {
req.flash('error', 'Please enter a name, email and password.');
cb(true); // will add this callback to the stack
} else if (req.body.join_password !== req.body.join_passwordConfirm) {
//...
}
// no more code here
}
or return:
function(cb) {
if (!req.body.join_firstname || !req.body.join_lastname || !req.body.join_email || !req.body.join_password) {
req.flash('error', 'Please enter a name, email and password.');
return cb(true); // will add this callback to the stack and return
}
// will only be executed if the if is false
// ...
}
Related
const emailaddress = req.body.emailaddress;
const password = req.body.password;
if (emailaddress && password) {
const query = `SELECT * FROM users where email=? AND password=?`;
connection.query(
query,
[emailaddress, password],
function (err, results, fields) {
if (err) {
res.send({
code: 500,
failed: "Error ocurred",
});
}
if (results.length > 0) {
req.session.regenerate(function (err) {
// if (err) next(err);
req.session.loggedin = true;
req.session.emailaddress = req.body.emailaddress;
req.session.save(function (err) {
// if (err) return next(err);
res.redirect("/home");
});
});
} else {
res.send("Incorrect email address and/or password!");
}
res.end();
}
);
} else {
res.send("Please enter username and/or password");
res.end();
}
I tried using the above code using express-session to store session values. but it gives the following error.
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
How can I avoid this. I already tried many solutions. I am following their documentation. Please help. Thank you.
https://www.npmjs.com/package/express-session
Possible problems:
Else block calls send and last line outside if and else calls end().
So if it falls into the Else block, both send and end will be called causing that error.
You also have multiple call to next(err), without knowing that is the next handler, I can tell you for sure, but they might be calling send as well. Which would fall into the same scenario as above.
I see you made some changes in your code after my answer.
Let me try again, you cannot use res.send and res.end together, you need to pick one. Also you need to make sure you are only calling res.send once.
your code should be something like this:
const emailaddress = req.body.emailaddress;
const password = req.body.password;
if (emailaddress && password) {
const query = `SELECT * FROM users where email=? AND password=?`;
connection.query(
query,
[emailaddress, password],
function (err, results, fields) {
if (err) {
res.send({
code: 500,
failed: "Error occurred",
});
return; // make sure your function execution stops here, as you already called send();
}
if (results.length > 0) {
req.session.regenerate(function (err) {
// if (err) next(err);
req.session.loggedin = true;
req.session.emailaddress = req.body.emailaddress;
req.session.save(function (err) {
// if (err) return next(err);
res.redirect("/home");
});
});
} else {
res.send("Incorrect email address and/or password!");
return; // same thing, called send() make sure to end the function
}
// res.end(); you probably don't need this
}
);
} else {
res.send("Please enter username and/or password");
// res.end(); you probably don't need this
return;
}
I actually work on a school project, I have to do a dating website with a micro-framework.
I choose to use ExpressJS to learn NodeJS, but I have some struggles with the asynchronous...
I try to make a profile update form where you have to put you're old password to update your e-mail address or your password or both on one form. But with the asynchronous i don't know how to validate the form.
We can't use ORM, validator or user manager (like passport).
Here is what i tried
First route to the form
router.get('/profile', isConnected, (request, response) => {
response.render('profile.ejs');
});
Second route to validate the form
router.post('/changeCredentials', isConnected, (request, response) => {
var user = User.formJSON(request.session.user);
if (User.hashPassword(request.body.old_password, user.salt) === user.password)
{
if (request.body.email !== undefined && request.body.email !== user.email)
{
request.formValidate('email', 'Email not valid').isNotEmpty().isEmail();
request.formValidate('email', 'Email already used').isUnique(User, () => {
if (request.formIsValid)
{
user.email = request.body.email;
request.user.connect(user);
console.log(request.session);
User.update(user, () => {
request.flash('success', 'Email updated');
});
}
});
}
if (request.body.new_password !== undefined && request.body.new_password !== '')
{
request.formValidate('new_password', 'Password is not valid.').isNotEmpty().isLength(6, 255).isGoodPassword();
request.formValidate('new_password', 'Password does not match').isEqualTo(request.body.new_password_conf);
if (request.formIsValid)
{
user.password = request.body.new_password;
User.update(user, () => {
request.flash('success', 'Password changed!');
}, true);
}
}
}
else
{
request.flash('error', 'Bad Old password');
}
response.redirect('/u/profile');
});
Full code here (you can find this part in 'routes/user.js')
Thanks for your help.
This is a common problem with asynchronous callbacks. You will need to have your response in every possible path because your catch-all redirect ignores asynchronous calls.
router.post('/changeCredentials', isConnected, (request, response) => {
var user = User.formJSON(request.session.user);
if (User.hashPassword(request.body.old_password, user.salt) === user.password)
{
if (request.body.email !== undefined && request.body.email !== user.email)
{
request.formValidate('email', 'Email not valid').isNotEmpty().isEmail();
request.formValidate('email', 'Email already used').isUnique(User, () => {
if (request.formIsValid)
{
user.email = request.body.email;
request.user.connect(user);
console.log(request.session);
User.update(user, () => {
request.flash('success', 'Email updated');
response.redirect('/u/profile');
});
return;
}
response.redirect('/u/profile');
});
return;
}
if (request.body.new_password !== undefined && request.body.new_password !== '')
{
request.formValidate('new_password', 'Password is not valid.').isNotEmpty().isLength(6, 255).isGoodPassword();
request.formValidate('new_password', 'Password does not match').isEqualTo(request.body.new_password_conf);
if (request.formIsValid)
{
user.password = request.body.new_password;
User.update(user, () => {
request.flash('success', 'Password changed!');
response.redirect('/u/profile');
}, true);
return;
}
}
} else {
request.flash('error', 'Bad Old password');
}
response.redirect('/u/profile');
});
The above code show a callback-style approach. A more elegant solution can be created with Promises and generators.
Disclaimer: I am a newb web dev.
I am creating a registration page. There are 5 input fields, with 3 of them (username, password, and email) requiring that they pass various forms of validation. Here is the code:
router.post('/register', function (req, res, next) {
user.username = req.body.username;
user.profile.firstName = req.body.firstName;
user.profile.lastName = req.body.lastName;
user.password = req.body.password;
user.email = req.body.email;
User.findOne({email: req.body.email}, function(err, existingEmail) {
if(existingEmail) {
console.log(req.body.email + " is already in use")
} else {
User.findOne({username: req.body.username}, function(err, existingUsername) {
if(existingUsername) {
console.log(req.body.username + " is already in use");
} else {
user.validate({password: req.body.password}, function(err) {
if (err) {
console.log(String(err));
} else {
user.save(function(err, user) {
if (err) {
return next(err);
} else {
return res.redirect('/')
}
})
}
});
}
});
}
});
});
Basically it first checks to see if it is a duplicate e-mail; if it is a duplicate e-mail, it says so in the console.log. If it isn't a duplicate e-mail, it then checks the username.... and then goes onto the password.
The issue is that it does this all one at a time; if the user inputs an incorrect email and username, it will only say that the email is incorrect (it won't say that both the email and username are incorrect).
How can I get this to validate all 3 forms at the same time?
You can use async to run them in parallel and it will also make your code cleaner and take care of that callback hell:
var async = require('async');
async.parallel([
function validateEmail(callback) {
User.findOne({email: req.body.email}, function(err, existingEmail) {
if(existingEmail) {
callback('Email already exists');
} else {
callback();
}
}
},
function validateUsername(callback) {
User.findOne({username: req.body.username}, function(err, existingUsername) {
if(existingUsername) {
callback('Username already exists');
} else {
callback();
}
}
},
function validatePassword() {
user.validate({password: req.body.password}, function(err) {
if(err) {
callback(err);
} else {
callback();
}
}
}
], function(err) {
if(err) {
console.error(err);
return next(err);
} else {
user.save(function(err, user) {
if (err) {
return next(err);
} else {
return res.redirect('/');
}
});
}
}
);
This way, all the validation methods inside the array will be run in parallel and when all of them are complete the user will be saved.
If you use else statements, you choose to make checks individually (one at a time) by design.
To achieve an 'all at once' behaviour, I would not use else statements (where possible, i.e. errors ar not fatal for next checks), but would do all tests in the same block, and would fill an object like this:
errors: {
existingEmail: false,
existingUserName: false,
invalidUserName: false,
wrongPassword: false,
...
};
And then I'd use it in the form to show user all errors together...
Something like this:
var errors = {};
if (existingEmail) {
console.log(req.body.email + " is already in use");
errors.existingEmail: true;
}
User.findOne({username: req.body.username}, function(err, existingUsername) {
if (existingUsername) {
console.log(req.body.username + " is already in use");
errors.existingUsername: true;
} else {
user.validate({password: req.body.password}, function(err) {
if (err) {
console.log(String(err));
errors.invalidUsername = true;
} else {
user.save(function(err, user) {
if (err) {
return next(err);
} else {
return res.redirect('/')
}
})
}
});
}
});
I need to validate a username (if one is provided) and an email (if one is provided) before saving to the db. I'm using MongooseJS, However, I'm not sure how to structure my code
Here is what I have so far:
var user = new User();
if(req.body.email) {
User.findOne({"email" : req.body.email}, function(err, found){
if(err) return next(err);
if(found) return res.status(200).send({"error_code" : "INVALID_REQUEST_ERROR", "message" : "Email address already exists"});
});
}
if(req.body.username) {
User.findOne({"username" : req.body.username}, function(err, found){
if(err) return next(err);
if(found) return res.status(200).send({"error_code" : "INVALID_REQUEST_ERROR", "message" : "Username already exists"});
});
}
user.save(function(err){
if(err) return next(err);
res.status(200).send(user);
});
but of course this won't work as the user.save will be executed before either of the validation blocks execute. I realise I could put user.save inside the callbacks but then I would be repeating code which I want to avoid.
The best thing to do is to add validation on to your mongoose Schema and let it do it automatically whenever you attempt to save a model (or you can call the validation function yourself earlier). Like so:
var userSchema = new Schema({
email: {
type: String,
required: true,
validate: {
validator: function(v) {
var emailRegexp = /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*#([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+)*\.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i;
return emailRegexp.test(v);
},
message: "{VALUE} does not appear to be a valid email address."
}
}
});
Then when you try to save it with incorrect data:
var user = new User({ email: "this_isnt_a_proper_email" });
user.save(function(err) {
if (err.name === "ValidationError") { // check that it comes from mongoose validation
console.log(err.errors.email.message)
res.status(400).send({ validationError: err }); // send "Bad Request" HTTP header
} else {
res.send(user) // status 200 is implicit when not set
}
});
To better organise the code that checks whether the username or email is already set in the database, I'd suggest looking up Bluebird (or similar) Promises, so that you can have a logical flow to the process.
Another simple way maybe
var user = new User();
var email = req.body.email || '';
var username = req.body.username || '';
User.find({
$or: [{"email": email}, {"username": username}]},
function(err, users){
if(err) return next(err);
if(users && users.length == 0) {
// save new user if none is matched
user.save(function(err){
if(err) return next(err);
res.status(200).send(user);
});
} else if (users && users.length > 0) {
// check users returned to determine which of following two error codes should be returned
//return res.status(200).send({"error_code" : "INVALID_REQUEST_ERROR", "message" : "Email address already exists"});
//return res.status(200).send({"error_code" : "INVALID_REQUEST_ERROR", "message" : "Username already exists"});
}
});
You could do it through Promise, here is one sample codes by using Q.js
function findUserByEmail() {
var deferred = Q.defer();
if(req.body.email) {
User.findOne({"email" : req.body.email}, function(err, found){
if(err) return deferred.reject(err);
if(found) {
res.status(200).send({"error_code" : "INVALID_REQUEST_ERROR", "message" : "Email address already exists"});
deferred.reject();
}else {
// no user is found, resolve it
deferred.resolve();
}
});
} else {
deferred.reject();
}
return deferred.promise;
}
function findUserByName() {
var deferred = Q.defer();
if(req.body.username) {
User.findOne({"username" : req.body.username}, function(err, found){
if(err) return deferred.reject(err);
if(found) {
res.status(200).send({"error_code" : "INVALID_REQUEST_ERROR", "message" : "Username already exists"});
deferred.reject();
} else {
// no user is found, resolve it
deferred.resolve();
}
});
} else {
deferred.reject();
}
return deferred.promise;
}
Q.all([findUserByName(), findUserByEmail()]).then (function() {
// in the resolve function, namely, no exist username and email
user.save(function(err){
if(err) return next(err);
res.status(200).send(user);
});
});
Here is my code:
socket.on('add user', function (data) {
d('"add user" event');
d(data, true);
debugger;
UserEngine.login({
username: data.username,
password: data.password
}, function(err, user) {
if (err) {
d('Bad Login. Username: ' + data.username);
return;
}
/*
** Code after this comment is never executed
*/
debugger;
d('Login OK: ' + data.username);
socket.username = data.username;
usernames[data.username] = data.username;
++numUsers;
addedUser = true;
socket.emit('login', {
numUsers: numUsers
});
socket.broadcast.emit('user joined', {
username: socket.username,
numUsers: numUsers
});
return;
});
});
If there is an error in UserEngine.login(...), the If statement before the comment works correctly and the callback returns. But if the method works correctly, the code after the If statement isn't executed.
Why?
Edit:
Here is the code of the UserEngine module:
http://pastebin.com/u2DQJrV3
My guess is that your code is throwing an exception somewhere in your login() method and because much of that code is executed in async callbacks, you may not get any exception info in the console.
Here are several suggestions for debugging this problem:
You need to follow the exact flow through your login() method. If what you say in your question is true, then it isn't calling the callback sometimes and you need to find out why. The simplest way to follow the flow is to insert a uniquely tagged console.log() statement in every branch of the login() method so you can see exactly which way the control flow goes and what is executed and what is not. Once you find that, you can then output various values to see why it goes that way or set an appropriate breakpoint and trace through it.
There also could be an exception of some kind getting thrown in the login() method or in something that it calls. One you get into an async callback, exceptions may not get logged in the console so things could fail silently and the callback could just be skipped without anything showing in the console. If you suspect an exception in a particular place, you can put your own exception handler there and log what the exception is. Remember, each async scope needs its own exception handler - you can't just use one exception handler at the top level. This is a complication of async coding and one reason why using promises instead of plain callbacks is massively more useful because the promise system will catch all async exceptions for you and turn it into a rejected promise with the exception as the reason (so async exceptions don't fail silently).
There is a logic problem in your login() method if it goes does the user.save() branch of code. I've documented that issue below, though I don't think that is your main issue - but it is something to fix.
I'd also suggest you put logging where you call your UserEngine.login() so you are logging ALL possible callback values.
Suggested logging:
UserEngine.login({
username: data.username,
password: data.password
}, function(err, user) {
// =========== Add this ============
console.log("UserEngine.login() callback");
console.log(err, user);
if (err) {
d('Bad Login. Username: ' + data.username);
return;
}
Here's the issue with the user.save() path in your login() method:
In your login() code, as part of the self._verifyToken() function call, you need to change this:
login: function(args, callback) {
/**
* username [String]
* password [String]
*/
var self = this;
if (!args.username || !args.password) {
return callback(Error.genObj(Error.code.MISSING_PARAMS));
}
User.findOne({
username: args.username
}, function(err, user) {
console.log('[Debug] In User.findOne(...) Callback;');
if (err) {
return callback(Error.genObj(Error.code.INTERNAL));
}
if (!user) {
return callback(Error.genObj(Error.code.TOKEN_AUTH_FAILED));
}
if (self._generateHash({ password: args.password, salt: user.salt }) != user.password) {
return callback(Error.genObj(Error.code.TOKEN_AUTH_FAILED));
}
self._verifyToken({
token: user.token,
username: args.username,
key: Config.tokenKey
}, function(err) {
if (err) {
var token = self._generateToken({ username: args.username, key: Config.key });
user.token = token;
user.save(function(err) {
if (err) {
return callback(Error.genObj(Error.code.INTERNAL));
}
return callback(null, user);
});
}
return callback(null, user);
});
});
},
To this:
login: function(args, callback) {
/**
* username [String]
* password [String]
*/
var self = this;
if (!args.username || !args.password) {
return callback(Error.genObj(Error.code.MISSING_PARAMS));
}
User.findOne({
username: args.username
}, function(err, user) {
console.log('[Debug] In User.findOne(...) Callback;');
if (err) {
return callback(Error.genObj(Error.code.INTERNAL));
}
if (!user) {
return callback(Error.genObj(Error.code.TOKEN_AUTH_FAILED));
}
if (self._generateHash({ password: args.password, salt: user.salt }) != user.password) {
return callback(Error.genObj(Error.code.TOKEN_AUTH_FAILED));
}
self._verifyToken({
token: user.token,
username: args.username,
key: Config.tokenKey
}, function(err) {
if (err) {
var token = self._generateToken({ username: args.username, key: Config.key });
user.token = token;
user.save(function(err) {
if (err) {
return callback(Error.genObj(Error.code.INTERNAL));
}
return callback(null, user);
});
} else {
// ======== put this in an else clause ==========
return callback(null, user);
}
});
});
},
The issue is that if you get an error from verifyToken(), then it will start user.save(), but since that's async, it will continue on and execute return callback(null, user) before the user.save() operation is done and then it will call the callback again when user.save() is done.