Files
schalli 60df92b23d
Tessera CI/CD / Lint & Type Check (push) Successful in 42s
Tessera CI/CD / Tests (push) Successful in 42s
Tessera CI/CD / Build & Publish Images (push) Successful in 1m37s
docs(quick-260707-lgh): Favoriten-Widget Icon-Proxy fuer CORP-Seiten
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 15:43:10 +02:00

2.4 KiB

status
status
complete

Quick Task 260707-lgh: Favoriten-Widget Icon-Proxy fuer Cross-Origin-Resource-Policy-Seiten

What shipped

  1. icon-discovery.service.ts — extracted the shared SSRF-guarded redirect loop into fetchWithRedirectGuard (exported isPublicHttpUrl), added fetchIconBytes(): image-content-type gate, 1MB cap, dedicated timeout. discoverFavoriteIconUrl unchanged. 10 new vitest specs, all passing.
  2. favorites.service.ts / favorites.controller.ts — getIconBytes(id, userId) (same userId-only ownership check as update/remove; 404 on mismatch/no-icon), new GET /favorites/:id/icon streaming endpoint (Cache-Control 86400s, 502 on upstream failure).
  3. favorites-widget.tsx — icon <img src> now points at /api-proxy/favorites/:id/icon (same pattern as the existing avatar image proxy) instead of hotlinking the external URL directly.

Bugs caught and fixed during manual testing (not in original plan)

  • Cloudflare WAF blocking the byte-fetch itself. After wiring the proxy, chatgpt.com's icon returned 502. Root cause isolated via direct fetch() comparison inside the API container: the tessera/1.0 User-Agent was blocked (403) reproducibly (3/3), while a realistic Chrome UA succeeded (3/3) — a bare image/* Accept header alone was a red herring from an earlier (cache-hit-masked) test. Fixed by parameterizing fetchWithRedirectGuard's User-Agent and overriding it only for fetchIconBytes (browser-realistic UA + full image Accept list); the HTML-discovery path keeps its original tessera/1.0 UA unchanged, per the plan's no-regression constraint.

Verification performed

  • vitest run icon-discovery.service.spec.ts — 10/10 passing.
  • API + web type-check, API build — all green.
  • Live end-to-end via Playwright against the running dev stack: both ChatGPT and Claude favorites now render their real logos (screenshot-confirmed) instead of the letter fallback; network tab shows both /api-proxy/favorites/:id/icon requests returning 200 from Tessera's own origin.

Known limitations

  • The realistic-browser-UA workaround is a pragmatic fix for one observed WAF behavior (Cloudflare on chatgpt.com); other sites' bot-mitigation could still reject the request for other reasons (rate limits, TLS fingerprinting, etc.) — those will still degrade gracefully to the letter fallback (502 -> onError hides the <img>), just without a logo.