fix(storage): emit origin-relative object URLs

Build signed object URLs as path-only (/api/objects/...) instead of
prefixing APP_BASE_URL, so media loads from whatever host or reverse
proxy served the app. Remove the now-unused base URL from the signer and
drop the APP_BASE_URL / Server.BaseURL config.
This commit is contained in:
iu
2026-10-12 12:14:46 +03:00
parent 4d0e24347c
commit eef7c6c5d0
18 changed files with 206 additions and 39 deletions
@@ -0,0 +1,69 @@
# Design
## Context
See `proposal.md` - Why. Relevant current state:
- `storage.Signer.URL` builds `baseURL + "/api/objects/<key>?e&s"` (`signer.go`), with
`baseURL` from `cfg.Server.BaseURL` (`APP_BASE_URL`, default `http://localhost:8080`).
- That absolute URL is the only use of `Server.BaseURL`; SSE and API traffic are already
root-relative (`/api/...`).
- `photos.Service.attachURLs` runs outside an HTTP request, so it cannot see the request
host to derive a base.
## Goals / Non-Goals
**Goals:**
- Photo/preview URLs work on any host or reverse proxy with no per-deployment base URL.
- Preserve signing, expiry, quantization and caching behavior exactly.
**Non-Goals:**
- Serving the app under a URL subpath.
- Absolute URL generation for non-browser consumers (no such consumer today).
- Changing the object route, storage backends, or delivery semantics.
## Decisions
### Emit origin-relative (path-only) object URLs
`Signer.URL(key)` returns `"/api/objects/<escaped-key>?e=<exp>&s=<sig>"`. A leading slash
makes it origin-relative: the browser resolves it against the page's scheme/host/port,
which is exactly the backend that served the page.
- Alternative considered: keep absolute URLs and require operators to set
`APP_BASE_URL`. Rejected - this is the current footgun; a wrong or default value
silently breaks all images.
- Alternative considered: derive the base from the request (`Host`,
`X-Forwarded-Proto`). Rejected - URLs are minted in `attachURLs`/SSE paths that have no
request; threading a base through every service is invasive for no benefit once URLs
are relative.
- Alternative considered: document-relative URLs without a leading slash. Rejected - the
app and its API calls already use root-relative paths, so root-relative is consistent.
### Remove the base URL from the signer and config
Drop the `baseURL` field and parameter from `Signer`/`NewSigner`, stop reading
`cfg.Server.BaseURL` in `storage.Open`, and remove `Server.BaseURL` from config,
`.env.example` and the README. Leaving a now-unused variable would invite the same
misconfiguration.
### Verification unchanged
`VerifyRequest` checks the key and query (`e`, `s`) only; it never used the host, so
signing and validation are untouched.
## Risks / Trade-offs
- [Subpath deployment (`https://host/galo/`) would break with root-relative URLs] ->
The app already assumes root (`/api/...`); subpath support would be a separate change
adding a base-path prefix. Documented as out of scope.
- [Non-browser clients need a base to resolve object URLs] -> None consume them today;
if one appears, it resolves against the instance base like any HTTP client.
- [Tests that assumed an absolute URL] -> They parse the URL and use `Path`/`RawQuery`;
update `NewSigner` call sites to drop the base argument.
## Migration Plan
Remove `APP_BASE_URL` from the server environment and deploy the backend. No data or
schema change. Rollback is reverting the deploy.
## Open Questions
None blocking.