Sessions and socket.io (node.js, express.js) - javascript

How to get user session with socket.io?
io.sockets.on('connection', function(socket) {
// Need to get user session
});

You can attach session with socket and get in connection event. Get the user session in authorization, if you're using express then do this:
var sessionStore = new express.session.MemoryStore();
io.set('authorization', function (handshake, accept){
var cookies = require('express/node_modules/cookie').parse(handshake.headers.cookie);
var parsed = require('express/node_modules/connect/lib/utils').parseSignedCookies(cookies, 'SESSION_SECRET');
sessionStore.get(parsed.sid, function (error, session){
if (session != null && session.user != null){
accept(null, true);
handshake.session = session;
}
});
});
And in your connection event you can get it like:
socket.handshake.session

Related

Express - socket.io - session. Refer to user id as socket id

I am using express-socket.io-session. I think I was able to setup the basic config by seeing the tutorials:
//BASIC CONFIG?
var clients = [];
var session = require("express-session")({
secret: 'some key',
resave: true,
saveUninitialized: true
});
var sharedsession = require("express-socket.io-session");
app.use(session);
io.use(function(socket, next){
next();
});
io.use(sharedsession(session, {
autoSave:true
}));
io.on('connection', function(socket) {
console.log("CLIENT CONNECTED");
var session = socket.handshake.session;
clients.push(socket);
socket.on('disconnect', function() {
console.log("CLIENT DISCONNECTED");
});
});
What I want to be able to do now is to refer to a specific client socket not by the socket but by the session id that should be assigned to that socket. When a user logins this happens:
req.session.user_id = user_id;
//(user_id is equal to DB {0,1,2,3...} ids
I was able to send sockets to specific clients when I did this:
clients[0].emit("to_do",info); // I don't know who is client index 0 compared to the login reference...
I would like to be able to do this or similar:
user_id = 3; // which would have a socket assigned
clients(user_id).emit("to_do",info);
That would mean each client would have a socket assigned to its previously assigned id. How could I do this so I could specify the socket by that id? I am not experienced at all with all of this so sorry for any big mistakes. Thanks
Your problem can be solved by each socket joining a group named after it's id:
socket.join(socket.id);
io.sockets.in(socket.id).emit('to_do', info);
//or
io.sockets.in(clients[0].id).emit('to_do', info);
Well I solved this out by iterating through the clients list and seeing which one had the socket I wanted
I ran into a similar issue, when using express-socket.io-session the user ID in socket.handshake.session.passport changes when a new user login, I used the below to solve it.
var userID;
if (!userID){
userID = socket.handshake.session.userID = socket.handshake.session.passport['user'];
}

How to load a specific session using express-session in node.js?

I need to create a websocket where I can connect to it from a different domain (websocket runs on 10.0.4.18:8020 and the client will establish connection from 10.0.4.160:443.)
Each user must have a unique session to be able to reuse his/her user data after authentication takes place.
Since the client "aka user's browser" is on a different host, I am having hard time keeping the session tied with with the client that creates it on page reloaded event!
I thought of this workaround to solve this problem
Create session using XMLHttpRequest() function from the client, then return a sessionID to the client
Save the sessionId in the user's browser using localStorage
Pass the sessionId to socket.io every time a user connects to the websocket.
Websocket then takes the sessionId and reloads it to make it available all over again.
To eliminate session fixation attack, I am going to add few more validation step to make sure the sessionId is not hijacked by using the client's IP and agent data.
Additionally, I need to fire a setInterval() method which will make external API call every second and updates the session variable.
Question
How can I properly reload a session data where I can auto save the variables without having to directly use store.get() to load the session data and save them?
Here is what I have done
I created the session using a file system. On every request I have to load the session store using store.get() method, update the session data, and then save it. But the problem that I have is every time I want to update the session as you can see my below code.
Here is what I have done!
var app = require('express')(),
https = require('https'),
fs = require('fs'),
session = require('express-session'),
fileStore = require('session-file-store')(session),
base64url = require('base64url'),
bodyParser = require("body-parser");
cookieParser = require("cookie-parser"),
env = require('./modules/config');
var server = https.createServer(
{
key: fs.readFileSync('certs/key.pem'),
cert: fs.readFileSync('certs/cert.pem')
}, app).listen(env.socket.port, env.socket.host, function () {
console.log('\033[2J');
console.log('Websocket is running at https://%s:%s', server.address().address, server.address().port);
});
var io = require('socket.io')(server);
var icwsReq = require('./modules/icws/request.js'),
icwsConn = require('./modules/icws/connection.js'),
icwsInter = require('./modules/icws/interactions.js'),
sessionValidator = require('./modules/validator.js');
var icwsRequest = new icwsReq();
var sessionChecker = new sessionValidator();
var sessionStoreFile = new fileStore({path: './tmp/sessions'});
var sessionOptions = {
store: sessionStoreFile,
secret: env.session.secret,
saveUninitialized: true,
resave: false,
cookie: {
path: '/',
httpOnly: true,
maxAge: 60 * 60 * 1000,
secure: true
}
};
app.use(session(sessionOptions)); // session support for the app
app.use(bodyParser.urlencoded({ extended: false })); //allows to pupulate req.body in the REST/PUT post requests!
// Set access control headers on every express route.
app.use(function (req, res, next){
res.setHeader('x-powered-by', 'Express');
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST');
next();
});
//Middleware for authorizing a user before establishing a connection
io.use(function(req, next) {
var sessionID = req.handshake.query.token || '';
var token = req.handshake.query.auth || '';
var origin = req.handshake.headers.origin;
var ip = req.request.socket.remoteAddress;
var userAgent = req.handshake.headers['user-agent'];
if(!sessionID || !token){
console.log('No Session found with this token! ' + sessionID);
return next(new Error('No Session found with this token!'));
}
sessionStoreFile.get(sessionID, function(err, session){
// session updated
if(err){
console.log(err);
return next(new Error(err));
}
if(!session){
console.log('Session Could not be loaded');
return next(new Error('Session Could not be loaded'));
}
if( session.token != token
|| session.ip != ip
|| session.agent != userAgent
|| session.origin != origin
){
session.token = null;
session.ip = null;
session.agent = null;
session.origin = null;
sessionStoreFile.set(sessionID, session);
console.log('This session is invalid! Please sign in');
return next(new Error('This session is invalid! Please sign in'));
}
next();
});
});
io.on('connection', function (socket) {
var sessionID = socket.handshake.query.token;
//load the session with the ID sessionID
sessionStoreFile.get(sessionID, function(err, session){
//add the socket.id to the queue
session.clients.push(socket.id);
//Save the session data after adding the connection to the queue
sessionStoreFile.set(sessionID, session, function(){
//Get the current session data "including current socket.id"
sessionStoreFile.get(sessionID, function(err, session){
//get an instance of icws connector
icwsRequest.setConnection(session.icwsHost, session.icwsPort);
var interactions = new icwsInter(icwsRequest);
//Call the API everysecond, update the session then save the session
setInterval(function(){
sessionStoreFile.get(sessionID, function(err, session){
//call the API and return the new data
session.queue = interactions.updateQueue();
//save the new data every second
sessionStoreFile.set(sessionID, session);
}
}, 1000);
//handle ICWS interactions
socket.on('interaction', function(data){
var task = data.task || '',
phone = data.phone || '',
interactionId = data.interactionId || '',
queueName = data.queueName || '';
//Place a phone call
if(task == 'call'){
interactions.call(phone);
}
//External transfer
if(task == 'eBlindTransfer'){
interactions.blindTransferCallExternal(interactionId, phone);
}
//Internal Transfer
if(task == 'iBlindTransfer'){
interactions.blindTransferCallInternal(interactionId, queueName);
}
});
//send a chat message to all browser's tabs associated with the currect session
socket.on('chat', function(msg){
var clients = session.clients;
console.log(clients);
for (var i in clients) {
var socketId = clients[i];
console.log('Client Said: ' + msg + ' socket Id: ' + socketId);
io.to(socketId).emit('chat', {message: 'Server Said: ' + msg});
}
});
//handle disconnect
socket.on('disconnect', function(msg){
var index = session.clients.indexOf(socket.id);
if(index > -1){
session.clients.splice(index, 1);
//save session after removing a client
sessionStoreFile.set(sessionID, session, function(error){
if(!error){
console.log('Closing tabs: ' + socket.id);
console.log(session);
}
});
}
});
//handle errors
socket.on('error', function(msg){
console.log('Error Message: ' + msg);
});
});
});
});
});
app.get('/', function (req, res) {
res.send('welcome: ' + req.sessionID);
});
app.get('/handshake/:token', function (req, res) {
var origin = req.headers.origin;
var ip = req.connection.remoteAddress;
var userAgent = req.headers['user-agent'];
if(!req.params || !req.params.token || !ip || !origin || !userAgent){
console.log('Missing Request!');
return false;
}
if(!originIsAllowed(origin)){
console.log('This is a cross-domain attack!');
return false;
}
req.session.token = req.params.token;
req.session.ip = ip;
req.session.origin = origin;
req.session.agent = userAgent;
req.session.clients = [];
req.session.save(function(err){
if (err) {
connectionError(res, session);
} else {
res.json({
token: req.sessionID
});
}
});
});
function originIsAllowed(origin) {
// put logic here to detect whether the specified origin is allowed.
var allowed = env.session.allowedOrigins || []
if(allowed.indexOf(origin) >= 0){
return true;
}
return false;
};

express.js parsing cookies?

I am trying to use an old library balloons.io as a base for a chat app, but it's quite out dated, in this particular code I am trying to figure out how to use express 4x to parse the cookie to get an sid without getting it from the req.session
Since express 4x is not using connect anymore how can I do something similar to the below but in the new express version?
/*
* Module dependencies
*/
var sio = require('socket.io')
, parseCookies = require('connect').utils.parseSignedCookies
, cookie = require('cookie')
, fs = require('fs');
/**
* Expose Sockets initialization
*/
module.exports = Sockets;
/**
* Socket.io
*
* #param {Express} app `Express` instance.
* #param {HTTPServer} server `http` server instance.
* #api public
*/
function Sockets (app, server) {
var config = app.get('config');
var client = app.get('redisClient');
var sessionStore = app.get('sessionStore');
var io = sio.listen(server);
io.set('authorization', function (hsData, accept) {
if(hsData.headers.cookie) {
var cookies = parseCookies(cookie.parse(hsData.headers.cookie), config.session.secret)
, sid = cookies['balloons'];
sessionStore.load(sid, function(err, session) {
if(err || !session) {
return accept('Error retrieving session!', false);
}
hsData.balloons = {
user: session.passport.user,
room: /\/(?:([^\/]+?))\/?$/g.exec(hsData.headers.referer)[1]
};
return accept(null, true);
});
} else {
return accept('No cookie transmitted.', false);
}
});
});
};
Not sure if this helps, but Cookie parsing in express 4.x has been extracted to the cookie-parser package. I'm not sure, but you may be able to swap out connect.util.parseSignedCookies with cookieParser.parseSignedCookies`.
That's about all I can help you with, as I haven't used socket.io much yet.
function Sockets (app, server, pub, sub, sessionStore) {
var config = app.get('config');
var secrets = require('./config/secrets');
var client = pub;
var io = sio.listen(server);
io.set('authorization', function (handshake, callback) {
if(handshake.headers.cookie) {
// pay attention here, this is how you parse, make sure you use
// cookie-parser and cookie
var cookies = cookie.parse(handshake.headers.cookie);
var sid = cookieParser.signedCookie(cookies['balloons'], secrets.sessionSecret);
// get the session data from the session store
sessionStore.load(sid, function(err, session) {
if(err || !session) {
return callback('Error retrieving session!', false);
}
// this is not storing the data into the handshake object
handshake.headers.xygaming = {
user: session.passport.user,
room: /\/(?:([^\/]+?))\/?$/g.exec(handshake.headers.referer)[1]
};
return callback(null, true);
});
} else {
return callback('No cookie transmitted.', false);
}
});
}

How can I have faye-websockets code running in the browser?

I'm new with node.js/express and all and I want to be able to notify any clients in browser about a new message received from some algorithm in the back-end. The publisher algorithm connect to the websocket and writes the message.
As far as I've looked there were examples which recommended websockets but I haven't been able to run that code in browser only in console.
Example client code:
var WebSocket = require('faye-websocket');
var ws = new WebSocket.Client('ws://localhost:1234');
var http = require('http');
var port = process.env.PORT || 1235;
var server = http.createServer()
.listen(port);
// receive a message from the server
ws.on('message', function(event) {
alert(JSON.parse(event.data));
});
Thank you
Found the answer after some trial/error iterations.
The algorithm now does a POST to an URL which in turn triggers a write to sockets for all connected clients via socket.io.
Client code:
var socket = io('http://localhost:7777');
socket.on('message', function (msg) {
document.body.insertAdjacentHTML( 'beforeend', '<div id="myID">'+msg+'</div>' );
});
And on the server, when client connects I retain it's socket into an array so I can write to each one:
Server code:
io.on('connection', function(socket){
console.log('a user connected: '+socket.id);
var id = clientCount++;
clientSockets[id] = socket;
socket.on('disconnect', function(){
console.log('user disconnected');
delete clientSockets[id];
socket = null
});
});
app.post('/alerts', function(req, res) {
req.accepts(['json', 'application']);
console.log("Algo did a POST on /alerts!");
// send the message to all clients
//console.log(req.body);
for(var i in clientSockets) {
clientSockets[i].send(JSON.stringify(req.body));
}
res.send(200);
});
In conclusion, I'm not using faye-websockets but instead socket.io

Connection Pool for NodeJS

I have an app that has been maxing out the number of connection to MongoDB and I was under the assumption that if the drivers were set up correctly you didn't need to worry about closing connections.
I've seen people mention the Generic Pool module but what is the best process for closing or pooling connections using Node & MongoDB?
Here is my connection code for the app:
var sys = require("sys");
var app = require('http').createServer(handler);
var io = require('socket.io').listen(app);
app.listen(1337);
io.configure(function () {
io.set('authorization', function (handshakeData, callback) {
callback(null, true);
});
});
function handler (req, res, data) {
sys.puts('request made to trackerapp.js');
res.writeHead(200);
res.end(data);
}
io.sockets.on('connection', function (socket) {
socket.on('adTracker', function (data) {
var adRequestData = data;
var databaseUrl = "mongodb://dbuser:dbpass#mongolab.com/tracker";
var collections = ["cmnads"]
var db = require("mongojs").connect(databaseUrl, collections);
db.cmnads.insert({adRequest : adRequestData},function(err, updated) {
if( err || !updated ) console.log("mongo not updated" + err);
else console.log("data stored");
});
});
});
After seeing JohnnyHK's comment I was able to pull the connection event out of the Socket.io connection and it worked fine, see the solution below:
var databaseUrl = "mongodb://dbuser:dbpass#mongolab.com/tracker";
var collections = ["cmnads"];
var db = mongojs.connect(databaseUrl, collections);
io.sockets.on('connection', function (socket) {
socket.on('adTracker', function (data) {
var adRequestData = data;
//vars for MongoDB used to be created here... so new connect function was called on every request to socket.io
db.cmnads.insert({adRequest : adRequestData},function(err, updated) {
if( err || !updated ) console.log("mongo not updated" + err);
else console.log("data stored");
});
});
});
A technique I used with my express apps that seems have some measure of success is to open a connection to a mongo instance (thereby getting a connection pool) then sharing that db (that is now in the "connected" state) instance wherever it is needed. Something like this:
server = new Server(app.settings.dbsettings.host, app.settings.dbsettings.port, {auto_reconnect: true, poolSize: 5})
db = new Db(app.settings.dbsettings.db, server, {native_parser:false})
db.open(function(err, db) {
app.db = db;
server = app.listen(app.settings.port);
console.log("Express server listening on port %d in %s mode", app.settings.port, app.settings.env);
require('./apps/socket-io')(app, server);
});
This connects to the database at the highest level in my app before the program moves into the wait listen state.
Before I used this pattern I would create a new database object whenever I needed to interact with the database. The problem I found is that the new database object would create a new thread pool, consuming a bunch of ports. These were never cleaned up properly. After a period of time the machine that hosted the app would run out of ports!
Anyway, a variation on the code I have shown should be where you should do your thinking I believe.

Categories

Resources