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) { ... });
精彩评论