Websockets not working: Failed without error - laravel-8

I setup a websocket server in a laravel 8 project that is accessible from the internet. I then installed the laravel-websockets package 1.12 together with the pusherphpserver package 5.0.
I have created an account on the Pusher's API website and added the credentials correctly in the .env file. The server is running correctly:
# php artisan websockets:serve
Starting the WebSocket server on port 6001...
I have also forwarded the port 6001 correctly in the router.
However, when clicking on connect on the websockets webpage on my server I receive this:
pusher.min.js:8 WebSocket connection to 'wss://mywebpage.com:6001/app/dfab1c1740a1ce5b1688?protocol=7&client=js&version=4.3.1&flash=false' failed:
No other error is present and the connection cannot be established.
This is the websockets.php config file:
<?php
use BeyondCode\LaravelWebSockets\Dashboard\Http\Middleware\Authorize;
return [
/*
* Set a custom dashboard configuration
*/
'dashboard' => [
'port' => env('LARAVEL_WEBSOCKETS_PORT', 6001),
],
/*
* This package comes with multi tenancy out of the box. Here you can
* configure the different apps that can use the webSockets server.
*
* Optionally you specify capacity so you can limit the maximum
* concurrent connections for a specific app.
*
* Optionally you can disable client events so clients cannot send
* messages to each other via the webSockets.
*/
'apps' => [
[
'id' => env('PUSHER_APP_ID'),
'name' => env('APP_NAME'),
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'path' => env('PUSHER_APP_PATH'),
'capacity' => null,
'enable_client_messages' => false,
'enable_statistics' => true,
],
],
/*
* This class is responsible for finding the apps. The default provider
* will use the apps defined in this config file.
*
* You can create a custom provider by implementing the
* `AppProvider` interface.
*/
'app_provider' => BeyondCode\LaravelWebSockets\Apps\ConfigAppProvider::class,
/*
* This array contains the hosts of which you want to allow incoming requests.
* Leave this empty if you want to accept requests from all hosts.
*/
'allowed_origins' => [
//
],
/*
* The maximum request size in kilobytes that is allowed for an incoming WebSocket request.
*/
'max_request_size_in_kb' => 250,
/*
* This path will be used to register the necessary routes for the package.
*/
'path' => 'laravel-websockets',
/*
* Dashboard Routes Middleware
*
* These middleware will be assigned to every dashboard route, giving you
* the chance to add your own middleware to this list or change any of
* the existing middleware. Or, you can simply stick with this list.
*/
'middleware' => [
'web',
Authorize::class,
],
'statistics' => [
/*
* This model will be used to store the statistics of the WebSocketsServer.
* The only requirement is that the model should extend
* `WebSocketsStatisticsEntry` provided by this package.
*/
'model' => \BeyondCode\LaravelWebSockets\Statistics\Models\WebSocketsStatisticsEntry::class,
/**
* The Statistics Logger will, by default, handle the incoming statistics, store them
* and then release them into the database on each interval defined below.
*/
'logger' => BeyondCode\LaravelWebSockets\Statistics\Logger\HttpStatisticsLogger::class,
/*
* Here you can specify the interval in seconds at which statistics should be logged.
*/
'interval_in_seconds' => 60,
/*
* When the clean-command is executed, all recorded statistics older than
* the number of days specified here will be deleted.
*/
'delete_statistics_older_than_days' => 60,
/*
* Use an DNS resolver to make the requests to the statistics logger
* default is to resolve everything to 127.0.0.1.
*/
'perform_dns_lookup' => false,
],
/*
* Define the optional SSL context for your WebSocket connections.
* You can see all available options at: http://php.net/manual/en/context.ssl.php
*/
'ssl' => [
/*
* Path to local certificate file on filesystem. It must be a PEM encoded file which
* contains your certificate and private key. It can optionally contain the
* certificate chain of issuers. The private key also may be contained
* in a separate file specified by local_pk.
*/
'local_cert' => env('LARAVEL_WEBSOCKETS_SSL_LOCAL_CERT', null),
/*
* Path to local private key file on filesystem in case of separate files for
* certificate (local_cert) and private key.
*/
'local_pk' => env('LARAVEL_WEBSOCKETS_SSL_LOCAL_PK', null),
/*
* Passphrase for your local_cert file.
*/
'passphrase' => env('LARAVEL_WEBSOCKETS_SSL_PASSPHRASE', null),
'verify_peer' => false,
],
/*
* Channel Manager
* This class handles how channel persistence is handled.
* By default, persistence is stored in an array by the running webserver.
* The only requirement is that the class should implement
* `ChannelManager` interface provided by this package.
*/
'channel_manager' => \BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManagers\ArrayChannelManager::class,
];
And this is the broadcasting.php config file:
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Broadcaster
|--------------------------------------------------------------------------
|
| This option controls the default broadcaster that will be used by the
| framework when an event needs to be broadcast. You may set this to
| any of the connections defined in the "connections" array below.
|
| Supported: "pusher", "redis", "log", "null"
|
*/
'default' => env('BROADCAST_DRIVER', 'null'),
/*
|--------------------------------------------------------------------------
| Broadcast Connections
|--------------------------------------------------------------------------
|
| Here you may define all of the broadcast connections that will be used
| to broadcast events to other systems or over websockets. Samples of
| each available type of connection are provided inside this array.
|
*/
'connections' => [
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => env('PUSHER_APP_CLUSTER'),
'useTLS' => true,
],
],
'ably' => [
'driver' => 'ably',
'key' => env('ABLY_KEY'),
],
'redis' => [
'driver' => 'redis',
'connection' => 'default',
],
'log' => [
'driver' => 'log',
],
'null' => [
'driver' => 'null',
],
],
];

The problem was that the website is only using https and I hadn't configured the SSL certificates correctly inside websockets.php config file.

Related

Cakephp 3.7, Middleware, Authentication and Routing

I'm using Cakephp 3.7 and authentication middleware.
My app is hosted locally at http://192.168.33.10/scoring.
I'm using the following middleware method in my Application.php.
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* #copyright Copyright (c) Cake Software Foundation, Inc.
(https://cakefoundation.org)
* #link https://cakephp.org CakePHP(tm) Project
* #since 3.3.0
* #license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace App;
use Authentication\AuthenticationService;
use Authentication\AuthenticationServiceProviderInterface;
use Authentication\Middleware\AuthenticationMiddleware;
use Cake\Core\Configure;
use Cake\Core\Exception\MissingPluginException;
use Cake\Error\Middleware\ErrorHandlerMiddleware;
use Cake\Http\BaseApplication;
use Cake\Routing\Middleware\AssetMiddleware;
use Cake\Routing\Middleware\RoutingMiddleware;
use Cake\Routing\Router;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
/**
* Application setup class.
*
* This defines the bootstrapping logic and middleware layers you
* want to use in your application.
*/
class Application extends BaseApplication implements
AuthenticationServiceProviderInterface
{
/**
* {#inheritDoc}
*/
public function bootstrap()
{
$this->addPlugin('CakeDC/Enum');
$this->addPlugin('Muffin/Trash');
$this->addPlugin('AuditStash');
// Call parent to load bootstrap from files.
parent::bootstrap();
// include required plugins
$this->addPlugin('Authentication');
if (PHP_SAPI === 'cli') {
try {
$this->addPlugin('Bake');
} catch (MissingPluginException $e) {
// Do not halt if the plugin is missing
}
$this->addPlugin('Migrations');
}
/*
* Only try to load DebugKit in development mode
* Debug Kit should not be installed on a production system
*/
if (Configure::read('debug')) {
$this->addPlugin(\DebugKit\Plugin::class);
}
}
/**
* Returns a service provider instance.
*
* #param \Psr\Http\Message\ServerRequestInterface $request Request
* #param \Psr\Http\Message\ResponseInterface $response Response
* #return \Authentication\AuthenticationServiceInterface
*/
public function getAuthenticationService(ServerRequestInterface $request, ResponseInterface $response)
{
$service = new AuthenticationService();
$fields = [
'username' => 'email',
'password' => 'password'
];
// Load identifiers
//$service->loadIdentifier('Authentication.Password', compact('fields'));
$service->loadIdentifier('Development', compact('fields'));
// Load the authenticators, you want session first
$service->loadAuthenticator('Authentication.Session');
$service->loadAuthenticator('Authentication.Form', [
'fields' => $fields
]);
return $service;
}
/**
* Setup the middleware queue your application will use.
*
* #param \Cake\Http\MiddlewareQueue $middlewareQueue The middleware queue to setup.
* #return \Cake\Http\MiddlewareQueue The updated middleware queue.
*/
public function middleware($middlewareQueue)
{
// Add the authentication middleware
$authentication = new AuthenticationMiddleware($this, [
'unauthenticatedRedirect' => Router::url(['controller' => 'Users', 'action' => 'login']),
]);
$middlewareQueue
// Catch any exceptions in the lower layers,
// and make an error page/response
->add(new ErrorHandlerMiddleware(null, Configure::read('Error')))
// Handle plugin/theme assets like CakePHP normally does.
->add(new AssetMiddleware([
'cacheTime' => Configure::read('Asset.cacheTime')
]))
// Add routing middleware.
// Routes collection cache enabled by default, to disable route caching
// pass null as cacheConfig, example: `new RoutingMiddleware($this)`
// you might want to disable this cache in case your routing is extremely simple
->add(new RoutingMiddleware($this, '_cake_routes_'))
// Add the authentication middleware to the middleware queue
->add($authentication);
return $middlewareQueue;
}
}
I have the following in config/routes.php:
<?php
/**
* Routes configuration
*
* In this
file, you set up routes to your controllers and their actions.
* Routes are very important mechanism that allows you to freely connect
* different URLs to chosen controllers and their actions (functions).
*
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* #copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* #link https://cakephp.org CakePHP(tm) Project
* #license https://opensource.org/licenses/mit-license.php MIT License
*/
use Cake\Http\Middleware\CsrfProtectionMiddleware;
use Cake\Routing\RouteBuilder;
use Cake\Routing\Router;
use Cake\Routing\Route\DashedRoute;
/**
* The default class to use for all routes
*
* The following route classes are supplied with CakePHP and are appropriate
* to set as the default:
*
* - Route
* - InflectedRoute
* - DashedRoute
*
* If no call is made to `Router::defaultRouteClass()`, the class used is
* `Route` (`Cake\Routing\Route\Route`)
*
* Note that `Route` does not do any inflections on URLs which will result in
* inconsistently cased URLs when used with `:plugin`, `:controller` and
* `:action` markers.
*
* Cache: Routes are cached to improve performance, check the RoutingMiddleware
* constructor in your `src/Application.php` file to change this behavior.
*
*/
Router::defaultRouteClass(DashedRoute::class);
Router::scope('/', function (RouteBuilder $routes) {
// Register scoped middleware for in scopes.
$routes->registerMiddleware('csrf', new CsrfProtectionMiddleware([
'httpOnly' => true
]));
/**
* Apply a middleware to the current route scope.
* Requires middleware to be registered via `Application::routes()` with `registerMiddleware()`
*/
$routes->applyMiddleware('csrf');
/**
* Here, we are connecting '/' (base path) to a controller called 'Pages',
* its action called 'display', and we pass a param to select the view file
* to use (in this case, src/Template/Pages/home.ctp)...
*/
$routes->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);
//connect login route
$routes->connect('/login', ['controller' => 'Users', 'action' => 'login']);
//connect logout route
$routes->connect('/logout', ['controller' => 'Users', 'action' => 'logout']);
/**
* ...and connect the rest of 'Pages' controller's URLs.
*/
$routes->connect('/pages/*', ['controller' => 'Pages', 'action' => 'display']);
/**
* Connect catchall routes for all controllers.
*
* Using the argument `DashedRoute`, the `fallbacks` method is a shortcut for
*
* ```
* $routes->connect('/:controller', ['action' => 'index'], ['routeClass' => 'DashedRoute']);
* $routes->connect('/:controller/:action/*', [], ['routeClass' => 'DashedRoute']);
* ```
*
* Any route class can be used with this method, such as:
* - DashedRoute
* - InflectedRoute
* - Route
* - Or your own route class
*
* You can remove these routes once you've connected the
* routes you want in your application.
*/
$routes->fallbacks(DashedRoute::class);
});
/**
* If you need a different set of middleware or none at all,
* open new scope and define routes there.
*
* ```
* Router::scope('/api', function (RouteBuilder $routes) {
* // No $routes->applyMiddleware() here.
* // Connect API actions here.
* });
* ```
*/
Router::prefix('admin', function ($routes) {
// All routes here will be prefixed with `/admin`
// And have the prefix => admin route element added.
$routes->fallbacks(DashedRoute::class);
});
The issue I'm having is that the redirect goes to http://192.168.33.10/login rather than going to http://192.168.33.10/scoring/login.
In troubleshooting my issue, I've discovered that the Router::url method will return /login if run in Application.php, but will return /scoring/login if run from AppController.php.
Obviously there's something I'm not seeing that's crossing up between the Routing middleware and the authentication middleware. I'm fairly new to the latest version of Cakephp and the integration of middleware, so I'm sure I've made an error somewhere.
Can someone help identify my error?

How to set cookie before test in Laravel?

I need to test a specific behaviour based on the presence of a cookie, how do I set a cookie before sending the request (or visiting the page) ? For the moment the following fails, it behaves likes nothing was set.
$this->actingAs($user)
->withSession(['user' => $user, 'profile' => $profile]) ;
#setcookie( 'locale_lc', "fr", time() + 60 * 60 * 24 * 900, '/', "domain.com", true, true) ;
$this->visit('/profile') ;
Or
$cookie = ['locale_lc' => Crypt::encrypt('fr')] ;
$this->actingAs($user)
->withSession(['user' => $user, 'profile' => $profile])
->makeRequest('GET', '/profile', [], $cookie) ;
The problem was in the setting and reading of cookies. Neither #setcookie nor $_COOKIE will work from a testing context. The method makeRequest with a cookie array is the right one.
However !
The reading script (controller, middleware) must be using Laravel's $request->cookie() method and not directly try to access it with $_COOKIE. In my case the cookie needs to be read by another app on our domain so I also had to disable the encryption for that specific cookie, which can be done in EncryptCookies.php
EncryptCookies
<?php
protected $except = [
'locale_lc'
];
Test
<?php
$cookie = ['locale_lc' => 'fr'] ;
$this->actingAs($user)
->withSession(['user' => $user, 'profile' => $profile])
->makeRequest('GET', '/profile', [], $cookie) ;
Middleware
<?php
public function handle($request, Closure $next){
if($request->cookie('locale_lc')){...}
}

Zend 2 and auth configuration routing

I'm working curently on a Zend2 project where there is an authentifaction system for the whole website, it was fine until we had to develop a module which is an public web service.
I would like to know if it's possible to allow users to access to a specific module/routing of Zend 2 ?
The Zend\Authentication\Adapter\Http provides an easy way for Apache like authentication in Zend Framework 2 applications.
It comes with two implementations Basic and Digest HTTP Authentication, which can be combined with two sub components - the class itself or a FileResolver. We are going to use the FileResolver to read the stored credentials and compare them to the submitted values.
First thing first. There are few important things to know.
Create a folder with name auth in MODULE_NAME/config/. Inside that folder create two files basic.txt and digest.txt. The file formats are smillar to Apache .htpasswd files.
Basic - <username>:<realm>:<credentials>, here credentials should be written in clear text, e.g.: basic:authentication:plaintextpassword.
Digest - <username>:<realm>:<credentials>, where <credentials> is the md5 hash of all 3 parts, e.g.: digest:authentication:dc45122ef294d83e84a8b5a3a6c5356b
In the same module, where we have just created our auth folder, open module.config.php file and place this code.
The code tells us which authentication schemes we accept, the realm (must be the same as the realm in the basic/digest.txt files, digest_domains (only when we use digest authentication) is the URL(s) where we want to apply the same valid information, nonce_timeout sets the number of seconds for which the nonce is valid.
/**
* Used for basic authentication
*/
'authentication_basic' => [
'adapter' => [
'config' => [
'accept_schemes' => 'basic',
'realm' => 'authentication',
'nonce_timeout' => 3600,
],
'basic' => __DIR__.'/auth/basic.txt',
],
],
/**
* Used for digest authentication
*/
'authentication_digest' => [
'adapter' => [
'config' => [
'accept_schemes' => 'digest',
'realm' => 'authentication',
'digest_domains' => '/learn-zf2-authentication/digest',
'nonce_timeout' => 3600,
],
'digest' => __DIR__.'/auth/digest.txt',
],
]
LearnZF2Authentication\Factory\BasicAuthenticationAdapterFactory
$config = $serviceLocator->get('Config');
$authConfig = $config['authentication_basic']['adapter'];
$authAdapter = new HttpAdapter($authConfig['config']);
$basic = new FileResolver();
$basic->setFile($authConfig['basic']);
$authAdapter->setBasicResolver($basic);
return $authAdapter;
LearnZF2Authentication\Factory\DigestAuthenticationAdapterFactory
$config = $serviceLocator->get('Config');
$authConfig = $config['authentication_digest']['adapter'];
$authAdapter = new HttpAdapter($authConfig['config']);
$digest = new FileResolver();
$digest->setFile($authConfig['digest']);
$authAdapter->setDigestResolver($digest);
return $authAdapter;
These are the codes we use to pass the authentication information
Module.php
/**
* #var MvcEvent $e
*/
$request = $e->getRequest();
$response = $e->getResponse();
$view = $e->getApplication()->getMvcEvent()->getViewModel();
$sm = $e->getApplication()->getServiceManager();
$authAdapter = $sm->get('LearnZF2Authentication\BasicAuthenticationAdapter');
/**
* Not HTTP? Stop!
*/
if (!($request instanceof Http\Request && $response instanceof Http\Response)) {
return;
}
/**
* Call the factory class and try to authenticate
*/
if ($e->getRouteMatch()->getParam('action') == 'digest') {
$authAdapter = $sm->get('LearnZF2Authentication\DigestAuthenticationAdapter');
}
$authAdapter->setRequest($request);
$authAdapter->setResponse($response);
if($e->getRouteMatch()->getParam('action') == 'basic' || $e->getRouteMatch()->getParam('action') == 'digest') {
$result = $authAdapter->authenticate();
/**
* Pass the information to the view and see what we got
*/
if ($result->isValid()) {
return $view->identity = $result->getIdentity();
} else {
/**
* Create a log function or just use the one from LearnZF2.
* Also make sure to redirect to another page, 404 for example
*/
foreach ($result->getMessages() as $msg) {
return $view->authProblem = $msg;
}
}
}
This is the code we use to pass the authentication information
One last important thing to note is that you must include a special header called Authorization n your request, replace :
RewriteRule ^(.*)$ %{ENV:BASE}index.php [NC,L]
with
PHP compiled as CGI does not support apache_response_headers function, but we need this header in order to do basic HTTP authtentication when running with CGI or FastCGI.
RewriteRule ^(.*)$ %{ENV:BASE}index.php [E=HTTP_AUTHORIZATION:% {HTTP:Authorization},L,NC]
and add in top of public/index.php
if (isset($_SERVER["REDIRECT_HTTP_AUTHORIZATION"])) {
$_SERVER["HTTP_AUTHORIZATION"] = $_SERVER["REDIRECT_HTTP_AUTHORIZATION"];
}
Some things to note. The auth folder as well the authentication code from module.config.php is best to be placed in your main config folder, where the global|local.php files are and excluded from commits.

How to set redis connection timeout?

Where in the configuration file do I set the connection timeout for Redis?
I know there is a timeout setting in the config file, but that only applies for idle connections. I want the timeout to apply when I want to read from redis. I want it to time out only after N seconds, rather than the default.
For anyone who is still looking for this, on a redis connnection string you can specify connectTimeout like below.
<add key="RedisConnectionString" value="your-redis-server,abortConnect=false,connectTimeout=1000,ssl=false,,password=your-password" />
On console of redis set this configuration
config set timeout 300
You basically need a read-timeout
This will be a client side property
Mention this in your application.
I'll examplify for Laravel framework of PHP
specify it at database.php file ('read_timeout' => 60)
'redis' => [
'client' => 'predis',
'cluster' => false,
'default' => [
'host' => env('REDIS_HOST', 'localhost'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => 0,
'read_timeout' => 60,
],
],
];

How to use Amazon RDS with Kohana's ORM

Is there any way to use Kohana's ORM with Amazon RDS?
I found the Amazon PHP SDK but I'm not sure how to plug it into Kohana so that the ORM uses it. I also couldn't find any Kohana module for Amazon RDS. Any suggestion?
Yes, this is absolutely possible. I have this exact configuration for my website.
In your AWS management console, you will need to get the "endpoint" of your RDS server. The name is quite long and begins with the name of your DB instance. (See the code below for example)
Next, open your database configuration file: application/config/database.php
In the 'default' configuration, change your hostname to the endpoint. Also change the database, username and password to whatever yours is set up with:
'default' => array
(
'type' => 'mysql',
'connection' => array(
/**
* The following options are available for MySQL:
*
* string hostname server hostname, or socket
* string database database name
* string username database username
* string password database password
* boolean persistent use persistent connections?
*
* Ports and sockets may be appended to the hostname.
*/
'hostname' => 'your-db-instance.njgo7sn43.us-east-1.rds.amazonaws.com',
'database' => 'db_name',
'username' => 'username',
'password' => 'SuperCaliFrajilisticExpiAliDocious',
'persistent' => FALSE,
),
'table_prefix' => '',
'charset' => 'utf8',
'caching' => FALSE,
'profiling' => TRUE,
),
Also, in your application/bootstrap.php file, make sure to UN-comment the database module:
Kohana::modules(array(
'database' => MODPATH.'database', // Database access
'orm' => MODPATH.'orm', // Object Relationship Mapping
));
The ORM module is optional but very nice to use.
Hope this helps!