Ratchet PHP Websockets: Private messaging (control who messages are being sent to) - javascript

Got another question.
I am starting to get used to how the websocket works.
I have even managed to implement communication cross domain.
But now I have yet to hit another milestone.
Here is a snippet from my current implementation
public function onMessage(ConnectionInterface $conn, $msg)
{
$msgjson = json_decode($msg);
$tag = $msgjson->tag;
global $users;
if($tag == "[msgsend]")
{
foreach($this->clients as $client)
{
$client->send($msg);
}
}
else if($tag == "[bye]")
{
foreach($this->clients as $client)
{
$client->send($msg);
}
foreach($users as $key => $user)
{
if($user->name == $msgjson->uname)
{
unset($users[$key]);
}
}
$this->clients->detach($conn);
}
else if($tag == "[connected]")
{
//store client information
$temp = new Users();
$temp->name = $msgjson->uname;
$temp->connection = $conn;
$temp->timestamp = new \DateTime();
$users[] = $temp;
usort($users, array($this, "cmp"));
//send out messages
foreach($this->clients as $client)
{
$client->send($msg);
}
}
else if($tag == "[imalive]")
{
//update user timestamp who sent [imalive]
global $users;
foreach($users as $user)
{
if($msgjson->uname == $user->name)
{
$user->timestamp = new \DateTime();
}
}
}
}
Now my question is. As we can see, in the onMessage() function, and tutorials that I have done, I know how to read and parse JSON data, make sense of messages, tell who the message is coming from ($conn).....
But say when I send a message in a JSON packet, I want to include the nicknames of who the message is from as well as who the message is going to.
This will allow me to implement private instant messaging in both my social network that i am building and in chatrooms.
Instead of a for loop sending messages to all connected clients, I only want to send messages to specific ones. I know clients have a property ($this->$client->resourceID or something along those lines) but not sure how to incorporate it as a solution either. I also want users to maintain a connection as they jump to different pages on the website and even after a refresh, still be able to continue messaging. I assume each refresh disconnects the client. So I have to have a way where the server can tell each time who is who and where messages are coming from and where they are going to.
But yeah, private messaging. I dont want to have to send unnessecary messages to everyone or unintended targets. How can I accomplish this?
I hope my question makes sense. thanks.

Being able to uniquely identify users that connect to your WebSocket server and then being able to target those users, specifically, when sending out messages needs to begin at the onOpen callback where the connection is actually negotiated with the server.
In your onOpen method you should have some way of uniquely identifying the user on your system via some user id that's globally stored in your database, or persistence store. Since the connection is negotiated over HTTP you have access to the HTTP request via the $conn->WebSocket->request, which is a GuzzleHttp object containing the client's HTTP request information. You could use this to pull a cookie, for example, that contains some user id data or token that you can compare to your database to figure out who the user is and then store that as property of the $client object.
For now, let's pretend you're writing a normal PHP script where you do user authentication over HTTP and store the user id in a session. This session sets a cookie on the client's machine (by default the cookie name is the session name, which is usually PHPSESSID unless you change it) containing a session id. This session id can be used in your WebSocket server to accessed the session storage the same way you do normally in PHP.
Here's a simple example, where we expect a cookie named PHPSESSID from the request to capture the session id from cookie.
public function onOpen(ConnectionInterface $conn) {
// extract the cookie header from the HTTP request as a string
$cookies = (string) $conn->WebSocket->request->getHeader('Cookie');
// look at each cookie to find the one you expect
$cookies = array_map('trim', explode(';', $cookies));
$sessionId = null;
foreach($cookies as $cookie) {
// If the string is empty keep going
if (!strlen($cookie)) {
continue;
}
// Otherwise, let's get the cookie name and value
list($cookieName, $cookieValue) = explode('=', $cookie, 2) + [null, null];
// If either are empty, something went wrong, we'll fail silently here
if (!strlen($cookieName) || !strlen($cookieValue)) {
continue;
}
// If it's not the cookie we're looking for keep going
if ($cookieName !== "PHPSESSID") {
continue;
}
// If we've gotten this far we have the session id
$sessionId = urldecode($cookieValue);
break;
}
// If we got here and $sessionId is still null, then the user isn't logged in
if (!$sessionId) {
return $conn->close(); // close the connection - no session!
}
}
Now that you actually have the $sessionId you can use it to access the session storage for that session, pull the session information into your WebSocket server and store it as a property of the client connection object $conn.
So continuing from the above example let's add this code to the onOpen method.
public function onOpen(ConnectionInterface $conn) {
$conn->session = $this->methodToGetSessionData($sessionId);
// now you have access to things in the session
$this->clinets[] = $conn;
}
Now, let's go back to your example where you want to send a message specifically to just one user. Let's assume we have the following properties stored in the session, which are now accessible from the client connection object...
$conn->session->userName = 'Bob';
$conn->session->userId = 1;
So let's say Bob wants to send a message to Jane. A request comes in to your WS server with something like {"from":1,"to":2,tag:"[msgsend]"} where the to and from properties of that JSON are basically the user ids of the user the message is from and the user the message is intended to be sent to, respectively. Let's assume Jane is userId = 2 for this example.
public function onMessage(ConnectionInterface $conn, $msg) {
$msgjson = json_decode($msg);
$tag = $msgjson->tag;
if ($tag == '[msgsend]') {
foreach($this->clients as $client) {
// only send to the designated recipient user id
if ($msgjson->to == $client->session->userId) {
$client->send($msg);
}
}
}
}
Obviously you might want to have more elaborate validation in there, but you should be able to expand on this from here.

Related

Creating a user session - NODE js

I am new to node js & javascript in general. I have the below piece of code that will handle a login. I have a MYSQL database with a customer table. When the customer enters their username and password, it checks does it exist in the database. This part is working.
I now want to enhance this feature so that it will take the username and create some sort of a session variable, which can be used across the application. I am new to JS so I am not yet sure which inbuilt facilities already exist, or best practice around sessions.
I want to be able to use this session variable across the application, and for subsequent logout facility.
Can someone advise me on this, or point me in the right direction? Thanks.
case "/login":
var body = '';
console.log("user Login ");
request.on('data', function (data) {
body += data;
});
request.on('end', function () {
var obj = JSON.parse(body);
console.log(JSON.stringify(obj, null, 2));
var query = "SELECT * FROM Customer where name='"+obj.name+"'";
response.writeHead(200, {
'Access-Control-Allow-Origin': '*'
});
db.query(
query,
[],
function(err, rows) {
if (err) {
response.end('{"error": "1"}');
throw err;
}
if (rows!=null && rows.length>0) {
console.log(" user in database" );
theuserid = rows[0].customerID;
var obj = {
id: theuserid
}
response.end(JSON.stringify(obj));
}
else{
response.end('{"error": "1"}');
console.log(" user not in database");
}
}
);
});
}
There can be multiple ways of implementing a user session.
One, you could use a browser cookie, it comes with many pros and cons and you should read about it a bit to see how its managed. This would also depend on the server you are using (express, hapi, etc).
Two, you can set a JWT token on the backend, and include it in the header of the response, then you can either use your application state or the local storage of the browser to save that token on the UI. Any such follow up requests requiring authentication should contain this auth token as a header for verification.
For more clarity, you can look into related libraries (such as passport), which make this task a lot easier.
PS: If you choose cookies, please make sure the business is going to allow it or not as the end-users do not like being tracked always. :)

Cordova fingerprint authentication on server

I am trying to create a authentication mechanism in my (cordova) app for android that will allow my users to sign in using a password and username, or allow them to scan their finger in order to sign in.
How can one verify a fingerprint registered on a client, server side? is this even possible at all using Cordova ? I tried transmitting the result of a finger scan to my server: this looked like:
FingerprintAuth.isAvailable(function(result) {
if (result.isAvailable) {
if(result.hasEnrolledFingerprints){
FingerprintAuth.show({
clientId: client_id,
clientSecret: client_secret
}, function (result) {
alert(JSON.stringify(result));
$http.post('http://192.168.149.33:3000/authorize', result).then(
function(response) {}
);
if (result.withFingerprint) {
$scope.$parent.loggedIn = true;
alert("Successfully authenticated using a fingerprint");
$location.path( "/home" );
} else if (result.withPassword) {
alert("Authenticated with backup password");
}
}, function(error) {
console.log(error); // "Fingerprint authentication not available"
});
} else {
alert("Fingerprint auth available, but no fingerprint registered on the device");
}
}
}, function(message) {
alert("Cannot detect fingerprint device : "+ message);
});
Server side i am receiving the following data (3 seperate scans):
{ withFingerprint: 't8haYq36fmBPUEPbVjiWOaBLjMPBeUNP/BTOkoVtZ2ZiX20eBVzZAs3dn6PW/R4E\n' }
{ withFingerprint: 'rA9H+MIoQR3au9pqgLAi/EOCRA9b0Wx1AvzC/taGIUc8cCeDfzfiDZkxNy5U4joB\n' }
{ withFingerprint: 'MMyJm46O8MTxsa9aofKUS9fZW3OZVG7ojD+XspO71LWVy4TZh2FtvPtfjJFnj7Sy\n' }
The patterns seems to vary every time, is there a way one can link the finger print to for example a pattern saved under a user on a database ?
Short answer
The strings returned by this API are not "fingerprint patterns". So you won't be able to authenticate the way you're thinking...
Long answer
Let's start by looking at the source code of the API it looks like you're using.
Looking at this file we see these methods:
public static void onAuthenticated(boolean withFingerprint) {
JSONObject resultJson = new JSONObject();
String errorMessage = "";
boolean createdResultJson = false;
try {
if (withFingerprint) {
// If the user has authenticated with fingerprint, verify that using cryptography and
// then return the encrypted token
byte[] encrypted = tryEncrypt();
resultJson.put("withFingerprint", Base64.encodeToString(encrypted, 0 /* flags */));
} else {
// Authentication happened with backup password.
resultJson.put("withPassword", true);
// if failed to init cipher because of InvalidKeyException, create new key
if (!initCipher()) {
createKey();
}
}
createdResultJson = true;
// ...
/**
* Tries to encrypt some data with the generated key in {#link #createKey} which is
* only works if the user has just authenticated via fingerprint.
*/
private static byte[] tryEncrypt() throws BadPaddingException, IllegalBlockSizeException {
return mCipher.doFinal(mClientSecret.getBytes());
}
Look at what's being put to "withFingerprint". It's a Base64 encoding of the encrypted client secret. Technically, this is your authentication. You would use this token to authenticate requests and your server would decrypt and validate the client secret.
Summary
Fingerprinting adds a level of security, but it is not the only means of security. A relationship needs to be established with the device and server beforehand.
I found this diagram to be helpful in understanding the intent of android's fingerprint authentication (ref: http://android-developers.blogspot.com/2015/10/new-in-android-samples-authenticating.html)
You can't authenticate fingerprint on the server, fingerprints are stored or authenticated using Live Scan/Biometric template. Authentication is done by comparing the current scan template with previously stored templates
First of all you don't have access to these stored templates(Not provided by the OS providers/Phone Manufacturers) and If we assume that you have access to those templates, then an efficient algorithm (Image based /Pattern based ) is required to compare the current template with previously stored templates. You can't simply authenticate it by string comparison.
Use cordova-plugin-fingerprint-aio for fingerprint authentication .
For further info you can consult https://www.npmjs.com/package/cordova-plugin-fingerprint-aio .

strophe.js, I use attach() to restore a Bosh session but get 'Status' ATTACHED to CONNFAIL and to DISCONNECTED

I use strophe.js to build a simple IM(web).
I have 2 pages:index.html(for login) and myChat.html(main chat view).
And when I login to openfire server by jid and password in index.html
connection = new Strophe.Connection(BOSH_SERVICE);
connection.connect($("#inputName").val()+"#openfireserver", $("#inputPassword").val(), onConnect);
Then I save jid,sid,rid in COOKIE in callback 'onConnect', and go to myChat.html
if (status == Strophe.Status.CONNECTED) {
console.log("success");
connected = true;
$.cookie('jid', connection.jid);
$.cookie('sid', connection._proto.sid);
$.cookie('rid', connection._proto.rid);
location.href='myChat.html';
}
In myChat.html, I use connection.attach() to restore the BOSH session.
var jid=$.cookie('jid');
var sid=$.cookie('sid');
var rid=$.cookie('rid');
connection = new Strophe.Connection(BOSH_SERVICE);
connection.attach(jid,sid,parseInt(rid,10)+1,onConnectAttach);
At first, in callback 'onConnectAttach', the status==Strophe.Status.ATTACHED,
but wait 1-3 mins, status will turn into Strophe.Status.CONNFAIL and Strophe.Status.CONNECTED!
If I use connection.connect() to re-connect the server, it will keep session all the time.
So I can not understand where I make the mistake? Why the status will change after I use attach()?
Got it.
connection.attach(jid,sid,parseInt(rid,10)+1,onConnectAttach);
It's wrong, because I look through some questions about "attach" function and I find someone say that 'rid' must be added one.
But, by my test, I don't need to do that.just:
connection.attach(jid,sid,rid,onConnectAttach);

secret key visible in javascript code (JavaScript, Firebase)?

I have a function that checks wether the password and user match before authenticating using a custom token.
function getUser(user, password) {
var usersRef = new Firebase("mydatabase/users");
var userRef = usersRef.child(user);
userRef.once("value",
function getHandler(snapshot) {
if (snapshot.val().password == password) {
var token = createToken(user);
ref.authWithCustomToken(token, authHandler);
} else {
alert("Gebruikersnaam en code komen niet overeen");
}
},
function errorHandler(errorObject) {
alert("Ophalen van gebruikersgegevens is mislukt: " + errorObject.code);
}
);
}
To create e token I instantiate the firebase class FirebaseTokenGenerator with the secret key. Like
function createToken(user) {
var tokenGenerator = new FirebaseTokenGenerator("<secret key...>");
var updatedObj = {
"uid": "custom:"+user,
"level": "docent"
}
return tokenGenerator.createToken(updatedObj);
However in that way the secret key would be visible for anyone looking into the .js source code. I'm pretty sure that is not how it should be done but what is the right way to do this then, the Firebase-way?
EDIT:
I tried to figure out the javascript way for this but got stuck there so switched back to php. Used the firebase-token-generator code from github (here), installed it including dependencies in my project with composer and that all seems to work fine (token is generated).
<?php
include_once "FirebaseToken.php";
$uid = $_POST['uid'];
$level = $_POST['level'];
$tokenGen = new Services_FirebaseTokenGenerator("<secret key>");
$token = $tokenGen->createToken(array("uid" => "custom:BAAJ"), array("admin" => False));
echo $token;
?>
Reading this SO post I found that following would be the way to embed it in my javascript code:
function createToken(user) {
$.post('php/createtoken.php', {uid: user, level: 'docent'}, function(data){
//successful ajax request
return data;
}).error(function(error){
alert("Create token mislukt: "+error);
});
};
But for som reason the token is not generated then. When called from getUser at first both the success and error part of the javascript createToken function are not executed at all (resulting in an undefined value for variable token. Then createToken is called a second time (??) and then the success part is executed however the data now does not contain the token but the complete php script...?
What is the problem and how to solve?
You should avoid to do anything 'secret' in the client browser. There, the secret will be visible, password will be hackable via XSS and sniffable and you could connect to any user with some XSS.
You should rather move this part onto the server side to be more secure, it could be NodeJS, PHP or anything else. (HTTPS connections, obviously)
You have to do this in your server side application and send the generated token to your client app. The token should be sent over HTTPS as well. Default token expiry is 24 hours. This can be changed.

JavaScript code substitution on client side

I'm now learning some new technologies (such as node.js, socket.io, redis etc.) and making some simple test applications to see how it can work.
My question is about security on a client-side javascript code: for example, i have a chat-server on node.js+express and when a user connects to this chat, server should assign his registred username (authorisation through oldschool php+mysql is used) to his socket. The question is, can user modify his client-side script and connect to chat under different users' names?
Some code is given below:
(server-side part of assigning username, which is just getting the username from client-side call)
// when the client emits 'adduser', this listens and executes
socket.on('adduser', function(username){
// store the username in the socket session for this client
socket.username = username;
// store the room name in the socket session for this client
socket.room = 'General';
// add the client's username to the global list
usernames[username] = username;
// send client to room 1
socket.join('General');
// echo to client they've connected
socket.emit('updatechat', 'SERVER', 'you have connected to General');
// echo to room 1 that a person has connected to their room
socket.broadcast.to('General').emit('updatechat', 'SERVER', username + ' has connected to this room');
socket.emit('updaterooms', rooms, 'General');
});
(client-side part of sending username to server, it looks like 'var username = "User";' for a particular user)
Yii::$app->view->registerJs('var username = "'.$user->identity->username.'";', yii\web\View::POS_HEAD);
(connect function)
chat.on('connect', function(){
// call the server-side function 'adduser' and send one parameter (value of prompt)
chat.emit('adduser', username);
});
SO the question is: can user change (for example, through chrome development tools) his username in line 'var username ...' and connect to chat under the different name?
P.S. this particular situation is just an example, obviously, changed nicknames in chat are not more than a simple joke, but similar situations can appear in other projects...
Supposing your variables are protected in closures and that it's not trivial to change them by typing username='root' in the console, a user could simply replace the whole code.
Everything that happens client side is totally out of your control.
The good news is that they are solutions not involving a duplicate authentication. Supposing you already authenticate the user in your express application, you can get the session and the user from that.
See how I do it in my chat server :
var sessionSockets = new SessionSockets(io, sessionStore, cookieParser);
sessionSockets.on('connection', function (err, socket, session) {
function die(err){
console.log('ERR', err);
socket.emit('error', err.toString());
socket.disconnect();
}
if (! (session && session.passport && session.passport.user && session.room)) return die ('invalid session');
var userId = session.passport.user;
if (!userId) return die('no authenticated user in session');
... handling socket for authenticated user
});
Basically, it uses the session.socket.io module to propagate the session from the standard http requests (authenticated using passport) to the socket.io connection. And everything that isn't supposed to be provided by the user is taken from the session/db/server.

Categories