Issues with req.flash within a post request - node.js

I check whether or not an email or username is taken. I then use then use flash to send a message to the client using req.flash('messages', 'That username is taken.'); The only problem is I can't call a request command within a function. I can send the message when I put it right after app.post('/signup', function(req, res) { How could I check if a username is taken and then send the req.flash command once it has been determined. I tried to create an if statement but because node is run asynchronously by the time the username is found and the variable is set to true, the if statement has been called. How could I use req.flash to send a message to the client within this post request.
app.post('/signup', function(req, res) {
var userDetails = User({
firstname: req.body.firstname,
username: req.body.username,
email: req.body.email,
password: bcrypt.hashSync(req.body.password1, bcrypt.genSaltSync(10))
});
User.findOne({
$or: [ { 'username': req.body.username}, {'email': req.body.email}]
}, function(err, user) {
if (user) {
if(user.username === req.body.username){
console.log('that username is taken');
req.flash('messages', 'that username is taken');
} else {
}
if(user.email === req.body.email){
console.log('that email is already in use');
req.flash('messages', 'that email is already in use');
} else {
}
} else {
userDetails.save(function(err) {
if (err) throw err;
});
console.log('change to login')
}
if (err) {
return done(err);
}
});
res.redirect('/');
});

It should be no problem to call req in a other function if req is defined in a higher level. I am not sure if your flash is session stored, if it not, the reasen why the messages is not flash is your redirection to route.
You redirect to root without waiting to the end of database request. I think you will only redirect if user is found? Include your redirect to the callback function.

Related

reset password token nodejs and express, throw er; // Unhandled 'error' event ^ Error: Callback was already called

I am trying to reset password using nodemailer. I am sending a mail to the user mail address with a link to reset password. After clicking on update password link on the page it is throwing the following error:-
throw er; // Unhandled 'error' event
^
Error: Callback was already called.
I can't get what's the error and what is meant by callback is already called?
here is my post request code :-
app.post('/reset/:token', function(req, res) {
async.waterfall([
function(done) {
User.findOne({ resetPasswordToken: req.params.token, resetPasswordExpires: { $gt: Date.now() } }, function(err, user) {
if (!user) {
req.flash('error', 'Password reset token is invalid or has expired.');
return res.redirect('back');
}
user.password = req.body.password;
user.resetPasswordToken = undefined;
user.resetPasswordExpires = undefined;
user.save(function(err) {
req.logIn(user, function(err) {
done(err, user);
});
});
});
// res.redirect('/login');
},
function(user, done) {
var smtpTransport = nodemailer.createTransport('SMTP', {
// host:'smtp.gmail.com',
service: 'Gmail',
auth: {
user: 'myemail',
pass: 'password'
}
});
var mailOptions = {
to: user.email,
from: 'my email',
subject: 'Your password has been changed',
text: 'Hello,\n\n' +
'This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n'
};
smtpTransport.sendMail(mailOptions, function(err) {
done(err);
});
}
], function(err) {
res.redirect('/');
});
});
I am not getting any confirmation mail too on the user email..Thanks in advance
There are several similar mistakes in your code that can cause the error Error: Callback was already called.
When you do a ( for example ) return res.redirect('back'); within a callback, the return statement does not affect the parent function. So, it can happen that res.something has already happened before res.anotherSomething is called, and that is not allowed.
Instead of using callbacks, try using async-await.
ok, I got the mistake, removing SMTP from nodemailer.createTransport solved the issue. However I am still not getting the flash-message.

Passport not working after mail-gun verify email

I am using passport.js and mailgun-validate-email to try and validate an email then create a user account based off that email. I can get it to validate the email but it will not finish the POST request to actually create the user. It will eventually show a post log but it does't even give a status code which I found weird. I am using morgan to see this. Normally it does but this time it is just - where the status code is to be.
Passport.use signup middleware
passport.use('signup', new LocalStrategy({
usernameField: 'email',
passReqToCallback : true
},
function(req, email, password, done) {
var findOrCreateUser = function(){
console.log(req.body.email);
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.'));
}
// 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
});
user.save(function(err) {
if (err) return done(err, false, req.flash('error', 'Error saving user.'));
var token = new Token({ _userId: user._id, token: crypto.randomBytes(16).toString('hex') });
token.save(function (err) {
if (err) return done(null, false, req.flash('error', err.message));
var email = req.body.email;
// Send the email
var message = 'Hello,\n\n' + 'Please verify your account by clicking the link: \nhttp:\/\/' + req.headers.host + '\/confirmation\/' + token.token + '\/' + email + '\n';
sendEmail('"Phantom Asset Management" noreply#phantomam.com', user.email, 'Account Verification Token', message);
});
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', 'A verification email has been sent to ' + user.email + '.'));
});
console.log('done');
});
};
process.nextTick(findOrCreateUser);
})
);
Then my controller
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');
}
validator(req.body.email, function (err, result){
if(err) {
return console.log('Error: ', err);
} else {
console.log('Result: ', result);
if(result.is_valid == false){
req.flash('error', 'Looks like your email is not valid did you mean ' + result.did_you_mean + '?');
return res.redirect(req.redirect.failure);
} else if(result.is_valid == true) {
// 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();
};
If my understanding of how I have it is correct it first checks to make sure it is an email then the password is the right length. If there is errors it lets the user know and redirect them back to /signup. Then it goes into the validator and either give an error to the console or goes into checking if it is valid or not. If it is not valid is shows the user an error but if valid goes into my passport middleware.
Which should take and check if a user exists then flag error is need be. It saves the user and a token for that user to use to verify their email address. This all works fine expect the saving of the user and token.
It looks like it might be a request.js issue, I am getting error: esockettimedout. The only dependencies for mailgun-validate-email is just request 2.25.0. The esockettimedout is on request.js:813:19

Node.js - Check if user exists

I'm using NodeJS and passport to let users create an account before they can see results of a quiz they've just taken. My challenge is I need to confirm the username is available before the page refreshes because the user will lose their results if this happens.
Again: I need to verify the username is not taken prior to refreshing.
I think I'm close but it is not working. How would I change my code to handle this challenge?
Currently if the user name is taken it returns an error on trying to create an account and the user ends up on the /failpage as shown below.
app.post('/quiz', usernameToLowerCase, emailToLowerCase, function(req, res) {
User.findOne({
username: req.body.username
}, function(err, user) {
if (err) {
alert(err)
if (user) {
alert('this username is already taken. Please choose another.')
console.log('there was a user');
return false;
}
}
});
var user = new User({
username: req.body.username,
email: req.body.email,
password: req.body.password,
})
user.save(function(err) {
console.log('this is the problem' + ' ' + err)
if (err) {
return res.redirect('/failpage')
}
req.logIn(user, function(err) {
if (err) {
console.log(err);
}
console.log('all looks good')
res.redirect('/results');
});
});
});
Solved it with this if anyone else is trying to do the same thing:
in app.js
app.get('/usercheck', function(req, res) {
User.findOne({username: req.query.username}, function(err, user){
if(err) {
console.log(err);
}
var message;
if(user) {
console.log(user)
message = "user exists";
console.log(message)
} else {
message= "user doesn't exist";
console.log(message)
}
res.json({message: message});
});
});
In js
$('#usercheck').on('change', function() {
$.get('/usercheck?username='+$('#usernameValue').val().toLowerCase(), function(response) {
$('#usernameResponseHidden').text(response.message)
if ($('#usernameResponseHidden').html() === "user exists"){
$('#usernameResponse').text('That username is taken. Please pick another')
}
To solve your problem I think you need to routes. At least a app.get('/quiz') which returns a boolean on if the user exists or not. The section User.findOne can be sent in that route instead. You just need to make a request using ajax when he looses focus of the username field of your form, and display a notification if the name is available or not.

socket.emit not firing inside post request

I wrapped io.on('connection', function (socket) { in a post request. I then call socket.emit('news', 'username taken'); within the post request. For some reason when I make this call it sends nothing to the client. When I change the emit to io.emit('connection', function (socket)) It works and sends the data to the client. My problem with that solution is using io.emit would send the data to all the sockets that are connected. My question is how can I use socket.emit within this post request.
io.on('connection', onConnection);
function onConnection(sock) {
sock.emit('news', 'username is taken');
}
app.post('/signup', function(req, res) {
var userDetails = User({
firstname: req.body.firstname,
username: req.body.username,
email: req.body.email,
password: bcrypt.hashSync(req.body.password1, bcrypt.genSaltSync(10))
});
User.findOne({
$or: [ { 'username': req.body.username}, {'email': req.body.email}]
}, function(err, user) {
if (user) {
if(user.username === req.body.username){
onConnection();
console.log('username is taken');
} else {
}
if(user.email === req.body.email){
console.log('email is taken')
} else {
}
} else {
userDetails.save(function(err) {
if (err) throw err;
});
res.redirect('/');
console.log('change to login')
}
if (err) {
return done(err);
}
});
});
});
The usual way you would approach this is you would use session middleware that would establish a user session when the first page on the site was loaded. Then, when the user connects with socket.io, you would put the socket.id into the session for that user. Then, when you get the app.post() and you want to send to their socket.io connection, you would look in the session for that user, get the socket.id for that user and then look up the socket using that. Once you have the socket, you can send a message to them.
You use the session to connect the socket.io connection and the app.post(). no event handlers are set inside another event handler.
I just fixed my problem. What I did was create a variable called socket1. Then I assigned the socket parameter to socket1 within the io.on annonymous function. I then have socket as a universal variable that I can call wherever I want in my code. I'm not sure if this is programatically correct but it works.
var SOCKET_LIST = {};
var socket1;
io.on('connection', function (socket) {
SOCKET_LIST[socket.id] = socket;
socket1 = socket;
socket.emit('news', 'username taken');
});
app.post('/signup', function(req, res) {
var userDetails = User({
firstname: req.body.firstname,
username: req.body.username,
email: req.body.email,
password: bcrypt.hashSync(req.body.password1, bcrypt.genSaltSync(10))
});
User.findOne({
$or: [ { 'username': req.body.username}, {'email': req.body.email}]
}, function(err, user) {
if (user) {
if(user.username === req.body.username){
socket1.emit('news', 'username taken');
console.log('username is taken');
} else {
}
if(user.email === req.body.email){
io.emit('news', 'email taken');
console.log('email is taken')
} else {
}
} else {
userDetails.save(function(err) {
if (err) throw err;
});
res.redirect('/');
console.log('change to login')
}
if (err) {
return done(err);
}
});
});

NodeJS, PassportJS: Adding another field for authentication

I'm getting start with Node and Password and I want to modify the default authentication of passport slightly.
I want to add a nickname field to authentication in addition to usual username/email and password fields. This was how I modified:
passport.use('local-signup', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'email',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, email, password, done) {
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
User.findOne({ 'local.email' : email }, function(err, user) {
// if there are any errors, return the error
if (err)
return done(err);
// check to see if theres already a user with that email
if (user) {
return done(null, false, req.flash('signupMessage', 'That email is already taken.'));
} else {
/////////////// MODIFIED PART ////////////////
User.findOne({ 'local.nickname' : req.body.nickname }, function(err, user) {
if (user) {
return done(null, false, req.flash('signupMessage', 'That nickname is already taken.'));
/////////////////////////////////////////////
}
});
...
...
But the program exits with :
throw new Error('Can\'t set headers after they are sent.');
^ POST /signup Error: Can't set headers after they are sent. 302 at ServerResponse.OutgoingMessage.setHeader (http.js:691:11)
72ms - 72b at ServerResponse.res.setHeader
(/home/madu/Programming/NodeJS/easy-node-authentication-local/node_modules/express/node_modules/connect/lib/patch.js:63:22)
at ServerResponse.res.set.res.header (/home/madu/Programming/NodeJS/easy-node-authentication-local/node_modules/express/lib/response.js:527:10)
at ServerResponse.res.location (/home/madu/Programming/NodeJS/easy-node-authentication-local/node_modules/express/lib/response.js:657:8)
at ServerResponse.res.redirect (/home/madu/Programming/NodeJS/easy-node-authentication-local/node_modules/express/lib/response.js:698:8)
at allFailed (/home/madu/Programming/NodeJS/easy-node-authentication-local/node_modules/passport/lib/passport/middleware/authenticate.js:124:20)
at attempt (/home/madu/Programming/NodeJS/easy-node-authentication-local/node_modules/passport/lib/passport/middleware/authenticate.js:231:28)
at Context.delegate.fail (/home/madu/Programming/NodeJS/easy-node-authentication-local/node_modules/passport/lib/passport/middleware/authenticate.js:226:9)
at Context.actions.fail (/home/madu/Programming/NodeJS/easy-node-authentication-local/node_modules/passport/lib/passport/context/http/actions.js:35:22)
at verified (/home/madu/Programming/NodeJS/easy-node-authentication-local/node_modules/passport-local/lib/passport-local/strategy.js:82:30)
Process finished with exit code 8
I have two questions:
How to fix this issue -
Is this the desired way of doing this (authenticating on nickname after username)
Thank you.
The problem is that you are using res.send twice (or anything that send the headers). You could simply debug headers like that in express:
app.use(express.json()); // or anything
...
app.use(function(req, res, next)
app.use(function(req, res, next) {
res.on('header', function() {
console.trace('HEADERS GOING TO BE WRITTEN');
});
next();
});
}
..
app.use(express.router);
see Best way to debug 'Can't set headers after they are sent' error in Express / Node.js?. Like so, you will be able to see what triggers the sending of the headers from the stacks.
That said, I use my, own route for user registration. Here is an adaptation (assuming the file is required and create is added into the router):
exports.create = function (req, res, next) {
User.findOne({
'local.email': req.body.email,
'local.nickname': req.body.nickname
}, function (err, user) {
if (user) {
req.flash('error', 'user already exists');
return res.redirect('/signup'):
}
var user = new User(req.body);
user.provider = 'local';
user.save(function (err) {
if (err) {
console.log(err);
return res.send(err);
}
// manually login the user once successfully signed up
req.logIn(user, function (err) {
if (err) return next(err);
return res.redirect('/');
});
});
});
};
You might also use schema validation . See https://github.com/madhums/node-express-mongoose-demo/blob/master/app/models/user.js
Example :
UserSchema.path('email').validate(function (email, fn) {
var User = mongoose.model('User')
if (this.doesNotRequireValidation()) fn(true)
// Check only when it is a new user or when email field is modified
if (this.isNew || this.isModified('email')) {
User.find({ email: email }).exec(function (err, users) {
fn(!err && users.length === 0)
})
} else fn(true)
}, 'Email already exists')
for the email. do the same for username.

Resources