Skip to content

Commit

Permalink
Merge pull request #299 from hermansc/remember-me
Browse files Browse the repository at this point in the history
Adds cookie based login, also called "remember me"-functionality.
  • Loading branch information
thedjpetersen committed Mar 5, 2014
2 parents 86a16e0 + 45fd5e2 commit c09e085
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 9 deletions.
25 changes: 25 additions & 0 deletions assets/js/client.js
Expand Up @@ -4,6 +4,7 @@
//= require 'libs/ICanHaz.min.js'
//= require 'libs/bootstrap.min.js'
//= require 'libs/ircparser.min.js'
//= require 'libs/jquery.cookie.min.js'
//= require 'utils.js'
//= require 'models.js'
//= require 'collections.js'
Expand Down Expand Up @@ -51,6 +52,14 @@ $(function() {
}
});

irc.delete_session = function() {
// Deletes the session cookie at both server and client side
if ($.cookie('auth_token')) {
irc.socket.emit('session_delete', { auth_token: $.cookie('auth_token') });
$.removeCookie('auth_token');
}
}

// Registration (server joined)
irc.socket.on('registered', function(data) {
var window = irc.chatWindows.getByName('status');
Expand All @@ -69,6 +78,9 @@ $(function() {

irc.socket.on('login_success', function(data) {
window.irc.loggedIn = true;

$.cookie('auth_token', data.auth_token, { expires: 7 });

if(data.exists){
irc.socket.emit('connect', {});
} else {
Expand All @@ -77,17 +89,28 @@ $(function() {
});

irc.socket.on('disconnect', function() {
// The server probably went down.
irc.connected = false;
irc.delete_session();
alert('You were disconnected from the server.');
$('.container-fluid').css('opacity', '0.5');
});


irc.socket.on('register_success', function(data) {
window.irc.loggedIn = true;
$.cookie('auth_token', data.auth_token, { expires: 7 });
irc.appView.overview.render({currentTarget: {id: "connection"}});
});

irc.socket.on('session_not_found', function(data) {
// The client has a session cookie, but it is not found server-side.
// Delete it at the client, as something is not in sync, and render the overview page.
console.log("A session was found at client, but not in the server.");
irc.delete_session();
irc.appView.overview.render();
});

irc.socket.on('restore_connection', function(data) {
irc.me = new User({nick: data.nick, server: data.server});
irc.connected = true;
Expand Down Expand Up @@ -283,6 +306,7 @@ $(function() {
channel.stream.add(quitMessage);
}
}
irc.delete_session();
});

irc.socket.on('names', function(data) {
Expand Down Expand Up @@ -347,6 +371,7 @@ $(function() {
});

irc.socket.on('reset', function(data) {
irc.delete_session();
irc.chatWindows = new WindowList();
irc.connected = false;
irc.loggedIn = false;
Expand Down
2 changes: 2 additions & 0 deletions assets/js/libs/jquery.cookie.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion assets/js/views/overview.js
@@ -1,6 +1,14 @@
var OverviewView = Backbone.View.extend({
initialize: function() {
this.render();
// Check if we are logged in with a cookie.
if ($.cookie('auth_token')){
irc.socket.emit('session_check', {
auth_token: $.cookie('auth_token'),
});
} else {
// If not, we render the overview page.
this.render();
}
},

events: {
Expand Down
8 changes: 7 additions & 1 deletion config.js
Expand Up @@ -33,5 +33,11 @@ module.exports = {
use_polling: process.env.USE_POLLING || false, // Use polling if websockets aren't supported

// limit each user's connection log to this amount of messages (***not implemented yet***)
max_log_size: 4096
max_log_size: 4096,

// How long you want to store a cookie, both server and client side, in hours.
cookie_time: 7 * 24,

// Secret key used to generate a unique and secure session cookie hash.
secret_key: "MY-SUPER-SECRET-KEY"
};
7 changes: 7 additions & 0 deletions lib/models.js
Expand Up @@ -52,6 +52,13 @@ module.exports = function (schema) {
chanserv_password: { type: String }
});

schema.define('Session', {
auth_token: { type: String },
username: { type: String },
user_id: { type: String },
expires: { type: Date }
});

schema.autoupdate();

return schema;
Expand Down
91 changes: 84 additions & 7 deletions lib/socket.js
@@ -1,5 +1,6 @@
var bcrypt = require('bcrypt-nodejs'),
uuid = require('node-uuid');
uuid = require('node-uuid'),
crypto = require('crypto');

function isChannel(name) {
return ['#','&'].indexOf(name[0]) >= 0;
Expand All @@ -23,6 +24,7 @@ module.exports = function(socket, app) {
var Message = app.db.models.Message;
var PM = app.db.models.PM;
var Channel = app.db.models.Channel;
var Session = app.db.models.Session;

// signal an IRC connection belonging to the user
socket.signalIRC = function(connection, event, dict) {
Expand All @@ -49,11 +51,13 @@ module.exports = function(socket, app) {
, password: hash
, joined: Date.now()
}, function (err, user) {
socket.emit('register_success', {username: data.username});
socket.userID = user.user_id;
// subscribe to all online IRC connections using socket.io room
socket.join(socket.userID);
socket.socketIORoom = socket.userID;
socket.session_create(data, user.user_id, function(data, auth_token) {
socket.emit('register_success', {username: data.username, auth_token: auth_token});
socket.userID = user.user_id;
// subscribe to all online IRC connections using socket.io room
socket.join(socket.userID);
socket.socketIORoom = socket.userID;
});
});
});
});
Expand Down Expand Up @@ -87,7 +91,11 @@ module.exports = function(socket, app) {
socket.connID = connections[0].id;
socket.conn = connections[0];
}
socket.emit('login_success', {username: data.username, exists: exists});
socket.session_create(data, user.user_id, function(data, auth_token){
socket.emit('login_success', { username: data.username,
exists: exists,
auth_token: auth_token });
});
});
} else {
socket.emit('login_error', {message: 'Wrong password'});
Expand All @@ -98,6 +106,75 @@ module.exports = function(socket, app) {
}
});
});

socket.session_create = function(data, user_id, callback) {
/*
* Add the successful login to the session database.
*/
// Create a sha1-hash based on username and secret key
var hash = crypto.createHash('sha1');
var auth_token = hash.update(data.username + app.config.secret_key).digest('hex');

// Delete any previous sessions for this user_id.
Session.all({ where: { user_id: user_id } }, function(err, sessions){
sessions.forEach(function(session){
session.destroy();
});
})

var now = new Date();
Session.create({
auth_token: auth_token,
username: data.username,
user_id: user_id,
expires: new Date().setHours(now.getHours() + app.config.cookie_time),
});
callback(data, auth_token);
}

socket.on('session_check', function(data){
/*
* Check if the token provides is valid, that is: in the database and not
* expired. If found we restore the users connections, if any, and log in.
*/
Session.findOne({ where: { auth_token: data.auth_token}}, function(err, session){
var now = new Date();
if(session && session.expires > now) {
// TODO: Join the logic inside here, with the logic inside on('login')
socket.userID = session.user_id;
socket.join(socket.userID);
socket.socketIORoom = socket.userID;

Connection.all({where: { user_id: session.user_id } }, function (err, connections) {
var exists = false;
if (connections.length > 0) {
exists = true;
// TEMPORARY - read note at top of this file on socket.connID
socket.connID = connections[0].id;
socket.conn = connections[0];
}
socket.emit('login_success', { username: session.username, exists: exists })
});
} else {
if (session && session.expires < now) {
// Delete session if it is expired.
session.destroy();
}
// In this situation, which should be rare, the client has a cookie
// which is not in the database. Thus we just want to delete the cookie
// at the client.
socket.emit('session_not_found');
}
})
});

socket.on('session_delete', function(data){
Session.findOne({ where: {auth_token: data.auth_token}}, function(err, session){
if (session) {
session.destroy();
}
});
});

// connection creation/restore
socket.on('connect', function(data) {
Expand Down
2 changes: 2 additions & 0 deletions views/templates.jade
Expand Up @@ -30,12 +30,14 @@ script(id="overview_home", type="text/html")
li.overview_button#settings
img(src="/assets/images/settings.svg")
span Settings
{{^loggedIn}}
li.overview_button#login
img(src="/assets/images/login.svg")
span Login
li.overview_button#register
img(src="/assets/images/register.svg")
span Register
{{/loggedIn}}

script(id="overview", type="text/html")
#overview
Expand Down

0 comments on commit c09e085

Please sign in to comment.