Include quality and fit in the URL signature (closes #60)
check / check (push) Successful in 12s

The signed data is now
host:path:query:width:height:format:expiration:quality:fit. The route
turns a missing q into 85 and a missing fit into cover before checking
the signature, so those are the values signed for a URL without them;
imgcache fills both from the parsed request.

imgcache.Service.GenerateSignedURL now writes q and fit into the URL
next to sig and exp, first setting an unset quality or fit to 85 or
cover, so a generated URL verifies for the values it signed.

The known-answer vectors in golden_test.go, including one for quality
40 and fit contain, and the README signature section describe the new
format.

Model: opus-4-8 (implementation); opus-5-5 (rework)
This commit was merged in pull request #116.
This commit is contained in:
2026-09-28 13:02:21 +02:00
parent b7c1226c38
commit db784bf561
8 changed files with 285 additions and 26 deletions
+15 -8
View File
@@ -79,14 +79,14 @@ hosts require an HMAC-SHA256 signature.
Signatures use HMAC-SHA256 and include an expiration timestamp to
prevent replay attacks. Signatures are **exact match only**: every
component (host, path, query, dimensions, format, expiration) must
match exactly what was signed. No suffix matching, wildcard matching,
or partial matching is supported.
component (host, path, query, dimensions, format, expiration, quality,
fit) must match exactly what was signed. No suffix matching, wildcard
matching, or partial matching is supported.
**Signed data format** (colon-separated):
```
HMAC-SHA256(secret, "host:path:query:width:height:format:expiration")
HMAC-SHA256(secret, "host:path:query:width:height:format:expiration:quality:fit")
```
Where:
@@ -98,18 +98,25 @@ Where:
- `height` — requested height in pixels, `0` for original
- `format` — output format (jpeg, png, webp, avif, gif, orig)
- `expiration` — Unix timestamp when signature expires
- `quality` — the URL's `q` query parameter (1-100), or `85` when the URL
has no `q`
- `fit` — the URL's `fit` query parameter (cover, contain, fill, inside,
outside), or `cover` when the URL has no `fit`
**Example:** resize
`https://cdn.example.com/photos/cat.jpg` to 800x600 WebP with
expiration 1704067200:
**Example:** resize `https://cdn.example.com/photos/cat.jpg` to 800x600
WebP with expiration 1704067200, default quality and fit:
1. Build input:
`cdn.example.com:/photos/cat.jpg::800:600:webp:1704067200`
`cdn.example.com:/photos/cat.jpg::800:600:webp:1704067200:85:cover`
2. Compute HMAC-SHA256 with your secret key
3. Base64URL-encode the result
4. URL:
`/v1/image/cdn.example.com/photos/cat.jpg/800x600.webp?sig=<base64url>&exp=1704067200`
For the same image at quality 40 with fit `contain`, the input ends in
`:40:contain` and the URL is
`/v1/image/cdn.example.com/photos/cat.jpg/800x600.webp?sig=<base64url>&exp=1704067200&q=40&fit=contain`.
**Allowlist patterns:**
- **Exact match**: `cdn.example.com` — matches only that host