Take the signature, the expiry and the URL scheme off the image request (closes #314)
check / check (push) Canceled after 0s
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 was merged in pull request #331.
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"sneak.berlin/go/pixa/internal/allowlist"
|
||||
"sneak.berlin/go/pixa/internal/httpfetcher"
|
||||
"sneak.berlin/go/pixa/internal/imageprocessor"
|
||||
"sneak.berlin/go/pixa/internal/imagerequest"
|
||||
@@ -26,7 +27,7 @@ import (
|
||||
const allowlistedHost = "allowed.example.com"
|
||||
|
||||
// newImageRoute returns the image route of a Handlers whose service fetches
|
||||
// with fetcher and checks signatures with testSigningKey.
|
||||
// with fetcher, and which checks signatures with testSigningKey.
|
||||
func newImageRoute(t *testing.T, fetcher httpfetcher.Fetcher) http.Handler {
|
||||
t.Helper()
|
||||
|
||||
@@ -46,15 +47,18 @@ func newImageRoute(t *testing.T, fetcher httpfetcher.Fetcher) http.Handler {
|
||||
MaxInputBytes: 50 << 20,
|
||||
MaxConcurrentProcessing: 2,
|
||||
}),
|
||||
SigningKey: testSigningKey,
|
||||
Allowlist: []string{allowlistedHost},
|
||||
MaxResponseSize: 50 << 20,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create service: %v", err)
|
||||
}
|
||||
|
||||
h := &Handlers{imgSvc: svc, log: slog.New(slog.DiscardHandler)}
|
||||
h := &Handlers{
|
||||
imgSvc: svc,
|
||||
log: slog.New(slog.DiscardHandler),
|
||||
signer: signature.New(testSigningKey),
|
||||
allowlist: allowlist.New([]string{allowlistedHost}),
|
||||
}
|
||||
|
||||
r := chi.NewRouter()
|
||||
r.Get("/v1/image/*", h.HandleImage())
|
||||
@@ -63,7 +67,7 @@ func newImageRoute(t *testing.T, fetcher httpfetcher.Fetcher) http.Handler {
|
||||
}
|
||||
|
||||
// newPhotoFetcher returns a mock fetcher that serves a JPEG at photoPath on
|
||||
// each of hosts, and answers any other URL with an upstream error.
|
||||
// each of hosts, and answers any other source with an upstream error.
|
||||
func newPhotoFetcher(t *testing.T, hosts ...string) *httpfetcher.MockFetcher {
|
||||
t.Helper()
|
||||
|
||||
@@ -90,10 +94,10 @@ func photoURLWithSig(host, sig string, expires time.Time) string {
|
||||
return fmt.Sprintf("%s?sig=%s&exp=%d", photoURL(host), sig, expires.Unix())
|
||||
}
|
||||
|
||||
// photoSignature returns the signature of photoURL(host) at the default
|
||||
// quality and fit, made with key and expiring at expires.
|
||||
func photoSignature(key, host string, expires time.Time) string {
|
||||
return signature.New(key).Sign(&imagerequest.Request{
|
||||
// photoRequest returns the image request of photoURL(host) at the default
|
||||
// quality and fit.
|
||||
func photoRequest(host string) *imagerequest.Request {
|
||||
return &imagerequest.Request{
|
||||
Source: imagerequest.Source{
|
||||
Host: host,
|
||||
Path: photoPath,
|
||||
@@ -104,8 +108,13 @@ func photoSignature(key, host string, expires time.Time) string {
|
||||
Quality: 85,
|
||||
FitMode: imagerequest.FitCover,
|
||||
},
|
||||
Expires: expires,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// photoSignature returns the signature of photoURL(host) at the default
|
||||
// quality and fit, made with key and expiring at expires.
|
||||
func photoSignature(key, host string, expires time.Time) string {
|
||||
return signature.New(key).Sign(photoRequest(host), expires)
|
||||
}
|
||||
|
||||
// sendGet sends a GET for target to route and returns the response.
|
||||
@@ -286,10 +295,9 @@ func TestHandleImage_AllowlistOrSignature(t *testing.T) {
|
||||
// host can be, and checks that each is refused with 400 naming the host as
|
||||
// decoded: allowlistedHost with a character after it that a URL host cannot
|
||||
// hold, percent-encoded, an IPv6 address without brackets, and brackets
|
||||
// around something that is not an IPv6 address. A "?" or "#" after
|
||||
// allowlistedHost would otherwise end the host when the source URL is read,
|
||||
// so the request would pass the allowlist and be fetched and cached under a
|
||||
// host that is not the one fetched.
|
||||
// around something that is not an IPv6 address. Later steps put the host
|
||||
// unparsed into the allowlist check, the cache key and the upstream URL, so
|
||||
// such a host is refused first.
|
||||
func TestHandleImage_InvalidHost_Returns400(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -360,10 +368,9 @@ func TestHandleImage_HostForms_Served(t *testing.T) {
|
||||
|
||||
// TestHandleImage_HashInSourcePathOrQuery_Returns400 sends image URLs whose
|
||||
// source path or source query holds a "#", written as %23, and checks that
|
||||
// each is refused with 400, the one signed for that source path included. The
|
||||
// source URL would otherwise end at the "#" when it is fetched, so each text
|
||||
// after it would fetch the image at photoPath and cache it under keys of its
|
||||
// own.
|
||||
// each is refused with 400, the one signed for that source path included.
|
||||
// A "#" starts a URL's fragment, so neither the source path nor the source
|
||||
// query may hold one.
|
||||
func TestHandleImage_HashInSourcePathOrQuery_Returns400(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -381,8 +388,7 @@ func TestHandleImage_HashInSourcePathOrQuery_Returns400(t *testing.T) {
|
||||
Quality: 85,
|
||||
FitMode: imagerequest.FitCover,
|
||||
},
|
||||
Expires: exp,
|
||||
})
|
||||
}, exp)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
Reference in New Issue
Block a user