Take the signature, the expiry and the URL scheme off the image request (closes #314)
check / check (push) Canceled after 0s

The image request now holds only its source and its transform, exactly what
the variant key covers. The /v1/image/ handler checks the allowlist and
verifies the signature itself with Signer.Verify(req, sig, expires). Both
image handlers keep the URL's expiry as a local value for Cache-Control. The
image service holds no signer, allowlist or AllowHTTP, and ValidateRequest is
gone. The fetcher takes the source and builds its URL with Source.URL from its
own AllowHTTP, so no URL string is built and parsed again. chooseAutoFormat
returns the format it chooses instead of writing into the request. The signed
URL format and every route's answers are unchanged.

Model: opus-5-5
This commit is contained in:
2026-10-26 11:54:15 +00:00
parent 63690a9ec9
commit 08028e4560
45 changed files with 729 additions and 884 deletions
@@ -38,10 +38,10 @@ func getImage(t *testing.T, fix *testFixtures, target string) int {
}
// generateSignedURL returns the /v1/image/ URL of req, signed with key and
// expiring after ttl, and sets req's expiry. An unset quality or fit of req is
// first set to 85 or cover, the values the route uses when a URL has no q or
// fit. The URL carries q and fit next to sig and exp, so the route verifies it
// for the quality and fit it was signed with.
// expiring after ttl. An unset quality or fit of req is first set to 85 or
// cover, the values the route uses when a URL has no q or fit. The URL carries
// q and fit next to sig and exp, so the route verifies it for the quality and
// fit it was signed with.
func generateSignedURL(
key string, req *imagerequest.Request, ttl time.Duration,
) string {
@@ -53,8 +53,8 @@ func generateSignedURL(
req.Transform.FitMode = imagerequest.FitCover
}
req.Expires = time.Now().Add(ttl)
sig := signature.New(key).Sign(req)
expires := time.Now().Add(ttl)
sig := signature.New(key).Sign(req, expires)
// A source query follows the source path after a "?". The image route
// reads the path decoded once, so the whole path is written in Go's own
@@ -70,7 +70,7 @@ func generateSignedURL(
req.Transform.Size.Width, req.Transform.Size.Height, req.Transform.Format)
return fmt.Sprintf("%s?sig=%s&exp=%d&q=%d&fit=%s",
(&url.URL{Path: unescapedPath}).EscapedPath(), sig, req.Expires.Unix(),
(&url.URL{Path: unescapedPath}).EscapedPath(), sig, expires.Unix(),
req.Transform.Quality, req.Transform.FitMode)
}
@@ -93,8 +93,7 @@ func TestHandleImage_SignatureCoversQualityAndFit(t *testing.T) {
Quality: 85,
FitMode: imagerequest.FitCover,
},
Expires: expires,
})
}, expires)
signedURL := fmt.Sprintf("/v1/image/%s/images/photo.jpg/50x50.jpeg?sig=%s&exp=%d",
signedHost, sig, expires.Unix())
@@ -277,8 +276,7 @@ func TestHandleImage_SourceQueryEncodedAnyWay(t *testing.T) {
Quality: 85,
FitMode: imagerequest.FitCover,
},
Expires: expires,
})
}, expires)
tests := []struct {
name string
@@ -344,8 +342,7 @@ func TestHandleImage_SourceURLHoldingPercent25Verifies(t *testing.T) {
Quality: 85,
FitMode: imagerequest.FitCover,
},
Expires: expires,
})
}, expires)
target := fmt.Sprintf("/v1/image/%s%s/50x50.jpeg?sig=%s&exp=%d",
signedHost, tt.writtenPathAndQuery, sig, expires.Unix())