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:
@@ -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.
|
||||
Reference in New Issue
Block a user