浏览代码

Enhance CSP directives to support Vite hot reload mode

Bubka 3 月之前
父节点
当前提交
f91ebfabb3

+ 8 - 6
app/Http/Controllers/SinglePageController.php

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

+ 31 - 8
app/Http/Middleware/AddContentSecurityPolicyHeaders.php

@@ -17,18 +17,41 @@ class AddContentSecurityPolicyHeaders
     public function handle(Request $request, Closure $next) : Response
     {
         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();
+            $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 . ';';
-            $directives['script-src-elem'] = "script-src-elem 'nonce-" . Vite::cspNonce() . "' " . $assetUrl . " 'strict-dynamic';";
-            $directives['style-src']       = "style-src 'self' " . $assetUrl . " 'unsafe-inline';";
-            $directives['connect-src']     = "connect-src 'self';";
-            $directives['img-src']         = "img-src 'self' data: " . $assetUrl . ';';
-            $directives['object-src']      = "object-src 'none';";
+            // We add 'ws://' protocole and localhost ip address to avoid error with
+            // Vite hot reload (when running 'npm run dev')
+            // For the record: 127.0.0.1 is the only supported ip address as CSP
+            // is intended to work with domain names -it's a www security mecanism)
+            if (config('app.env') === 'development' && Vite::isRunningHot()) {
+                $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 */
             /** @phpstan-ignore-next-line */

+ 2 - 1
resources/views/landing.blade.php

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