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
136 lines
3.7 KiB
Go
136 lines
3.7 KiB
Go
package signature_test
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"sneak.berlin/go/pixa/internal/imagerequest"
|
|
"sneak.berlin/go/pixa/internal/signature"
|
|
)
|
|
|
|
// goldenExpiresUnix is the fixed expiration timestamp used by all golden
|
|
// vectors: 2024-01-01T00:00:00Z.
|
|
const goldenExpiresUnix int64 = 1704067200
|
|
|
|
// goldenSigningKey is the fixed signing key used by all golden vectors.
|
|
const goldenSigningKey = "golden-test-key"
|
|
|
|
type goldenVector struct {
|
|
name string
|
|
req imagerequest.Request
|
|
// wantSignature is the exact base64url (RFC 4648 URL-safe,
|
|
// padded) HMAC-SHA256 signature for the request expiring at
|
|
// goldenExpiresUnix.
|
|
wantSignature string
|
|
}
|
|
|
|
// goldenVectors returns the known-answer vectors. The expected values
|
|
// were computed once and are hardcoded here.
|
|
func goldenVectors() []goldenVector {
|
|
return []goldenVector{
|
|
{
|
|
name: "resized without query",
|
|
req: imagerequest.Request{
|
|
Source: imagerequest.Source{
|
|
Host: testHost,
|
|
Path: testPath,
|
|
Query: "",
|
|
},
|
|
Transform: imagerequest.Transform{
|
|
Size: imagerequest.Size{Width: 800, Height: 600},
|
|
Format: testFormatWebP,
|
|
Quality: 85,
|
|
FitMode: testFitCover,
|
|
},
|
|
},
|
|
// Signed data:
|
|
// "cdn.example.com:/photos/cat.jpg::800:600:webp:1704067200:85:cover"
|
|
wantSignature: "kdqeGoW2SX7qnaYtoB970wEnLydn0UnIgQYQLfAnjXQ=",
|
|
},
|
|
{
|
|
name: "resized with query string",
|
|
req: imagerequest.Request{
|
|
Source: imagerequest.Source{
|
|
Host: testHost,
|
|
Path: testPath,
|
|
Query: "token=abc&v=2",
|
|
},
|
|
Transform: imagerequest.Transform{
|
|
Size: imagerequest.Size{Width: 800, Height: 600},
|
|
Format: testFormatWebP,
|
|
Quality: 85,
|
|
FitMode: testFitCover,
|
|
},
|
|
},
|
|
// Signed data:
|
|
// "cdn.example.com:/photos/cat.jpg:token=abc&v=2:800:600:webp:1704067200:85:cover"
|
|
wantSignature: "pKgVBOTd_Q_EikI7MNQLC9Q8Hurdxzyv3EIYvVhqc2I=",
|
|
},
|
|
{
|
|
name: "original size without query",
|
|
req: imagerequest.Request{
|
|
Source: imagerequest.Source{
|
|
Host: testHost,
|
|
Path: testPath,
|
|
Query: "",
|
|
},
|
|
Transform: imagerequest.Transform{
|
|
Size: imagerequest.Size{Width: 0, Height: 0},
|
|
Format: testFormatPNG,
|
|
Quality: 85,
|
|
FitMode: testFitCover,
|
|
},
|
|
},
|
|
// Signed data:
|
|
// "cdn.example.com:/photos/cat.jpg::0:0:png:1704067200:85:cover"
|
|
wantSignature: "6_rZ0yyVbGZRs8kG7n7HLgLi5Jt8vjiWQljIEL1jbIs=",
|
|
},
|
|
{
|
|
name: "non-default quality and fit",
|
|
req: imagerequest.Request{
|
|
Source: imagerequest.Source{
|
|
Host: testHost,
|
|
Path: testPath,
|
|
Query: "",
|
|
},
|
|
Transform: imagerequest.Transform{
|
|
Size: imagerequest.Size{Width: 800, Height: 600},
|
|
Format: testFormatWebP,
|
|
Quality: 40,
|
|
FitMode: testFitContain,
|
|
},
|
|
},
|
|
// Signed data:
|
|
// "cdn.example.com:/photos/cat.jpg::800:600:webp:1704067200:40:contain"
|
|
wantSignature: "pGaXpPUbI3A7nMx-4T9bfq9bYWBNL0kY4bxlcv3g1F8=",
|
|
},
|
|
}
|
|
}
|
|
|
|
// TestSigner_GoldenVectors pins the exact HMAC-SHA256 signature output for
|
|
// fully-specified requests with a hardcoded signing key.
|
|
//
|
|
// If any of these assertions fail, the signed byte format
|
|
// ("host:path:query:width:height:format:expiration:quality:fit") or the
|
|
// base64url encoding has changed. Update these constants only when that
|
|
// change is intended.
|
|
func TestSigner_GoldenVectors(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
signer := signature.New(goldenSigningKey)
|
|
|
|
for _, tt := range goldenVectors() {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
signReq := tt.req
|
|
|
|
gotSignature := signer.Sign(&signReq, time.Unix(goldenExpiresUnix, 0))
|
|
if gotSignature != tt.wantSignature {
|
|
t.Errorf("Sign() = %q, want %q (signed byte format changed?)",
|
|
gotSignature, tt.wantSignature)
|
|
}
|
|
})
|
|
}
|
|
}
|