Skip to content
This repository was archived by the owner on Oct 23, 2019. It is now read-only.

Commit 6792648

Browse files
author
Justin Slattery
committed
Basic account auth and password hash working.
1 parent 1e00a96 commit 6792648

File tree

9 files changed

+211
-42
lines changed

9 files changed

+211
-42
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.swp

.monitor

Whitespace-only changes.

controllers/controllers.js

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
//Controllers
33
//
44
var NodeChatController = {
5-
init: function() {
5+
init: function(options) {
66
this.socket = new io.Socket(null, {port: 8000});
77
var mysocket = this.socket;
8+
this.userName = options.userName;
9+
this.hashPass = options.hashPass;
810

9-
this.model = new models.NodeChatModel();
11+
this.model = new models.NodeChatModel({userName: this.userName, hashPass: this.hashPass});
1012
this.view = new NodeChatView({model: this.model, socket: this.socket, el: $('#content')});
1113
var view = this.view;
1214

@@ -18,11 +20,3 @@ var NodeChatController = {
1820
return this;
1921
}
2022
};
21-
22-
//
23-
// Bootstrap the app
24-
//
25-
$(document).ready(function () {
26-
window.app = NodeChatController.init({});
27-
});
28-

core.js

Lines changed: 70 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
var app = require('express').createServer()
1+
var express = require('express')
2+
, app = express.createServer()
3+
, connect = require('connect')
24
, jade = require('jade')
35
, socket = require('socket.io').listen(app)
46
, _ = require('underscore')._
57
, Backbone = require('backbone')
6-
, redis = require('redis')
8+
, models = require('./models/models')
9+
, auth = require('./lib/auth');
10+
11+
var redis = require('redis')
712
, rc = redis.createClient()
8-
, models = require('./models/models');
13+
, redisStore = require('connect-redis');
914

1015
rc.on('error', function(err) {
1116
console.log('Error ' + err);
@@ -16,41 +21,81 @@ rc.on('error', function(err) {
1621
app.set('view engine', 'jade');
1722
app.set('view options', {layout: false});
1823

24+
//configure express to use redis as session store
25+
app.use(express.bodyParser());
26+
app.use(express.cookieParser());
27+
app.use(express.session({ store: new redisStore(), secret: 'Secretly I am an elephant' }));
28+
1929

2030
//setup routes
31+
app.get('/logout', function(req, res){
32+
// destroy the user's session to log them out
33+
// will be re-created next request
34+
req.session.destroy(function(){
35+
res.redirect('home');
36+
});
37+
});
38+
39+
app.get('/login', function(req, res){
40+
console.log('GET /login');
41+
res.render('login');
42+
});
43+
44+
app.get('/disconnect', function(req, res){
45+
res.render('disconnect');
46+
});
47+
48+
app.post('/login', function(req, res){
49+
console.log('POST /login');
50+
auth.authenticateUser(req.body.username, req.body.password, function(err, user){
51+
if (user) {
52+
// Regenerate session when signing in
53+
// to prevent fixation
54+
req.session.regenerate(function(){
55+
// Store the user's primary key
56+
// in the session store to be retrieved,
57+
// or in this case the entire user object
58+
console.log('regenerated session id ' + req.session.id);
59+
req.session.cookie.maxAge = 100 * 24 * 60 * 60 * 1000; //Force longer cookie age
60+
req.session.cookie.httpOnly = false;
61+
req.session.user = user;
62+
req.session.hashPass = user.hashPass || 'No Hash';
63+
64+
console.log('Storing new hash for user ' + user.name + ': ' + req.session.hash);
65+
res.redirect('/');
66+
});
67+
} else {
68+
req.session.error = 'Authentication failed, please check your username and password.';
69+
res.redirect('back');
70+
}
71+
});
72+
});
73+
2174
app.get('/*.(js|css)', function(req, res){
2275
res.sendfile('./'+req.url);
2376
});
2477

25-
app.get('/', function(req, res){
26-
res.render('index');
78+
app.get('/', restrictAccess, function(req, res){
79+
res.render('index', {
80+
locals: { name: req.session.user.name, hashPass: JSON.stringify(req.session.hashPass) }
81+
});
2782
});
2883

84+
//This method decides what a valid login looks like. In this case, just verify that we have a session object for the user
85+
function restrictAccess(req, res, next) {
86+
if (req.session.user) {
87+
next();
88+
} else {
89+
req.session.error = 'Access denied!';
90+
res.redirect('/login');
91+
}
92+
};
93+
2994

3095
//create local state
3196
var activeClients = 0;
3297
var nodeChatModel = new models.NodeChatModel();
3398

34-
rc.lrange('chatentries', -10, -1, function(err, data) {
35-
if (err)
36-
{
37-
console.log('Error: ' + err);
38-
}
39-
else if (data) {
40-
_.each(data, function(jsonChat) {
41-
var chat = new models.ChatEntry();
42-
chat.mport(jsonChat);
43-
nodeChatModel.chats.add(chat);
44-
});
45-
46-
console.log('Revived ' + nodeChatModel.chats.length + ' chats');
47-
}
48-
else {
49-
console.log('No data returned for key');
50-
}
51-
});
52-
53-
5499
socket.on('connection', function(client){
55100
activeClients += 1;
56101
client.on('disconnect', function(){clientDisconnect(client)});

lib/auth.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/* Auth.js
2+
*
3+
* Handles new user accounts and authentication
4+
*
5+
*/
6+
7+
(function () {
8+
if (typeof exports !== 'undefined') {
9+
auth = exports;
10+
redis = require('redis');
11+
rc = redis.createClient();
12+
13+
//joose is required to support the hash lib we are using
14+
require('joose');
15+
require('joosex-namespace-depended');
16+
require('hash');
17+
}
18+
else {
19+
auth = this.auth = {};
20+
}
21+
22+
auth.authenticateUser = function(name, pass, fn) {
23+
console.log('[authenticate] Starting auth for ' + name + ' with password ' + pass);
24+
25+
var rKey = 'user:' + name;
26+
rc.get(rKey, function(err, data){
27+
if(err) return fn(new Error('[authenticate] SET failed for key: ' + rKey + ' for value: ' + name));
28+
29+
if (!data) {
30+
console.log('[authenticate] user: ' + name + ' not found in store. Creating new user.');
31+
auth.createNewUserAccount(name, pass, fn);
32+
}
33+
else {
34+
console.log('[authenticate] user: ' + name + ' found in store. Verifying password.');
35+
auth.verifyUserAccount(data, pass, fn)
36+
}
37+
});
38+
};
39+
40+
auth.verifyUserAccount = function(foundUserName, pass, fn) {
41+
var rKey = 'user:' + foundUserName;
42+
43+
var user = {};
44+
user.name = foundUserName;
45+
46+
rc.get(rKey + '.salt', function(err, data){
47+
if(err) return fn(new Error('[authenticate] GET failed for key: ' + rKey + '.salt'));
48+
49+
var calculatedHash = Hash.sha512(data + '_' + pass);
50+
rc.get(rKey + '.hashPass', function(err, data) {
51+
if(err) return fn(new Error('[authenticate] GET failed for key: ' + rKey + '.hashPass'));
52+
53+
if (calculatedHash === data) {
54+
user.hashPass = calculatedHash;
55+
console.log('[authenticate] Auth succeeded for ' + foundUserName + ' with password ' + pass);
56+
return fn(null, user);
57+
}
58+
59+
fn(new Error('invalid password'));
60+
});
61+
});
62+
}
63+
64+
auth.createNewUserAccount = function(name, pass, fn) {
65+
var rKey = 'user:' + name;
66+
67+
rc.set(rKey, name, function(err, data){
68+
if(err) return fn(new Error('[authenticate] SET failed for key: ' + rKey + ' for value: ' + name));
69+
70+
var salt = new Date().getTime();
71+
rc.set(rKey + '.salt', salt, function(err, data) {
72+
if(err) return fn(new Error('[authenticate] SET failed for key: ' + rKey + '.salt' + ' for value: ' + salt));
73+
74+
var hashPass = Hash.sha512(salt + '_' + pass);
75+
rc.set(rKey + '.hashPass', hashPass, function(err, data) {
76+
if(err) return fn(new Error('[authenticate] SET failed for key: ' + rKey + '.hashPass' + ' for value: ' + hashPass));
77+
78+
var user = {};
79+
user.name = name;
80+
user.hashPass = hashPass;
81+
return fn(null, user);
82+
});
83+
});
84+
});
85+
}
86+
})()
87+

nodemon-ignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.swp
2+
*.css

views/index.jade

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,42 @@ html(lang="en")
99
script(type="text/javascript", src="/models/models.js")
1010
script(type="text/javascript", src="/controllers/controllers.js")
1111
script(type="text/javascript", src="/views/views.js")
12-
body
12+
script
13+
//Fake out FF and IE8
14+
function log() {
15+
if (typeof console == 'undefined') {
16+
return;
17+
}
18+
console.log.apply(console, arguments);
19+
}
20+
21+
$(document).ready(function () {
22+
window.app = NodeChatController.init({hashPass: !{locals.hashPass}, userName: '!{locals.name}'});
23+
});
24+
body
1325
#heading
1426
h1 nodechat
1527
#content
1628
p
1729
| connected clients
1830
span#client_count 0
31+
p
32+
a(href='/logout') logout
33+
34+
p
35+
label You are logged in as:
36+
= locals.name
37+
br
38+
label Your password hash is:
39+
= locals.hashPass
40+
br
41+
| don't you feel secure? :)
42+
1943
p
2044
| Fun Chat Messages
2145
ul#chat_list
22-
46+
2347
form(method="post", action="#", onsubmit="return false")#messageForm
24-
p
25-
label Name:
26-
input(type='text', name='user_name')
2748
p
2849
label Message:
2950
input(type='text', name='message')

views/login.jade

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
!!! 5
2+
html(lang="en")
3+
head
4+
title nodechat login
5+
body
6+
#heading
7+
h1 nodechat login
8+
#content
9+
p
10+
| You may logon with any user name that has not already been claimed. Your account will be created automatically.
11+
form(method="post", action="/login")
12+
p
13+
label Username:
14+
input(type='text', name='username')
15+
p
16+
label Password:
17+
input(type='password', name='password')
18+
P
19+
input(type='submit', value='send')

views/views.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ var ClientCountView = Backbone.View.extend({
3030

3131
var NodeChatView = Backbone.View.extend({
3232
initialize: function(options) {
33+
_.bindAll(this, 'sendMessage');
3334
this.model.chats.bind('add', this.addChat);
3435
this.socket = options.socket;
3536
this.clientCountView = new ClientCountView({model: new models.ClientCountModel(), el: $('#client_count')});
@@ -62,8 +63,7 @@ var NodeChatView = Backbone.View.extend({
6263

6364
, sendMessage: function(){
6465
var inputField = $('input[name=message]');
65-
var nameField = $('input[name=user_name]');
66-
var chatEntry = new models.ChatEntry({name: nameField.val(), text: inputField.val()});
66+
var chatEntry = new models.ChatEntry({name: this.model.get('userName'), text: inputField.val()});
6767
this.socket.send(chatEntry.xport());
6868
inputField.val('');
6969
}

0 commit comments

Comments
 (0)