Files
archivmail/features/PROJ-7-email-ansicht.md
T
sysops d360c9a5ba feat(PROJ-17): Admin Dashboard Systemauslastung immer anzeigen
- 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>
2026-03-14 11:43:19 +01:00

7.3 KiB
Raw Blame History

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-frontend läuft npm run start (Port 3000)
  • Go-Backend läuft auf Port 8080, Next.js proxied /api/* dorthin (siehe next.config.ts)