ClankBundle - WebSocket in Symfony - php

I try to use webscoket in my symfony project. I found this bundle, but i can't setup it.
https://github.com/JDare/ClankBundle
My ChatTopic.php
<?php
namespace My\ChatBundle\Topic;
use JDare\ClankBundle\Topic\TopicInterface;
use Ratchet\ConnectionInterface as Conn;
class ChatTopic implements TopicInterface
{
/**
* This will receive any Subscription requests for this topic.
*
* #param \Ratchet\ConnectionInterface $conn
* #param $topic
* #return void
*/
public function onSubscribe(Conn $conn, $topic)
{
//this will broadcast the message to ALL subscribers of this topic.
$topic->broadcast($conn->resourceId . " has joined " . $topic->getId());
}
/**
* This will receive any UnSubscription requests for this topic.
*
* #param \Ratchet\ConnectionInterface $conn
* #param $topic
* #return void
*/
public function onUnSubscribe(Conn $conn, $topic)
{
//this will broadcast the message to ALL subscribers of this topic.
$topic->broadcast($conn->resourceId . " has left " . $topic->getId());
}
/**
* This will receive any Publish requests for this topic.
*
* #param \Ratchet\ConnectionInterface $conn
* #param $topic
* #param $event
* #param array $exclude
* #param array $eligible
* #return mixed|void
*/
public function onPublish(Conn $conn, $topic, $event, array $exclude, array $eligible)
{
/*
$topic->getId() will contain the FULL requested uri, so you can proceed based on that
e.g.
if ($topic->getId() == "acme/channel/shout")
//shout something to all subs.
*/
$topic->broadcast(array(
"sender" => $conn->resourceId,
"topic" => $topic->getId(),
"event" => $event
));
}
}
Now my services
my_chat.chat_topic_handle:
class: My\ChatBundle\Topic\ChatTopic
config
# Clank Configuration
clank:
web_socket_server:
port: 8080 #The port the socket server will listen on
host: 127.0.0.1 #(optional) The host ip to bind to
topic:
-
name: "chat"
service: "my_chat.chat_topic_handle"
This is my js code:
var myClank = Clank.connect("ws://localhost:8080");
myClank.on("socket/connect", function(session){
session.publish("chat/channel", {msg: "This is a message!"});
//the callback function in "subscribe" is called everytime an event is published in that channel.
session.subscribe("chat/channel", function(uri, payload){
console.log("Received message", payload.msg);
});
session.unsubscribe("chat/channel");
session.publish("chat/channel", {msg: "I won't see this"});
})
myClank.on("socket/disconnect", function(error){
//error provides us with some insight into the disconnection: error.reason and error.code
console.log("Disconnected for " + error.reason + " with code " + error.code);
})
After refreshing page i have nothing from websocket in my console. Webscoket connects with server, but I think that my ChatTopic.php doesn't work, and I don't know why. Thanks for help.

I think the problem is in your js code.
You connect to socket, subscribe to "chat/channel" and immediately unsubscribe. This prevents you from receiving any message.
You should, in order:
subscribe
publish
never unsubscribe (or at least, I still did not find a reason to do so)
By the way, you do not have to "refresh the page", you should open two browser pages on the same url: when the second loads you should see a message in the other one too.

Related

Throttle Laravel Exception email notification per Exception type

Does anyone know of a way to throttle email notifications for a certain Exception in Laravel?
I made my app send me an email when there is a DB error by check for the QueryException. This is a rough example of what I did in Exception handler in Laravel:
class Handler{
/**
* Report or log an exception.
*
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
*
* #param \Exception $exception
* #return void
*/
public function report(Exception $e)
{
if($e instanceof QueryException){
if( App::environment(['production']) ){
Notification::route('mail', 'myemail#test.com')
->notify(new DbErrorNotification($e));
}
}
parent::report($e);
}
}
Short of tracking in DB, is there a way I could throttle DB errors by exception type so that I don't end up getting thousands of emails if there is a consistent DB error.
I looked at Swift Mailer's anti-flood and throttling plugins but those affect the system globally which I don't want to do.
Thank you in advance
There is a few way how you can achieve that. Right before dispatching a job you can add delay on it. Example:
use App\Http\Request;
use App\Jobs\SendEmail;
use App\Mail\VerifyEmail;
use Carbon\Carbon;
/**
* Store a newly created resource in storage.
*
* #param Request $request
* #return \Illuminate\Http\RedirectResponse
* #throws \Symfony\Component\HttpKernel\Exception\HttpException
*/
public function store(Request $request)
{
$baseDelay = json_encode(now());
$getDelay = json_encode(
cache('jobs.' . SendEmail::class, $baseDelay)
);
$setDelay = Carbon::parse(
$getDelay->date
)->addSeconds(10);
cache([
'jobs.' . SendEmail::class => json_encode($setDelay)
], 5);
SendEmail::dispatch($user, new VerifyEmail($user))
->delay($setDelayTime);
}
Or if you don't like the idea about a job you can also delay it via Mail. Example:
Mail::to($user)->later($setDelayTime);
And finally via Redis Rate Limiting. Example:
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Redis;
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
Redis::throttle('SendEmail')
->allow(1)
->every(10)
->then(function () {
Mail::to($this->user)->send($this->mail);
}, function () {
return $this->release(10);
});
}
Allowing one email to be sent every ten seconds. The string SendEmail passed to the throttle() method is a name that uniquely identifies the type of job being rate-limited. You can set this to whatever you want.
The release() method is an inherited member of your job class and
instructs Laravel to release the job back onto the queue, with an
optional delay in seconds, in the event a lock cannot be obtained.
When the job is dispatched to the queue, Redis is instructed to only
run one SendEmail job every ten seconds.
Keep in mind that for all of this you need a Redis
Source: https://medium.com/#bastones/a-simple-guide-to-queuing-mail-in-laravel-f4ff94cdaa59

laravel and react pusher setup

I am working on a web project.
I have laravel as my back-end reactjs as my front-end
I have users, posts comments in my DB.
I wanted to implement pusher so i can have this real time posts showing up once any user posted a new post.
I am very close to achieve this behavior.
on laravel
i have this event
class NewPostsCast implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $post;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct($post)
{
$this->post = $post;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new Channel('posts');
}
}
and once the post stored to my database I trigger this event
event(new NewPostsCast($output));
on react
let channel = pusher.subscribe("posts");
// i guess error is here because this function never called
// it's only called at startup
channel.bind("NewPostsCast", post => {
console.log("pusher::: ", post);
});
might be useful
from pusher logs
Pusher : No callbacks on posts for App\Events\NewPostsCast
however pusher returns this log also
Pusher : Event recd : {"event":"App\\Events\\NewPostsCast","channel":"posts","data":{"post":{"id":19,"title":"sd","body":"eksdl","tags":"[\"ds\",\"dsf\"]","user_id":1,"created_at":"2019-04-18 14:22:00","updated_at":"2019-04-18 14:22:00","votes":0,"user":{"id":1,"name":"user1","username":"user1","email":"user1#health.io","address":null,"state":null,"country":null,"gender":"female","phone":null,"avatar":"http://healthqo.api/public/profile_pics/default/female.png","created_at":"2019-04-09 08:30:12","updated_at":"2019-04-09 08:30:12"}}}}
in case anyone is still struggling with the same problem
from pusher logs it says (No Callbacks on posts for App\Events\NewPostsCast)
that quite solves the problem
since react and laravel are separated in my project (NewPostsCast) is not defined
so i had to change first parameter of the bind function
like so:
FROM
let channel = pusher.subscribe("posts");
channel.bind("NewPostsCast", post => {
console.log("pusher::: ", post);
});
TO
let channel = pusher.subscribe("posts");
channel.bind("App\\Events\\NewPostsCast", post => {
console.log("pusher::: ", post);
});
not sure why double double back slashes but it gives me a syntax error with single back slash

Thruway manage subscriptions

I try to set up a websocket server via Thruway which can manage multiple groups. Something like a chat application where each client may subscribe to one or multiple ones at the same time and broadcast messages to an entire chat room. I managed to do that with an ancient version of Ratchet but since it doesn't run very smooth, I wanted to switch to Thruway. Sadly I can't find anything to manage groups. So far I have the following as the websocket-manager and the clients are using the current version of Autobahn|js (18.x).
Does anyone have any clue if it is possible to manage subscription groups with something like the following?
<?php
require_once __DIR__.'/../vendor/autoload.php';
use Thruway\Peer\Router;
use Thruway\Transport\RatchetTransportProvider;
$router = new Router();
$router->addTransportProvider(new RatchetTransportProvider("0.0.0.0", 9090));
$router->start();
With ThruWay, things are a little different than old Ratchet. First of all Thruway is not a WAMP Server. It is just a router. So it doesn't have a server instance like old Rathcet has lets you wrap all your server side functionality wrapped up. But it will only get the message packet and will route them to other sessions in same realm depending on their subscriptions. If you ever used socket.io, realm idea is similar to different connections so you can limit your sessions or connections to a single namespace or split functionality of different socket instances like administration, visitors etc.
On client side with autobahn ( latest version ) once you subscribe to a topic, then publish in that topic, thruway will automatically detect topic subscribers and emit message to them in same realm. But in old ratchet you need to handle this manually by keeping an array of available channels, and add users to each channel when they subscribes as well as broadcast message to these users in topic by iterating over them. This was really painful.
If you want to use RPC calls in server side and don't want to include some of your stuff on client side, you can still use a class called internalClient on server side. Conceptually Internal Client is another session connects to your thruway client and handles some functions internally without exposing other clients. It receives message packages and does stuff in it then returns result back to requested client connection. It took a while for me to understand how it works but once I figured out the idea behind made more sense.
so little bit code to explain better,
In your router instance you will need to add a module, ( note that, in voxys/thruway package examples are little confusing about internal client )
server.php
require __DIR__ . "/../bootstrap.php";
require __DIR__ . '/InternalClient.php';
$port = 8080;
$output->writeln([
sprintf('Starting Sockets Service on Port [%s]', $port),
]);
$router = new Router();
$router->registerModule(new RatchetTransportProvider("127.0.0.1", $port)); // use 0.0.0.0 if you want to expose outside world
// common realm ( realm1 )
$router->registerModule(
new InternalClient() // instantiate the Socket class now
);
// administration realm (administration)
// $router->registerModule(new \AdminClient());
$router->start();
This will initialize Thruway router and will attach internalclient instance to it. Now in InternalClient.php file you will be able to access actual route as well as current connected clients. With the example they provided, router is not part of instance so you are stuck with only session id property of new connections.
InternalClient.php
<?php
use Thruway\Module\RouterModuleInterface;
use Thruway\Peer\Client;
use Thruway\Peer\Router;
use Thruway\Peer\RouterInterface;
use Thruway\Logging\Logger;
use React\EventLoop\LoopInterface;
class InternalClient extends Client implements RouterModuleInterface
{
protected $_router;
/**
* Contructor
*/
public function __construct()
{
parent::__construct("realm1");
}
/**
* #param RouterInterface $router
* #param LoopInterface $loop
*/
public function initModule(RouterInterface $router, LoopInterface $loop)
{
$this->_router = $router;
$this->setLoop($loop);
$this->_router->addInternalClient($this);
}
/**
* #param \Thruway\ClientSession $session
* #param \Thruway\Transport\TransportInterface $transport
*/
public function onSessionStart($session, $transport)
{
// TODO: now that the session has started, setup the stuff
echo "--------------- Hello from InternalClient ------------\n";
$session->register('com.example.getphpversion', [$this, 'getPhpVersion']);
$session->subscribe('wamp.metaevent.session.on_join', [$this, 'onSessionJoin']);
$session->subscribe('wamp.metaevent.session.on_leave', [$this, 'onSessionLeave']);
}
/**
* Handle on new session joined.
* This is where session is initially created and client is connected to socket server
*
* #param array $args
* #param array $kwArgs
* #param array $options
* #return void
*/
public function onSessionJoin($args, $kwArgs, $options) {
$sessionId = $args && $args[0];
$connectedClientSession = $this->_router->getSessionBySessionId($sessionId);
Logger::debug($this, 'Client '. $sessionId. ' connected');
}
/**
* Handle on session left.
*
* #param array $args
* #param array $kwArgs
* #param array $options
* #return void
*/
public function onSessionLeave($args, $kwArgs, $options) {
$sessionId = $args && $args[0];
Logger::debug($this, 'Client '. $sessionId. ' left');
// Below won't work because once this event is triggered, client session is already ended
// and cleared from router. If you need to access closed session, you may need to implement
// a cache service such as Redis to access data manually.
//$connectedClientSession = $this->_router->getSessionBySessionId($sessionId);
}
/**
* RPC Call messages
* These methods will run internally when it is called from another client.
*/
private function getPhpVersion() {
// You can emit or broadcast another message in this case
$this->emitMessage('com.example.commonTopic', 'phpVersion', array('msg'=> phpVersion()));
$this->broadcastMessage('com.example.anotherTopic', 'phpVersionRequested', array('msg'=> phpVersion()));
// and return result of your rpc call back to requester
return [phpversion()];
}
/**
* #return Router
*/
public function getRouter()
{
return $this->_router;
}
/**
* #param $topic
* #param $eventName
* #param $msg
* #param null $exclude
*/
protected function broadcastMessage($topic, $eventName, $msg)
{
$this->emitMessage($topic, $eventName, $msg, false);
}
/**
* #param $topic
* #param $eventName
* #param $msg
* #param null $exclude
*/
protected function emitMessage($topic, $eventName, $msg, $exclude = true)
{
$this->session->publish($topic, array($eventName), array('data' => $msg), array('exclude_me' => $exclude));
}
}
Few things to note in above example code,
- In order to receive a message in a topic, in client side you need to be subscribed to that topic.
- Internal client can publish/emit/broadcast any topic without any subscription in same realm.
- broadcast/emit functions are not part of original thruway, something I come up with to make publications little easier on my end. emit will will send message pack to everyone has subscribed to topic, except sender. Broadcast on the other hand won't exclude sender.
I hope this information would help a little to understand the concept.

Swagger-php with Swagger-ui not working with Basic Authentication

I use swagger-ui and swagger-json generated with swagger-php. I m not able to do a basic auth to use my endpoint. I m able to get the swagger json file from my application but not to use exposed endpoint. I don't see what i m misunderstanding. If someone could show me a full example with basic auth in swagger 2.0 ?
CORS is enabled and totally working. Swagger-ui is running on localhost:3000 with nodejs and my application is running php with nginx on localhost:80.
I use swagger-ui-dist 3.14.1 that is compatible with swagger 2.0 (swagger-php is 2.0)
3.14.1 | 2018-05-04 | 2.0, 3.0 | tag v3.14.1
I m using theses SWG comments in my controllers to use basicAuth, (server-side)
/**
* #SWG\SecurityScheme(
* securityDefinition="basicAuth",
* name="authorization",
* type="basic",
* scheme="http"
* )
*/
and this comments
/**
* #SWG\Get(
* description="Get all catalog",
* path="/ott/catalogs",
* produces={"application/json"},
* security = {"basicAuth"},
* #SWG\Response(response="200", description="Get all catalogs"),
* #SWG\Response(response="401",description="You are not authorized")
* )
* )
*/
Here is my client-side code:
window.onload = function() {
// Build a system
const ui = SwaggerUIBundle({
url: "http://ott/ott/tools/swagger",
host: 'ott',
basePath: 'ott/',
schemes: 'http',
enableCORS: true,
dom_id: '#swagger-ui',
deepLinking: true,
validatorUrl:null,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout",
requestInterceptor: (req) => {
if (req.loadSpec) {
let hash = btoa("*******" + ":" + "********");
req.headers.Authorization = "Basic " + hash;
}
return req
},
onComplete : () => {
ui.preauthorizeBasic("basicAuth","*******","*******")
}
});
window.ui = ui
When i click to the lock, i have the first error in my console and then when i try to get my catalogs i get a 401 - not authorized because the Basic Authentication header is not sent.
Annotations do not look right. Change #SWG\SecurityScheme to:
/**
* #SWG\SecurityScheme(
* securityDefinition="basicAuth",
* type="basic"
* )
*/
(without the name and scheme attributes), and change the security in #SWG\Get as follows:
/**
* #SWG\Get(
* ...
* security = {{"basicAuth": {}}},
* ...

Laravel, WebSockets - Verify user on server

I currently ran into the problem of handling the authentification of a user on the server, using Laravel and RachetPHP.
What I tried so far:
I changed the driver type of the session to database, giving me an id and payload column. Using \Session::getId() returns a 40 character string.
The cookie information, sent by the WebSocket-Connection does contain a XSRF-TOKEN and a laravel_session, both containing > 200 characters string. The database ID of the users session differs from the id, returned by \Session::getId().
I am already sending the current CSRF-token via the websocket message, but I have no clue how to verify it (the built-in verifier uses requests - which I don't have in the websocket server scope).
Generic Use case:
A User posts a comment in thread. The payload of the sent object would then be:
Something to verify the user (an ID or a token).
The comment itself
If you were to send the user ID, anyone could temper the packet and send the message under another ones user.
My use case:
A user can have n-characters. A character has an avatar, an id, a name, etc.
The user is only used to:
authenticate at the server.
access his characters, and thus perform basic CRUD operations on his characters.
I also have a table locations - a "virtual place", a character can be in... so I got a one-to-one relationship between character and location. The user (character) can then send messages in a location via websocket. The message is inserted at the database on the server. At this point, I need to know:
If the user is authenticated (csrf-token ?)
If the user is the owner of the character (it's very simple to spoof the request with another user's character id)
If you need more information, please let me know.
So this is how I solved this a while ago. In my example, I'm working with Socket.IO, but I'm pretty sure you can easily rewrite the Socket.IO part to get it to work with RachetPHP as well.
Socket Server
The socket server depends on the files cookie.js and array.js, and the node modules express, http, socket.io, request and dotenv. I'm not the original author of cookie.js, but there is no author mentioned in the comments, so I'm not able to give any credits for this, sorry.
This is the server.js file which starts the server. It is a simple socket server that tracks who is currently online. The interesting part however is when the server makes a POST request to socket/auth on the Laravel application:
var express = require('express');
var app = express();
var server = require('http').Server(app)
var io = require('socket.io')(server);
var request = require('request');
var co = require('./cookie.js');
var array = require('./array.js');
// This loads the Laravel .env file
require('dotenv').config({path: '../.env'});
server.listen(process.env.SOCKET_SERVER_PORT);
var activeSockets = {};
var disconnectTimeouts = {};
// When a client connects
io.on('connection', function(socket)
{
console.log('Client connected...');
// Read the laravel_session cookie.
var cookieManager = new co.cookie(socket.handshake.headers.cookie);
var sess = cookieManager.get("laravel_session"); // Rename "laravel_session" to whatever you called it
// This is where the socket asks the Laravel app to authenticate the user
request.post('http://' + process.env.SOCKET_SERVER_HOST + '/socket/auth?s=' + sess, function(error, response, body)
{
try {
// Parse the response from the server
body = JSON.parse(body);
}
catch(e)
{
console.log('Error while parsing JSON', e);
error = true;
}
if ( ! error && response.statusCode == 200 && body.authenticated)
{
// Assign users ID to the socket
socket.userId = body.user.id;
if ( ! array.contains(activeSockets, socket.userId))
{
// The client is now 'active'
activeSockets.push(socket.userId);
var message = body.user.firstname + ' is now online!';
console.log(message);
// Tell everyone that the user has joined
socket.broadcast.emit('userJoined', socket.userId);
}
else if (array.hasKey(disconnectTimeouts, 'user_' + socket.userId))
{
clearTimeout(disconnectTimeouts['user_' + socket.userId]);
delete disconnectTimeouts['user_id' + socket.userId];
}
socket.on('disconnect', function()
{
// The client is 'inactive' if he doesn't reastablish the connection within 10 seconds
// For a 'who is online' list, this timeout ensures that the client does not disappear and reappear on each page reload
disconnectTimeouts['user_' + socket.userId] = setTimeout(function()
{
delete disconnectTimeouts['user_' + socket.userId];
array.remove(activeSockets, socket.userId);
var message = body.user.firstname + ' is now offline.';
console.log(message);
socket.broadcast.emit('userLeft', socket.userId);
}, 10000);
});
}
});
});
I added some comments to the code, so it should be pretty self-explanatory. Please note that I added SOCKET_SERVER_HOST and SOCKET_SERVER_PORT to my Laravel .env-file in order to be able to change the host and port without editing the code and run the server on different environments.
SOCKET_SERVER_HOST = localhost
SOCKET_SERVER_PORT = 1337
Authenticating a user by a session cookie with Laravel
This is the SocketController which parses the cookie and responds whether the user could be authenticated or not (JSON response). Its the same mechanism that you described in your answer. It's not the best design to handle the cookie parsing in the controller, but it should be OK in this case, because the controller only handles that one thing and its functionality isn't used at another point in the application.
/app/Http/Controllers/SocketController.php
<?php namespace App\Http\Controllers;
use App\Http\Requests;
use App\Users\UserRepositoryInterface;
use Illuminate\Auth\Guard;
use Illuminate\Database\DatabaseManager;
use Illuminate\Encryption\Encrypter;
use Illuminate\Http\Request;
use Illuminate\Routing\ResponseFactory;
/**
* Class SocketController
* #package App\Http\Controllers
*/
class SocketController extends Controller {
/**
* #var Encrypter
*/
private $encrypter;
/**
* #var DatabaseManager
*/
private $database;
/**
* #var UserRepositoryInterface
*/
private $users;
/**
* Initialize a new SocketController instance.
*
* #param Encrypter $encrypter
* #param DatabaseManager $database
* #param UserRepositoryInterface $users
*/
public function __construct(Encrypter $encrypter, DatabaseManager $database, UserRepositoryInterface $users)
{
parent::__construct();
$this->middleware('internal');
$this->encrypter = $encrypter;
$this->database = $database;
$this->users = $users;
}
/**
* Authorize a user from node.js socket server.
*
* #param Request $request
* #param ResponseFactory $response
* #param Guard $auth
* #return \Symfony\Component\HttpFoundation\Response
*/
public function authenticate(Request $request, ResponseFactory $response, Guard $auth)
{
try
{
$payload = $this->getPayload($request->get('s'));
} catch (\Exception $e)
{
return $response->json([
'authenticated' => false,
'message' => $e->getMessage()
]);
}
$user = $this->users->find($payload->{$auth->getName()});
return $response->json([
'authenticated' => true,
'user' => $user->toArray()
]);
}
/**
* Get session payload from encrypted laravel session.
*
* #param $session
* #return object
* #throws \Exception
*/
private function getPayload($session)
{
$sessionId = $this->encrypter->decrypt($session);
$sessionEntry = $this->getSession($sessionId);
$payload = base64_decode($sessionEntry->payload);
return (object) unserialize($payload);
}
/**
* Fetches base64 encoded session string from the database.
*
* #param $sessionId
* #return mixed
* #throws \Exception
*/
private function getSession($sessionId)
{
$sessionEntry = $this->database->connection()
->table('sessions')->select('*')->whereId($sessionId)->first();
if (is_null($sessionEntry))
{
throw new \Exception('The session could not be found. [Session ID: ' . $sessionId . ']');
}
return $sessionEntry;
}
}
In the constructor you can see that I refer to the internal middleware. I added this middleware to only allow the socket server to make requests to socket/auth.
This is what the middleware looks like:
/app/Http/Middleware/InternalMiddleware.php
<?php namespace App\Http\Middleware;
use Closure;
use Illuminate\Routing\ResponseFactory;
class InternalMiddleware {
/**
* #var ResponseFactory
*/
private $response;
/**
* #param ResponseFactory $response
*/
public function __construct(ResponseFactory $response)
{
$this->response = $response;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (preg_match(env('INTERNAL_MIDDLEWARE_IP'), $request->ip()))
{
return $next($request);
}
return $this->response->make('Unauthorized', 401);
}
}
To get this middleware to work, register it in the Kernel and add the INTERNAL_MIDDLEWARE_IP property - that is just a regular expression defining which IP addresses are allowed - to your .env-file:
Local testing (any IP):
INTERNAL_MIDDLEWARE_IP = /^.*$/
Production env:
INTERNAL_MIDDLEWARE_IP = /^192\.168\.0\.1$/
I'm sorry I could not help you out with RachetPHP, but I think you get a good idea how this can be solved.
I think I found a solution. Although not very clean, it does what it's supposed to do (I guess...)
The WebSocket-Server gets started by an Artisan Command (by mmochetti#github). I inject these classes into the Command:
Illuminate\Contracts\Encryption\Encrypter
App\Contracts\CsrfTokenVerifier - a custom CsrfTokenVerifier, that simply compares 2 strings (going to put more of the follow logic code in there)
I pass these instances from the command to the server. On the onMessage method, I parse the message sent, containing:
The CSRF-Token of the user
The character-id of the user
I then check if the token is valid, and if the user is the owner of the character.
public function onMessage(ConnectionInterface $from, NetworkMessage $message) {
if (!$this->verifyCsrfToken($from, $message)) {
throw new TokenMismatchException;
}
if (!$this->verifyUser($from, $message)) {
throw new \Exception('test');
}
...
}
private function verifyUser(ConnectionInterface $conn, NetworkMessage $message) {
$cookies = $conn->WebSocket->request->getCookies();
$laravel_session = rawurldecode($cookies['laravel_session']);
$id = $this->encrypter->decrypt($laravel_session);
$session = Session::find($id);
$payload = unserialize(base64_decode($session->payload));
$user_id = $payload['user_id'];
$user = User::find($user_id);
$characters = $this->characterService->allFrom($user);
$character_id = $message->getHeader()['character_id'];
return $characters->contains($character_id);
}
private function verifyCsrfToken($from, NetworkMessage $message) {
$header = $this->getHeaderToken($from);
return $this->verifier->tokensMatch($header, $message->getId());
}
The code could be cleaner for sure, but as a quick hack, it works. I think, instead of using a model for the Session, I should use the Laravel DatabaseSessionHandler.
For Laravel > 5 i use this code:
$cookies = $conn->WebSocket->request->getCookies();
$laravel_session = rawurldecode($cookies['laravel_session']);
$id = $this->encrypter->decrypt($laravel_session);
if(Config::get('session.driver', 'file') == 'file')
{
$session = File::get(storage_path('framework/sessions/' . $id));
}
$session = array_values(unserialize($session));
return $session[4]; // todo: Hack, please think another solution
To get cookies from client through websocket you must change domain in session config and change everywhere websocket host to your domain:
'domain' => 'your.domain.com',

Categories