Commit Graph

12 Commits

Author SHA1 Message Date
schalli 2dd11b439d fix(favorites): Logo auch fuer Seiten, die es per JavaScript setzen
Tessera CI/CD / Lint & Type Check (push) Successful in 49s
Tessera CI/CD / Tests (push) Successful in 1m28s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 20s
Tessera CI/CD / Build & Publish Images (push) Successful in 3m18s
hosteurope.de liefert im HTML nur einen leeren data:-Platzhalter, das
echte Symbol setzt erst JavaScript; /favicon.ico antwortet mit HTML. Die
Kachel zeigte deshalb nur den Buchstaben. Scheitert das gespeicherte
Symbol, fragt getIconBytes jetzt einmal den DuckDuckGo-Symboldienst -
nur fuer oeffentlich erreichbare Seiten, interne Hostnamen verlassen das
Haus nicht; kennt der Dienst nichts (404), bleibt es beim Buchstaben.
Wirkt auch fuer bestehende Favoriten.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 12:28:32 +02:00
schalli b15c74632b fix(favorites): Symbol-Adresse auch speichern, wenn nur der Browser sie laden kann
- API: ausdrueckliche iconUrl wird nur auf Form (http/https, <= 2048) geprueft
  und auch gespeichert, wenn der Server sie nicht abrufen kann; keine 422 mehr
- Erkennung: Seite mit Fehlerstatus, aber HTML mit <link rel=icon>, liefert
  diesen Verweis (docuvita); og:image einer Fehlerseite zaehlt nicht
- Kachel: Proxy -> iconUrl direkt im Browser (no-referrer, nur http/https) ->
  Origin-Favicon -> Buchstabe
- Meldung iconUrlUnreachable (de/en) entfernt, Hinweis zum Vorrang angepasst

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 15:34:58 +02:00
schalli 7188c5b958 fix(favorites): geaenderte Symbol-Adresse wird sofort angezeigt
- eine neue, ausdruecklich eingetragene Logo-Adresse verdraengt ein frueher
  hochgeladenes Symbol (Vorrang der Datei liess die Adresse unsichtbar)
- iconVersion steigt mit, die Kachel laedt das Bild neu
- Regressionstests: Upload wird ersetzt, unveraenderte Adresse laesst Upload stehen

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 15:32:30 +02:00
schalli 7704372c3c feat(260923-lrr): API — Favoriten-Symbol hochladen, Vorrang, Versionszaehler, Abrufprobe
- FavoriteLink: neue Spalten uploadedIconMime/iconVersion (Migration 20260923160000)
- favorite-icon-files.ts: Erkennung PNG/JPEG/GIF/WebP/ICO/SVG, Pfadbildung ohne
  Byte aus der Anfrage im Pfad (T-LRR-01), best-effort Dateientfernung
- FavoritesService: uploadIcon/removeUploadedIcon, Vorrang der hochgeladenen
  Datei in getIconBytes, Abrufprobe fuer eine neue iconUrl (422 statt stiller
  Speicherung), iconVersion-Erhoehung bei jeder Aenderung der Symbolquelle
- FavoritesController: POST/DELETE /favorites/:id/icon, Cache-Control private
- T-LRR-07 (Restrisiko aus dem Plan-Threat-Model geschlossen, ueber den Plan
  hinaus): DashboardService.removeWidget/deleteDashboard raeumen jetzt die
  Symboldateien der per Datenbank-Kaskade mitgeloeschten Favoriten auf
  (best effort, nie blockierend)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 16:04:29 +02:00
schalli b188946e31 refactor(quick-260921-m34): Aufgabe 1 - Mandantenbindung entzaubert, 105 unnoetige any-Zusicherungen entfernt
- prisma-tenant.extension.ts: (prisma as any) und die Handannotation an
  $allOperations in forTenant()/forSystem() entfernt; Kopfkommentar
  unveraendert. .then((results: any[]) => ...) auf unknown[] umgestellt.
- 105 Aufrufstellen `const X = forTenant(...) as any` / `forSystem(...) as
  any` von der Zusicherung befreit, Zuweisungsform woertlich erhalten
  (rls-access-inventory.spec.ts bleibt scharf, 30/30 gruen einzeln
  geprueft).
- withTenantTransaction(): Prisma.TransactionClient fuer tx probiert,
  gemessen verworfen - bricht das Testdoppel in
  prisma-tenant.extension.spec.ts (TS2322 auf einem absichtlich
  unvollstaendigen Fake-Objekt). tx bleibt any, mit Begruendung am Typ.
- Gefolge des jetzt getypten Klienten entfernt: any[]-Annotationen und
  .map((x: any) => ...) in groups.service.ts, module-grants.service.ts,
  dkv.service.ts, ldap-config.service.ts, tenders.controller.ts:270.
- Befund (D-03): tender-matching.service.ts:159 trug eine Handannotation
  (match: { tender: unknown }), die den Wert nur deshalb auf unknown
  verengte, um TS7006 unter dem alten any-Klienten zu vermeiden - mit dem
  getypten Klienten war das falsch. Annotation geloescht, kein Ersatz
  durch Zusicherung.
- Zwei any bleiben gezielt in groups.service.ts (u/a in
  ensureDefaultGroup(), gefolge von tx: any) - Begruendung am Code.

noExplicitAny apps/api/src: 288 -> 149 (Schranke 155). type-check 4/4,
lint 5/5 (0 error). apps/api 72/1143 gruen, apps/web 73/531 gruen,
rls-access-inventory.spec.ts 30/30 gruen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 16:19:16 +02:00
schalli 2a562d0b14 feat(api): Favoriten — Symbol trotz Zertifikatsfehler holen, Reihenfolge per PUT /favorites/order speichern
- icon-discovery.service.ts: undicis eigenes fetch mit Modul-Singleton
  LENIENT_TLS_AGENT (Agent({ connect: { rejectUnauthorized: false } }))
  als dispatcher in fetchWithRedirectGuard, der einzigen Ausgangsstelle
  fuer HTML-Ermittlung und Icon-Byte-Holen; SSRF-Schutz unveraendert
- undici 7.28.0 (bereits im Lockfile aufgeloest) als direkte Abhaengigkeit
  von @tessera/api via pnpm add --offline
- PUT /favorites/order (ReorderFavoritesDto) vor den :id-Routen;
  FavoritesService.reorder() setzt position=index fuer die Favoriten
  eines Widgets in EINER withTenantTransaction, userId+widgetId in jeder
  Bedingung (zweites Netz), eine BadRequestException fuer alle
  Abweichungen (T-JDD-06)
- getIcon: X-Content-Type-Options nosniff + restriktive CSP (T-JDD-02)
- 10 neue Tests (3 Dispatcher, 7 reorder); volle API-Suite 68 Dateien/
  1101 Tests und type-check gruen

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 14:36:03 +02:00
schalli 07fc653f52 feat(quick-260911-nke): Benutzer an 34 Aufrufstellen gesetzt, zehn Tabellen gemessen, sechs Pruefungen umgedreht
- 30 verbleibende forTenant()-Aufrufstellen in sieben Diensten (calendar 6,
  dashboard 9, favorites 5, tender-email-config 3, tender-notification-pref 2,
  tender-rss-feed 2, tender-triage 3) reichen userId als drittes Argument
  durch. tender-digest.scheduler.ts bleibt zweistellig (Hintergrunddienst,
  Etappe 3c), mit Begruendung im Kommentar. Keine Methodensignatur, kein
  Controller angefasst, keine anwendungsseitige userId-Filterung entfernt.
- rls-scratch-check.mjs: zwoelf Extraktionsstellen auf die neue Migration
  umgeleitet (TenderEmailConfig/TenderNotificationPref/TenderSavedSearch/
  TenderTriage/TenderRssFeedSource in runTendersAreaChecks, SearchProvider in
  runSearchProviderAreaChecks/runDashboardAreaChecks, DashboardLayout/
  WidgetInstance, CalendarSource/FavoriteLink samt regelstand-eindeutig-Gates).
  SearchProvider/TenderRssFeedSource jetzt mit extractAllPolicySql (4 Regeln).
  runUserDimensionChecks() um die uebrigen neun Tabellen erweitert (neue
  Routine runCommandSeparatedPersonalTableCheck fuer die zwei NULL-faehigen
  Tabellen inkl. gemeinsame-Zeile-Pruefungen).
- Sechs Loch-Pruefungen umgedreht (dashboardlayout, widgetinstance,
  searchprovider, calendarsource, favoritelink-Doppelaussage getrennt) —
  alte Messung ohne Benutzer bleibt unter neuem Namen, Umkehrung MIT
  Benutzer erwartet das Gegenteil; kein alter Name mehr als Kennung.
- Baseline: 1020/62 Tests weiterhin gruen, Typpruefung sauber, Werkzeug
  203/203 bestanden (vorher 146).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-11 17:39:17 +02:00
schalli b5f22e2c4a feat(260911-gwh): GREEN — favorites/settings binden, Startpfad umbenennen, Widget-Besitzriegel
- favorites.service.ts: alle fuenf Methoden nehmen tenantId als ersten
  Parameter, laufen je ueber EINEN Klienten tenantPrisma (7 gebundene
  Favoritenzugriffe, 5 Aufrufstellen); create() prueft vor der Icon-Suche,
  dass das Ziel-Widget dem Aufrufer gehoert (T-GWH-05, Befund F aus Aufgabe 1
  bestaetigt den Fremdschluessel-Durchgriff) -- Widget not found fuer alle
  drei Faelle (existiert nicht/Kollege/fremder Mandant)
- favorites.controller.ts: reicht tenantId an alle fuenf Aufrufe durch,
  extractContext unveraendert (dashboard-Praezedenzfall)
- settings.service.ts: getSmtpConfig/saveSmtpConfig/getDecryptedSmtpConfig
  je EIN Klient (3 gebundene Zugriffe, 3 Aufrufstellen) -- Befund K damit
  erfuellt; Startpfad umbenannt in loadAnySmtpConfigForStartupTransport(),
  bleibt bewusst ungebunden (sechster Fall der Hintergrunddienst-Falle,
  WINDOWS #TBD-GWH -- Aufgabe 3 vergibt die Nummer)
- mail.module.ts: ruft den umbenannten Startpfad auf, Kommentar nennt beide
  Zustaende statt "single-tenant default"
- Vier Falsifizierungsnachweise durchgefuehrt und zurueckgenommen (siehe
  SUMMARY): (a) 3 Faelle rot, (b) 4 Faelle rot, (c) 9 Faelle rot, (d) 4 Faelle
  rot

Bekannt und erwartet (siehe SUMMARY, Praezedenzfall 260911-fh9): zwischen
dieser Aufgabe und Aufgabe 3 ist rls-access-inventory.spec.ts rot (2
Faelle) -- die Klassifikationstabelle ist noch nicht nachgezogen, das ist
Aufgabe 3.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-11 13:57:02 +02:00
schalli 9b65ac63c3 fix(favorites): normalize scheme-less URLs so favicons resolve
Tessera CI/CD / Lint & Type Check (push) Successful in 38s
Tessera CI/CD / Tests (push) Successful in 40s
Tessera CI/CD / Build & Publish Images (push) Successful in 22s
A favorite entered as a bare host ("ctl.de") passed @IsUrl() but had no
scheme, so `new URL()` threw inside icon discovery and it silently fell
back to a relative "/favicon.ico" — which 502'd through the icon proxy
and left the widget showing the first-letter placeholder ("C").

- add normalizeUrl() (prepend https:// when no scheme present)
- apply it in discoverFavoriteIconUrl and when storing the favorite url,
  so both the link and discovery use the normalized value
- on update, re-run discovery when the icon field is cleared, so editing
  a previously-broken favorite repairs its icon
- tests: normalizeUrl cases + end-to-end discovery (apple-touch extraction,
  scheme-less fallback stays absolute)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 13:58:44 +02:00
schalli d99253ba79 feat(favorites): GET /favorites/:id/icon proxy endpoint
Ownership-scoped (userId, matching update/remove) icon byte proxy.
Loads the row's stored iconUrl server-side and streams it through
IconDiscoveryService.fetchIconBytes -- never accepts a client-supplied
URL, so this can't become an open SSRF proxy.

Not-found/not-owned/no-icon -> 404. Upstream fetch failure (unreachable,
timeout, non-image, SSRF-blocked) -> 502, never a 200 with a placeholder.
Success sets Cache-Control so the browser doesn't refetch every load.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 15:34:02 +02:00
schalli eebceb298d fix(08): apply code review findings (CR-01, CR-02, WR-01–05, IN-01)
- CR-01: fix SSRF bypass — isPrivateIpv6 now delegates ::ffff:<ipv4> to
  isPrivateIpv4, covering 172.16-31.x and 169.254.x ranges
- CR-02: add ParseUUIDPipe to GET /favorites widgetId param + service guard
  so missing widgetId returns 400 instead of leaking all user favorites
- WR-01: link-widget — replace raw 'link.error' key with t('link.error') (4 sites)
- WR-02: favorites-widget — fix load-path error to use t('favorites.error')
- WR-03: widget-catalog-modal — move aria-hidden from outer wrapper to backdrop
- WR-04: calculator — remove duplicate M button (MR clone); MC/MR/M+/M−/MS remain
- WR-05: schema — add FavoriteLink→WidgetInstance FK with onDelete:Cascade
- IN-01: create-widget.dto.ts — update comment from four to eight supported types

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 11:03:16 +02:00
schalli 758d246e98 feat(08-03): FavoriteLink schema + FavoritesModule (CRUD + SSRF icon discovery)
- Add FavoriteLink Prisma model (userId/tenantId/widgetId scope, iconUrl nullable, position)
- IconDiscoveryService: port SSRF-protected icon discovery with redirect: 'manual',
  private IP / blocked-hostname checks, 4000ms timeout, 200k HTML cap (T-08-05)
- FavoritesService: list/create/update/remove all scoped by userId (T-08-06 / Pitfall 3)
- FavoritesController: GET /favorites?widgetId, POST, PATCH :id, DELETE :id
- FavoritesModule registered in AppModule
- tsc --noEmit passes for @tessera/api
2026-07-01 10:25:52 +02:00