package company import ( "bytes" "image" "image/png" "os" "path/filepath" "strings" "testing" "time" "github.com/descrybe/descrybe-v2/apps/api/internal/security" "github.com/google/uuid" ) func TestValidateLogoURL_HostedAndHTTPS(t *testing.T) { cid := uuid.MustParse("11111111-1111-1111-1111-111111111111") name := "22222222-2222-2222-2222-222222222222.png" got, err := ValidateLogoURL(BrandLogoURLPrefix+name, cid) if err != nil || got != BrandLogoURLPrefix+name { t.Fatalf("hosted: got=%q err=%v", got, err) } got, err = ValidateLogoURL("https://example.com/logo.png", cid) if err != nil || !strings.HasPrefix(got, "https://") { t.Fatalf("https: got=%q err=%v", got, err) } _, err = ValidateLogoURL(BrandLogoURLPrefix+"../etc/passwd", cid) if err == nil { t.Fatal("expected traversal reject") } _, err = ValidateLogoURL("/api/brand/logo/files/not-a-uuid.png", cid) if err == nil { t.Fatal("expected invalid name reject") } _, err = ValidateLogoURL("https://192.168.1.5/logo.png", cid) if err == nil || !(err == security.ErrBlockedURL || err == security.ErrBlockedHost) { t.Fatalf("expected blocked private host, got %v", err) } } func TestSaveAndResolveBrandLogo(t *testing.T) { dir := t.TempDir() cid := uuid.New() var buf bytes.Buffer img := image.NewRGBA(image.Rect(0, 0, 8, 8)) if err := png.Encode(&buf, img); err != nil { t.Fatal(err) } logoURL, abs, ct, size, err := SaveBrandLogo(dir, cid, "mark.png", "image/png", bytes.NewReader(buf.Bytes())) if err != nil { t.Fatal(err) } if ct != "image/png" || size <= 0 { t.Fatalf("ct=%s size=%d", ct, size) } name, ok := HostedLogoFilename(logoURL) if !ok { t.Fatalf("logoURL=%s", logoURL) } if _, err := os.Stat(abs); err != nil { t.Fatal(err) } resolved, err := ResolveBrandLogoPath(dir, cid, name) if err != nil { t.Fatal(err) } if filepath.Clean(resolved) != filepath.Clean(abs) { t.Fatalf("resolved=%s abs=%s", resolved, abs) } // Wrong company must not resolve another company's file via path tricks. other := uuid.New() _, err = ResolveBrandLogoPath(dir, other, name) if err != nil { // file simply missing for other company is fine; open should 404 } _, _, err = OpenBrandLogo(dir, other, name) if err != ErrLogoNotFound { t.Fatalf("expected not found for other company, got %v", err) } // Reject path traversal names. _, err = ResolveBrandLogoPath(dir, cid, "../../etc/passwd") if err != ErrLogoInvalidName { t.Fatalf("got %v", err) } } func TestSaveBrandLogo_RejectsNonImage(t *testing.T) { dir := t.TempDir() _, _, _, _, err := SaveBrandLogo(dir, uuid.New(), "x.png", "image/png", strings.NewReader("not-an-image")) if err != ErrLogoInvalidType { t.Fatalf("got %v", err) } } func TestSignAndVerifyPublicBrandLogo(t *testing.T) { cid := uuid.New() name := uuid.New().String() + ".png" secret := "test-secret" u, err := SignPublicBrandLogoURL("https://api.example.com", secret, cid, name, time.Hour) if err != nil { t.Fatal(err) } if !strings.Contains(u, PublicBrandLogoPathPrefix) { t.Fatalf("url=%s", u) } // Parse query exp := time.Now().Add(time.Hour).Unix() sig := signBrandLogo(secret, cid, name, exp) // Use exact exp from signed URL parts := strings.Split(u, "?") if len(parts) != 2 { t.Fatalf("url=%s", u) } q := map[string]string{} for _, kv := range strings.Split(parts[1], "&") { p := strings.SplitN(kv, "=", 2) if len(p) == 2 { q[p[0]] = p[1] } } expVal := mustParseInt(t, q["exp"]) if err := VerifyPublicBrandLogoSig(secret, cid, name, expVal, q["sig"]); err != nil { t.Fatal(err) } if err := VerifyPublicBrandLogoSig(secret, cid, name, expVal, "deadbeef"); err != ErrLogoBadSig { t.Fatalf("got %v", err) } _ = sig } func mustParseInt(t *testing.T, s string) int64 { t.Helper() var n int64 for _, c := range s { if c < '0' || c > '9' { t.Fatalf("bad int %q", s) } n = n*10 + int64(c-'0') } return n }