ldc-ovoteam/app/Http/Middlewares/SecureHeaders.php
Jérémy Munsch 481d80c8ea
refactor(requests): simplify nutritional values handling in product requests
Updated the handling of nutritional values in StoreProductRequest and UpdateProductRequest
to improve clarity and maintainability. The logic for processing nutritional values has been
centralized into a dedicated method, enhancing code readability.
2026-11-05 13:25:39 +01:00

177 lines
6.2 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Http\Middlewares;
use Illuminate\Contracts\Container\Container;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Vite;
use Illuminate\Support\Str;
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');
$s3PublicEndpoint = Str::of(Config::string('filesystems.disks.s3.url'))
->chopEnd('/')->append('/')->value();
// * 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',
$s3PublicEndpoint,
$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);
}
}