d360c9a5ba
- Systemauslastungs-Sektion wird immer gerendert (nicht nur bei Erfolg) - Fehlermeldung wenn /api/admin/system/stats nicht erreichbar ist - Feature-Status auf In Review gesetzt Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7.3 KiB
7.3 KiB
PROJ-7: E-Mail-Ansicht (Lesen & Anhänge)
Status: In Progress
Created: 2026-03-12 Last Updated: 2026-03-12
Dependencies
- Requires: PROJ-1 (Authentifizierung) – nur eingeloggte Nutzer mit Zugriffsrecht
- Requires: PROJ-5 (Speicherung & Indexierung) – E-Mail-Daten aus der Datenbank
User Stories
- Als Nutzer möchte ich eine E-Mail aus den Suchergebnissen öffnen und lesen, damit ich den vollständigen Inhalt sehe.
- Als Nutzer möchte ich Anhänge herunterladen, damit ich auf angefügte Dokumente zugreifen kann.
- Als Nutzer möchte ich die originalen E-Mail-Header einsehen (technische Details), damit ich Routing und Authentizität prüfen kann.
- Als Nutzer möchte ich E-Mails im HTML-Format sehen (mit sanitizierten externen Inhalten), damit die Formatierung erhalten bleibt.
- Als Nutzer möchte ich die Originalmail als EML herunterladen können.
Acceptance Criteria
- E-Mail-Detailansicht zeigt: Von, An, CC, Datum, Betreff, Body
- HTML-Body wird original dargestellt – kein Entfernen oder Verändern von Inhalten
- Darstellung in einem vollständig isolierten
<iframe sandbox>– kein JavaScript aus der Mail kann ausgeführt werden - Externe Bilder/Ressourcen standardmäßig blockiert, Nutzer kann per Button "externe Inhalte laden" freischalten
- Fallback auf Plain-Text wenn kein HTML vorhanden
- Anhang-Liste mit Dateiname, Typ und Größe
- Anhänge einzeln herunterladbar
- Header-Ansicht (klappbar) zeigt alle Original-MIME-Header
- Download der Original-E-Mail als .eml Datei
- Zugriffsschutz: Nutzer kann nur E-Mails aus eigenen Postfächern öffnen
- Jeder Zugriff auf eine E-Mail wird im Audit-Log erfasst
Edge Cases
- E-Mail mit nur Plain-Text → normales Rendering ohne HTML
- HTML mit JavaScript → Script wird durch iframe-Sandbox blockiert, HTML-Inhalt bleibt unverändert sichtbar
- Externe Tracker (Pixel, Links) → standardmäßig blockiert durch CSP, auf Wunsch des Nutzers freischaltbar
- E-Mail mit sehr großen Anhängen (> 100 MB) → Download-Streaming, kein Speicher-Overflow
- E-Mail mit verschachteltem MIME (E-Mail in E-Mail als Anhang) → als EML-Anhang anzeigen
- Nicht unterstützte Zeichenkodierung → graceful Fallback mit Hinweis
Technical Requirements
- Kein HTML-Sanitizing – originale Darstellung ohne Veränderung des Inhalts
- Isolation über
<iframe sandbox="allow-same-origin">– JavaScript blockiert, Inhalt originalgetreu - Externe Ressourcen über CSP (
Content-Security-Policy) serverseitig blockiert, opt-in per Nutzer-Aktion - Anhang-Downloads als Stream (kein vollständiges In-Memory-Laden)
- Audit-Log-Eintrag: Nutzer-ID, E-Mail-ID, Zeitstempel bei jedem Lesezugriff
Tech Design (Solution Architect)
Komponentenstruktur
Next.js Frontend:
/mail/[message_id]
├── MailHeader
│ ├── Betreff
│ ├── Von / An / CC / Datum / Größe
│ └── HeaderToggle (klappbar)
│ └── RawHeaderView ← alle Original-MIME-Header als Text
├── ActionBar
│ ├── EML-Download Button ← lädt Original-Mail herunter
│ ├── Externe Inhalte laden ← Button, standardmäßig deaktiviert
│ └── Zurück zur Suche
├── MailBody
│ ├── HtmlView ← originales HTML in <iframe sandbox>
│ │ └── ExternalContentBanner ← Hinweis "Externe Inhalte blockiert [Laden]"
│ └── PlainTextView ← Fallback wenn kein HTML vorhanden
└── AttachmentList
└── AttachmentItem (pro Anhang)
├── Icon (nach MIME-Type)
├── Dateiname + Typ + Größe
└── Download-Button ← direkter Stream vom Go-Backend
Go Backend:
GET /api/mails/{message_id}
├── Session Middleware ← Auth prüfen
├── Zugriffsrecht prüfen ← user: nur eigenes Postfach / auditor: alle
├── .m-Datei von Disk lesen ← Pfad aus PostgreSQL
├── AES-256-GCM entschlüsseln ← Schlüssel aus Prozessspeicher
├── MIME-Parser ← Body + Header + Anhang-Metadaten extrahieren
└── JSON-Antwort ← Metadaten + originaler HTML-Body + Anhang-Liste
GET /api/mails/{message_id}/attachments/{index}
├── Session Middleware
├── Zugriffsrecht prüfen
├── Hash aus PostgreSQL ← welche astore/-Datei?
├── astore/-Datei öffnen
├── AES-256-GCM entschlüsseln (stream)
└── HTTP-Streaming-Response ← Content-Disposition: attachment
GET /api/mails/{message_id}/raw
├── Session Middleware
├── Zugriffsrecht prüfen
├── .m-Datei entschlüsseln
└── HTTP-Streaming-Response ← Content-Type: message/rfc822
Datenabruf-Fluss
Browser klickt MailCard aus Suchergebnissen
│
│ GET /api/mails/<message_id>
▼
Zugriffsrecht prüfen
│
▼
PostgreSQL → store_path
│
▼
.m-Datei lesen + AES-256-GCM entschlüsseln
│
▼
MIME-Parser → body_html (original, unverändert), body_plain, headers[], attachments[]
│
▼
JSON-Antwort an Next.js
│
▼
Next.js:
├── HTML → <iframe sandbox="allow-same-origin">
│ └── CSP-Header blockiert externe Ressourcen
│ Nutzer klickt "Externe Inhalte laden"
│ → iframe neu laden ohne CSP-Restriction
└── Anhang-Liste → Download-Links
Technische Entscheidungen
| Entscheidung | Begründung |
|---|---|
| Kein HTML-Sanitizing | Originale Darstellung – kein Inhalt wird verändert oder entfernt |
<iframe sandbox> |
JavaScript aus der Mail wird blockiert ohne den HTML-Inhalt zu verändern – Inhalt bleibt originalgetreu |
| CSP für externe Ressourcen | Tracking-Pixel und externe Bilder standardmäßig blockiert – Nutzer kann bewusst freischalten |
| Entschlüsselung nur im Backend | Verschlüsselte Rohdaten verlassen den Server nie |
| Anhang-Download als Stream | Große Anhänge (>100 MB) nie komplett in RAM – direkt von Disk zum Browser |
| Kein Audit-Log bei Lesezugriff | Bewusste Entscheidung (PROJ-11): Lesezugriffe werden nicht geloggt |
Abhängigkeiten
Go Backend:
| Paket | Zweck |
|---|---|
mime, mime/multipart |
MIME-Parsing (Stdlib) |
Next.js Frontend: Nur shadcn/ui (bereits installiert), kein zusätzliches Paket nötig.
QA Test Results
To be added by /qa
Deployment
Lokal bauen
# Im Projektverzeichnis
npm run build
Build-Artefakt liegt danach in .next/.
Auf Server übertragen (192.168.1.131)
# Next.js-Build + Abhängigkeiten übertragen
rsync -avz --delete \
.next/ \
package.json \
package-lock.json \
next.config.ts \
root@192.168.1.131:/opt/archivmail/frontend/
# Auf dem Server: Abhängigkeiten installieren & Dienst neu starten
ssh root@192.168.1.131 "cd /opt/archivmail/frontend && npm ci --omit=dev && systemctl restart archivmail-frontend"
Voraussetzungen auf dem Server
- Node.js ≥ 20 installiert (
node -v) - Verzeichnis
/opt/archivmail/frontend/existiert - Systemd-Unit
archivmail-frontendläuftnpm run start(Port 3000) - Go-Backend läuft auf Port 8080, Next.js proxied
/api/*dorthin (siehenext.config.ts)