From 8234ed3510bbeb8170e0f1757a988aaf636cf141 Mon Sep 17 00:00:00 2001 From: WuYongJie <549727955@qq.com> Date: Mon, 27 Jul 2026 10:56:43 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=9D=E6=8C=81=E4=B8=83=E7=89=9B?= =?UTF-8?q?=E9=9D=99=E6=80=81=E8=B5=84=E6=BA=90=E6=A1=B6=E7=A7=81=E6=9C=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Middleware/EnforceLocalOnly.php | 12 ++- config/app.php | 6 +- manifest.json | 38 +++++----- scripts/sync-static-assets-to-qiniu.ps1 | 48 ++++++++++-- serviceworker.js | 4 +- .../Http/Middleware/EnforceLocalOnlyTest.php | 76 ------------------- 6 files changed, 68 insertions(+), 116 deletions(-) delete mode 100644 tests/Unit/Http/Middleware/EnforceLocalOnlyTest.php diff --git a/app/Http/Middleware/EnforceLocalOnly.php b/app/Http/Middleware/EnforceLocalOnly.php index 3acbb1f3..15bfab85 100644 --- a/app/Http/Middleware/EnforceLocalOnly.php +++ b/app/Http/Middleware/EnforceLocalOnly.php @@ -15,20 +15,18 @@ class EnforceLocalOnly return $response; } - $staticOrigin = rtrim((string) config('app.asset_url'), '/'); - $response->headers->set('Content-Security-Policy', implode('; ', [ "default-src 'self'", "base-uri 'self'", "connect-src 'self'", - "font-src 'self' {$staticOrigin} data:", + "font-src 'self' data:", "form-action 'self'", "frame-src 'self'", - "img-src 'self' {$staticOrigin} data: blob:", - "media-src 'self' {$staticOrigin}", + "img-src 'self' data: blob:", + "media-src 'self'", "object-src 'none'", - "script-src 'self' {$staticOrigin} 'unsafe-inline' 'unsafe-eval'", - "style-src 'self' {$staticOrigin} 'unsafe-inline'", + "script-src 'self' 'unsafe-inline' 'unsafe-eval'", + "style-src 'self' 'unsafe-inline'", "navigate-to 'self'", ])); diff --git a/config/app.php b/config/app.php index 7c9c6432..71ba6c27 100644 --- a/config/app.php +++ b/config/app.php @@ -77,11 +77,7 @@ return [ 'url' => env('APP_URL', ''), - /* - * 项目静态资源统一由唯一的七牛云空间 caiwu-jiansun 提供。 - * 不允许通过环境变量切换到其他存储桶,避免不同环境引用不同资源。 - */ - 'asset_url' => 'https://caiwu.jiansun.vip', + 'asset_url' => env('ASSET_URL'), /* |-------------------------------------------------------------------------- diff --git a/manifest.json b/manifest.json index e078abcd..f2175ebb 100644 --- a/manifest.json +++ b/manifest.json @@ -25,48 +25,48 @@ } ], "splash": { - "640x1136": "https://caiwu.jiansun.vip/public/img/pwa/splash-640x1136.png", - "750x1334": "https://caiwu.jiansun.vip/public/img/pwa/splash-750x1334.png", - "828x1792": "https://caiwu.jiansun.vip/public/img/pwa/splash-828x1792.png", - "1242x2208": "https://caiwu.jiansun.vip/public/img/pwa/splash-1242x2208.png", - "1242x2688": "https://caiwu.jiansun.vip/public/img/pwa/splash-1242x2688.png", - "1536x2048": "https://caiwu.jiansun.vip/public/img/pwa/splash-1536x2048.png", - "1668x2224": "https://caiwu.jiansun.vip/public/img/pwa/splash-1668x2224.png", - "1668x2388": "https://caiwu.jiansun.vip/public/img/pwa/splash-1668x2388.png", - "2048x2732": "https://caiwu.jiansun.vip/public/img/pwa/splash-2048x2732.png" + "640x1136": "public/img/pwa/splash-640x1136.png", + "750x1334": "public/img/pwa/splash-750x1334.png", + "828x1792": "public/img/pwa/splash-828x1792.png", + "1242x2208": "public/img/pwa/splash-1242x2208.png", + "1242x2688": "public/img/pwa/splash-1242x2688.png", + "1536x2048": "public/img/pwa/splash-1536x2048.png", + "1668x2224": "public/img/pwa/splash-1668x2224.png", + "1668x2388": "public/img/pwa/splash-1668x2388.png", + "2048x2732": "public/img/pwa/splash-2048x2732.png" }, "icons": [ { - "src": "https://caiwu.jiansun.vip/public/img/pwa/icon-192x192.png", + "src": "public/img/pwa/icon-192x192.png", "type": "image/png", "sizes": "192x192" }, { "purpose": "maskable", - "src": "https://caiwu.jiansun.vip/public/img/pwa/icon-192x192-maskable.png", + "src": "public/img/pwa/icon-192x192-maskable.png", "type": "image/png", "sizes": "192x192" }, { - "src": "https://caiwu.jiansun.vip/public/img/pwa/icon-512x512.png", + "src": "public/img/pwa/icon-512x512.png", "sizes": "512x512", "type": "image/png" }, { "purpose": "maskable", - "src": "https://caiwu.jiansun.vip/public/img/pwa/icon-512x512-maskable.png", + "src": "public/img/pwa/icon-512x512-maskable.png", "sizes": "512x512", "type": "image/png" } ], "screenshots": [ { - "src": "https://caiwu.jiansun.vip/public/img/pwa/screenshot-dashboard.png", + "src": "public/img/pwa/screenshot-dashboard.png", "sizes": "1932x1394", "type": "image/png" }, { - "src": "https://caiwu.jiansun.vip/public/img/pwa/screenshot-invoice.png", + "src": "public/img/pwa/screenshot-invoice.png", "sizes": "2748x1986", "type": "image/png" } @@ -79,7 +79,7 @@ "url": "sales/invoices/create?utm_source=pwa", "icons": [ { - "src": "https://caiwu.jiansun.vip/public/img/pwa/icon-192x192.png", + "src": "public/img/pwa/icon-192x192.png", "sizes": "192x192" } ] @@ -91,7 +91,7 @@ "url": "banking/transactions/create?type=income&utm_source=pwa", "icons": [ { - "src": "https://caiwu.jiansun.vip/public/img/pwa/icon-192x192.png", + "src": "public/img/pwa/icon-192x192.png", "sizes": "192x192" } ] @@ -103,7 +103,7 @@ "url": "purchases/bills/create?utm_source=pwa", "icons": [ { - "src": "https://caiwu.jiansun.vip/public/img/pwa/icon-192x192.png", + "src": "public/img/pwa/icon-192x192.png", "sizes": "192x192" } ] @@ -115,7 +115,7 @@ "url": "banking/transactions/create?type=expense&utm_source=pwa", "icons": [ { - "src": "https://caiwu.jiansun.vip/public/img/pwa/icon-192x192.png", + "src": "public/img/pwa/icon-192x192.png", "sizes": "192x192" } ] diff --git a/scripts/sync-static-assets-to-qiniu.ps1 b/scripts/sync-static-assets-to-qiniu.ps1 index 164fff8a..f8d6b3d1 100644 --- a/scripts/sync-static-assets-to-qiniu.ps1 +++ b/scripts/sync-static-assets-to-qiniu.ps1 @@ -107,7 +107,7 @@ function Get-QiniuBucketNames { ) } -function Set-QiniuBucketPublic { +function Set-QiniuBucketPrivate { param( [Parameter(Mandatory = $true)] [string]$AccessKey, @@ -115,7 +115,7 @@ function Set-QiniuBucketPublic { [string]$SecretKey ) - $uri = [Uri]"https://uc.qiniuapi.com/private?bucket=$([Uri]::EscapeDataString($bucket))&private=0" + $uri = [Uri]"https://uc.qiniuapi.com/private?bucket=$([Uri]::EscapeDataString($bucket))&private=1" $headers = Get-QiniuManagementHeaders ` -Method 'POST' ` -Uri $uri ` @@ -129,10 +129,31 @@ function Set-QiniuBucketPublic { -ContentType 'application/x-www-form-urlencoded' ` -SkipHttpErrorCheck if ($response.StatusCode -notin @(200, 204)) { - throw "无法将项目静态资源桶设置为公开读取:HTTP $($response.StatusCode)。" + throw "无法保持项目静态资源桶为私有读取:HTTP $($response.StatusCode)。" } } +function New-QiniuPrivateDownloadUrl { + param( + [Parameter(Mandatory = $true)] + [string]$AccessKey, + [Parameter(Mandatory = $true)] + [string]$SecretKey, + [Parameter(Mandatory = $true)] + [string]$Url, + [int]$ExpiresInSeconds = 300 + ) + + $deadline = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() + $ExpiresInSeconds + $separator = if ($Url.Contains('?')) { '&' } else { '?' } + $urlToSign = "${Url}${separator}e=$deadline" + $signature = Get-HmacSha1Signature ` + -SecretKey $SecretKey ` + -Value $urlToSign + + return "${urlToSign}&token=${AccessKey}:$signature" +} + function Get-QiniuRemoteFiles { param( [Parameter(Mandatory = $true)] @@ -378,7 +399,7 @@ if ($bucketNames -notcontains $bucket) { throw "当前七牛云账号中不存在项目唯一存储桶 $bucket。" } -Set-QiniuBucketPublic -AccessKey $accessKey -SecretKey $secretKey +Set-QiniuBucketPrivate -AccessKey $accessKey -SecretKey $secretKey $sources = Get-StaticAssetSources $remoteFiles = Get-QiniuRemoteFiles -AccessKey $accessKey -SecretKey $secretKey @@ -420,7 +441,20 @@ if (-not $sources.ContainsKey($probeKey)) { } $probeHash = (Get-FileHash -LiteralPath $sources[$probeKey] -Algorithm MD5).Hash.ToLowerInvariant() -$probeUri = "${assetDomain}/${probeKey}?check=${probeHash}" +$unsignedProbeUri = "${assetDomain}/${probeKey}?check=${probeHash}" +$unsignedProbeResponse = Invoke-WebRequest ` + -Uri $unsignedProbeUri ` + -Method Head ` + -TimeoutSec 20 ` + -SkipHttpErrorCheck +if ($unsignedProbeResponse.StatusCode -notin @(401, 403)) { + throw "七牛云存储桶 $bucket 未保持私有读取,匿名请求返回 HTTP $($unsignedProbeResponse.StatusCode)。" +} + +$probeUri = New-QiniuPrivateDownloadUrl ` + -AccessKey $accessKey ` + -SecretKey $secretKey ` + -Url $unsignedProbeUri $probeStatus = 0 foreach ($attempt in 1..10) { $probeResponse = Invoke-WebRequest ` @@ -435,7 +469,7 @@ foreach ($attempt in 1..10) { Start-Sleep -Seconds 2 } if ($probeStatus -ne 200) { - throw "七牛云资源无法通过正式 HTTPS 域名读取:HTTP $probeStatus。请检查唯一存储桶 $bucket 的公开权限和域名证书。" + throw "七牛云私有资源无法通过带签名的正式 HTTPS 域名读取:HTTP $probeStatus。" } -Write-Host "七牛云静态资源同步完成:桶=$bucket,上传=$uploaded,跳过=$skipped,总数=$($sources.Count)。" +Write-Host "七牛云静态资源同步完成:私有桶=$bucket,上传=$uploaded,跳过=$skipped,总数=$($sources.Count)。" diff --git a/serviceworker.js b/serviceworker.js index c6a3c5f0..bcca3fb2 100644 --- a/serviceworker.js +++ b/serviceworker.js @@ -1,7 +1,7 @@ var staticCacheName = "pwa-v2"; var filesToCache = [ - 'https://caiwu.jiansun.vip/public/img/pwa/icon-192x192.png', - 'https://caiwu.jiansun.vip/public/img/pwa/icon-512x512.png' + '/public/img/pwa/icon-192x192.png', + '/public/img/pwa/icon-512x512.png' ]; // Cache on install diff --git a/tests/Unit/Http/Middleware/EnforceLocalOnlyTest.php b/tests/Unit/Http/Middleware/EnforceLocalOnlyTest.php deleted file mode 100644 index 4ba1bf71..00000000 --- a/tests/Unit/Http/Middleware/EnforceLocalOnlyTest.php +++ /dev/null @@ -1,76 +0,0 @@ -container = new Container(); - $this->container->instance('config', new Repository([ - 'app' => [ - 'offline' => true, - 'asset_url' => 'https://caiwu.jiansun.vip', - ], - ])); - - Container::setInstance($this->container); - } - - protected function tearDown(): void - { - Container::setInstance(null); - - parent::tearDown(); - } - - public function test_offline_policy_allows_the_fixed_qiniu_asset_origin(): void - { - $response = (new EnforceLocalOnly())->handle( - Request::create('/auth/login'), - fn () => new Response('ok') - ); - - $policy = (string) $response->headers->get('Content-Security-Policy'); - - $this->assertStringContainsString( - "script-src 'self' https://caiwu.jiansun.vip", - $policy - ); - $this->assertStringContainsString( - "style-src 'self' https://caiwu.jiansun.vip", - $policy - ); - $this->assertStringContainsString( - "font-src 'self' https://caiwu.jiansun.vip", - $policy - ); - $this->assertStringContainsString( - "img-src 'self' https://caiwu.jiansun.vip", - $policy - ); - } - - public function test_online_mode_does_not_add_an_offline_policy(): void - { - $this->container->make('config')->set('app.offline', false); - - $response = (new EnforceLocalOnly())->handle( - Request::create('/auth/login'), - fn () => new Response('ok') - ); - - $this->assertFalse($response->headers->has('Content-Security-Policy')); - } -}