Thursday, 27 April 2017

Node + Express + Passport + passportSocketIo + Express-Mysql-session - req.user undefined

I reviewed similar questions that have been asked before, but I have not been able to resolve my issue. Currently, users are able to login to the application without any issue, but sometimes during the session, req.user will be undefined. I am not sure if this is just a configuration issue, or if there is something else going on.

Right now, I am using the local strategy for passport with a MySQL database. I am also using a MySQLStore for the session store.

Here is the code I currently have in my index.js file:

/******************************************
 * Modules *
******************************************/
require('dotenv').config();
var flash = require('connect-flash');
var express = require('express');
var app = express();
var request = require('request');
var http = require('http').Server(app);
var io = require('socket.io')(http);
var fs = require('fs');
var mysql = require('mysql');
var passport = require('passport');
var Strategy = require('passport-local').Strategy;
var ensureLogin = require('connect-ensure-login');
var winston = require('winston');
var session = require('express-session');
var MySQLStore = require('express-mysql-session')(session);
var passportSocketIo = require("passport.socketio");
var cookieParser = require('cookie-parser');
var helmet = require('helmet');
var RateLimit = require("express-rate-limit");
var csrf = require('csurf');
var bcrypt = require('bcrypt');
const saltRounds = 10;

app.use(flash());
app.use(helmet());
app.set('port', (process.env.PORT || 5000)); // the port number for the application
app.use(express.static(__dirname + '/public')); // directory for the css, images, and js files
app.set('views', __dirname + '/views'); // views is directory for all template files
app.set('view engine', 'ejs'); // our templating engine

app.use(require('body-parser').urlencoded({ extended: true }));

winston.configure({
  transports: [
    new (winston.transports.File)({ filename: 'error.log' })
  ]
});

var csrfProtection = csrf();
var MsgAgent = require('./lib/MsgAgent');

/******************************************
 * MySQL Setup *
******************************************/

var connection = mysql.createConnection({
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_DATABASE,
  port: process.env.DB_PORT
});

connection.connect(function(err) {
  if (err) {
    console.error('error connecting: ' + err.stack);
  }
});

app.use(cookieParser());
var sessionStore = new MySQLStore({expiration: 36000000, checkExpirationInterval: 900000}/* session store options */, connection);
app.use(session({ 
  secret: process.env.SESSION_SECRET,
  rolling: true,
  resave: true, 
  saveUninitialized: false,
  store:sessionStore,
  cookie: {
    secure: false,
    httpOnly: true,
    sameSite: true,
    domain: process.env.COOKIE_DOMAIN // needs to be updated once pushed to aws
  }
}));

/******************************************
 * Passport Session Setup *
******************************************/
passport.serializeUser(function(user, cb) {
  cb(null, user.agent_id);
});

passport.deserializeUser(function(id, cb) {
  var query = "SELECT agent_id, "+
    "CAST(AES_DECRYPT(rt_user_data.name, UNHEX(SHA2('Rt17ix90',512))) AS CHAR) name, "+
    "CAST(AES_DECRYPT(rt_user_data.username, UNHEX(SHA2('Rt17ix90',512))) AS CHAR) username, "+
    "CAST(AES_DECRYPT(rt_user_data.password, UNHEX(SHA2('Rt17ix90',512))) AS CHAR) password, "+
    "CAST(AES_DECRYPT(rt_user_data.role, UNHEX(SHA2('Rt17ix90',512))) AS CHAR) role, "+
    "CAST(AES_DECRYPT(rt_user_data.maxChats, UNHEX(SHA2('Rt17ix90',512))) AS CHAR) maxChats, "+
    "CAST(AES_DECRYPT(rt_user_data.locked, UNHEX(SHA2('Rt17ix90',512))) AS CHAR) locked, "+
    "CAST(AES_DECRYPT(rt_user_data.failedAttempts, UNHEX(SHA2('Rt17ix90',512))) AS CHAR) failedAttempts "+
    "FROM rt_user_data WHERE agent_id = " + connection.escape(id) + ";";
  connection.query(query, function(error, results, fields) {
    if(error){
      console.log("error deserializeUser: ", error);
      cb(error);
    }else{
      cb(null, results[0]);
    }
  });
});

passport.use(new Strategy(
  function(username, password, cb) {
    var query = "SELECT agent_id, "+
      "CAST(AES_DECRYPT(rt_user_data.name, UNHEX(SHA2('Rt17ix90',512))) AS CHAR) name, "+
      "CAST(AES_DECRYPT(rt_user_data.username, UNHEX(SHA2('Rt17ix90',512))) AS CHAR) username, "+
      "CAST(AES_DECRYPT(rt_user_data.password, UNHEX(SHA2('Rt17ix90',512))) AS CHAR) password, "+
      "CAST(AES_DECRYPT(rt_user_data.role, UNHEX(SHA2('Rt17ix90',512))) AS CHAR) role, "+
      "CAST(AES_DECRYPT(rt_user_data.maxChats, UNHEX(SHA2('Rt17ix90',512))) AS CHAR) maxChats, "+
      "CAST(AES_DECRYPT(rt_user_data.locked, UNHEX(SHA2('Rt17ix90',512))) AS CHAR) locked, "+
      "CAST(AES_DECRYPT(rt_user_data.failedAttempts, UNHEX(SHA2('Rt17ix90',512))) AS CHAR) failedAttempts "+
      "FROM rt_user_data WHERE username = AES_ENCRYPT("+ connection.escape(username) +", UNHEX(SHA2('Rt17ix90',512)));";
    connection.query(query, function(error, results, fields) {
      if(error){ 
        console.log(error);
        return cb(error); 
      }
      if(!results.length){
        return cb(null, false, {message: "username"}); 
      }else if(results[0].locked == 1){
        return cb(null, false, {message: "locked"});
      }else {
        bcrypt.compare(password, results[0].password, function(err, res2) {
          if(res2){
            return cb(null, results[0]);
          }else{
            failedPasswordAttempt(results[0], function(){
              return cb(null, false, {message: "password"}); 
            });
          }
        });
      }
    });
  }
));

// Initialize Passport and restore authentication state, if any, from the session.
app.use(passport.initialize());
app.use(passport.session());

/******************************************
 * URL Endpoints *
******************************************/

/* URL to login to the rollover tool */
app.get('/', /*csrfProtection,*/ function(req, res){
  res.render('pages/login', { user: null, message: req.flash('error'), csrfToken: "temp"/*req.csrfToken()*/ }); // renders the index page of the application
});

/* URL to login to the rollover tool */
app.get('/login', /*csrfProtection,*/ function(req, res){
  res.render('pages/login', { user: null, message: req.flash('error'), csrfToken: "temp"/*req.csrfToken()*/ }); // renders the index page of the application
});

/* URL for logging in to the rollover tool - form data is posted here */
app.post('/login', /*loginLimiter,*/ passport.authenticate('local', {failureRedirect: '/login', failureFlash: true}), function(req, res) {
  res.redirect('/loading');
});

/* URL for logging the user out of the rollover tool */
app.get('/logout', function(req, res){
  //logoutAllLEUsers(req.user.agent_id); // logout all LiveEngage users for this agent
  req.logout(); // force logout the session
  res.redirect('/login'); // redirect to the login page
});

/* URL to access the main dashboard of the rollover tool */
app.get('/dashboard', /*csrfProtection,*/ ensureLogin.ensureLoggedIn('/login'), function(req, res){
  //console.log(req.user);
  res.render('pages/index', { user: req.user, csrfToken: "temp"/*req.csrfToken()*/ });
});

/******************************************
 * Socket IO *
******************************************/
io.use(passportSocketIo.authorize({
  cookieParser: cookieParser,       // the same middleware you registrer in express
  key:          'connect.sid',       // the name of the cookie where express/connect stores its session_id
  secret:       process.env.SESSION_SECRET,    // the session_secret to parse the cookie
  store:        sessionStore,        // we NEED to use a sessionstore. no memorystore please
  success:      onAuthorizeSuccess,  // *optional* callback on success - read more below
  fail:         onAuthorizeFail,     // *optional* callback on fail/error - read more below
}));

io.on('connection', function(socket){
  var arrayIndex = findWithAttr(agentDetail, "id", socket.request.user.agent_id);
  if(arrayIndex != -1){
    agentDetail[arrayIndex].disconnected = false;
  }
  socket.request.user.socketID = socket.id;
  var userId = socket.request.user;
  //console.log("Your User ID is", userId);
  updateAgentObjectWithSocket(socket.request.user.agent_id, socket.id); // update the agent object with the new socket information

  socket.on('disconnect', function(){
    /* Get the user that just left the page and update their disconnect to true */
    var tempArrayIndex = findWithAttr(agentDetail, "id", socket.request.user.agent_id);
    if(tempArrayIndex != -1){
      agentDetail[tempArrayIndex].disconnected = true;
    }
    /* Set a timeout in case user refreshs the pages or leaves and comes back. This is used to make sure we are logging out the correct users */
    setTimeout(function() {
      var tempArrayIndex2 = findWithAttr(agentDetail, "id", socket.request.user.agent_id);
      if(tempArrayIndex2 != -1){
        if(agentDetail[tempArrayIndex2].hasOwnProperty("disconnected")){
          if(agentDetail[tempArrayIndex2].disconnected){
            if(socket.request.user.hasOwnProperty("agent_id")){
              logoutAllLEUsers(socket.request.user.agent_id); // logout all LiveEngage users for this agent
            }
            socket.request.logout();
            sessionStore.destroy(socket.request.sessionID, function(data) {
              //console.log("Callback called: ", data);
            });
          }
        }
      }
    }, 5000);
    /* end of setTimeout */
  });
});

function onAuthorizeSuccess(data, accept){
  accept(null, true);
}

function onAuthorizeFail(data, message, error, accept){
  if(error)
    throw new Error(message);
  console.log('failed connection to socket.io:', message);
  accept(null, false);
}

/******************************************
 * Start Server *
******************************************/
http.listen(app.get('port'), function(){
  console.log('Node app is running on port', app.get('port'));
});

I tried updating the order of the middleware, but the issue still persists. Any help would be much appreciated.

Also, here are the versions of the modules that I am currently using:

  • Express - 4.15.2
  • Passport - 0.3.2
  • Passport.Socket.io - 3.7.0
  • Express-mysql-session - 1.2.0


via Scott

No comments:

Post a Comment