Enhance CSP directives to support Vite hot reload mode

This commit is contained in:
Bubka 2025-04-01 10:02:09 +02:00
parent 9c8b904120
commit f91ebfabb3
3 changed files with 41 additions and 15 deletions

View File

@ -48,12 +48,9 @@ class SinglePageController extends Controller
$installDocUrl = config('2fauth.installDocUrl'); $installDocUrl = config('2fauth.installDocUrl');
$ssoDocUrl = config('2fauth.ssoDocUrl'); $ssoDocUrl = config('2fauth.ssoDocUrl');
$exportSchemaUrl = config('2fauth.exportSchemaUrl'); $exportSchemaUrl = config('2fauth.exportSchemaUrl');
$cspNonce = Vite::cspNonce();
$isSecure = str_starts_with(config('app.url'), 'https'); $isSecure = str_starts_with(config('app.url'), 'https');
// if (Auth::user()->preferences) $viewData = [
return view('landing')->with([
'appSettings' => $settings, 'appSettings' => $settings,
'appConfig' => collect([ 'appConfig' => collect([
'proxyAuth' => $proxyAuth, 'proxyAuth' => $proxyAuth,
@ -76,8 +73,13 @@ class SinglePageController extends Controller
'isTestingApp' => $isTestingApp, 'isTestingApp' => $isTestingApp,
'lang' => $lang, 'lang' => $lang,
'locales' => $locales, 'locales' => $locales,
'cspNonce' => $cspNonce,
'isSecure' => $isSecure, 'isSecure' => $isSecure,
]); ];
if (config('2fauth.config.contentSecurityPolicy')) {
$viewData['cspNonce'] = Vite::cspNonce();
}
return view('landing')->with($viewData);
} }
} }

View File

@ -17,18 +17,41 @@ class AddContentSecurityPolicyHeaders
public function handle(Request $request, Closure $next) : Response public function handle(Request $request, Closure $next) : Response
{ {
if (config('2fauth.config.contentSecurityPolicy')) { if (config('2fauth.config.contentSecurityPolicy')) {
// Some CSP directives can be used with nonce but not all of them.
// We build a space separated list of addresses to be allowed.
Vite::useCspNonce(); Vite::useCspNonce();
$authorizedAddresses[] = config('app.url') . ':*';
$assetUrl = config('app.asset_url') != config('app.url') ? config('app.asset_url') : ''; // We add custom asset url if defined
if (config('app.asset_url') && config('app.asset_url') != config('app.url')) {
$authorizedAddresses[] = config('app.asset_url') . ':*';
}
$directives['script-src'] = "script-src 'nonce-" . Vite::cspNonce() . "' " . $assetUrl . ';'; // We add 'ws://' protocole and localhost ip address to avoid error with
$directives['script-src-elem'] = "script-src-elem 'nonce-" . Vite::cspNonce() . "' " . $assetUrl . " 'strict-dynamic';"; // Vite hot reload (when running 'npm run dev')
$directives['style-src'] = "style-src 'self' " . $assetUrl . " 'unsafe-inline';"; // For the record: 127.0.0.1 is the only supported ip address as CSP
$directives['connect-src'] = "connect-src 'self';"; // is intended to work with domain names -it's a www security mecanism)
$directives['img-src'] = "img-src 'self' data: " . $assetUrl . ';'; if (config('app.env') === 'development' && Vite::isRunningHot()) {
$directives['object-src'] = "object-src 'none';"; $authorizedAddresses[] = 'ws://' . $request->getHttpHost() . ':*';
$authorizedAddresses[] = 'http://127.0.0.1:*';
$authorizedAddresses[] = 'ws://127.0.0.1:*';
}
$csp = implode(' ', $directives); $authorizedAddresses = implode(' ', $authorizedAddresses);
$directives['script-src'] = "script-src 'nonce-" . Vite::cspNonce() . "' 'strict-dynamic'";
$directives['style-src'] = "style-src 'self' " . $authorizedAddresses . " 'unsafe-inline'";
$directives['connect-src'] = "connect-src 'self' " . $authorizedAddresses;
$directives['img-src'] = "img-src 'self' " . $authorizedAddresses;
$directives['object-src'] = "object-src 'none'";
$directives['default-src'] = "default-src 'self'";
// This one is to allow eval used by the vue devtools extension
if (config('app.env') === 'development') {
$directives['script-src'] .= " 'unsafe-eval'";
}
$csp = implode('; ', $directives);
/** @disregard Undefined function */ /** @disregard Undefined function */
/** @phpstan-ignore-next-line */ /** @phpstan-ignore-next-line */

View File

@ -22,7 +22,8 @@
<div id="app"> <div id="app">
<app></app> <app></app>
</div> </div>
<script type="text/javascript" nonce="{{ $cspNonce }}"> <script type="text/javascript"
{!! isset($cspNonce) ? "nonce='" . $cspNonce . "'" : "" !!} >
var appSettings = {!! $appSettings !!}; var appSettings = {!! $appSettings !!};
var appConfig = {!! $appConfig !!}; var appConfig = {!! $appConfig !!};
var urls = {!! $urls !!}; var urls = {!! $urls !!};