bernatixer bernatixer - 2 months ago 14
Node.js Question

Error: Can't set headers after they are sent using passportjs/Express

The error in question


Error: Can't set headers after they are sent.


server.js

// set up ======================================================================
var express = require('express')
, app = express()
, cookieParser = require('cookie-parser')
, bodyParser = require('body-parser')
, expressSession = require('express-session')
, server = require('http').createServer(app)
, passport = require('passport')
, local = require('passport-local').Strategy
, md5 = require('md5')
, util = require('util')
, flash = require('connect-flash')
, port = 80
, url = require('url')
, db = require('./db');

// optimization ================================================================
var compress = require('compression');
app.use(compress());

// configuration ===============================================================
app.use(express.static('public'));
app.use(cookieParser());
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');

app.use(bodyParser.urlencoded({
extended: true
}));
app.use(bodyParser.json());

app.use(expressSession({
secret: 'key',
resave: false,
saveUninitialized: false
}));
app.use(passport.initialize());
app.use(passport.session());
app.use(flash());

// passport ====================================================================
passport.use(new local(
function(username, password, done) {
// asynchronous verification, for effect...
process.nextTick(function () {
db.findUserByName(username, function (err, user) {
if (err) { done(err); }
if (!user) { done(null, false, {message: 'Unknown user: ' + username})}
if (md5(password) == user.password) {
done(null, user);
} else {
done(null, false, {message: 'Invalid username or password'});
}
});
});
}
));
passport.serializeUser(function(user, done) {
done(null, user.uuid);
});

passport.deserializeUser(function (id, done) {
db.findUserByUUID(id, done);
});

// routes ======================================================================
require('./routes')(app, passport);

// launch ======================================================================
server.listen(port, function(){
console.log('server started');
});


routes.js

var flash = require('connect-flash');

module.exports = function(app, passport) {

app.get('/', function(req, res) {
res.render('index', { num: 0, logged: false });
});

app.get('/login', function (req, res) {
if (typeof req.user !== 'undefined') {
// User is logged in.
res.redirect('/');
} else {
req.user = false;
var message = req.flash('error');
if (message.length < 1) {
message = false;
}
res.render('login', { logged: false, message: messageĀ });
}
});

app.post('/login',
passport.authenticate('local', {
failureRedirect: '/login',
failureFlash: true
}),
function(req, res) {
res.redirect('/');
}
);

app.get('/logout', function(req, res){
req.logout();
res.redirect('/');
});


};


db.js

var MongoClient = require('mongodb').MongoClient;
var assert = require('assert');
var ObjectId = require('mongodb').ObjectID;
var url = 'mongodb://localhost:27017/db';

exports.findUserByName = function (username, callback) {
MongoClient.connect(url, function(err, db) {
var cursor = db.collection('players').find( { "name": username } );
cursor.each(function(err, doc) {
if (doc != null) {
console.log('YES');
callback(false, doc)
} else {
console.log('NO');
callback(false, null);
}
db.close();
});

});
};

var findUserByUUID = function(uuid, db, callback) {
var cursor = db.collection('players').find( { "uuid": uuid } );
cursor.each(function(err, doc) {
assert.equal(err, null);
if (doc != null) {
callback(false, doc)
} else {
callback(false, null);
}
});
};
exports.findUserByUUID = function (uuid, callback) {
MongoClient.connect(url, function(err, db) {
assert.equal(null, err);
findUserByUUID(uuid, db, function(err, data) {
callback(err, data);
db.close();
});
});
};


I understand that i'm calling done() twice, but when? I'm unable to find it.
I suppose that the problem is on the routes, but I don't know where.

Answer

In your passport.use() handler, each time you call done(), you need to then return so that the code after it is not also executed.

Right now you have a series of if statements that can each call done(), but they aren't if/else statements so more than one condition you are testing for can be met and thus done() can be called more than once.

For example, change this:

if (!user) { done(null, false, {message: 'Unknown user: ' + username})}

to this:

if (!user) { 
    done(null, false, {message: 'Unknown user: ' + username});
    return;
}

And, change this:

if (err) { done(err); }

to this:

if (err) { 
    done(err); 
    return;
}

Or, you could make everything into if/else if/else if/else such that only one branch could ever execute. The problem occurs when more than one of your if statements has its condition met and then is executed.


Now, your findUserByName() calls its callback in cursor.each() so it can call it multiple times which then causes you to call done() multiple times.