Socket.io - Implementing a user-socket association map for private messaging - javascript

I'm trying to create a private messaging system using socket.io
In order to associate the users with their sockets, most sites are suggesting something like this:
var people = {};
client.on('connection', function(socket) {
//join the server
socket.on('add user', function(user_id) {
//create user-socket map
people[user_id] = socket.id;
});
});
In my opinion, the problem with the above code is that the user_id is sent from the client side, so if the user somehow modify it and send another user_id an impersonation will take place.
Also, I can't access req.user._id under client.on('connection'... so how am I supposed to get the user_id from the server's side? I'm using node.js, passport and express.

I would use jsonwebtoken and socketio-jwt modules for solving this security issue.
Server:
var secret = 'shhhhhh';
app.get('/getJWT', function(req, res) {
/* Do your authentication here using header and get the userId */
var userId = 'someId';
var jwt = require('jsonwebtoken');
var token = jwt.sign({ 'userId': userId }, secret);
res.json({
token: token
});
});
var socketioJwt = require('socketio-jwt');
io.use(socketioJwt.authorize({
secret: secret,
handshake: true
}));
io.sockets.on('connection', function (socket) {
var userId = socket.decoded_token.userId;
/* your logic */
Client:
var token = "token you got from the /getJWT"
var c = io.connect('http://localhost:3000/', { query: "token=" + token });
As the token is encoded with a secret, client cannot change and send it.
Refer this article to know why this is better.

You're missing the most important part... Your code has to verify the usr is who he says he is. Plain and simple. I've done this multiple ways:
If users are logging in via PHP code, I move the session data to a mysql database. I then use a string on the PHP side to generate a response for a challenge to the client, who sends it to my web socket server. The WS server will challenge the client and look up the session information in the mysqldb. Done.
In my more recent developments, the actual login process is done via the web socket server. I verify the user credentials via whatever DB (in my instance, MySQL) and tie the username to the socket. Finished...
Do not purely rely on the javascript-based site to say "My name is." Otherwise, as you said, user impersonation becomes a walk in the park. You MUST validate that the user is who he says he is IF you're implementing a system where that matters. "Web sockets" themselves are not magical components that do this for you.

var people will be accessible on the same process.
When you want to scale with multiple socket server and balancing between them, then this idea for keeping people object locally will be not helpful.
Create authenticate event for authentication and set socket.userId and socket.isAuthenticate = true flag. In other events if socket.isAuthenticate is false, kick them out.
Make use of 'socket.io-redis' adpater for communication among many socket.io server. ( So when user1 from server1 send message to user2 which is in server2, will work ).
For socket - user association with multiple process, you can join Room with their userId, on authentication, join room like socket.join('myuserId');
and when to send message to that user, you can use io.to('myuserId').emit('message', "Hi How are you?"):

you can Send additional data on socket connection like this:
client side :
var c = io.connect('http://localhost:3000/', { query: "userId=value01" });
server side :
// extract userId param from connected url
io.on('connection', function(socket) {
var socket_param_userId = socket.handshake['query']['userId'];
console.log(socket_param_userId); //show value01
});

Related

How to read response in server after sending an alert to specific user in socket.io?

I'm new to Socket.io. Here is my problem.
I want to get the real time location of Android device. So I created a simple app to get current GPS location. And Node.js server and also Angular Admin panel.
Here is my Node.js server code.
//Some import statements are missing here. Not added.
var app = require('express')();
var server = require('http').createServer(app);
var io = require('socket.io')(server);
// Creating the socket connection.
io.sockets.on('connection', function (socket) {
socket.on('join', function (data) {
socket.join(data.id);
});
});
module.exports = function (app, express) {
var api = express.Router();
api.post('/device_location/:child_id', function (req, res) {
var child_id = req.body.child_id;
io.sockets.in(child_id).emit('new_msg', {msg: 'gps_data'}); //Sending request to registered android device.
var gps_location = ""; //Here i want to take GPS data from android client.
res.json({ type: "success", code: 200, data: gps_location }); //Then i can send the response to anyone who need that.
});
return api;
};
If this is not clear, please check this gist : https://gist.github.com/chanakaDe/123080fa5427c38c37f36afdac10be7c
First I created the socket connection. Then Android client can connect to it with unique user ID.
Then I'm sending the request to get GPS data when admin press the button from the Administration button.
io.sockets.in(child_id).emit('new_msg', {msg: 'gps_data'});
Here, child_id is the ID of android application.
Then I want to get the response from Android app and read it in server.
After reading that, I can send that to anyone asking for it.
res.json({ type: "success", code: 200, data: gps_location });
This is the normal Node.js way of sending a response.
How can we get the response to this var gps_location = ""; variable which we are getting GPS value of specific user.
Please help me on this. Tried many ways, but not good. I just need to read the response from Android client in the server and process it.
You need to emit your GPS location from Android to your server using something like :
socket.emit("gpsdata", data)
and then receive it in Node using socket.on("gpsdata", doSomething) :
io.sockets.on('connection', socket => {
socket.on("gpsdata", data => { // Receive data from one socket
io.sockets.emit('gps_location', { gps_location : data }) // Emit to everyone
})
});
https://socket.io/docs/emit-cheatsheet/

How to build local node.js mail server

I have spent couple of days implementing my own mail server using node.js. I used modules like "smtp-server" for creating smtp server and also "smtp-connection" to connect and send mail to it. But I'm getting confused because I don't know how to send mails from my smtp server to providers smtp servers like google or yahoo.
Can anyone help me?
Here is my code for more information:
My index.js file:
var SMTPServer = require('smtp-server').SMTPServer;
var port = 9025;
var serverOptions = {
name: "smtp-interceptor",
onConnect: onConnect,
onAuth: onAuth,
onData: onData
};
var server = new SMTPServer(serverOptions);
server.listen(port, 'localhost', function () {
console.log('SMTP server is listening on port ' + port);
});
function onConnect(session, callback) {
console.log('Connected');
return callback(); // Accept the connection
}
function onData(stream, session, callback) {
stream.pipe(process.stdout); // print message to console
console.log('Session \n', session.envelope);
stream.on('end', callback);
}
function onAuth(auth, session, callback){
if(auth.username !== 'Mahan' || auth.password !== 'Tafreshi') {
return callback(new Error('Invalid username or password'));
}
callback(null, {user: 123}); // where 123 is the user id or similar property
}
And my connection.js file:
var SMTPConnection = require('smtp-connection');
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
var connection = new SMTPConnection({
host: 'localhost',
port: 9025,
secure: false
});
connection.connect(function (){
console.log('Connceted to SMTP server');
var auth = {
user: 'Mahan',
pass: 'Tafreshi'
};
connection.login(auth, function (err) {
if(err)
return console.log('Login Failed \n', err);
console.log('Login Successful');
var envelope = {
from: "testapp#testapplocal.com",
to: "mahantp19#gmail.com"
};
var message = 'Test message1';
connection.send(envelope, message, function (err, info) {
if(err)
return console.log('Error : ' + err);
console.log('Message sent');
console.log('Accepted : ' + info.accepted);
console.log('Rejected : ' + info.rejected);
console.log(info.response);
connection.quit();
console.log('Quit connection');
connection.close();
});
});
});
There are many checks an email must pass before it's accepted by most mail providers. These checks attempt to validate the server sending the message is authorized to send on behalf of the sender.
IE: My server can send an email saying it's from "someone-special#somewhere-important.com"... That doesn't mean I'm "anywhere important" by any means.
While you may have had success sending mail from an SMTP server in the past using another technology such as PHP or an Exchange Server, the rules have changed significantly. Gmail has just began full enforcement this year.
I would assume your current issue has nothing to do with node as much as recent changes by the big providers.
Some of the checks that are needed include:
DKIM Keys (DNS Record)
SPF Record (DNS Record)
DMARK has been setup.
Dedicated IP Address for the server is required.
Your servers IP not being blacklisted.
The content of your email passes their filters.
Attempt to have an email sent from your server appear to be from a visitor or customer.
Among many others.
Any domain you want to "Act as Sender" must have these in place for most of the major providers to accept you message.
Google has a great set of tools and walkthroughs on getting an IP/Domain setup.
Visit the Google MX Record Checker and enter in the domain/subdomain you want to use as sender and it will tell you everything that is failing as well as passing.
Alternative Solutions
I use sendgrid.com. They have A node library that makes sending mail very easy. They also provide me the ability to proxy messages via SMTP. This means you can utilize the standard methods to deliver messages. You will just change out "localhost" with an hostname they provide. However, if this is for a new setup, go for the API.
Whomever is hosting your email should offer the ability for you send messages via SMTP or an API
An endless supply of other providers are out their, most of which allow low volume senders to send for FREE.
Word of warning
I tried for a few years keeping up with all the changes and inevitably, I continued to hit barriers of blocked messages with no ability to know until someone did not get an email. If your sending low volume, you should be able to use third parties without paying paying for it. If you are sending high volume, the cost of their service is cheap compared to the endless issues you will encounter even once you get it initially rolling.
PS. I have no affiliation with any email provider or sender. I pay them too.

url signature missing or invalid getstream

var client = stream.connect('my-client-id', null, '7723');
var user1 = client.feed('flat', 'my-client-id', 'NuAW6yHVQ2sr9RQvBE-cCuewUlo'); // What is this token param (3rd one)? How is this generated?
var acticity = {
actor: 'QUXdERFPto',
tweet: 'Hello world',
verb: 'tweet',
object: 1
}
user1.addActivity(acticity).then(null).catch(function(e) {
// Error object is
// code: null
// detail: "url signature missing or invalid"
// duration: "10ms"
// exception: "AuthenticationFailed"
// status_code: 403
});
What is the signature that I am missing?
Stream-JS client-side feed tokens
When using the stream-js library on the client you should initiate the connect without your secret key, to avoid sharing your private key with the world (its secret).
var client = stream.connect('api-key', null, 'app-id');
Initiating the client this way does not allow you to read or write from any feed created from this client, thus the following feed will return 403 errors when you try to read or write from it.
client.feed('flat', 'user-id');
But if you generate a read/write token on server side you can initiate a feed with this token and allow read/writes from the client-side:
client.feed('flat', 'user-id', 'read/write token');
To generate a read/write token on the server initiate a client with your secret key and call the following methods:
var client = stream.connect('api-key', 'api-secret', 'app-id');
var readToken = client.getReadOnlyToken('flat', 'user-id');
var readWriteToken = client.getReadWriteToken('flat', 'user-id');
Supply one of these tokens to your client and create a feed instance with this token.
When to use Stream-JS on the client
In most use-cases however you would want to use the stream-js client on your server side and retrieve/publish activities there, enrich these activities with data stored in your local database and send this to the client. One use-case for using stream-js on the client is for realtime notifications

how to send cookies from client to server?

I'm using MEAN Stack. I made login/signup and profile page. I'm saving user id in cookies and now I want to send my id in server.
I'm getting my id from cookies like that:
userId = $cookies.get('userId')
How can i send this information to server ? I must find this information in DB with id like that:
var userId = "receivedId";
User.UserModel.findOne({ _id: userId }, function(err, data) {
var User = {_id: data._id, user: data.user, date: data.date};
res.send(User); //I know how to send from server to client.
});
Please, check your web server documentation. If you're using express, you'll have to configure cookie-parser middleware first. After that you'll be able to access client cookies using req.cookies. – Leonid Beschastny
answers author is: Leonid Beschastny
thank you. it works!

How can I authorize and identify users with a websocket connection request?

When I set up a websocket connection from JavaScript, how can I authorize that it is a legit user on the serverside? I am using JSON Web Tokens and when doing regular calls to REST backend I automatically add an Authorization: Bearer (JWT..) header on AngularJS and then check that on the server side to see if a user is logged in. How can I do that when upgrading the connection to a websocket connection? I am afraid that some people with connect to the server requesting a websocket connection and spoof some of the users id's and receive their messages without being logged in to the service.
I request a websocket connection like this:
var conn = new WebSocket("ws://localhost:8080/api/ws");
conn.onclose = function (e) {
console.log("disconnected");
};
conn.onopen = function (e) {
console.log("connected");
};
conn.onmessage = function (e) {
console.log(e.data);
};
On the first part, is that a GET request or a POST request? Can I add parameters to the url and check them on the serverside? For example:
var conn = new WebSocket("ws://localhost:8080/api/ws/token/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ");
Or is this not a good idea? I also thought sending a JWT would be a good idea because I would be able to extract the user_id from the JWT and associate a websocket connection to a specific user.
How can I solve this problem?

Categories