Sails, Sockets, and Authorization - node.js

I want to build a chat like whatsapp.
(Each user has a contact list which he can speak only with them)
My server auth is based on facebook tokens.
And I would like my sockets auth to be the same.
The current scenario :
-- after the user logged in, the front end sends a socket connection request to my sails server.
-- In the server "beforeConnect" function, I find the user by his fb token.
-- the user is found , the socket is connected successfully.
The problem : I need the user details to be available on the socket, That's because I want to find his chats, and to update him about only his chats and new messages.
That's how my beforeConnect function looks like now.
P.S: Don't know if it's worth mentioning, but I'm using socket.io in the client side, and not the sails.io.js I saw in the documents to connect my sails Server sockets.

onConnect method is deprecated for newer version, you can do authorization with token on connect event , you have to store token while connect and check on other listener for authentication, put this code in config/bootstrap.js
module.exports.bootstrap = function(cb) {
// handle connect socket first event executes after logged in
sails.io.on('connect', function (socket){
// store facebook token here
});
// handle custom listener for other stuff
sails.io.on('doSomeStuff', function (socket){
// check facebook token match with requested token
});
cb();
};
client :
you can simple emit "doSomeStuff" after logged in with facebook and pass token with each request

Related

Getting the 'current user' in an emit listener in nestjs app

My nestjs + React app has a Google oauth flow loosely based on this process. One thing that the Google library tries to help with is to take a refresh_token (that you've likely stored in your app's db) and use it to automatically retrieve a new access_token if the old one is expired. When it does this refresh, it emits a 'tokens' signal, and in my code I need something like
oauth2Client.on('tokens', async (tokens) => {
if (tokens.refresh_token) {
// store the refresh_token in your secure persistent database
console.log(tokens.refresh_token);
}
console.log(tokens.access_token);
});
It appears that the Google library intentionally does not let you proactively make a call to do the token refresh. The refresh happens automatically when you've set a refresh_token on the oauth2 client object and use that client object to next make any Google API call where the previous access_token has expired.
What I'm finding tricky is that when the above listener runs, I ideally would be able to get the 'current user' whose initial client session is what led to this server code path running. I can certainly create a chain of events like
User is logged into my app on the client
User does something on the frontend
A call to the server is made that has #UseGuards(AuthGuard()) and where I can get the user from the #Req
The above controller calls some additional functions, one of which can use the oauth2 client to make any random Google API call
If the random Google API call caused a token refresh, it would run the listener quoted above.
...but then, when #5 happens, is there any way to get the user detected in #3? Perhaps put another way, is there any way to 'inject' more info when the certain signal is emitted (but it's emitted in the Google library, not my code), or is there a way for the listener to pull the user from some kind of context?
(In case it matters, the emitter looks like this)

How to preserve Socket.io sockets app-wide

I am trying to add socket.io functionality to my App.
I have never used socket.io before, so I have no idea how to progress from here.
I've used the MERN Stack until now, and the next step would be to implement socket.io for chat functionality. The problem is, I don't know when to connect, and how to preserve my sockets. The user can sign in, so I thought I could just connect after signing the user in, but then the socket is created in a component, and I can't access it from anywhere else.
The problem is, I use JWT tokens for authentication, so I have a function, that "signs the user in" when going to a new page, if the token hasn't expired yet.
if(localStorage.jwtToken){
const token = localStorage.jwtToken;
setAuthToken(token);
const user = jwt_decode(token);
store.dispatch(action_setCurrentUser(user));
store.dispatch(setGroupsOfUser({ id: user.id }));
const currentTime = Date.now() / 1000;
if(user.exp < currentTime){
store.dispatch(logoutUser());
window.location.href = './login';
}
}
I thought I could just connect in here, but then my ChatView component can't access it to send messages and stuff. I need a socket to send notifications, even if the user isn't in a chat room, and the ChatView component needs it to send messages.
Tried to connect after the login dispatch, and store the online users on the server, with their socketIDs.
If I try to search for a solution, every hit I get is about authentication using socket.io, but the authentication is already done for me so I'm not sure how to proceed.
As suggested, I decided to create the socket in my App.js and store it in my state.
I can use this stored state then in my subcomponents, and assign it on the server to a user after sign in.
You might want to look in redux. Since your having all the auth stuff and all . It might get messy handling app wide authentication .

How can I identify user when user reload the page

I'm creating a card game like crazy8. And I already publish prototype.
Look here http://himapoyo.com
My problem is, when I reload the page during the game, socket is disconnected and my socket.id is changed.
So server side program can't identify me. Now, server remove player from table when socket is disconnected.(because if server don't remove the player who server can't identify, game is stopped, so I program). But if I can identify user, I don't want to remove player who just reload the page from table.
QUESTION: how can I identify user when user reload the page?
I think using cookie as ID is best for this problem. Are there Other solutions?
Other options would include:
Using local storage on the client
Passing query string values in the url
Posting the user id as part as the refresh
Storing the user in a server side session
Storing user information in redis cache.
I'm sure there are many more but this should be enough
After reading your question I understand (correct me if I'm wrong) that once a user refreshes the page (reconnects his socket) the socket ID changes (obviously) and you can't identify the user.
First of all, you shouldn't keep track of user using his socket ID as the socket ID will change every time the user reconnects. You should keep track of the socket ID for communication with the user AFTER you have identified the user.
So, what I would do is make an "authenticate" event on the server side and disconnect those sockets who don't emit the "authenticate" event. This event can expect any form of credentials (JWT, username:password etc).
After the user has been authenticated, you can take that socket ID and map that to the user ID and use the socket ID for further communication.
So the server side flow will be like this:
io.on("connect", function (socket) {
socket.on("authenticate", function(data) {
// do auth and then make other events
// if auth
socket.auth = true;
socket.emit("message", "User authenticated");
});
// If socket fails to authenticate within 3 seconds after connect, disconnect it
setTimeout(function() {
if (!socket.auth) {
socket.disconnect();
}
}, 3000);
});
After going to your app, I found that there is no authentication required. This should not be the case as I can highjack any user's slot at the table by just sending his/her name.
If you are okay with that then perhaps you can just keep track of the user name and map it to socket ID on connect.

How do I emit data over socket io every time the database is updated?

I am building a system where I have a standalone administrative dashboard with a client interface. The front end is built on angularjs, and I'm using a boilerplate Node.js/Express server on the backend, which I have connected to a MySql database.
Every time a client submits new information from the client interface, it is submitted to the server, routed by the router to a controller, which passes the data to a model and uploads it to the database.
What I would like to do is every time the controller is called that handles the request, after the request has completed, I want to emit the new data over socket.io to the administrative dashboard.
My challenge is I have no idea how to access the socket from within the controller??? Any help would be greatly appreciated!
Yeah it's tricky. Like Express, Socket.io a request handling library of its own. They both work separately and independently, so there's no easy way to "switch" from Express to Socket.
However if you can identify a client uniquely, you can store its socket.id somewhere and then you can use the io from your Express controller to emit to that client's socket.
You can do io.to(socket.id).emit which is same as socket.emit, so as long as you have socket.id you can emit to it using io which is globally available.
I use Passport authentication in most apps so I find that using req.user is a great way to uniquely identify a client. It can even be a behind-the-scenes "pseudo" authentication by generating random userid/pass for each client.
Then there's this passport.socketio module to Access passport.js user information from a socket.io connection. Here's an article from the author that goes into the details of it all.
Using them together you can use the user object to store and access socket.id and use it to communicate to the client via socket.io
io.on('connection', function(socket){
var user = socket.request.user; // from socketio.passport
// store the socket.id inside the user so it can be retrieved from Express
user.socketid = socket.id;
});
app.post('/form', function(req, res, next){
var user = req.user; // from Passport
var socketid = user.socket.id // from socket above
var data = req.body;
doStuff(data);
io.to(socketid).emit('done');
});

nodejs push notification that subscribes to redis

For logged in users only, I want to somehow notify them if they have any e.g. new notifications.
For example, say a member has sent them a private message, I want to tell the user that they have a new message to view (assuming they have not refreshed the page).
With Nodejs and redis, how would I go about doing this?
Note: I only need nodejs to send a small json to the user saying they have a new message.
The workflow is as follows that I was thinking:
1. user is logged in, a new message is sent to them.
2. somehow using nodejs and redis and long-polling, nodejs communicates back to the logged in users browser they have a pending message.
3. when nodejs sends this push notification, I then call another javascript function that will call a rest service to pull down additional json with the message.
I am integrating nodejs into an existing application, so I want to keep it as simple as possible with nodejs responsible for only notifying and not doing any additional logic.
Can someone outline how I should get going with this?
Should I be using redis http://redis.io/topics/pubsub somehow?
I'm not really sure how that works even after reading the page about it.
If you are integrating your node service into an existing application I would rather use some sort of messaging system to communicate messages from that application to node instead of a DB, even an in-memory DB. For clarity, I will assume you can use rabbitmq. If you do need to use redis, you will just need to find a way to use its publishing instead of rabbitmq publishing and corresponding node-side subscription, but I would imagine that the overall solution would be identical.
You need the following modules:
rabbitmq server (installation complexity about the same as for redis)
rabbitmq library in your external application to send messages, most languages are supported
rabit.js module for node to subscribe to messages or to communicate back to the external application
socket.io module for node to establish real-time connection between the node server and clients
I will also assume that both your external application and your node server have access to some shared DB (which can be redis), where node client session information is stored (e.g. redis-session-store for node). This would allow to use sessionId to validate who the message is for, if the user in the session is logged in and if certain users need to be sent notifications at all (by an external app).
This is how your stack might look like (unpolished):
Define a publisher in node to notify your external application that it needs to start/stop sending messages for a given sessionId. I will assume that for a given sessionId the user information can be recovered on either side (node or external application) from the shared DB and the user can be validated (here for simplicity by checking session.authenticated_user). Also define a subscriber to listen to incoming messages for the users:
var context = require('rabbit.js').createContext();
var pub = context.socket('PUB');
var sub = context.socket('SUB');
Define a socket.io connection(s) from your node server to the clients. As soon the client's web page is (re)loaded and io.connect() is called the below code will be executed (see clinet side at the end of the answer). As a new connection is established, validate the user is logged in (meaning its credentials are in the session), register the socket handler and publish a notification to the external application to start sending messages for this sessionId. The code here assumes a page reload on login/logout (and thus new socket.io session). If this is not the case, just emit a corresponding socket.io message from the client to node and register a handler in the method below in the same way as it is done for a new connection (this is beyond the scope of this example):
var sessionStore = undefined; // out-of-scope: define redis-session-store or any other store
var cookie = require("cookie"),
parseSignedCookie = require('connect').utils.parseSignedCookie;
// will store a map of all active sessionIds to sockets
var sockets = {};
// bind socket.io to the node http server
var io = require('socket.io').listen(httpServer);
// assumes some config object with session secrect and cookie sid
io.sockets.on("connection", function(socket) {
if (socket.handshake.headers.cookie) {
var cks = cookie.parse(socket.handshake.headers.cookie);
var sessionId = parseSignedCookie(cks[config.connectSid], config.sessionSecret);
// retrieve session from session store for sessionId
sessionStore.get(sessionId, function(err, session) {
// check if user of this session is logged in,
// define your elaborate method here
if (!err && session.authenticated_user) {
// define cleanup first for the case when user leaves the page
socket.on("disconnect", function() {
delete sockets[sessionId];
// notify external app that it should STOP publishing
pub.connect('user_exchange', function() {
pub.write(JSON.stringify({sessionId: sessionId, action: 'stop', reason: 'user disconnected'}), 'utf8');
});
});
// store client-specific socket for emits to the client
sockets[sessionId] = socket;
// notify external app that it should START publishing
pub.connect('user_exchange', function() {
pub.write(JSON.stringify({sessionId: sessionId, action: 'start'}), 'utf8');
});
}
});
}
});
Connect subscriber to the rabbitmq exchange to catch messages and emit them to clients:
sub.connect('messages_exchange', function() {
sub.on("readable", function() {
// parse incoming message, we need at least sessionId
var data = JSON.parse(sub.read());
// get socket to emit for this sessionId
var socket = sockets[data.sessionId];
if (socket) {
socket.emit("message", data.message);
} else {
// notify external app that it should STOP publishing
pub.connect('user_exchange', function() {
pub.write(JSON.stringify({sessionId: sessionId, action: 'stop', reason: 'user disconnected'}), 'utf8');
});
// further error handling if no socket found
}
});
});
Finally your client will look roughly like this (here in Jade, but that's just because I already have this whole stack along these lines):
script(src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js")
script(src="/socket.io/socket.io.js")
script(type='text/javascript').
$(function(){
var iosocket = io.connect();
iosocket.on('connect', function () {
// do whatever you like on connect (re-loading the page)
iosocket.on('message', function(message) {
// this is where your client finally gets the message
// do whatever you like with your new message
});
});
// if you want to communicate back to node, e.g. that user was logged in,
// do it roughly like this
$('#btnSend').click(function(event) {
iosocket.send('a message back to the node server if you need one');
});
});
Here is also a really nice explanation from Flickr on how they created a highly available and scalable push notification system with NodeJS and Redis.
http://code.flickr.net/2012/12/12/highly-available-real-time-notifications/

Resources