fix(tls): force https URL generation for the active domain (external-proxy mode)
Behind an external TLS proxy, Clusev's own Caddy only honors the upstream's
X-Forwarded-Proto when TRUSTED_PROXY_CIDR is set. Without it the app saw http,
asset() emitted http:// URLs, and the HTTPS page's CSP ('self') blocked them —
the panel loaded with no CSS/JS over the domain. PanelScheme now forces the URL
root + scheme to https://<domain> whenever the active domain is being served,
regardless of the (possibly untrusted) request scheme. The bare-IP recovery
path returns earlier and stays on HTTP.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat/v1-foundation
v0.9.17
parent
7d35f3abac
commit
4bcdafbeff
13
CHANGELOG.md
13
CHANGELOG.md
|
|
@ -13,6 +13,19 @@ getaggte Releases (Kanal `stable`, optional `beta`) — niemals Entwicklungs-Bui
|
|||
|
||||
_Keine offenen Änderungen — der nächste Stand wird hier gesammelt und als `vX.Y.Z` getaggt._
|
||||
|
||||
## [0.9.17] - 2026-06-19
|
||||
|
||||
### Behoben
|
||||
- **Panel über die Domain hinter einem externen TLS-Proxy war unbenutzbar (CSS/JS per CSP
|
||||
blockiert).** Im Externer-Proxy-Modus terminiert der Upstream HTTPS und reicht das Schema per
|
||||
`X-Forwarded-Proto` weiter; Clusevs eigener Caddy vertraut dem aber nur, wenn `TRUSTED_PROXY_CIDR`
|
||||
gesetzt ist. War es das nicht, sah die App `http`, erzeugte `http://`-Asset-URLs — und die über
|
||||
HTTPS ausgelieferte Seite (CSP `'self'`) blockierte diese → kein CSS/JS, kein Login. Bei aktiver
|
||||
Domain wird die URL-Erzeugung (Assets, Routen) jetzt fest auf `https://<domain>` gezwungen
|
||||
(`URL::forceRootUrl`), unabhängig vom (evtl. nicht vertrauten) Request-Schema. Der Bare-IP-
|
||||
Recovery-Pfad (HTTP) bleibt unberührt. Hinweis: für korrekte `Secure`-Cookies im Externer-Proxy-
|
||||
Modus weiterhin `TRUSTED_PROXY_CIDR` auf die Proxy-Adresse setzen.
|
||||
|
||||
## [0.9.16] - 2026-06-19
|
||||
|
||||
### Behoben
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ namespace App\Http\Middleware;
|
|||
use App\Services\DeploymentService;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
|
|
@ -76,6 +77,14 @@ class PanelScheme
|
|||
return redirect()->to('https://'.$domain.$request->getRequestUri(), 301);
|
||||
}
|
||||
|
||||
// Serving the active domain over HTTPS: force generated URLs (assets, routes) to
|
||||
// https://<domain> so they MATCH the page scheme. Critical in external-proxy mode, where
|
||||
// the upstream terminates TLS and the forwarded scheme may not be trusted here — without
|
||||
// this asset() emits http:// URLs that the HTTPS page's CSP ('self') blocks, leaving the
|
||||
// panel with no CSS/JS. The bare-IP recovery path returned earlier and stays on HTTP.
|
||||
URL::forceRootUrl('https://'.$domain);
|
||||
URL::forceScheme('https');
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
return [
|
||||
// First tagged release is v0.1.0 (semantic, not -dev).
|
||||
'version' => '0.9.16',
|
||||
'version' => '0.9.17',
|
||||
|
||||
// Deployed commit + branch. install.sh bakes these into .env from the host's .git (the prod
|
||||
// image ships no .git); the Versions page prefers them and falls back to a live .git read in
|
||||
|
|
|
|||
|
|
@ -99,4 +99,19 @@ class ExternalTlsModeTest extends TestCase
|
|||
$this->expectException(HttpException::class);
|
||||
$this->runPanelScheme(Request::create('http://evil.example.com/dashboard'));
|
||||
}
|
||||
|
||||
public function test_external_mode_forces_https_url_generation_for_the_active_domain(): void
|
||||
{
|
||||
Setting::put('panel_domain', 'panel.example.com');
|
||||
app(DeploymentService::class)->setTlsMode('external');
|
||||
|
||||
// External proxy terminates TLS; the app itself sees a plain-HTTP request here.
|
||||
$response = $this->runPanelScheme(Request::create('http://panel.example.com/dashboard'));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
|
||||
// Generated URLs (assets, routes) must be https://<domain> so the HTTPS page's CSP
|
||||
// ('self') does not block them — the bug that left the panel with no CSS/JS.
|
||||
$this->assertSame('https://panel.example.com/build/app.css', url('build/app.css'));
|
||||
$this->assertStringStartsWith('https://panel.example.com', route('login'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue