diff --git a/app/Http/Middleware/TrustHosts.php b/app/Http/Middleware/TrustHosts.php index 3d9b77734..bb2687083 100644 --- a/app/Http/Middleware/TrustHosts.php +++ b/app/Http/Middleware/TrustHosts.php @@ -4,6 +4,7 @@ use App\Models\InstanceSettings; use Illuminate\Http\Middleware\TrustHosts as Middleware; +use Illuminate\Support\Facades\Cache; use Spatie\Url\Url; class TrustHosts extends Middleware @@ -16,19 +17,27 @@ class TrustHosts extends Middleware public function hosts(): array { $trustedHosts = []; - // Trust the configured FQDN from InstanceSettings - try { - $settings = InstanceSettings::get(); - if ($settings && $settings->fqdn) { - $url = Url::fromString($settings->fqdn); - $host = $url->getHost(); - if ($host) { - $trustedHosts[] = $host; + + // Trust the configured FQDN from InstanceSettings (cached to avoid DB query on every request) + $fqdnHost = Cache::remember('instance_settings_fqdn_host', 300, function () { + try { + $settings = InstanceSettings::get(); + if ($settings && $settings->fqdn) { + $url = Url::fromString($settings->fqdn); + $host = $url->getHost(); + + return $host ?: null; } + } catch (\Exception $e) { + // If instance settings table doesn't exist yet (during installation), + // return null to fall back to APP_URL only } - } catch (\Exception $e) { - // If instance settings table doesn't exist yet (during installation), - // fall back to APP_URL only + + return null; + }); + + if ($fqdnHost) { + $trustedHosts[] = $fqdnHost; } // Trust all subdomains of APP_URL as fallback diff --git a/app/Models/InstanceSettings.php b/app/Models/InstanceSettings.php index ac95bb8a9..1251e146e 100644 --- a/app/Models/InstanceSettings.php +++ b/app/Models/InstanceSettings.php @@ -42,6 +42,11 @@ protected static function booted(): void } }); } + + // Clear trusted hosts cache when FQDN changes + if ($settings->isDirty('fqdn')) { + \Cache::forget('instance_settings_fqdn_host'); + } }); } diff --git a/tests/Feature/TrustHostsMiddlewareTest.php b/tests/Feature/TrustHostsMiddlewareTest.php index 2e6169643..7c02aa7e9 100644 --- a/tests/Feature/TrustHostsMiddlewareTest.php +++ b/tests/Feature/TrustHostsMiddlewareTest.php @@ -2,9 +2,15 @@ use App\Http\Middleware\TrustHosts; use App\Models\InstanceSettings; +use Illuminate\Support\Facades\Cache; uses(\Illuminate\Foundation\Testing\RefreshDatabase::class); +beforeEach(function () { + // Clear cache before each test to ensure isolation + Cache::forget('instance_settings_fqdn_host'); +}); + it('trusts the configured FQDN from InstanceSettings', function () { // Create instance settings with FQDN InstanceSettings::updateOrCreate( @@ -140,3 +146,57 @@ // IPv6 addresses are enclosed in brackets, getHost() should handle this expect($hosts)->toContain('[2001:db8::1]'); }); + +it('invalidates cache when FQDN is updated', function () { + // Set initial FQDN + $settings = InstanceSettings::updateOrCreate( + ['id' => 0], + ['fqdn' => 'https://old-domain.com'] + ); + + // First call should cache it + $middleware = new TrustHosts($this->app); + $hosts1 = $middleware->hosts(); + expect($hosts1)->toContain('old-domain.com'); + + // Verify cache exists + expect(Cache::has('instance_settings_fqdn_host'))->toBeTrue(); + + // Update FQDN - should trigger cache invalidation + $settings->fqdn = 'https://new-domain.com'; + $settings->save(); + + // Cache should be cleared + expect(Cache::has('instance_settings_fqdn_host'))->toBeFalse(); + + // New call should return updated host + $middleware2 = new TrustHosts($this->app); + $hosts2 = $middleware2->hosts(); + expect($hosts2)->toContain('new-domain.com'); + expect($hosts2)->not->toContain('old-domain.com'); +}); + +it('caches trusted hosts to avoid database queries on every request', function () { + InstanceSettings::updateOrCreate( + ['id' => 0], + ['fqdn' => 'https://coolify.example.com'] + ); + + // Clear cache first + Cache::forget('instance_settings_fqdn_host'); + + // First call - should query database and cache result + $middleware1 = new TrustHosts($this->app); + $hosts1 = $middleware1->hosts(); + + // Verify result is cached + expect(Cache::has('instance_settings_fqdn_host'))->toBeTrue(); + expect(Cache::get('instance_settings_fqdn_host'))->toBe('coolify.example.com'); + + // Subsequent calls should use cache (no DB query) + $middleware2 = new TrustHosts($this->app); + $hosts2 = $middleware2->hosts(); + + expect($hosts1)->toBe($hosts2); + expect($hosts2)->toContain('coolify.example.com'); +});