diff --git a/dankclaw/Caddyfile b/dankclaw/Caddyfile index a14d157..0f36be7 100644 --- a/dankclaw/Caddyfile +++ b/dankclaw/Caddyfile @@ -46,17 +46,41 @@ # --- Homepage (password protected) --- dankclaw.franksdank.cloud { - @admin path /admin/* - @user not path /admin/* - basicauth @user { - dank REPLACE_WITH_HASH - } - basicauth @admin { - frank REPLACE_WITH_ADMIN_HASH - } - rewrite @admin /admin.html root * /srv/homepage - file_server + + # Operator-only admin page (Q19). Auth is checked on the original path, + # before any rewrite, so the path matcher is reliable. Caddy sorts a + # top-level 'rewrite' before 'basicauth'; that would change the path and + # break the path-based auth matchers. 'handle' preserves order. + handle /admin* { + basicauth { + frank REPLACE_WITH_ADMIN_HASH + } + try_files /admin.html + file_server + } + + # Shared data + assets: the family OR the operator, so both pages can load + # them (the browser reuses the page's credential for these fetches). A + # 'handle' inline matcher is a single token, so the multi-path match is a + # named matcher. + @data path /services.json /status.json /app.js /style.css /deployed.js + handle @data { + basicauth { + dank REPLACE_WITH_HASH + frank REPLACE_WITH_ADMIN_HASH + } + file_server + } + + # Everything else (the user page): the family (Q18). + handle { + basicauth { + dank REPLACE_WITH_HASH + } + try_files /index.html + file_server + } } # --- Legacy name: the media host has no Caddy of its own anymore --- diff --git a/tests/homepage_caddy.bats b/tests/homepage_caddy.bats index 0a30ff5..1f871d7 100644 --- a/tests/homepage_caddy.bats +++ b/tests/homepage_caddy.bats @@ -16,8 +16,12 @@ setup() { } @test "the admin basicauth is scoped to /admin/ (Q19)" { - grep -qE "@admin[[:space:]]+path[[:space:]]+/admin/" "$CADDY" - grep -qE "basicauth[[:space:]]+@admin" "$CADDY" + # The admin page is a 'handle /admin*' block with the operator (frank) + # credential. Caddy sorts a top-level 'rewrite' before 'basicauth', which + # would change the path and break the path-based auth matcher; 'handle' + # preserves order, so the auth is checked on the original path. + grep -qE "handle[[:space:]]+/admin\*" "$CADDY" + grep -qE "frank[[:space:]]+REPLACE_WITH_ADMIN_HASH" "$CADDY" } @test "the homepage serves /srv/homepage" { @@ -25,9 +29,19 @@ setup() { grep -q "file_server" "$CADDY" } -@test "the /admin/ path is rewritten to admin.html" { - # The matcher token follows the directive (rewrite @admin ...), per the - # Caddyfile matcher docs. A leading '@admin rewrite' is parsed as a - # one-liner matcher DEFINITION and re-defines @admin (invalid). - grep -qE "rewrite[[:space:]]+@admin[[:space:]]+/admin.html" "$CADDY" +@test "the /admin/ page serves admin.html and / serves index.html" { + # Each page is served by its own 'handle' block via 'try_files' (which + # serves the named file without a top-level 'rewrite' that Caddy would + # sort before the basicauth). + grep -qE "try_files[[:space:]]+/admin.html" "$CADDY" + grep -qE "try_files[[:space:]]+/index.html" "$CADDY" +} + +@test "the shared data + assets accept both the family and the operator" { + # The admin page (frank) and the user page (dank) both fetch the shared + # data (services.json, status.json) and assets. The browser reuses the + # page's credential for these, so the block must accept both users. + grep -qE "@data[[:space:]]+path[[:space:]]+/services\.json[[:space:]]+/status\.json" "$CADDY" + grep -qE "handle[[:space:]]+@data" "$CADDY" + grep -qE "dank[[:space:]]+REPLACE_WITH_HASH" "$CADDY" }