/
app.js
169 lines (161 loc) · 5.75 KB
/
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
var port = 1337;
var express = require('express');
var app = express()
, server = require('http').createServer(app)
, connect = require('express/node_modules/connect')
, cookie = require('cookie')
, parseSignedCookie = connect.utils.parseSignedCookie
, path = require('path');
/** Configuration */
app.configure(function() {
this.set('views', path.join(__dirname, 'views'));
this.set('view engine', 'ejs');
this.use(express.static(path.join(__dirname, '/public')));
// Allow parsing cookies from request headers
this.use(express.cookieParser());
// Session management
// Internal session data storage engine, this is the default engine embedded with connect.
// Much more can be found as external modules (Redis, Mongo, Mysql, file...). look at "npm search connect session store"
this.sessionStore = new express.session.MemoryStore({ reapInterval: 60000 * 10 });
this.use(express.session({
// Private crypting key
"secret": "fuck",
"store": this.sessionStore
}));
// Allow parsing form data
this.use(express.bodyParser());
});
app.configure('development', function(){
this.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});
app.configure('production', function(){
this.use(express.errorHandler());
});
/** Routes */
app.get('/session-index', function (req, res, next) {
// Increment "index" in session
req.session.index = (req.session.index || 0) + 1;
// View "session-index.ejs"
res.render('session-index', {
"index": req.session.index,
"sessId": req.sessionID
});
});
/** Middleware for limited access */
function requireLogin (req, res, next) {
if (req.session.username) {
// User is authenticated, let him in
next();
} else {
// Otherwise, we redirect him to login form
res.redirect("/login");
}
}
/** Home page (requires authentication) */
app.get('/', [requireLogin], function (req, res, next) {
res.render('index', { "username": req.session.username });
});
/** Login form */
app.get("/login", function (req, res) {
// Show form, default value = current username
res.render("login", { "username": req.session.username, "error": null });
});
app.post("/login", function (req, res) {
var options = { "username": req.body.username, "error": null };
if (!req.body.username) {
options.error = "User name is required";
res.render("login", options);
} else if (req.body.username == req.session.username) {
// User has not changed username, accept it as-is
res.redirect("/");
} else if (!req.body.username.match(/^[a-zA-Z0-9\-_]{3,}$/)) {
options.error = "User name must have at least 3 alphanumeric characters";
res.render("login", options);
} else {
// Validate if username is free
req.sessionStore.all(function (err, sessions) {
if (!err) {
var found = false;
for (var i=0; i<sessions.length; i++) {
var session = JSON.parse(sessions[i]);
if (session.username == req.body.username) {
err = "User name already used by someone else";
found = true;
break;
}
}
}
if (err) {
options.error = ""+err;
res.render("login", options);
} else {
req.session.username = req.body.username;
res.redirect("/");
}
});
}
});
/** WebSocket */
var sockets = require('socket.io').listen(server).of('/chat');
sockets.authorization(function (handshakeData, callback) {
// Read cookies from handshake headers
var cookies = cookie.parse(handshakeData.headers.cookie);
// We're now able to retrieve session ID
var sessionID = parseSignedCookie(cookies['connect.sid'], "fuck");
// No session? Refuse connection
if (!sessionID) {
callback('No session', false);
} else {
// Store session ID in handshake data, we'll use it later to associate
// session with open sockets
handshakeData.sessionID = sessionID;
// On récupère la session utilisateur, et on en extrait son username
app.sessionStore.get(sessionID, function (err, session) {
if (!err && session && session.username) {
// On stocke ce username dans les données de l'authentification, pour réutilisation directe plus tard
handshakeData.username = session.username;
// OK, on accepte la connexion
callback(null, true);
} else {
// Session incomplète, ou non trouvée
callback(err || 'User not authenticated', false);
}
});
}
});
// Active sockets by session
var connections = {};
sockets.on('connection', function (socket) { // New client
var sessionID = socket.handshake.sessionID; // Store session ID from handshake
// this is required if we want to access this data when user leaves, as handshake is
// not available in "disconnect" event.
var username = socket.handshake.username; // Same here, to allow event "bye" with username
if ('undefined' == typeof connections[sessionID]) {
connections[sessionID] = { "length": 0 };
// First connection
sockets.emit('join', username, Date.now());
}
// Add connection to pool
connections[sessionID][socket.id] = socket;
connections[sessionID].length ++;
// When user leaves
socket.on('disconnect', function () {
// Is this socket associated to user session ?
var userConnections = connections[sessionID];
if (userConnections.length && userConnections[socket.id]) {
// Forget this socket
userConnections.length --;
delete userConnections[socket.id];
}
if (userConnections.length == 0) {
// No more active sockets for this user: say bye
sockets.emit('bye', username, Date.now());
}
});
// New message from client = "write" event
socket.on('write', function (message) {
sockets.emit('message', username, message, Date.now());
});
});
/** Start server */
server.listen(port);