开发者

Freeze on connect-redis session destroy?

I'm implementing an authentication system in node.js with express backed by a redis database for users and connect-redis for persistant, scalable session stores.

Here is the heart of my app, the server:

// Module Dependencies

var express = require('express');
var redis = require('redis');
var client = redis.createClient();
var RedisStore = require('connect-redis')(express);
var crypto = require('crypto');

var app = module.exports = express.createServer();  

// Configuration

app.configure(function(){
  app.set('views', __dirname + '/views');
  app.set('view engine', 'jade');
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(express.cookieParser());
  app.use(express.session({ secret: 'obqc487yusyfcbjgahkwfet73asdlkfyuga9r3a4', store: new RedisStore }));
  app.use(require('stylus').middleware({ src: __dirname + '/public' }));
  app.use(app.router);
  app.use(express.static(__dirname + '/public'));
});

app.configure('development', function(){
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

app.configure('production', function(){
  app.use(express.errorHandler()); 
});

// Message Helper

app.dynamicHelpers({
  // Index Alerts
  indexMessage: function(req){
    var msg = req.sessionStore.indexMessage;
    if (msg) return '<p class="message">' + msg + '</p>';
  },
  // Login Alerts
  loginMessage: function(req){
    var err = req.sessionStore.loginError;
    var msg = req.sessionStore.loginSuccess;
    delete req.sessionStore.loginError;
    delete req.sessionStore.loginSuccess;
    if (err) return '<p class="error">' + err + '</p>';
    if (msg) return '<p class="success">' + msg + '</p>';
  },
  // Register Alerts
  registerMessage: function(req){
    var err = req.sessionStore.registerError;
    var msg = req.sessionStore.registerSuccess;
    delete req.sessionStore.registerError;
    delete req.sessionStore.registerSuccess;
    if (err) return '<p class="error">' + err + '</p>';
    if (msg) return '<p class="success">' + msg + '</p>';
  },
  // Session Access
  sessionStore: function(req, res){
    return req.sessionStore;
  }
});

// Salt Generator

function generateSalt(){
  var text = "";
  var possible= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&am开发者_Python百科p;*"
  for(var i = 0; i < 40; i++)
    text += possible.charAt(Math.floor(Math.random() * possible.length));
  return text;
}

// Generate Hash

function hash(msg, key){
  return crypto.createHmac('sha256', key).update(msg).digest('hex');
}

// Authenticate

function authenticate(username, pass, fn){
  client.get('username:' + username + ':uid', function(err, uid){
    if (uid !== null){
      client.hgetall('uid:' + uid, function(err, user){
        if (user.pass == hash(pass, user.salt)){
          return fn(null, user);
        }
        else{
          fn(new Error('invalid password'));
        }
      });
    }
    else{
      return fn(new Error('cannot find user'));
    }
  });
}

function restrict(req, res, next){
  if (req.sessionStore.user) {
    next();
  } else {
    req.sessionStore.loginError = 'Access denied!';
    res.redirect('/login');
  }
}

function accessLogger(req, res, next) {
  console.log('/restricted accessed by %s', req.sessionStore.user.username);
  next();
}

// Routes

app.get('/', function(req, res){
  res.render('index', {
    title: 'TileTabs'
  });
});

app.get('/restricted', restrict, accessLogger, function(req, res){
  res.render('restricted', {
    title: 'Restricted Section'
  });
});

app.get('/logout', function(req, res){
  req.sessionStore.destroy(function(err){
    if (err){
      console.log('Error destroying session...');
    }
    else{
      console.log(req.sessionStore.user.username + ' has logged out.');
      res.redirect('home');
    }
  });
});

app.get('/login', function(req, res){
  res.render('login', {
    title: 'TileTabs Login'
  });
});

app.post('/login', function(req, res){
  var usernameLength = req.body.username.length;
  var passwordLength = req.body.password.length;
  if (usernameLength == 0 && passwordLength == 0){
    req.sessionStore.loginError = 'Authentication failed, please enter a username and password!';
    res.redirect('back');
  }
  else{
    authenticate(req.body.username, req.body.password, function(err, user){
      if (user) {
        req.session.regenerate(function(){
          req.sessionStore.user = user;
          req.sessionStore.indexMessage = 'Authenticated as ' + req.sessionStore.user.name + '.  Click to <a href="/logout">logout</a>. ' + ' You may now access <a href="/restricted">the restricted section</a>.';
          console.log(req.sessionStore.user.username + ' logged in!');
          res.redirect('home');
        });
      } else {
        req.sessionStore.loginError = 'Authentication failed, please check your username and password.';
        res.redirect('back');
      }
    });
  }
});

app.get('/register', function(req, res){
  res.render('register', {
    title: 'TileTabs Register'
  });
});

app.post('/register', function(req, res){
  var name = req.body.name;
  var username = req.body.username;
  var password = req.body.password;
  var salt = generateSalt();

  if (name.length == 0 && username.length == 0 && password.length == 0){
    req.sessionStore.registerError = 'Registration failed, please enter a name, username and password!';
    res.redirect('back');
  }
  else{
    client.get('username:' + username + ':uid', function(err, uid){
      if (uid !== null){
        req.sessionStore.registerError = 'Registration failed, ' + username + ' already taken.';
        res.redirect('back');
      }
      else{
        client.incr('global:nextUserId', function(err, uid){
          client.set('username:' + username + ':uid', uid);
          client.hmset('uid:' + uid, {
            name: name,
            username: username,
            salt: salt,
            pass: hash(password, salt)
          }, function(){
            req.sessionStore.loginSuccess = 'Thanks for registering!  Try logging in!';
            console.log(username + ' has registered!');
            res.redirect('/login');
          });
        });
      }
    });
  }
});

// Only listen on $ node app.js

if (!module.parent) {
  app.listen(80);
  console.log("Express server listening on port %d", app.address().port);
}

Registration and loggin authentication work great, but for some reason, I'm getting a hang when a connected user attempts to loggout.

As you can see from my /logout route,

app.get('/logout', function(req, res){
  req.sessionStore.destroy(function(err){
    if (err){
      console.log('Error destroying session...');
    }
    else{
      console.log(req.sessionStore.user.username + ' has logged out.');
      res.redirect('home');
    }
  });
});

I have two console.log's to try and determine where the freeze occurs. Interestingly, nothing gets logged.

So, for whatever reason, destroy() isn't being called properly.

I'm not sure whether or not I'm just goofing on syntax, or what, but according to the connect documentation it appears as though I'm setting this up correctly.


You must use req.session instead of req.sessionStore. req.sessionStore is a single RedisStore instance and is not being set dynamically by connect. This means that your code works for one user only. Your users will share the same session data this way.

req.sessionStore has a destroy method too and that's why you are not getting any errors. Your callback is not being called because in this method the callback is the second parameter.

Just replace req.sessionStore for req.session in all your code. E.g.:

 req.session.destroy(function(err) { ... });
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜