docs(quick-260707-lgh): Favoriten-Widget Icon-Proxy fuer CORP-Seiten
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

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 15:43:10 +02:00
parent f06a2ff397
commit 60df92b23d
2 changed files with 28 additions and 2 deletions
@@ -0,0 +1,25 @@
---
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.