Send CORS headers only from the image routes (closes #98) #162
@@ -238,7 +238,7 @@ variables set by the file's `env:` section are checked the same way.
|
|||||||
| `PIXA_UPSTREAM_FETCH_TIMEOUT` | `upstream_fetch_timeout` | Time allowed for one fetch from an upstream host; default `30s` |
|
| `PIXA_UPSTREAM_FETCH_TIMEOUT` | `upstream_fetch_timeout` | Time allowed for one fetch from an upstream host; default `30s` |
|
||||||
| `PIXA_UPSTREAM_MAX_RESPONSE_SIZE` | `upstream_max_response_size` | Largest upstream response accepted, in bytes; default 50 MiB |
|
| `PIXA_UPSTREAM_MAX_RESPONSE_SIZE` | `upstream_max_response_size` | Largest upstream response accepted, in bytes; default 50 MiB |
|
||||||
| `PIXA_DOWNSTREAM_TIMEOUT` | `downstream_timeout` | Time allowed for answering one client request; default `60s` |
|
| `PIXA_DOWNSTREAM_TIMEOUT` | `downstream_timeout` | Time allowed for answering one client request; default `60s` |
|
||||||
| `PIXA_ACCESS_CONTROL_ALLOW_ORIGIN` | `access_control_allow_origin` | CORS origin allowed to read responses: `*` or one origin; default `*` |
|
| `PIXA_ACCESS_CONTROL_ALLOW_ORIGIN` | `access_control_allow_origin` | CORS origin allowed to read image responses: `*` or one origin; default `*` |
|
||||||
| `PIXA_METRICS_USERNAME` | `metrics.username` | Username for `/metrics`, which is served only when both are set |
|
| `PIXA_METRICS_USERNAME` | `metrics.username` | Username for `/metrics`, which is served only when both are set |
|
||||||
| `PIXA_METRICS_PASSWORD` | `metrics.password` | Password for `/metrics`; set together with the username |
|
| `PIXA_METRICS_PASSWORD` | `metrics.password` | Password for `/metrics`; set together with the username |
|
||||||
| `PIXA_SENTRY_DSN` | `sentry_dsn` | Sentry DSN for error reporting; empty disables it |
|
| `PIXA_SENTRY_DSN` | `sentry_dsn` | Sentry DSN for error reporting; empty disables it |
|
||||||
@@ -247,8 +247,9 @@ variables set by the file's `env:` section are checked the same way.
|
|||||||
|
|
||||||
Key settings in more detail:
|
Key settings in more detail:
|
||||||
|
|
||||||
- `access_control_allow_origin` — the origin a browser lets read pixa's
|
- `access_control_allow_origin` — the origin a browser lets read the responses
|
||||||
responses, sent as the CORS `Access-Control-Allow-Origin` header: `*`, the
|
of the image routes, `/v1/image/` and `/v1/e/`, sent as the CORS
|
||||||
|
`Access-Control-Allow-Origin` header; no other route sends it. `*`, the
|
||||||
default, is any site; otherwise one `http` or `https` origin such as
|
default, is any site; otherwise one `http` or `https` origin such as
|
||||||
`https://example.com`, whose host is a lowercase host name (letters,
|
`https://example.com`, whose host is a lowercase host name (letters,
|
||||||
digits, hyphens and dots, with a letter in its last part) or an IP address
|
digits, hyphens and dots, with a letter in its last part) or an IP address
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ P2: security: referer blacklist
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 2026-09-29 only the image routes send CORS headers (closes #98): the CORS
|
||||||
|
middleware, with the `access_control_allow_origin` origin, moved from the
|
||||||
|
router root onto a `/v1` subrouter holding `/v1/image/` and `/v1/e/`, where it
|
||||||
|
still answers a preflight `OPTIONS` request; the login and URL generator
|
||||||
|
pages, `/metrics` and the other routes send no `Access-Control-Allow-Origin`;
|
||||||
|
documented in `README.md` and `config.example.yml`.
|
||||||
- 2026-09-29 the container makes `/var/lib/pixa` usable by itself (closes
|
- 2026-09-29 the container makes `/var/lib/pixa` usable by itself (closes
|
||||||
#159): `deploy/docker-entrypoint.sh` creates the directory if it is missing,
|
#159): `deploy/docker-entrypoint.sh` creates the directory if it is missing,
|
||||||
gives the directory and everything in it to `pixad` when the directory or one
|
gives the directory and everything in it to `pixad` when the directory or one
|
||||||
|
|||||||
+3
-2
@@ -103,8 +103,9 @@ upstream_max_response_size: 52428800
|
|||||||
# longer than upstream_fetch_timeout plus 20 seconds.
|
# longer than upstream_fetch_timeout plus 20 seconds.
|
||||||
downstream_timeout: 60s
|
downstream_timeout: 60s
|
||||||
|
|
||||||
# The origin a browser lets read pixa's responses, sent as the CORS
|
# The origin a browser lets read the responses of the image routes,
|
||||||
# Access-Control-Allow-Origin header: "*" (the default) is any site;
|
# /v1/image/ and /v1/e/, sent as the CORS Access-Control-Allow-Origin
|
||||||
|
# header; no other route sends it. "*" (the default) is any site;
|
||||||
# otherwise one http or https origin such as https://example.com, whose
|
# otherwise one http or https origin such as https://example.com, whose
|
||||||
# host is a lowercase host name (letters, digits, hyphens and dots, with a
|
# host is a lowercase host name (letters, digits, hyphens and dots, with a
|
||||||
# letter in its last part) or an IP address (IPv6 in brackets, in its
|
# letter in its last part) or an IP address (IPv6 in brackets, in its
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ func (s *Server) SetupRoutes() {
|
|||||||
s.router.Use(s.mw.Metrics())
|
s.router.Use(s.mw.Metrics())
|
||||||
}
|
}
|
||||||
|
|
||||||
s.router.Use(s.mw.CORS())
|
|
||||||
s.router.Use(middleware.Timeout(s.config.DownstreamTimeout))
|
s.router.Use(middleware.Timeout(s.config.DownstreamTimeout))
|
||||||
|
|
||||||
if s.sentryEnabled {
|
if s.sentryEnabled {
|
||||||
@@ -74,22 +73,31 @@ func (s *Server) SetupRoutes() {
|
|||||||
|
|
||||||
s.router.Get("/logout", s.h.HandleLogout())
|
s.router.Get("/logout", s.h.HandleLogout())
|
||||||
|
|
||||||
// Image routes, refused while maintenance mode is on. Only these: the
|
// Image routes, the only ones that send CORS headers, as pages on other
|
||||||
// image's Docker HEALTHCHECK requests the health check, a 503 there
|
// sites read them. They are a subrouter rather than a group: a group's
|
||||||
// would make the container unhealthy, and upaas marks a deploy failed
|
// middleware runs only for a request that matches one of its routes,
|
||||||
|
// and a browser's preflight OPTIONS request matches none, so the CORS
|
||||||
|
// middleware could not answer it.
|
||||||
|
s.router.Route("/v1", func(r chi.Router) {
|
||||||
|
r.Use(s.mw.CORS())
|
||||||
|
|
||||||
|
// Refused while maintenance mode is on. Only these: the image's
|
||||||
|
// Docker HEALTHCHECK requests the health check, a 503 there would
|
||||||
|
// make the container unhealthy, and upaas marks a deploy failed
|
||||||
// when its container is unhealthy.
|
// when its container is unhealthy.
|
||||||
s.router.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
r.Use(s.refuseDuringMaintenance)
|
r.Use(s.refuseDuringMaintenance)
|
||||||
|
|
||||||
// Main image proxy route
|
// Main image proxy route
|
||||||
// /v1/image/<host>/<path>/<width>x<height>.<format>
|
// /v1/image/<host>/<path>/<width>x<height>.<format>
|
||||||
r.Get("/v1/image/*", s.h.HandleImage())
|
r.Get("/image/*", s.h.HandleImage())
|
||||||
r.Head("/v1/image/*", s.h.HandleImage())
|
r.Head("/image/*", s.h.HandleImage())
|
||||||
|
|
||||||
// Encrypted image URL route
|
// Encrypted image URL route
|
||||||
// The trailing filename (e.g., /img.jpg) is ignored but helps
|
// The trailing filename (e.g., /img.jpg) is ignored but helps
|
||||||
// browsers with content type
|
// browsers with content type
|
||||||
r.Get("/v1/e/{token}/*", s.h.HandleImageEnc())
|
r.Get("/e/{token}/*", s.h.HandleImageEnc())
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Metrics endpoint with auth
|
// Metrics endpoint with auth
|
||||||
|
|||||||
Reference in New Issue
Block a user