summaryrefslogtreecommitdiff
path: root/app/cache_test.go
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-08-07 18:34:59 -0500
committerChristian Cleberg <[email protected]>2026-08-07 18:45:03 -0500
commitee9fc6db38dbbee7418f8a3be2ce25eaa76f90ec (patch)
treee89f137e9814106ba54f7c5735773e9bfa2f3b60 /app/cache_test.go
parentddb8aa297962379fa7731032140221ba35f84b50 (diff)
downloadskunky-art-ee9fc6db38dbbee7418f8a3be2ce25eaa76f90ec.tar.gz
skunky-art-ee9fc6db38dbbee7418f8a3be2ce25eaa76f90ec.tar.bz2
skunky-art-ee9fc6db38dbbee7418f8a3be2ce25eaa76f90ec.zip
fix: add blur op for mature deviations with blur-constrained tokens
Mature deviations are signed with a watermark-service token whose obj carries a "blur": ">=N" constraint. The composed wixmp /v1/fit transform had no blur operation, so wixmp rejected it with 403, which the proxy passed through as a broken image. buildMediaURL now decodes the token and, when it demands a blur, appends a matching blur_N op to the transform (e.g. w_1280,h_1920,blur_10). Unconstrained media and tokens that don't parse are left untouched, so only media that requires it is affected. Closes #14
Diffstat (limited to 'app/cache_test.go')
-rw-r--r--app/cache_test.go77
1 files changed, 77 insertions, 0 deletions
diff --git a/app/cache_test.go b/app/cache_test.go
index eafd8b1..4fcae94 100644
--- a/app/cache_test.go
+++ b/app/cache_test.go
@@ -2,8 +2,11 @@ package app
import (
"bytes"
+ "encoding/base64"
+ "encoding/json"
"net/http/httptest"
"net/url"
+ "strings"
"sync"
"testing"
)
@@ -46,6 +49,80 @@ func TestBuildMediaURLRejectsForgedSubdomain(t *testing.T) {
}
}
+// makeMediaToken builds a JWT-shaped token whose obj carries the given blur
+// value, mirroring the wixmp media tokens DeviantArt signs. Pass a ">=N" string
+// for a blur-constrained (mature) token, or nil for an unconstrained one.
+func makeMediaToken(t *testing.T, blur any) string {
+ t.Helper()
+ claims := map[string]any{
+ "obj": [][]map[string]any{{{"path": "/f/x.png", "blur": blur}}},
+ }
+ payload, err := json.Marshal(claims)
+ if err != nil {
+ t.Fatal(err)
+ }
+ enc := base64.RawURLEncoding.EncodeToString
+ return enc([]byte(`{"alg":"none"}`)) + "." + enc(payload) + ".sig"
+}
+
+// TestBlurConstraint is the regression test for the mature-media 403: a token
+// whose obj demands a blur must yield that radius, and anything else must yield
+// 0 so the transform is left untouched.
+func TestBlurConstraint(t *testing.T) {
+ if got := blurConstraint(makeMediaToken(t, ">=10")); got != 10 {
+ t.Errorf("blur-constrained token: got %d, want 10", got)
+ }
+ if got := blurConstraint(makeMediaToken(t, nil)); got != 0 {
+ t.Errorf("null-blur token: got %d, want 0", got)
+ }
+ // Nothing parseable as a claims payload: fail open, leaving the URL alone.
+ for _, tok := range []string{"", "not-a-jwt", "a.b", "a.!!!.c"} {
+ if got := blurConstraint(tok); got != 0 {
+ t.Errorf("unparseable token %q: got %d, want 0", tok, got)
+ }
+ }
+}
+
+// TestAddBlurToTransform checks the string surgery: a blur op is inserted into a
+// /v1/fit transform, paths without one are untouched, and an existing op is not
+// doubled.
+func TestAddBlurToTransform(t *testing.T) {
+ got := addBlurToTransform("f/u/x.png/v1/fit/w_1280,h_1920/x.png", 10)
+ if want := "f/u/x.png/v1/fit/w_1280,h_1920,blur_10/x.png"; got != want {
+ t.Errorf("got %q, want %q", got, want)
+ }
+ if got := addBlurToTransform("f/u/x.gif", 10); got != "f/u/x.gif" {
+ t.Errorf("path without a transform was modified: %q", got)
+ }
+ blurred := "f/u/x.png/v1/fit/w_1280,h_1920,blur_10/x.png"
+ if got := addBlurToTransform(blurred, 10); got != blurred {
+ t.Errorf("existing blur op was doubled: %q", got)
+ }
+}
+
+// TestBuildMediaURLAddsBlurWhenTokenDemandsIt drives the whole path: a
+// blur-constrained token gains a matching blur op in the composed URL, and an
+// unconstrained one does not.
+func TestBuildMediaURLAddsBlurWhenTokenDemandsIt(t *testing.T) {
+ path := "f/u/x.png/v1/fit/w_1280,h_1920/x.png"
+
+ got, ok := buildMediaURL("ed30a86b", path, makeMediaToken(t, ">=10"))
+ if !ok {
+ t.Fatal("a plain label was rejected, want accepted")
+ }
+ u, err := url.Parse(got)
+ if err != nil {
+ t.Fatalf("built an unparseable URL %q: %v", got, err)
+ }
+ if !strings.Contains(u.Path, "w_1280,h_1920,blur_10") {
+ t.Errorf("transform is %q, want a blur_10 op added", u.Path)
+ }
+
+ if got, _ := buildMediaURL("ed30a86b", path, makeMediaToken(t, nil)); strings.Contains(got, "blur") {
+ t.Errorf("unconstrained media gained a blur op: %q", got)
+ }
+}
+
// TestBuildMediaURLKeepsHostOnWixmp is the property that actually matters: for
// anything accepted, the host the client ends up talking to is the CDN.
func TestBuildMediaURLKeepsHostOnWixmp(t *testing.T) {