1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2026-08-06 06:34:03 +00:00

Copy the broadcaster auth state directly

Instead of re-registering the central channels on the tenant broadcaster one by one, copy the properties directly. Also the authenticated user callback wasn't copied before, so user authentication (/broadcasting/user-auth) that worked in central context would 403 in tenant context if the callback was registered in central context.
This commit is contained in:
lukinovec 2026-07-16 13:22:02 +02:00
parent 50afc49d2b
commit fe7468ab72

View file

@ -91,27 +91,11 @@ class BroadcastingConfigBootstrapper implements TenancyBootstrapper
// contract gets the same tenant broadcaster that the manager uses, instead of the stale central one.
// The closure runs immediately (the extended singleton is already resolved), and it's also what makes
// channel auth work in tenant context -- the broadcaster resolved here gets cached as the tenant
// manager's default driver and receives the central channel auth closures (see below).
// manager's default driver and receives the central broadcaster's auth state (see copyAuthState()).
$this->app->extend(BroadcasterContract::class, function (BroadcasterContract $centralBroadcaster) {
$tenantBroadcaster = $this->app->make(BroadcastManager::class)->connection();
// The newly resolved broadcaster doesn't have any channel auth closures registered, so the
// closures registered in central context (e.g. in routes/channels.php) have to be passed to it
// manually, otherwise, Broadcast::auth() would throw a 403 for those channels.
// Since Laravel only ever uses the default broadcaster's channel auth closures for broadcasting auth,
// we only have to pass the channel closures to the default broadcaster.
//
// The channel() and getChannels() methods aren't part of the Broadcaster contract -- they come
// from the abstract Broadcaster class, so the closures can only be copied between broadcasters extending it
// (which all of Laravel's default broadcasters, e.g. PusherBroadcaster, do).
if ($centralBroadcaster instanceof Broadcaster && $tenantBroadcaster instanceof Broadcaster) {
// invade() because the channel options can't be retrieved through any of the broadcaster's public methods
$channelOptions = invade($centralBroadcaster)->channelOptions;
foreach ($centralBroadcaster->getChannels() as $channel => $callback) {
$tenantBroadcaster->channel($channel, $callback, $channelOptions[$channel] ?? []);
}
}
$this->copyAuthState($centralBroadcaster, $tenantBroadcaster);
return $tenantBroadcaster;
});
@ -122,6 +106,32 @@ class BroadcastingConfigBootstrapper implements TenancyBootstrapper
Broadcast::clearResolvedInstance();
}
/**
* Copy the auth state (the channel auth closures, their options, and the authenticated user
* callback) from one broadcaster to another. A freshly resolved broadcaster has no auth state,
* so without the copying, channel auth and user auth would stop working (403) in tenant context.
*
* The auth state is stored on the abstract Broadcaster class, not in the Broadcaster
* contract, and it's stored in protected properties. Because of that, we have
* to check that both broadcasters are instances of the abstract Broadcaster class and
* use invade() to access the protected properties (for the $channels property, there
* is a public accessor -- getChannels() -- but since invade is already used here,
* we access the property directly for consistency).
*/
protected function copyAuthState(BroadcasterContract $from, BroadcasterContract $to): void
{
if (! $from instanceof Broadcaster || ! $to instanceof Broadcaster) {
return;
}
$fromState = invade($from);
$toState = invade($to);
$toState->channels = $fromState->channels;
$toState->channelOptions = $fromState->channelOptions;
$toState->authenticatedUserCallback = $fromState->authenticatedUserCallback;
}
public function revert(): void
{
// Revert the bound BroadcastManager and Broadcaster singletons back to their original state