Socket.IO socket protection without authentication - node.js

I've created a simple Node.js app using Express.js and socket.io (available here), where the user clicks a button, and it increments a number on the page. This number is also incremented live among all clients connected to the page. I am using web sockets and socket.io to get the client-server communication and live number updating system.
I am using the flood-protection module to limit socket emits to 5 per second, but this really doesn't make the game very fun because of the low amount of clicks per second you can have, and hackers could just use a setInterval and still make considerable progress automatically, even at such a low rate.
My issue:
I don't want the user to have to authenticate themselves - anybody should be able to play!
I want to keep the click rate around 15 clicks per second, if possible.
I don't want people to be able to send socket messages and automatically click the button from the browser console.
Here's the program:
index.js
var express = require("express");
var http = require("http");
var socketIO = require("socket.io");
var path = require("path");
var fs = require("fs");
var FloodProtection = require("flood-protection").default;
__dirname = path.resolve();
function btoa(str) {
return new Buffer(str, 'latin1').toString('base64');
};
function atob(b64Encoded) {
return new Buffer(b64Encoded, 'base64').toString('latin1');
};
var app = express();
app.get("/", function(req, res){
res.sendFile(__dirname + "/index.html");
});
var temp;
num = temp | parseInt(atob(fs.readFileSync("num.txt"))) | 0
var server = http.createServer(app);
var io = socketIO.listen(server, {log: true});
io.sockets.on("connection", (socket) => {
protector = new FloodProtection({rate: 5, per: 1})
io.sockets.emit("update", num);
socket.on("push", (value) => {
if (protector.check()) {
num++;
temp = num
io.sockets.emit("update", temp);
} else {
io.sockets.emit("update", "You can only click the button five times per second.")
socket.disconnect(2)
setTimeout(()=>{}, 3000)
}
});
socket.on("disconnect", () => {
fs.writeFile("num.txt", btoa(String(temp)), (err) => {
if (err) throw err;
console.log("saved | new num: " + temp);
})
})
});
server.listen(5000);
index.html
<html>
<head>
<title>A Button</title>
</head>
<body>
<button onclick='push();'>Click me!</button>
<p id="out"></p>
</body>
<script type="text/javascript" src="/socket.io/socket.io.js"></script>
<script type="text/javascript">
var variableFromFrontEnd = 2;
var socket = io.connect("/");
socket.on("connect", function() {
socket.on("update", function(val) {
document.getElementById("out").innerHTML = val
});
});
socket.on("disconnect", function() {
setTimeout(()=>{socket.connect();}, 1000);
});
function push() {
if (socket.connected) {
socket.emit("push");
}
}
</script>
</html>
num.txt is a base-64 encoded number.
So, is there a way to be able to do this without significant rate limiting or authentication? Or am I just going to have to use rate limiting?

There's a lot of different ways for users to cheat, and just as many ways to prevent them. The general rule is that you can only "make things harder" (if you're lucky, hard enough that the potential cheater loses interest before succeeding).
For a browser-based game, I would make sure that you are at least ensuring your game gets totally minified/tersed (so your javascript code is as unreadable as possible, and it's more difficult to just call a "click" function directly), and build in checksums in your messages (so the user can't just make socket calls directly to the server).
Once you've done that, you still have to deal with users who generate click events on the element directly with code or a plugin, or users who use a program outside the browser to generate click events above the button repeatedly. Your best defense against this is not to prevent it, but instead to detect it -- probably on the server side, by watching for users that have a sustained click rate that is not humanly possible, and then blowing up their game / temporarily banning their IP / etc.
See a related question Ways to prevent or reduce cheating for more related ideas (this question is about general client-server games, not browser games, but some of the discussion is still useful).

Related

How do I determine when a browser completely disconnects from my site using socket.io?

Because people can open many tabs and use many browsers, I have some troubles of determining when user close all tabs of each browser.
If all tabs are closed, the user is no longer connected so I assume you want to know on the server if he is completely disconnected?
You should hold a list of sockets against a user identifier (login name or similar) on the server, when a new tab is opened it will have a new socket connection so add it to the list.
When a socket connection is closed, remove it from the socket collection for that user.
When the user's last socket connection is closed, you know that the user has completely disconnected.
EDIT with example
something like this (untested and hastily written!)
'use strict';
var userConnections = [];
io.on('connection', function (socket) {
var username = socket.request.user.username;
var existingUser = userConnections.find(function(userConnection){
return userConnection.username === username;
})
if (!existingUser){
existingUser = {
username: username,
sockets: []
}
}
existingUser.sockets.push(socket);
socket.on('disconnect', function () {
var socketIndex = existingUser.indexOf(socket);
existingUser.sockets.splice(socketIndex, 1);
if (existingUser.sockets.length === 0){
//user has completely disconnected
}
});
});
EDIT - after clarification (see comments)
OP has indicated he wishes to know when all connections for a particular browser instance have disconnected.
Since you cannot access any system information about the browser process from javascript I don't see any way of achieving this.
It is possible to detect the browser type (Chrome/IE/Edge etc) on the client and send this information on socket connection. You could then store your socket information referencing this information. However I don't think this is what the OP wants.
Here is my solution, it depends on #Banners's one.
"socket.cookies" stores the browser's cookies
Please let me now if I was missing something.
'use strict';
var userConnections = {};
io.on('connection', function (socket) {
var username = socket.request.user.username;
var visit_id = (socket.cookies.vid) ? socket.cookies.vid : random_unique_id();
//set cookie here
setCookie('vid', visit_id, expire);
if (!userConnections[visit_id])
userConnections[visit_id] = [];
userConnections[visit_id].push(socket.id);
socket.on('disconnect', function () {
var vid = socket.cookies.vid;
if (userConnections[vid]) {
var index = userConnections[vid].indexOf(socket.id);
if (index != -1)
userConnections[vid].splice(index, 1);
if (userConnections[vid].length === 0) {
delete userConnections[vid];
//All tabs have been closed
}
}
});
});

Is it possible to have a one way socket.io connection?

My application only needs socket.io to send data from the server to the client. To prevent Denial Of Service attacks, I want to disconnect the client if i tries to emit data. Is this possible?
I've looked at some stackoverflow questions:
force client disconnect from server with socket.io and nodejs
How to protect against distributed denial-of-service attacks in Node.js with Socket.io?
But I've not been able to find a working solution.
There is a option to cache all events (from here Socket.io Client: respond to all events with one handler?).
Then on any event you will just disconnect client on server side.
var socket = io.connect();
var globalEvent = "*";
socket.$emit = function (name) {
if(!this.$events) return false;
for(var i=0;i<2;++i){
if(i==0 && name==globalEvent) continue;
var args = Array.prototype.slice.call(arguments, 1-i);
var handler = this.$events[i==0?name:globalEvent];
if(!handler) handler = [];
if ('function' == typeof handler) handler.apply(this, args);
else if (io.util.isArray(handler)) {
var listeners = handler.slice();
for (var i=0, l=listeners.length; i<l; i++)
listeners[i].apply(this, args);
} else return false;
}
return true;
};
socket.on(globalEvent,function(event){
//Force disconnect
socket.disconnect();
});
This probably isn't very helpful, but the best I've heard of doing is Comet streams. It's an older method, and a lot of people don't like it (myself included), but it's an option for one-way server to client updates.
Essentially, on the client side you have an iframe that connects to the server, and the server sends back a response in the form of a multipart response, occasionally sending back script tags with bits of stuff to execute. So, a trivial (and probably broken) example would be this:
<!--index.html-->
<html>
<body>
<iframe src="/comet/status"></iframe>
</body>
</html>
And then the server code...
// server code (I like Node.JS)
app.get('/comet/status', function (req, res) {
// A function that does a lot of work,
// and occasionally calls a callback with progress
doWork(function (progress) {
res.write('<script>console.log("Progress: " + progress);</script>');
});
res.end();
});
Like I said, this is a pretty incomplete example, but it's a way to accomplish what you're looking for, even if in an older way. Instead of console logging, you'd probably update an element that displays progress.

Advice on implementing "presence" for a web site?

Ideally, I'd like to find simple, lightweight code that allows all the web clients connected to my site to maintain real-time status of who else is currently online.
I know ejabberd does this, but it also does a lot of other things, and I'd prefer a small code footprint so I can customize and understand its performance characteristics.
I like the non-blocking aspect of node.js, and was wondering if there's an open source project that does all this logic.
I'd also like to see a JavaScript implementation of maintaining this model on the client side.
For real time status, use socket.io. Every time someone connects add them to the connected users list. For accurate stats you will need to keep track of user sessions. See http://www.danielbaulig.de/socket-ioexpress/
var onlineUsers = {};
var online = 0;
io.sockets.on('connection', function (socket) {
onlineUsers[socket.handshake.sessionID] = socket.handshake.session;
online = Object.keys(onlineUsers).length;
socket.broadcast.emit('online', online);
socket.on('disconnect', function () {
delete onlineUsers[socket.handshake.sessionID];
online--;
socket.broadcast.emit('online', online);
});
});
For anyone reading this in the future. I started with the answer written by fent, but I needed some modifications since things have changed in the intervening 6 years since his answer was published.
I used a Map rather than an Object for storing the session information. this means that the sessionID is no longer required.
const onlineUsers = new Map();
io.on('connection', function (socket) {
console.log('connection initiated');
socket.on('userInfo', userInfo => {
console.log('userInfo', userInfo);
onlineUsers.set(socket, userInfo);
io.emit('onlineUsers', Array.from(onlineUsers.values()));
});
socket.on('disconnect', function () {
console.log(onlineUsers.get(socket));
onlineUsers.delete(socket);
io.emit('onlineUsers', Array.from(onlineUsers.values()));
});
});

Sending message to specific client with socket.io and empty message queue

I´m going crazy with socket.io!
Documentation is so bad it's simply not true.
I want to send a feedback to specific client over socket.io
My server side looks like this:
app.get('/upload', requiresLogin, function(request, response) {
response.render('upload/index.jade');
io.sockets.on('connection', function (socket) {
console.log('SOCKET ID ' + socket.id);
io.sockets.socket(socket.id).emit('new', 'hello');
});
});
and the client side looks like this:
$(document).ready(function() {
var socket = io.connect('http://localhost:80/socket.io/socket.io.js');
socket.on('new', function (data) {
console.log(socket.id);
console.log(data);
//$('#state').html(data.status);
});
});
but the client does simply nothing. I have tried nearly everything. Can someone tell me what I am doing wrong, please! :(
to send a message to a specific client save every one that connects to the server in an Object.
var socketio = require('socket.io');
var clients = {};
var io = socketio.listen(app);
io.sockets.on('connection', function (socket) {
clients[socket.id] = socket;
});
then you can later do something like this:
var socket = clients[sId];
socket.emit('show', {});
A couple of ways to send feedback to a specific client over socket.io include:
As stated by pkyeck, save all clients to an Object, so you can send to these specific clients later in your route handlers, e.g.:
var sessionsConnections = {};
sio.on('connection', function (socket) {
// do all the session stuff
sessionsConnections[socket.handshake.sessionID] = socket;
});
or, use socket.io's built in support for rooms - subscribe each client to a room on connection and send via this room within route handlers, e.g.:
sio.on('connection', function (socket) {
// do all the session stuff
socket.join(socket.handshake.sessionID);
// socket.io will leave the room upon disconnect
});
app.get('/', function (req, res) {
sio.sockets.in(req.sessionID).send('Man, good to see you back!');
});
Acknowledgement:
http://www.danielbaulig.de/socket-ioexpress/#comment-1158
Note that both these example solutions assume that socket.io and express have been configured to use the same session store and hence refer to the same session objects. See the links above and below for more details on achieving this:
https://github.com/LearnBoost/socket.io/wiki/Authorizing
2 things
1) You should really place your io.sockets.on(..) outside your app/update route to prevent adding multiple listeners for clients.
2) io.sockets.socket(id); should not be used, it should have been socket.emit('new', 'hello')
In socket.io 1.0, this is how it would work. It may work for lower versions, but I cannot guarantee it.
socket.to(socket_id_here).emit('new', 'hello');
This works because socket.io automatically adds a socket to a room with the socket's id on connection.
Also, if you plan to upgrade to version 1.0, there are a lot of changes to the api, so you'll sometimes have to change some code to make it work in 1.0.
The correct way to do this in Socket.io 1.0+ is:
io.to(users_socket_id).emit('new', 'hello');
You can also substitute a room name for 'users_socket_id' to emit to a specific socket.io room.
First of all, you cannot use socket.id on client side.
And then change the line
var socket = io.connect('http://localhost:80/socket.io/socket.io.js');
to
var socket = io.connect('http://localhost:80/');
I believe io.sockets.socket has been removed and has been a problem in Socket.IO (https://github.com/socketio/socket.io/issues/1618).
You can use io.sockets.connected[socket.id] and store the socket.id to reference with the user via username:
var usernames = {};
usernames[username] = socket.id;
// Sending the message
io.sockets.connected[usernames[username]].emit(...);
I don't see it anywhere on the documentation so I can see how this hasn't been answered. Also, if you don't have any reference via usernames, you can instead try:
users[socket.id] = socket.id;
to duplicate the key and value for easy access.
There is also possibly another way by using io.clients as described below, but I haven't used that method.
OTHER SOLUTION: Send message to specific client with socket.io and node.js
Have you tried using?
var socket = io.connect('http://localhost:80/');
i tired with the latest version of node and socket.io
below i am going to post complete code
<ul id="messages"></ul>
<form action="">
<input id="id1" /><button>Send</button>
</form>
<script src="https://cdn.socket.io/socket.io-1.2.0.js"></script>
<script src="http://code.jquery.com/jquery-1.11.1.js"></script>
<script>
var io = io.connect();
$('form').submit(function(){
io.emit('drumevent', $('#id1').val());
$('#id1').val('');
return false;
});
io.on('drumevent', function(msg){
console.log(msg);
$('#messages').append($('<li></li>').text(msg.message+' quantity = '+msg.quantity));
});
</script>
server side code
var usernames = {};io.on('connection', function(socket){usernames["username"] = socket.id;
socket.on('drumevent', function(msg){
var socketId = socket.id;io.to(socketId).emit('drumevent', data);

Node JS Express JS Client / Server game

I'm writing a two player card game, (say it's regular poker for simplicity) in Node.js and Express js. There's a couple of things I'm having trouble with. First, how do I make sure that there are only 2 players that can access an instance of the game, and is it possible to have them reconnect if they lose the connection? Second, how do send a message from the server to the client? I can send it within the "socket.on" listener call, but within the normal scope of the program I can't get it to work.
var socket = io.listen(app);
socket.on('connection', function(client){
player++;
if(player <= 2) {
var messageout = "player " + player + " connected";
client.broadcast(messageout);
client.on('message', function(data){console.log(data); })
client.on('disconnect', function(){console.log('disconnected');})
}
else {
socket.end;
}
});
I'm having trouble conceptually what's going on here and how to approach the problem. For example, do I do it all with sockets? Or do I return a web page with the updated state of the game (cards, bets, etc.) every turn?
First, how do I make sure that there
are only 2 players that can access an
instance of the game?
Create an array of instance objects.
When a new player joins either create a new instance and set them as player1, or add them to an existing instance as player two.
var instances = [];
function Instance () {
return {
name = 'game' + instances.length + 1,
gameVariables = defaults,
player1 = null,
player2 = null,
player1UUID = UUID(),
player2UUID = UUID()
}
}
Is it possible to have them reconnect
if they lose the connection?
If you send each player a UUID when they initially connect you can have them use it to authenticate when they reconnect.
How do I send a message from the
server to the client?
client.send({ gameState: gameState() });
of if you've saved the client into an object: instances[ 'game1' ].player1.send( data );
I do it all with sockets?
I would deal with all dynamic interactions with web sockets.
Do I return a web page with the updated state of the game (cards, bets, etc.) every turn?
I wouldn't send html over web sockets. Instead send json and use a client side template to render it.
Try now.
// server
var nowjs = require("now")
var everyone = nowjs.initialize(app);
everyone.connected(function() {
if (users.length < 2) {
users.push(this);
}
});
everyone.now.sendMessage = function(message, cb) {
gameLogic(message);
users.forEach(function(user) {
user.update(gameLogic.getState());
});
};
//client
$("#action").click(function() {
now.sendMessage($(this).data("action"));
});
now.update = function(data) {
// update UI
};

Resources