diff --git a/deploy/Caddyfile.example b/deploy/Caddyfile.example index 32557e8..e3557f1 100644 --- a/deploy/Caddyfile.example +++ b/deploy/Caddyfile.example @@ -11,6 +11,14 @@ primeintellectgrowth.com, www.primeintellectgrowth.com { # app so it shares the session and needs no CORS allowance. reverse_proxy 127.0.0.1:8920 + + # The inline script hash covers the pre-paint theme script in index.html, + # which sets light/dark before first paint so dark-mode users do not get a + # white flash. It cannot be an external file without reintroducing that + # flash, and it cannot use 'unsafe-inline' without defeating the CSP. + # + # IMPORTANT: editing that script changes its hash and CSP will silently + # block it. The browser console says exactly which hash it wants. header { Strict-Transport-Security "max-age=31536000; includeSubDomains" X-Content-Type-Options "nosniff" @@ -18,7 +26,7 @@ primeintellectgrowth.com, www.primeintellectgrowth.com { Referrer-Policy "strict-origin-when-cross-origin" # The app is entirely first-party except for the auth provider, which # it must reach over XHR. - Content-Security-Policy "default-src 'self'; connect-src 'self' https://*.supabase.co; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'; frame-ancestors 'none'; base-uri 'self'" + Content-Security-Policy "default-src 'self'; connect-src 'self' https://*.supabase.co; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self' 'sha256-1tTDwCq+TCEyPDSZeYqW5HbmP+unUg8hrgRiZBiH/IU='; frame-ancestors 'none'; base-uri 'self'" -Server } } diff --git a/deploy/README.md b/deploy/README.md index 4270dfa..b79be3c 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -46,6 +46,21 @@ will adopt its volumes, which is a memorable way to lose a database. See `Caddyfile.example`. Serve the app and API from the **same** origin. +Two things that will otherwise cost you an hour: + +- **If other sites on the host use `bind
`, yours must too.** Caddy + groups site blocks into servers by listen address. A block without `bind` + lands in a *separate* server on `:443`, and the more specific listener wins + for traffic arriving on that address — which is all public traffic after NAT. + The symptom is a valid certificate, a 200 response, an empty body, and none + of your headers. It looks like the app is broken; it is that the request + never reached it. + +- **The CSP must carry the hash of the inline theme script** in `index.html`. + That script sets light or dark before first paint so dark-mode users do not + get a white flash. Editing it changes the hash and CSP will silently block + it — the browser console prints the hash it expects. + ## 5. Verify ```bash