Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #299 from hermansc/remember-me

Adds cookie based login, also called "remember me"-functionality.
  • Loading branch information...
commit c09e085733ab4a5e6a102a0b7acfed29b4c8c0da 2 parents 86a16e0 + 45fd5e2
@thedjpetersen authored
View
25 assets/js/client.js
@@ -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'
@@ -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');
@@ -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 {
@@ -77,7 +89,9 @@ $(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');
});
@@ -85,9 +99,18 @@ $(function() {
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;
@@ -283,6 +306,7 @@ $(function() {
channel.stream.add(quitMessage);
}
}
+ irc.delete_session();
});
irc.socket.on('names', function(data) {
@@ -347,6 +371,7 @@ $(function() {
});
irc.socket.on('reset', function(data) {
+ irc.delete_session();
irc.chatWindows = new WindowList();
irc.connected = false;
irc.loggedIn = false;
View
2  assets/js/libs/jquery.cookie.min.js
@@ -0,0 +1,2 @@
+/*! jquery.cookie v1.4.0 | MIT */
+!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a(jQuery)}(function(a){function b(a){return h.raw?a:encodeURIComponent(a)}function c(a){return h.raw?a:decodeURIComponent(a)}function d(a){return b(h.json?JSON.stringify(a):String(a))}function e(a){0===a.indexOf('"')&&(a=a.slice(1,-1).replace(/\\"/g,'"').replace(/\\\\/g,"\\"));try{a=decodeURIComponent(a.replace(g," "))}catch(b){return}try{return h.json?JSON.parse(a):a}catch(b){}}function f(b,c){var d=h.raw?b:e(b);return a.isFunction(c)?c(d):d}var g=/\+/g,h=a.cookie=function(e,g,i){if(void 0!==g&&!a.isFunction(g)){if(i=a.extend({},h.defaults,i),"number"==typeof i.expires){var j=i.expires,k=i.expires=new Date;k.setDate(k.getDate()+j)}return document.cookie=[b(e),"=",d(g),i.expires?"; expires="+i.expires.toUTCString():"",i.path?"; path="+i.path:"",i.domain?"; domain="+i.domain:"",i.secure?"; secure":""].join("")}for(var l=e?void 0:{},m=document.cookie?document.cookie.split("; "):[],n=0,o=m.length;o>n;n++){var p=m[n].split("="),q=c(p.shift()),r=p.join("=");if(e&&e===q){l=f(r,g);break}e||void 0===(r=f(r))||(l[q]=r)}return l};h.defaults={},a.removeCookie=function(b,c){return void 0!==a.cookie(b)?(a.cookie(b,"",a.extend({},c,{expires:-1})),!0):!1}});
View
10 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: {
View
8 config.js
@@ -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"
};
View
7 lib/models.js
@@ -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;
View
91 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;
@@ -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) {
@@ -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;
+ });
});
});
});
@@ -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'});
@@ -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) {
View
2  views/templates.jade
@@ -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
Please sign in to comment.
Something went wrong with that request. Please try again.