fix(files): general.host must be a bare hostname, not a URL
The SPA computes its redirect origin as (force_ssl ? "https://" : "http://") + general.host. With host set to "https://files.${BASE_DOMAIN}" that produced "http://https://files.ulicraft.net", so every client-side redirect broke and the page never routed anywhere. Server-side this was invisible: SecureOrigin strips the scheme before the Host check, so `curl /` returned 200, the API answered, and the uptime monitor stayed green — only real browsers failed. The tell is a `POST /report?...msg=Redirecting to http://https://...` line in the log. Set host to the bare hostname and force_ssl=true (which the origin needs to build https://, and which only emits an HSTS header — it does not redirect, so it can't loop behind the plain-http nginx→caddy hop). Verified: origin is now "https://files.ulicraft.net"; login + ls still work. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"general": {
|
"general": {
|
||||||
"name": "Ulicraft Files",
|
"name": "Ulicraft Files",
|
||||||
"host": "https://files.${BASE_DOMAIN}",
|
"host": "files.${BASE_DOMAIN}",
|
||||||
|
"force_ssl": true,
|
||||||
"secret_key": "${FILES_SECRET_KEY}",
|
"secret_key": "${FILES_SECRET_KEY}",
|
||||||
"editor": "base",
|
"editor": "base",
|
||||||
"display_hidden": false,
|
"display_hidden": false,
|
||||||
|
|||||||
@@ -121,12 +121,35 @@ config nobody can log into. (The *admin* password is checked by Go's bcrypt
|
|||||||
directly, which does accept `$2y$` — but `caddy hash-password` gives `$2a$`
|
directly, which does accept `$2y$` — but `caddy hash-password` gives `$2a$`
|
||||||
anyway, so just use that for both.)
|
anyway, so just use that for both.)
|
||||||
|
|
||||||
### `general.host` must match the incoming `Host` header
|
### `general.host` is a BARE HOSTNAME — a scheme breaks the browser only
|
||||||
Filestash blocks every non-`/admin/` request whose `Host` differs from its
|
`"host": "files.ulicraft.net"`, **not** `"https://files.ulicraft.net"`, plus
|
||||||
configured `general.host`, with *"only traffic from X is allowed"*. It's a 403
|
`"force_ssl": true`. The SPA computes its redirect origin as
|
||||||
that looks like an auth failure. nginx forwards the original Host and caddy
|
`(force_ssl ? "https://" : "http://") + host`, so a scheme in `host` yields
|
||||||
passes it through — don't rewrite it anywhere. If `files.` starts 403ing after a
|
**`http://https://files.ulicraft.net`** and every client-side redirect dies —
|
||||||
domain change, this is why.
|
the page loads, then silently fails to route anywhere.
|
||||||
|
|
||||||
|
The vicious part: the server-side Host check (`SecureOrigin`) **strips** the
|
||||||
|
scheme before comparing, so the server is perfectly happy. `curl /` returns 200,
|
||||||
|
the API answers, and the Uptime Kuma monitor stays green — **only real browsers
|
||||||
|
break**. Symptom in the log is a `POST /report?...msg=Redirecting to
|
||||||
|
http://https://...` line. Hit this on 2026-07-14; fixed in `config.json.tmpl`.
|
||||||
|
|
||||||
|
`force_ssl` only sets an HSTS header — it does **not** redirect, so it can't loop
|
||||||
|
behind the nginx→caddy (plain http) hop. It must be `true` or the SPA builds
|
||||||
|
`http://` origins on an https page.
|
||||||
|
|
||||||
|
### `general.host` must also match the incoming `Host` header
|
||||||
|
Filestash blocks every non-`/admin/` request whose `Host` differs from
|
||||||
|
`general.host`, with *"only traffic from X is allowed"* — a 403 that looks like
|
||||||
|
an auth failure. nginx forwards the original Host and caddy passes it through —
|
||||||
|
don't rewrite it anywhere.
|
||||||
|
|
||||||
|
### The login form only renders at `?action=redirect`
|
||||||
|
`GET /api/session/auth/` renders the htpasswd form **only** when
|
||||||
|
`action=redirect` is present; any other request falls through to the credential
|
||||||
|
check, fails with empty creds, and 303s back to `?action=redirect`. So the real
|
||||||
|
form URL is `/api/session/auth/?action=redirect&label=files`. A bare
|
||||||
|
`/api/session/auth/?label=files` returning a 303 is **normal**, not a bug.
|
||||||
|
|
||||||
### `config.json` is a single-file bind mount → stale inode
|
### `config.json` is a single-file bind mount → stale inode
|
||||||
Same shape as the caddy `conf.d` gotcha. The mount pins the **inode**, so
|
Same shape as the caddy `conf.d` gotcha. The mount pins the **inode**, so
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ render nmsr/config.toml.tmpl nmsr/config.toml
|
|||||||
# admin console writes it back at runtime. We render it and mount it read-only,
|
# admin console writes it back at runtime. We render it and mount it read-only,
|
||||||
# so git stays authoritative (the console renders but cannot save — by design).
|
# so git stays authoritative (the console renders but cannot save — by design).
|
||||||
#
|
#
|
||||||
|
# general.host in the template is a BARE HOSTNAME (files.$BASE_DOMAIN, no
|
||||||
|
# scheme) and general.force_ssl is true. The SPA builds its redirect origin as
|
||||||
|
# (force_ssl ? "https://" : "http://") + host, so a scheme in `host` produces
|
||||||
|
# "http://https://files..." and every client-side redirect breaks. The
|
||||||
|
# server-side Host check strips the scheme first, so curl/monitors see a healthy
|
||||||
|
# 200 and ONLY browsers fail. Don't "fix" it by putting the URL back.
|
||||||
|
#
|
||||||
# FILES_AUTH picks the identity_provider block. The params fields are JSON
|
# FILES_AUTH picks the identity_provider block. The params fields are JSON
|
||||||
# *strings* holding escaped JSON (that's Filestash's schema, not a typo) —
|
# *strings* holding escaped JSON (that's Filestash's schema, not a typo) —
|
||||||
# built here rather than in the template because the shape differs per mode.
|
# built here rather than in the template because the shape differs per mode.
|
||||||
|
|||||||
Reference in New Issue
Block a user