|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace App\Http\Middleware; |
|
4
|
|
|
|
|
5
|
|
|
use Closure; |
|
6
|
|
|
use Illuminate\Http\Request; |
|
7
|
|
|
use Symfony\Component\HttpFoundation\Response; |
|
8
|
|
|
|
|
9
|
|
|
class ContentSecurityPolicy |
|
10
|
|
|
{ |
|
11
|
|
|
/** |
|
12
|
|
|
* Handle an incoming request. |
|
13
|
|
|
*/ |
|
14
|
|
|
public function handle(Request $request, Closure $next): Response |
|
15
|
|
|
{ |
|
16
|
|
|
$response = $next($request); |
|
17
|
|
|
|
|
18
|
|
|
// Check if Turnstile is enabled |
|
19
|
|
|
$turnstileEnabled = config('captcha.provider') === 'turnstile' |
|
20
|
|
|
&& config('captcha.turnstile.enabled') === true; |
|
21
|
|
|
|
|
22
|
|
|
// If Turnstile is enabled, skip CSP to avoid sandbox iframe issues |
|
23
|
|
|
// Turnstile creates sandboxed iframes that conflict with strict CSP |
|
24
|
|
|
if ($turnstileEnabled) { |
|
25
|
|
|
// Set minimal security headers only |
|
26
|
|
|
$response->headers->set('X-Frame-Options', 'SAMEORIGIN'); |
|
27
|
|
|
$response->headers->set('X-Content-Type-Options', 'nosniff'); |
|
28
|
|
|
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin'); |
|
29
|
|
|
|
|
30
|
|
|
return $response; |
|
31
|
|
|
} |
|
32
|
|
|
|
|
33
|
|
|
// Generate nonce for inline scripts |
|
34
|
|
|
$nonce = function_exists('csp_nonce') ? csp_nonce() : base64_encode(random_bytes(16)); |
|
35
|
|
|
|
|
36
|
|
|
// Build CSP directives for non-Turnstile pages |
|
37
|
|
|
$directives = [ |
|
38
|
|
|
"default-src 'self'", |
|
39
|
|
|
"script-src 'self' 'nonce-{$nonce}' 'unsafe-inline' 'unsafe-eval' https://www.google.com https://www.gstatic.com https://cdn.jsdelivr.net https://unpkg.com blob:", |
|
40
|
|
|
"script-src-elem 'self' 'nonce-{$nonce}' 'unsafe-inline' https://www.google.com https://www.gstatic.com https://cdn.jsdelivr.net https://unpkg.com", |
|
41
|
|
|
"script-src-attr 'unsafe-inline'", |
|
42
|
|
|
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com", |
|
43
|
|
|
"font-src 'self' https://fonts.gstatic.com data:", |
|
44
|
|
|
"img-src 'self' data: https: blob:", |
|
45
|
|
|
"connect-src 'self' https://www.google.com", |
|
46
|
|
|
"frame-src 'self' https://www.google.com https://www.gstatic.com data: blob:", |
|
47
|
|
|
"child-src 'self' https://www.google.com blob:", |
|
48
|
|
|
"worker-src 'self' blob:", |
|
49
|
|
|
"object-src 'none'", |
|
50
|
|
|
"base-uri 'self'", |
|
51
|
|
|
"form-action 'self'", |
|
52
|
|
|
"frame-ancestors 'self'", |
|
53
|
|
|
]; |
|
54
|
|
|
|
|
55
|
|
|
$csp = implode('; ', $directives); |
|
56
|
|
|
|
|
57
|
|
|
$response->headers->set('Content-Security-Policy', $csp); |
|
58
|
|
|
|
|
59
|
|
|
// Also set X-Frame-Options for older browsers |
|
60
|
|
|
$response->headers->set('X-Frame-Options', 'SAMEORIGIN'); |
|
61
|
|
|
|
|
62
|
|
|
// Set X-Content-Type-Options |
|
63
|
|
|
$response->headers->set('X-Content-Type-Options', 'nosniff'); |
|
64
|
|
|
|
|
65
|
|
|
// Set Referrer-Policy |
|
66
|
|
|
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin'); |
|
67
|
|
|
|
|
68
|
|
|
return $response; |
|
69
|
|
|
} |
|
70
|
|
|
} |
|
71
|
|
|
|