From f63fcb8a2d16b71855e6208a81f1107634d47972 Mon Sep 17 00:00:00 2001 From: patrick Date: Fri, 16 Oct 2026 06:51:29 +0000 Subject: [PATCH] UX-03: anhang-vorschau Frontend (web/mail-search): Anhang-Vorschau fuer PDF (Canvas-Rendering via pdfjs-dist, gleiches Muster wie DOC-05s web/pdf-viewer/PdfViewer.tsx -- kein aequivalentes PdfCanvasPreview im Repo gefunden, daher PdfViewer als Vorlage uebernommen statt neu erfunden) und Bilder ( auf einem authentifiziert geladenen Blob). Die Vorschauart haengt ausschliesslich vom serverseitig per Magic-Bytes bestimmten contentType ab, nie von einem Dateinamen (den es an der Schnittstelle gar nicht gibt); nicht unterstuetzte Typen werden nur als Hinweistext angezeigt, nie eingebettet. Kein iframe//dangerouslySetInnerHTML -- durch automatisierten Sicherheitsreview-Test abgesichert. Neue lib/http.ts (authentifizierter Zugriff ueber @nexarch/shl, identisch zu web/pdf-viewer/lib/http.ts) und lib/attachments.ts (Anhangsliste/ -inhalt der Mail-API). Pruefprotokoll in mail/docs/UX-03-PRUEFPROTOKOLL.md. Co-Authored-By: Claude Sonnet 5 --- .gitignore | 1 + mail/docs/UX-03-PRUEFPROTOKOLL.md | 227 ++++++++++++++ .../mail/[messageId]/AttachmentList.test.tsx | 60 ++++ .../app/mail/[messageId]/AttachmentList.tsx | 50 ++++ .../[messageId]/AttachmentPreview.test.tsx | 63 ++++ .../mail/[messageId]/AttachmentPreview.tsx | 33 +++ .../app/mail/[messageId]/ImagePreview.tsx | 54 ++++ .../app/mail/[messageId]/PdfCanvasPreview.tsx | 106 +++++++ .../attachment-preview.security.test.ts | 33 +++ web/mail-search/app/mail/[messageId]/page.tsx | 2 + web/mail-search/lib/attachmentKind.test.ts | 32 ++ web/mail-search/lib/attachmentKind.ts | 22 ++ web/mail-search/lib/attachments.test.ts | 62 ++++ web/mail-search/lib/attachments.ts | 58 ++++ web/mail-search/lib/http.ts | 33 +++ web/mail-search/package-lock.json | 278 ++++++++++++++++++ web/mail-search/package.json | 4 + web/mail-search/vitest.config.ts | 5 + 18 files changed, 1123 insertions(+) create mode 100644 mail/docs/UX-03-PRUEFPROTOKOLL.md create mode 100644 web/mail-search/app/mail/[messageId]/AttachmentList.test.tsx create mode 100644 web/mail-search/app/mail/[messageId]/AttachmentList.tsx create mode 100644 web/mail-search/app/mail/[messageId]/AttachmentPreview.test.tsx create mode 100644 web/mail-search/app/mail/[messageId]/AttachmentPreview.tsx create mode 100644 web/mail-search/app/mail/[messageId]/ImagePreview.tsx create mode 100644 web/mail-search/app/mail/[messageId]/PdfCanvasPreview.tsx create mode 100644 web/mail-search/app/mail/[messageId]/attachment-preview.security.test.ts create mode 100644 web/mail-search/lib/attachmentKind.test.ts create mode 100644 web/mail-search/lib/attachmentKind.ts create mode 100644 web/mail-search/lib/attachments.test.ts create mode 100644 web/mail-search/lib/attachments.ts create mode 100644 web/mail-search/lib/http.ts diff --git a/.gitignore b/.gitignore index b543ce14..5a08459e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ web/*/.next/ web/*/next-env.d.ts web/*/tsconfig.tsbuildinfo web/pdf-viewer/public/pdf.worker.min.mjs +web/mail-search/public/pdf.worker.min.mjs __pycache__/ diff --git a/mail/docs/UX-03-PRUEFPROTOKOLL.md b/mail/docs/UX-03-PRUEFPROTOKOLL.md new file mode 100644 index 00000000..b03cde6d --- /dev/null +++ b/mail/docs/UX-03-PRUEFPROTOKOLL.md @@ -0,0 +1,227 @@ +# UX-03 — Anhang-Vorschau: Prüfprotokoll + +Datum: 2026-10-16 +Host: 192.168.1.204 (Build/Test), frischer Klon in eigenem Arbeitsverzeichnis +Pakete: `mail/internal/mailapi` (geändert), `mail/internal/attachments` (Test ergänzt), `web/mail-search` (neu: Anhang-Vorschau) + +## Umsetzung + +### Backend (`mail/internal/mailapi`) + +- Neue Route `GET /api/v1/mail/messages/{messageID}/attachments` — + liefert die Anhangsliste als `{index, contentType, size}[]`. Es gibt + keinen eigenen Anhang-Katalog (keine DB-Tabelle mit Dateinamen); wie + `mailexport.fetchAttachments` tastet der Handler die Indizes ab 0 ab, + bis `storage.ErrNotFound` den ersten fehlenden Index meldet + (`maxAttachmentProbe = 200`, dieselbe Schutzgrenze wie in + `mailexport`). +- `contentType` wird für JEDEN Anhang mit `net/http.DetectContentType` + auf den tatsächlich gelesenen Bytes bestimmt (Akzeptanzkriterium 2). + Es gibt an dieser Stelle gar keinen Dateinamen, der stattdessen + herangezogen werden könnte — `attachmentListItem` hat bewusst kein + Filename-Feld. +- `handleGetAttachment` (bestehende Route, INT-01) ist geändert: der + bisher hartkodierte `Content-Type: application/octet-stream` wird + durch `http.DetectContentType(content)` ersetzt; zusätzlich + `Content-Disposition: inline` (vorher kein Header) statt eines + erzwungenen Downloads — Grundlage für Akzeptanzkriterium 1 ("ohne + externen Download"). +- `openapi.yaml` um den neuen Pfad `/messages/{messageID}/attachments` + samt Schema `AttachmentListResponse`/`AttachmentListItem` ergänzt; + `openapi_test.go`s Endpunkt-Abgleich (`TestOpenAPIDocument_MatchesActualEndpoints`) + und der Auth-Pflichtprüfungstest (`TestRoutes_RejectMissingOrInvalidToken`) + um die neue Route erweitert. + +### Backend (`mail/internal/attachments`, IMP-02) + +- Kein Verhaltens-, nur ein Testzugang: neuer Test + `TestExtract_WrongExtensionRealPDFContentDetectedByMagicBytes` belegt, + dass `VerifiedContentType` bereits vor UX-03 korrekt per Magic-Bytes + bestimmt wurde (`http.DetectContentType` auf den echten Bytes) und + dabei sowohl eine falsche Dateiendung (`.txt`) als auch einen falsch + deklarierten MIME-Typ (`text/plain`) ignoriert. + +### Frontend (`web/mail-search`) + +- `lib/http.ts` (neu): authentifizierter Zugriff auf Core-Modul-Token- + geschützte APIs über `@nexarch/shl` (`createSession`/`apiFetch`) — + identisches Muster wie `web/pdf-viewer/lib/http.ts` (DOC-05/UX-10), + nicht neu erfunden. `NEXT_PUBLIC_AUTH_MODE`, `NEXT_PUBLIC_CORE_API_BASE`, + `NEXT_PUBLIC_LOGIN_URL` (alles Umgebungsvariablen, keine Zugangsdaten + im Code). +- `lib/attachments.ts` (neu): dünner Client für die Anhangsliste/den + Anhanginhalt der Mail-API (`NEXT_PUBLIC_MAIL_API_URL`). Direkter + authentifizierter Aufruf wie bei `web/pdf-viewer/lib/api.ts` + (`documentContentUrl`) — kein Backend-for-Frontend-Proxy, weil die + Mail-API ein Bearer-Modul-Token verlangt, das ein Next.js-Server-Route- + Handler nicht eigenständig ausstellen kann. Liefert bei Ausfall + `{status:"unavailable"}` statt zu werfen (gleiche Konvention wie + `lib/caseContext.ts`). +- `lib/attachmentKind.ts` (neu): `classifyAttachment(contentType)` ist + die EINZIGE Stelle, die über die Vorschauart entscheidet — Eingabe + ausschließlich der serverseitig bestimmte `contentType`. +- `app/mail/[messageId]/PdfCanvasPreview.tsx` (neu): PDF-Vorschau per + Canvas-Rendering (pdfjs-dist, wie DOC-05s `PdfViewer.tsx` — gleiches + Grundmuster, hier auf Laden + Seitennavigation reduziert, ohne + Suche/Zoom). **Kein äquivalentes, bereits existierendes + `PdfCanvasPreview` wurde im Repository gefunden** (durchsucht: + gesamtes Repo inkl. aller Branches, auch DMS/dms-browser) — DOC-05s + `web/pdf-viewer/components/PdfViewer.tsx` ist das einzige vorhandene + Canvas-PDF-Muster und wurde als Vorlage übernommen (`workerSrc` aus + `public/pdf.worker.min.mjs`, `copy-pdf-worker`-Skript in + `package.json`, identisch zu pdf-viewer). +- `app/mail/[messageId]/ImagePreview.tsx` (neu): Bild-Vorschau per + `` auf einem via `apiFetch` geladenen Blob (nativ sicheres + Bild-Decoding, kein Skript-/HTML-Rendering wie bei iframe — anders + als PDF ist hier kein Canvas-Zwang nötig, das Ticket verlangt Canvas + ausdrücklich nur für PDF). +- `app/mail/[messageId]/AttachmentPreview.tsx` (neu): wählt anhand + `classifyAttachment` zwischen PDF-/Bild-Vorschau oder einem neutralen + Hinweis ("Für diesen Dateityp ist keine Vorschau verfügbar.") — nie + iframe/``/`dangerouslySetInnerHTML`. +- `app/mail/[messageId]/AttachmentList.tsx` (neu): lädt die Anhangsliste + und rendert je Anhang eine `AttachmentPreview`; in `page.tsx` unter + `CaseContextPanel` eingebunden. +- `.gitignore`: `web/mail-search/public/pdf.worker.min.mjs` ergänzt + (gleiche Ausnahme wie für `web/pdf-viewer`). + +## Prüfung 1: Test — Anhang mit falscher Dateiendung, aber echtem PDF-Inhalt wird korrekt als PDF erkannt und angezeigt + +- Backend, Erkennung bei Extraktion (IMP-02): + `TestExtract_WrongExtensionRealPDFContentDetectedByMagicBytes` + (`mail/internal/attachments/attachments_test.go`) — Anhang mit Endung + `.txt`, deklariertem MIME-Typ `text/plain`, echtem PDF-Inhalt. + `VerifiedContentType` == `application/pdf`. **BESTANDEN.** +- Backend, Erkennung beim Abruf (UX-03): + `TestGetAttachment_ContentTypeFromMagicBytesIgnoresDeclaredType` und + `TestListAttachments_ContentTypesDetectedFromMagicBytes` + (`mail/internal/mailapi/mailapi_test.go`) — Anhang mit real im + Speicher abgelegtem PDF-Inhalt, aber als `text/plain` bzw. + `application/octet-stream` deklariert abgelegt. `GET .../attachments/0` + liefert `Content-Type: application/pdf`; `GET .../attachments` listet + `contentType: "application/pdf"`. **BESTANDEN.** +- Frontend: `lib/attachmentKind.test.ts` — `classifyAttachment` liefert + `"pdf"` für `application/pdf`, unabhängig von jeder Namensinformation + (`AttachmentInfo` trägt ohnehin keinen Dateinamen). `AttachmentPreview.test.tsx` + zeigt, dass ein Anhang mit `contentType: "application/pdf"` tatsächlich + die Canvas-PDF-Vorschau rendert. **BESTANDEN.** + +## Prüfung 2: Sicherheitsreview bestätigt: keine iframe-Einbindung von Anhangsinhalt + +- Automatisiert statt nur behauptet: + `app/mail/[messageId]/attachment-preview.security.test.ts` liest den + Quelltext von `PdfCanvasPreview.tsx`, `ImagePreview.tsx`, + `AttachmentPreview.tsx`, `AttachmentList.tsx` (Kommentare vor dem + Abgleich entfernt) und prüft die Abwesenheit von `` + `CanvasRenderingContext2D` (pdfjs-dist `page.render`); + `ImagePreview` ausschließlich über `` (natives + Decoding, kein HTML-Parsing von Anhangsinhalt); `AttachmentPreview` + bettet für nicht unterstützte Typen ausschließlich Text ein, nie eine + Fremdinhalt-Einbettung. Kein `handleGetAttachment`-Aufrufer im + gesamten Frontend navigiert direkt zur Anhang-URL (kein ``/`