173 lines
6 KiB
PHP
173 lines
6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Http\Middlewares;
|
|
|
|
use Closure;
|
|
use Illuminate\Contracts\Container\Container;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Config;
|
|
use Illuminate\Support\Facades\Vite;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class SecureHeaders
|
|
{
|
|
/**
|
|
* Enumerate headers which you do not want in your application's responses.
|
|
* Great starting point would be to go check out @Scott_Helme's:
|
|
* https://securityheaders.com/
|
|
*
|
|
* @var string[]
|
|
*/
|
|
private $unwantedHeaderList = [
|
|
'X-Powered-By',
|
|
'Server',
|
|
];
|
|
|
|
public function __construct(
|
|
private readonly Container $container
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Handle an incoming request.
|
|
*
|
|
* @param \Closure(\Illuminate\Http\Request):(\Illuminate\Http\Response) $next
|
|
*/
|
|
public function handle(Request $request, Closure $next): Response
|
|
{
|
|
$appUrl = Config::string('app.url');
|
|
$appDebug = Config::boolean('app.debug');
|
|
$telescopeEnabled = Config::boolean('telescope.enabled', false);
|
|
|
|
// * For Vite DEV
|
|
$host = \parse_url($appUrl, \PHP_URL_HOST);
|
|
$viteDevOrigin = $appDebug && \is_string($host) && $host !== ''
|
|
? "https://{$host}:4443"
|
|
: '';
|
|
$viteDevSocket = $appDebug && \is_string($host) && $host !== ''
|
|
? "wss://{$host}:4443"
|
|
: '';
|
|
|
|
$nonce = Vite::cspNonce();
|
|
$jsBo = $request->routeIs('bo*') ? "'unsafe-eval'" : '';
|
|
$jsdelivrUrl = 'https://cdn.jsdelivr.net';
|
|
$openStreetMapTiles = 'https://*.tile.openstreetmap.org';
|
|
|
|
$this->declareNonce($nonce);
|
|
$this->removeUnwantedHeaders($this->unwantedHeaderList);
|
|
|
|
$response = $next($request);
|
|
|
|
$response->headers->set('Referrer-Policy', 'no-referrer-when-downgrade');
|
|
$response->headers->set('X-Content-Type-Options', 'nosniff');
|
|
$response->headers->set('X-XSS-Protection', '1; mode=block');
|
|
$response->headers->set('X-Frame-Options', 'SAMEORIGIN');
|
|
$response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
|
|
|
|
// * Ignore CSP on some conditions.
|
|
if (
|
|
$appDebug && (
|
|
$response->isClientError() || // Ignition
|
|
$response->isServerError() || // Ignition
|
|
Vite::isRunningHot() || // Vite
|
|
($telescopeEnabled && str_starts_with($request->path(), 'telescope')) // Telescope
|
|
)
|
|
) {
|
|
return $response;
|
|
}
|
|
|
|
$cspPolicies = [
|
|
"default-src 'none';",
|
|
"base-uri 'self';",
|
|
"manifest-src 'self';",
|
|
"media-src 'self' blob: {$appUrl};",
|
|
// * CKEDITOR Specific https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/csp.html
|
|
"connect-src 'self' blob: " . implode(' ', [
|
|
'https://yl.s-web.fr/',
|
|
// * BAN address autocomplete (api-adresse.data.gouv.fr)
|
|
'https://api-adresse.data.gouv.fr',
|
|
'https://maps.googleapis.com',
|
|
// * Google reCAPTCHA
|
|
'https://www.google.com/recaptcha/',
|
|
'https://recaptcha.google.com/recaptcha/',
|
|
'https://www.google.com',
|
|
'https://www.recaptcha.net/',
|
|
// * VITE DEV
|
|
$viteDevSocket,
|
|
$viteDevOrigin,
|
|
]) . ';',
|
|
"img-src 'self' data: blob: " . implode(' ', [
|
|
'https://maps.googleapis.com',
|
|
'https://maps.gstatic.com',
|
|
$viteDevOrigin,
|
|
$openStreetMapTiles,
|
|
]) . ' data:;',
|
|
'child-src ' . implode(' ', []) . ';',
|
|
"frame-src 'self' " . implode(' ', [
|
|
'https://www.google.com/recaptcha/',
|
|
'https://recaptcha.google.com/recaptcha/',
|
|
'https://www.google.com/maps/',
|
|
'https://www.youtube.com/embed/',
|
|
'https://player.vimeo.com/',
|
|
'https://e.issuu.com/embed.html?',
|
|
]) . ';',
|
|
"object-src 'none';",
|
|
"font-src 'self' data: " . \implode(' ', [
|
|
'https://fonts.gstatic.com',
|
|
// * VITE DEV
|
|
$viteDevOrigin,
|
|
]) . ';',
|
|
// ! Using CKEditor will must have unsafe-inline style
|
|
"style-src 'self' 'unsafe-inline' " . \implode(' ', [
|
|
$jsdelivrUrl,
|
|
'https://cdnjs.cloudflare.com',
|
|
'https://fonts.googleapis.com',
|
|
'https://use.fontawesome.com',
|
|
// * File Manager
|
|
// * VITE DEV
|
|
$viteDevOrigin,
|
|
]) . ';',
|
|
|
|
// ! If you dont use ckeditor use this
|
|
// "style-src 'self' 'nonce-$nonce';",
|
|
"script-src 'self' 'nonce-$nonce' 'strict-dynamic' " . \implode(' ', [
|
|
'https: blob:',
|
|
$jsBo,
|
|
'https://www.googletagmanager.com',
|
|
'https://cdnjs.cloudflare.com',
|
|
'https://www.google-analytics.com',
|
|
// * VITE DEV
|
|
$viteDevOrigin,
|
|
]) . ';',
|
|
];
|
|
|
|
$response->headers->set(
|
|
'Content-Security-Policy',
|
|
implode(' ', $cspPolicies)
|
|
);
|
|
$response->headers->set('Feature-Policy', 'fullscreen \'self\'; geolocation \'self\';');
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Remove unwanted headers
|
|
*
|
|
* @param string[] $headerList List of headers to remove from the response
|
|
* @return void
|
|
*/
|
|
private function removeUnwantedHeaders(array $headerList)
|
|
{
|
|
foreach ($headerList as $header) {
|
|
header_remove($header);
|
|
}
|
|
}
|
|
|
|
private function declareNonce(?string $value): void
|
|
{
|
|
Vite::useCspNonce($value);
|
|
$this->container->singleton('csp_nonce', $value);
|
|
}
|
|
}
|