# FrankenPHP Caddyfile for the ui container.
# Serves Slim from public/ on :8080.
{
    frankenphp
    order php_server before file_server
    auto_https off
    admin off
    servers {
        trusted_proxies static private_ranges
    }
}

:8080 {
    root * /app/public
    encode zstd gzip

    # ── Security headers (M14) ──────────────────────────────────────────
    # CSP is set per-response by `App\Http\CspMiddleware` so the
    # `script-src 'nonce-…'` value can change per request, dropping
    # `'unsafe-inline'` / `'unsafe-eval'` (SEC_REVIEW F24).
    header {
        -Server
        -X-Powered-By
        X-Content-Type-Options "nosniff"
        X-Frame-Options "DENY"
        Referrer-Policy "strict-origin-when-cross-origin"
        # SEC_REVIEW F61: extended deny-list for browser features
        # the admin UI never uses. The narrow `geolocation=(),
        # microphone=(), camera=()` was the M14 starter; this is the
        # full hardening list. `clipboard-read` is blocked but
        # `clipboard-write` is left at its same-origin default so
        # the "copy raw token" button (rawTokenCopy Alpine
        # component) still works on the Tokens page.
        Permissions-Policy "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), bluetooth=(), camera=(), clipboard-read=(), display-capture=(), encrypted-media=(), fullscreen=(), gamepad=(), geolocation=(), gyroscope=(), hid=(), idle-detection=(), interest-cohort=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), screen-wake-lock=(), serial=(), speaker-selection=(), usb=(), web-share=(), xr-spatial-tracking=()"
        # SEC_REVIEW F59: modern cross-origin isolation headers.
        # - COOP `same-origin` isolates the browsing context from any
        #   popups it opens; a `window.opener.location = …` from a
        #   newly-spawned cross-origin tab can no longer reach back.
        # - CORP `same-origin` tells the browser this resource may
        #   only be loaded by same-origin documents (defeats sub-
        #   resource leaks via cross-origin <img>/<script>/<link>
        #   inclusion).
        # - X-Permitted-Cross-Domain-Policies `none` blocks legacy
        #   Adobe Flash / Acrobat cross-domain.xml lookups.
        # COEP `require-corp` is deliberately NOT set — that requires
        # every cross-origin resource (e.g. the jsDelivr-hosted
        # RapiDoc on /api/docs) to opt in via CORP, which we don't
        # control. We're only after the COOP/CORP/legacy-Flash
        # benefits the SEC_REVIEW called out.
        Cross-Origin-Opener-Policy "same-origin"
        Cross-Origin-Resource-Policy "same-origin"
        X-Permitted-Cross-Domain-Policies "none"
    }

    # HSTS: prod-only (setting it in dev would lock you out of plain-HTTP
    # localhost on the same hostname for a year). The value is operator-
    # tuneable via the `HSTS_HEADER` env var so a deployment that wants
    # to apply for the browser preload list can opt in by setting:
    #     HSTS_HEADER="max-age=31536000; includeSubDomains; preload"
    # Default keeps the conservative no-preload value — preload-listing
    # is a one-way commitment (browser preload removals take months) so
    # we don't enable it by default (SEC_REVIEW F60).
    @prod expression `{env.APP_ENV} == "production"`
    header @prod Strict-Transport-Security "{$HSTS_HEADER:max-age=31536000; includeSubDomains}"

    # Public /about landing page — served as the static about.html file
    # so it works without going through Slim or the auth middleware.
    @about path /about
    rewrite @about /about.html

    php_server
}
