fix(realtime): keep Reverb handshake under its size limit with large cookie headers

Raise the Reverb max request size and store the remembered list view
cookies unencrypted, so the WebSocket handshake no longer fails with 413
and live updates (like proxy restart logs) reach the browser.
This commit is contained in:
Andras Bacsai
2026-11-06 11:01:06 +01:00
parent b12c965fbe
commit c8c639e3d6
8 changed files with 47 additions and 37 deletions
+10
View File
@@ -14,4 +14,14 @@ class EncryptCookies extends Middleware
protected $except = [
//
];
/**
* The remembered list views (RemembersCanvasView) hold only an allow-listed view name.
* Encryption makes each of these per-user, per-page cookies about 330 bytes, and the
* browser sends all of them on the Reverb WebSocket handshake too.
*/
public function isDisabled($name)
{
return parent::isDisabled($name) || preg_match('/^coolify_\d+_[a-z_]+_view$/', $name) === 1;
}
}
+1 -1
View File
@@ -36,7 +36,7 @@ return [
'options' => [
'tls' => [],
],
'max_request_size' => 10_000,
'max_request_size' => 64_000,
'scaling' => [
'enabled' => false,
'channel' => 'reverb',
+4 -4
View File
@@ -116,17 +116,17 @@ test('the dashboard opens the canvas again when the user left it there', functio
->get(route('dashboard.canvas'))
->assertOk()
->assertSee('data-endpoint="/dashboard/canvas"', false)
->assertCookie($cookie, 'canvas');
->assertPlainCookie($cookie, 'canvas');
dashboardCanvasSession($this, $this->admin, $this->team)
->withCookie($cookie, 'canvas')
->withUnencryptedCookie($cookie, 'canvas')
->get(route('dashboard'))
->assertRedirect(route('dashboard.canvas'));
// The list link on the canvas opens the list and makes it the remembered view.
dashboardCanvasSession($this, $this->admin, $this->team)
->withCookie($cookie, 'canvas')
->withUnencryptedCookie($cookie, 'canvas')
->get(route('dashboard', ['view' => 'list']))
->assertOk()
->assertCookie($cookie, 'list');
->assertPlainCookie($cookie, 'list');
});
+1 -1
View File
@@ -37,7 +37,7 @@ it('remembers the view of each dashboard section on its own', function () {
expect(Cookie::queued("coolify_{$this->user->id}_dashboard_projects_view")?->getValue())->toBe('grid')
->and(Cookie::queued("coolify_{$this->user->id}_dashboard_servers_view"))->toBeNull();
$this->withCookie("coolify_{$this->user->id}_dashboard_projects_view", 'grid')
$this->withUnencryptedCookie("coolify_{$this->user->id}_dashboard_projects_view", 'grid')
->get(route('dashboard'))
->assertOk()
->assertSee("viewMode: 'grid'", false)
+8 -8
View File
@@ -134,19 +134,19 @@ test('the project page reopens the canvas when the user left it on the canvas vi
->get(route('project.canvas', $parameters))
->assertOk()
->assertSee('data-endpoint="/projects/'.$this->project->uuid.'/canvas"', false)
->assertCookie("coolify_{$this->admin->id}_project_view", 'canvas');
->assertPlainCookie("coolify_{$this->admin->id}_project_view", 'canvas');
projectCanvasSession($this, $this->admin, $this->team)
->withCookie("coolify_{$this->admin->id}_project_view", 'canvas')
->withUnencryptedCookie("coolify_{$this->admin->id}_project_view", 'canvas')
->get(route('project.show', $parameters))
->assertRedirect(route('project.canvas', $parameters));
// The table / grid links on the canvas pick the list view again.
projectCanvasSession($this, $this->admin, $this->team)
->withCookie("coolify_{$this->admin->id}_project_view", 'canvas')
->withUnencryptedCookie("coolify_{$this->admin->id}_project_view", 'canvas')
->get(route('project.show', [...$parameters, 'view' => 'grid']))
->assertOk()
->assertCookie("coolify_{$this->admin->id}_project_view", 'grid');
->assertPlainCookie("coolify_{$this->admin->id}_project_view", 'grid');
});
test('the projects canvas shows every project of the team as a zone, grouped by environment', function () {
@@ -230,18 +230,18 @@ test('the projects page reopens the canvas when the user left it on the canvas v
projectCanvasSession($this, $this->admin, $this->team)
->get(route('project.canvas.index'))
->assertOk()
->assertCookie("coolify_{$this->admin->id}_projects_view", 'canvas');
->assertPlainCookie("coolify_{$this->admin->id}_projects_view", 'canvas');
projectCanvasSession($this, $this->admin, $this->team)
->withCookie("coolify_{$this->admin->id}_projects_view", 'canvas')
->withUnencryptedCookie("coolify_{$this->admin->id}_projects_view", 'canvas')
->get(route('project.index'))
->assertRedirect(route('project.canvas.index'));
projectCanvasSession($this, $this->admin, $this->team)
->withCookie("coolify_{$this->admin->id}_projects_view", 'canvas')
->withUnencryptedCookie("coolify_{$this->admin->id}_projects_view", 'canvas')
->get(route('project.index', ['view' => 'table']))
->assertOk()
->assertCookie("coolify_{$this->admin->id}_projects_view", 'table');
->assertPlainCookie("coolify_{$this->admin->id}_projects_view", 'table');
});
test('an admin creates an environment from the project canvas', function () {
+15 -15
View File
@@ -221,22 +221,22 @@ test('the servers page reopens the canvas when the user left it on the canvas vi
serverCanvasSession($this, $this->admin, $this->team)
->get(route('server.canvas.index'))
->assertOk()
->assertCookie("coolify_{$this->admin->id}_servers_view", 'canvas');
->assertPlainCookie("coolify_{$this->admin->id}_servers_view", 'canvas');
serverCanvasSession($this, $this->admin, $this->team)
->withCookie("coolify_{$this->admin->id}_servers_view", 'canvas')
->withUnencryptedCookie("coolify_{$this->admin->id}_servers_view", 'canvas')
->get(route('server.index'))
->assertRedirect(route('server.canvas.index'));
// The table / grid links on the canvas pick the list view again.
serverCanvasSession($this, $this->admin, $this->team)
->withCookie("coolify_{$this->admin->id}_servers_view", 'canvas')
->withUnencryptedCookie("coolify_{$this->admin->id}_servers_view", 'canvas')
->get(route('server.index', ['view' => 'grid']))
->assertOk()
->assertCookie("coolify_{$this->admin->id}_servers_view", 'grid');
->assertPlainCookie("coolify_{$this->admin->id}_servers_view", 'grid');
serverCanvasSession($this, $this->admin, $this->team)
->withCookie("coolify_{$this->admin->id}_servers_view", 'grid')
->withUnencryptedCookie("coolify_{$this->admin->id}_servers_view", 'grid')
->get(route('server.index'))
->assertOk();
});
@@ -247,18 +247,18 @@ test('the environment page reopens the canvas when the user left it on the canva
serverCanvasSession($this, $this->admin, $this->team)
->get(route('project.environment.canvas', $parameters))
->assertOk()
->assertCookie("coolify_{$this->admin->id}_environment_view", 'canvas');
->assertPlainCookie("coolify_{$this->admin->id}_environment_view", 'canvas');
serverCanvasSession($this, $this->admin, $this->team)
->withCookie("coolify_{$this->admin->id}_environment_view", 'canvas')
->withUnencryptedCookie("coolify_{$this->admin->id}_environment_view", 'canvas')
->get(route('project.resource.index', $parameters))
->assertRedirect(route('project.environment.canvas', $parameters));
serverCanvasSession($this, $this->admin, $this->team)
->withCookie("coolify_{$this->admin->id}_environment_view", 'canvas')
->withUnencryptedCookie("coolify_{$this->admin->id}_environment_view", 'canvas')
->get(route('project.resource.index', [...$parameters, 'view' => 'table']))
->assertOk()
->assertCookie("coolify_{$this->admin->id}_environment_view", 'table');
->assertPlainCookie("coolify_{$this->admin->id}_environment_view", 'table');
});
test('the server resources page reopens the one server canvas when the user left it on the canvas view', function () {
@@ -267,18 +267,18 @@ test('the server resources page reopens the one server canvas when the user left
serverCanvasSession($this, $this->admin, $this->team)
->get(route('server.canvas', $parameters))
->assertOk()
->assertCookie("coolify_{$this->admin->id}_server_resources_view", 'canvas');
->assertPlainCookie("coolify_{$this->admin->id}_server_resources_view", 'canvas');
serverCanvasSession($this, $this->admin, $this->team)
->withCookie("coolify_{$this->admin->id}_server_resources_view", 'canvas')
->withUnencryptedCookie("coolify_{$this->admin->id}_server_resources_view", 'canvas')
->get(route('server.resources', $parameters))
->assertRedirect(route('server.canvas', $parameters));
serverCanvasSession($this, $this->admin, $this->team)
->withCookie("coolify_{$this->admin->id}_server_resources_view", 'canvas')
->withUnencryptedCookie("coolify_{$this->admin->id}_server_resources_view", 'canvas')
->get(route('server.resources', [...$parameters, 'view' => 'list']))
->assertOk()
->assertCookie("coolify_{$this->admin->id}_server_resources_view", 'list');
->assertPlainCookie("coolify_{$this->admin->id}_server_resources_view", 'list');
});
test('the canvas API works with the browser session when the instance API is disabled', function () {
@@ -295,7 +295,7 @@ test('the remembered view belongs to the user, not to the browser', function ()
$this->team->members()->attach($other->id, ['role' => 'admin']);
serverCanvasSession($this, $other, $this->team)
->withCookie("coolify_{$this->admin->id}_servers_view", 'canvas')
->withUnencryptedCookie("coolify_{$this->admin->id}_servers_view", 'canvas')
->get(route('server.index'))
->assertOk();
});
@@ -315,7 +315,7 @@ test('the list page remembers the table or grid choice and opens it again', func
expect(Cookie::getQueuedCookies())->toBeEmpty();
serverCanvasSession($this, $this->admin, $this->team)
->withCookie($cookie, 'grid')
->withUnencryptedCookie($cookie, 'grid')
->get(route('server.index'))
->assertOk()
->assertSee("viewMode: 'grid'", false);
+4 -4
View File
@@ -297,18 +297,18 @@ test('each shared variables page reopens its canvas when the user left it on the
sharedVariableCanvasAs($this, $this->admin, $this->team)
->get(route("shared-variables.{$scope}.canvas", $parameters))
->assertOk()
->assertCookie($cookie, 'canvas');
->assertPlainCookie($cookie, 'canvas');
sharedVariableCanvasAs($this, $this->admin, $this->team)
->withCookie($cookie, 'canvas')
->withUnencryptedCookie($cookie, 'canvas')
->get(route($listRoute, $parameters))
->assertRedirect(route("shared-variables.{$scope}.canvas", $parameters));
sharedVariableCanvasAs($this, $this->admin, $this->team)
->withCookie($cookie, 'canvas')
->withUnencryptedCookie($cookie, 'canvas')
->get(route($listRoute, [...$parameters, 'view' => 'list']))
->assertOk()
->assertCookie($cookie, 'list');
->assertPlainCookie($cookie, 'list');
})->with(['team', 'project', 'environment', 'server']);
test('an admin adds a shared variable from the canvas modal and the canvas reloads', function () {
+4 -4
View File
@@ -211,16 +211,16 @@ test('the tags page reopens the canvas when the user left it on the canvas view'
tagCanvasAs($this, $this->admin, $this->team)
->get(route('tags.canvas'))
->assertOk()
->assertCookie("coolify_{$this->admin->id}_tags_view", 'canvas');
->assertPlainCookie("coolify_{$this->admin->id}_tags_view", 'canvas');
tagCanvasAs($this, $this->admin, $this->team)
->withCookie("coolify_{$this->admin->id}_tags_view", 'canvas')
->withUnencryptedCookie("coolify_{$this->admin->id}_tags_view", 'canvas')
->get(route('tags.show'))
->assertRedirect(route('tags.canvas'));
tagCanvasAs($this, $this->admin, $this->team)
->withCookie("coolify_{$this->admin->id}_tags_view", 'canvas')
->withUnencryptedCookie("coolify_{$this->admin->id}_tags_view", 'canvas')
->get(route('tags.show', ['view' => 'grid']))
->assertOk()
->assertCookie("coolify_{$this->admin->id}_tags_view", 'grid');
->assertPlainCookie("coolify_{$this->admin->id}_tags_view", 'grid');
});