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.
Related
I'm trying to add a conditional to passport.js that also checks if the account is confirmed or not by inserting an 'if statement' to the bottom of the code after user email and password have been checked.
I tried several ways to add the if statement to the last part of the code in the 'LocalStrategy' function and tried reworking the whole thing with elif, and else statements. I'm getting 'Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client' and other errors as it is below.
I'm afraid I can't provide all the code for you to reproduce this.
Unless you have a mock sign page and mongodb running with passport.js sign in and an account with a true/false like user.confirmed is for my account.
const LocalStrategy = require('passport-local').Strategy;
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
// Load User model
const User = require('../models/User');
module.exports = function(passport) {
passport.use(
new LocalStrategy({ usernameField: 'email' }, (email, password, done) => {
// Match user
User.findOne({
email: email
}).then(user => {
if (!user) {
return done(null, false, { message: 'That email is not registered' });
}
// Match password
bcrypt.compare(password, user.password, (err, isMatch) => {
if (err) {
console.log(err);
res.sendStatus(500);
return;
} if (isMatch) {
return done(null, user);
} else {
return done(null, false, { message: 'Password incorrect' });
}
});
// LINES I'M TRYING TO ADD HERE BELOW
if (!user.confirmed) {
return done(null, false, { message: 'This account is not confirmed' });
// LINES I'M TRYING TO ADD HERE ABOVE
}
});
})
);
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
};
In my setup the above code is in the config file as passport.js and used by a js file in the 'routes' folder that logs the user in with
router.post('/login', (req, res, next) => {
passport.authenticate('local', {
successRedirect: '/dashboard',
failureRedirect: '/users/login',
failureFlash: true
})(req, res, next);
The field 'confirmed' is either 'true' or 'false'. Read with !user.confirmed. (The process of confirmed or !confirmed is handled with a link sent to the user's email. This is outside the scope of my issue but sharing in case this helps frame the problem.)
I would like for passport.js to check the account with the email exists, that the password is correct, THEN let the user login ONLY if the account is confirmed 'true' and post the message that the account is not confirmed and deny entry if it is not confirmed.
I can't seem to add the confirmed check without errors and losing the other checks for things. With this current setup it tells you if the email is NOT registered. It tells you if the user is NOT confirmed, but it crashes if you enter the wrong password with a registered email.
So unless I am missing something
bcrypt.compare(password, user.password, (err, isMatch) => {
if (err) {
console.log(err);
res.sendStatus(500);
return;
}
if (isMatch && user.confirmed) {
return done(null, user);
} else if (isMatch) {
return done(null, false, { message: 'This account is not confirmed' });
} else {
return done(null, false, { message: 'Password incorrect' });
}
});
Other permutations are available.
I've been searching stackoverflow for reasons why my node.js (express) app hangs on some posts with passport.js.
I've been looking at theese two questions:
More Passport.js woes - hangs on form submission
Passport (local) is hanging on Authentication
My code for creating a user works fine, and looks like this:
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 in the req from our route (lets us check if a user is logged in or not)
},
function(req, email, password, done) {
if (email)
email = email.toLowerCase(); // Use lower-case e-mails to avoid case-sensitive e-mail matching
// asynchronous
process.nextTick(function() {
// if the user is not already logged in:
if (!req.user) {
User.findOne({
where: {
local_email: email
}
}).then(function(user) {
if (user){
return done(null, false, req.flash('signupMessage', 'That email is already taken.'));
}else{
// create the user
User
.build({
local_email: email,
local_password: generateHash(password),
id: 1 //Normal activated user
})
.save()
.then(newUser => {
//Our newly crated user
return done(null, newUser);
})
.catch(error =>{
//Woops something went wrong
return done(error);
})
//var newUser = new User();
//newUser.localemail = email;
//newUser.localpassword = generateHash(password);
//User.create(newUser).then(function(newUser, created) {
// if (!newUser) {
// return done(err);
// }
// if (newUser) {
// return done(null, newUser);
// }
//});
}
});
// if the user is logged in but has no local account...
} else if ( !req.user.local.email ) {
// ...presumably they're trying to connect a local account
// BUT let's check if the email used to connect a local account is being used by another user
User.findOne({
where: {
localemail: email
}
}).then(function(user) {
if (err)
return done(err);
if (user){
return done(null, false, req.flash('loginMessage', 'That email is already taken.'));
// Using 'loginMessage instead of signupMessage because it's used by /connect/local'
} else {
// create the user
var newUser = new User();
newUser.local.email = email;
newUser.local.password = generateHash(password);
User.create(newUser).then(function(newUser, created) {
if (!newUser) {
return done(err);
}
if (newUser) {
return done(null, newUser);
}
});
}
});
} else {
// user is logged in and already has a local account. Ignore signup. (You should log out before trying to create a new account, user!)
return done(null, req.user);
}
});
}));
In my routs file the post to that function looks like this:
// process the signup form
app.post('/signup', passport.authenticate('local-signup', {
successRedirect : '/profile', // redirect to the secure profile section
failureRedirect : '/signup', // redirect back to the signup page if there is an error
failureFlash : true // allow flash messages
}));
My code for signing in however dosen't work that well. The function in passport.js looks like this:
passport.use('local-login', 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 in the req from our route (lets us check if a user is logged in or not)
},
function(req, email, password, done) {
if (email)
email = email.toLowerCase(); // Use lower-case e-mails to avoid case-sensitive e-mail matching
// asynchronous
process.nextTick(function() {
User.findOne({
where: {
local_email: email
}
}).then(function(user){
console.log("TEST: "+user);
// if no user is found, return the message
if (!user){
console.log("no user with the following email: " +email);
return done(null, false, req.flash('loginMessage', 'No user found.'));
}else if(!validPassword(password,user.local_password)){
console.log("wrong password");
return done(null, false, req.flash('loginMessage', 'Oops! Wrong password.'));
// all is well, return user
}else{
return done(null, user);
return;
}
}).catch(function(err){
console.log("woops der skete en fejl: "+err);
console.log("email is: "+email);
console.log("password is: "+password);
return done(err);
});
});
}));
The corresponding route looks like this:
// process the login form
app.post('/login', passport.authenticate('local-login', {
successRedirect : '/', // redirect to the secure profile section
failureRedirect : '/login', // redirect back to the signup page if there is an error
failureFlash : true // allow flash messages
}));
The above code hangs when the form is being submitted. If I changes the code to:
app.post('/login', function(req, res, next) {
passport.authenticate('local-login', function(err, user, info) {
if (err) { return next(err); }
if (!user) {
return res.render('/login', {
pageTitle: 'Sign in',
form: req.body
});
}
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.redirect('/');
});
})(req, res, next);
});
I receive an 500.
Any ideas why this isen't working?
Updated with console output:
Executing (default): SELECT `id`, `role`, `local_email`, `local_password`, `facebook_id`, `facebook_token`, `facebook_email`, `facebook_name`, `twitter_id`, `twitter_token`, `twitter_displayname`, `twitter_username`, `google_id`, `google_token`, `google_email`, `google_name`, `createdAt`, `updatedAt` FROM `users` AS `users` WHERE `users`.`local_email` = 'test#local' LIMIT 1;
TEST: [object SequelizeInstance:users]
POST /login 302 87.021 ms - 46
Executing (default): SELECT `id`, `role`, `local_email`, `local_password`, `facebook_id`, `facebook_token`, `facebook_email`, `facebook_name`, `twitter_id`, `twitter_token`, `twitter_displayname`, `twitter_username`, `google_id`, `google_token`, `google_email`, `google_name`, `createdAt`, `updatedAt` FROM `users` AS `users` WHERE `users`.`id` = 1;
Updated with changed passport.deserializeUser
passport.deserializeUser(function(id, done) {
/*User.findById(id, function(err, user) {
done(err, user);
});*/
User.findById(id).then(user => {
if (!user){
done(null);
}else{
done(user);
}
})
});
Your passport.deserializeUser isn't correct. As with all Node.js callbacks, the first argument is reserved for errors, and should be null (or another falsy value) if there aren't any errors. In your case, you're passing user as first argument, which makes Passport think there was an error.
Try this:
passport.deserializeUser(function(id, done) {
User.findById(id)
.then(user => done(null, user))
.catch(done);
});
How can i catch errorfrom local-signup? i cant find a way to catch them.
The idea is to catch the errors, then send them as a message back to client with
res.json
Current output:
already taken????????????????????
node.js
router.post('/register', passport.authenticate('local-signup'),function(req, res, next) {
console.log("registration");
console.log("ERROR?");
console.log(req);
console.log(res);
// res.json({type: 'danger',message: info});
});
passport:
passport.use('local-signup', new LocalStrategy({
usernameField : 'username',
passwordField : 'password',
passReqToCallback : true
},
function(req, username, password, done) {
process.nextTick(function() {
console.log("doing local signup");
Account.findOne({username : username }, function(err, user) {
if (err)
return done(err);
if (user) {
console.log("already taken????????????????????");
return done(null, false, { message: 'That username is already taken.'});
return done(err);
} else {
var newUser = new Account();
newUser.username = username;
newUser.password = newUser.encryptPassword(password);
// save the user
newUser.save(function(err) {
if (err)
throw err;
return done(null, newUser);
});
}
});
});
}));
Update:
I tried custom error callback code, but then i cant get req property to send request back to client.
I also tried to move the authenticate middleware to be called within the function req, res, next, but then it wont be called at all.
Do you have a generic error handler in your app? If you do, you could pass the option failWithError to the LocalStrategy.
Example:
passport.authenticate('local-signup', { failWithError: true })
passport code snipped:
...
if (options.failWithError) {
return next(new AuthenticationError(http.STATUS_CODES[res.statusCode], rstatus));
}
...
And in case, when error occurs, passport will pass the error for the error handler.
Error handler example:
...
// it has to be the last operation in the app object
app.use(errorHandler);
function errorHandler(err, req, res, next) {
if(typeof error == AuthenticationError) {
res.status(403);
res.json('error', { error: err });
}
else{ /* anything else */}
}
Hope it help you.
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.
I'm new to node express and passport so I'm not sure what I'm missing. I'm trying to use a custom callback as all I want is a json response from my registration strategy. Currently I keep getting the following error "Missing credentials". I've been at it for a while now and I'm stuck.
Here is my controller:
app.post('/services/authentication/registration', function(req, res, next) {
console.log('before authentication')
passport.authenticate('local-registration', function(err, user, info) {
console.log('authentication callback');
if (err) { return res.send({'status':'err','message':err.message});}
if (!user) { return res.send({'status':'err','message':info.message});}
if (user!=false) { return res.send({'message':'registration successful'});}
})(req, res, next);
},
function(err, req, res, next) {
return res.send({'status':'err','message':err.message});
});
And my passport strategy:
passport.use('local-registration', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'email',
passwordField : 'password',
passReqToCallback : true
},
function(req, email, password, done) {
console.log('credentials passed to passport' + email + '' + password)
// asynchronous
// User.findOne wont fire unless data is sent back
process.nextTick(function() {
// 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, {message: 'User already exists'});
} else {
// if there is no user with that email
// create the user
var newUser = new User();
// set the user's local credentials
newUser.local.email = email;
newUser.local.password = newUser.generateHash(password);
// save the user
newUser.save(function(err) {
if (err)
throw err;
return done(null, newUser);
});
}
});
});
}));
I dont think you should send "req" as a parameter to the authentication function. take a look at this example from the passport docs:
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({ username: username }, function (err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
if (!user.validPassword(password)) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
});
}
));