Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d03600ab0 |
@@ -289,7 +289,8 @@ path under `/v1/` answers 200, in maintenance mode too.
|
||||
- 400 for a token that does not decrypt.
|
||||
- 400 for a token that asks for a size or fit that is not valid.
|
||||
- 410 once the token has expired.
|
||||
- 400 for an `Accept` header that is not valid.
|
||||
- 400 when the token's format is `auto` and the `Accept` header is not
|
||||
valid.
|
||||
- These answers, for the same causes as on `/v1/image/`:
|
||||
- 406
|
||||
- 403
|
||||
|
||||
@@ -54,9 +54,12 @@ P2: security: per-origin rate limiting
|
||||
warn level.
|
||||
- The "image served" line carries the host, the path and `fetched_bytes` on
|
||||
both routes. The debug line "encrypted image request" is gone.
|
||||
- A failed write is logged once, as "failed to write response" at error
|
||||
level. It carries the `request_id`, the host, the path, `served_bytes` and
|
||||
the error, and is not also logged as "image served".
|
||||
- The debug line "failed to decrypt URL" is now "invalid encrypted URL". A
|
||||
token that decrypts to a payload that does not decode also gets it.
|
||||
- A failed write is logged once, as "failed to write response" at warn
|
||||
level, since most often the client has gone away. It carries the
|
||||
`request_id`, the host, the path, `served_bytes` and the error, and is not
|
||||
also logged as "image served".
|
||||
- 2026-10-26 every key derived from the signing key is derived in one place
|
||||
(closes #315): `seal.DeriveKeys` returns them as one `seal.Keys`, with their
|
||||
salts declared beside it. `internal/providers` calls it once at startup. Each
|
||||
|
||||
@@ -190,8 +190,8 @@ func (s *Handlers) validateSignature(
|
||||
}
|
||||
|
||||
// parseExpires reads the exp query parameter, a Unix time in seconds. An exp
|
||||
// missing from the URL gives the zero time, which the signature check takes
|
||||
// as no expiration. An exp in the URL that is not a whole number, an empty
|
||||
// missing from the URL gives the zero time, which the signature check refuses
|
||||
// as a missing exp. An exp in the URL that is not a whole number, an empty
|
||||
// one included, is an error naming exp and the value.
|
||||
func parseExpires(query url.Values) (time.Time, error) {
|
||||
if !query.Has("exp") {
|
||||
@@ -221,8 +221,9 @@ func (s *Handlers) HandleTooManyRequests() http.HandlerFunc {
|
||||
|
||||
// respondImageError answers err, the image service's error for req, on both
|
||||
// image routes. It logs a 500 at error level, as pixa's own failure. It logs
|
||||
// every other answer at warn level, since the client, the upstream or a full
|
||||
// limit caused it.
|
||||
// the other answers at warn level, since the client, the upstream or a full
|
||||
// limit caused them. The 415 for a source refused for its first bytes is not
|
||||
// logged here, because the image service logs it.
|
||||
func (s *Handlers) respondImageError(
|
||||
w http.ResponseWriter, req *imagerequest.Request, err error,
|
||||
) {
|
||||
@@ -394,9 +395,11 @@ func (s *Handlers) serveImage(
|
||||
// Stream the response
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
// A failed write is most often a client that has gone away, not pixa's
|
||||
// failure, so it is logged at warn level
|
||||
servedBytes, err := io.Copy(w, resp.Content)
|
||||
if err != nil {
|
||||
s.log.Error("failed to write response",
|
||||
s.log.Warn("failed to write response",
|
||||
"request_id", middleware.GetReqID(r.Context()),
|
||||
"host", req.Source.Host,
|
||||
"path", req.Source.Path,
|
||||
|
||||
@@ -518,7 +518,7 @@ func (failingResponseWriter) Write([]byte) (int, error) {
|
||||
}
|
||||
|
||||
// TestImageRoutes_FailedWriteLoggedOnce checks that both image routes log a
|
||||
// failed write of the image once, at error level, with the request's ID, the
|
||||
// failed write of the image once, at warn level, with the request's ID, the
|
||||
// source host and path, the bytes served and the error. Neither may also log
|
||||
// the image as served.
|
||||
func TestImageRoutes_FailedWriteLoggedOnce(t *testing.T) {
|
||||
@@ -568,7 +568,7 @@ func TestImageRoutes_FailedWriteLoggedOnce(t *testing.T) {
|
||||
}
|
||||
|
||||
want := map[string]any{
|
||||
"level": slog.LevelError.String(),
|
||||
"level": slog.LevelWarn.String(),
|
||||
"request_id": requestID,
|
||||
"host": signedHost,
|
||||
"path": photoPath,
|
||||
|
||||
@@ -54,7 +54,7 @@ func (s *Handlers) parseImageEncRequest(
|
||||
return nil, time.Time{}, false
|
||||
}
|
||||
|
||||
s.log.Debug("failed to decrypt URL", "error", err)
|
||||
s.log.Debug("invalid encrypted URL", "error", err)
|
||||
s.respondError(w, "invalid encrypted URL", http.StatusBadRequest)
|
||||
|
||||
return nil, time.Time{}, false
|
||||
@@ -62,8 +62,9 @@ func (s *Handlers) parseImageEncRequest(
|
||||
|
||||
// Apply the same dimension and fit-mode bounds as the plain image
|
||||
// route: a sealed payload is trusted for its origin, not for staying
|
||||
// within limits, so an over-limit size or unknown fit mode is a 400
|
||||
// here rather than an out-of-memory or a 500 from the processor.
|
||||
// within limits. This refuses an over-limit size before the source is
|
||||
// fetched. An unknown fit mode is a 400 here rather than a 500 from the
|
||||
// processor.
|
||||
err = imagerequest.ValidateImageRequest(req)
|
||||
if err != nil {
|
||||
s.log.Debug("encrypted URL failed validation", "error", err)
|
||||
|
||||
Reference in New Issue
Block a user