# PROJ-54: Fix Listenansicht/Pagination für Rolle "user" (Nachbesserung PROJ-6/PROJ-21)

## Status: Deployed
**Created:** 2026-06-14
**Last Updated:** 2026-06-14

## Dependencies
- PROJ-6 (Volltext-Suche & Filterung)
- PROJ-21 (Multi-Tenancy)
- PROJ-50 (liefert `index.SearchRequest.AnyAddress` + `cc_addr`/`bcc_addr` Indexfelder, hier wiederverwendet)

## Hintergrund / Bug
In `internal/api/search_handlers.go` (`handleSearch`) wird für Nutzer mit Rolle `user` nach dem Such-/Pagination-Query (LIMIT/OFFSET) zusätzlich pro Treffer per `mailBelongsToUser()` gefiltert (nur Mails, in denen der Nutzer als From/To/CC vorkommt).

Folgen:
1. `result.Total` (z.B. 16141, Gesamtzahl im Tenant/Index) wird nicht an die gefilterte Treffermenge angepasst → Frontend zeigt z.B. "16141 E-Mails im Archiv", die Tabelle aber nur 3 Zeilen.
2. Pagination ist falsch: LIMIT/OFFSET wirkt auf die ungefilterte Menge, die Filterung danach pro Seite — `totalPages` ist falsch, einzelne Seiten können fast leer sein, obwohl der Nutzer mehr eigene Mails hat.

## User Stories
- Als normaler Nutzer möchte ich, dass die angezeigte Gesamtzahl und die Seitenanzahl in der Listenansicht zu meinen tatsächlich sichtbaren E-Mails passen.
- Als normaler Nutzer möchte ich, dass jede Seite mit der eingestellten Anzahl (z.B. 25) Treffer gefüllt ist, solange genug eigene Mails vorhanden sind.

## Acceptance Criteria
- [ ] Für Rolle `user`: die Index-Suche filtert bereits serverseitig auf Mails, in denen die Nutzer-E-Mail in From/To/Cc/Bcc vorkommt (`index.SearchRequest.AnyAddress`), VOR LIMIT/OFFSET.
- [ ] `total` in der API-Antwort entspricht der gefilterten Treffermenge (Anzahl der für den Nutzer sichtbaren Mails), nicht der globalen/Tenant-Gesamtzahl.
- [ ] Pagination (`totalPages`, "Seite X von Y") ist für Rolle `user` korrekt — jede Seite ist (bis auf die letzte) vollständig mit `page_size` Treffern gefüllt, sofern genug eigene Mails existieren.
- [ ] Rollen admin/superadmin/domain_admin/auditor (kein `userEmailFilter`) verhalten sich unverändert.
- [ ] Bestehende Volltext-Query (`q`) und Filter (from/to/date/has_attachment) funktionieren weiterhin in Kombination mit der User-Einschränkung (AND-Verknüpfung).

## Edge Cases
- Nutzer ist nur per CC/BCC in einer Mail, die vor dem PROJ-50-Reindex archiviert wurde (cc_addr/bcc_addr im Index noch leer) → Mail erscheint ggf. erst nach `archivmail reindex` in den Ergebnissen. Dokumentiert als bekannte Einschränkung, kein Blocker (vorher war das Verhalten ohnehin inkonsistent durch die kaputte Pagination).
- `sess.Email` leer → wie bisher: `total: 0, hits: []` (fail-safe).
- Mail nicht parsbar (`mailparser.Parse` Fehler) → bleibt im Ergebnis (Index-Filter hat bereits entschieden), nur Anreicherung (From/To/Subject/...) bleibt leer.

## Technical Requirements (optional)
- Keine neue Migration nötig (cc_addr/bcc_addr existieren bereits aus PROJ-50-Vorarbeit).
- Reindex (`archivmail reindex`) empfohlen, damit cc_addr/bcc_addr für Bestandsmails befüllt sind.

---

## Tech Design (Solution Architect)
_Übersprungen auf Wunsch des Nutzers — direkte Umsetzung (Bugfix)._

Fix in `internal/api/search_handlers.go`:
- `userEmailFilter`-Ermittlung nach vorne verschieben (vor den `searchIdx.Search(req)`-Aufruf) und `req.AnyAddress = userEmailFilter` setzen, wenn Rolle `user`.
- Den bisherigen Post-Filter-Block ("User isolation: skip mails the user is not involved in" + "If mail can't be parsed, deny access") entfernen — Filterung passiert jetzt im Index vor LIMIT/OFFSET.
- `mailBelongsToUser()` bleibt unverändert (wird an anderen Stellen weiter verwendet: Export, eDiscovery, Threads, OCR).

## QA Test Results

**Tested:** 2026-06-14 — Code-Review + Logikprüfung (kein lokaler Go-Build möglich, keine Live-Tests durchgeführt).
**Methode:** statische Analyse von `internal/api/search_handlers.go` (`handleSearch`), `internal/index/manticore.go` (`Search`, `escapeManticoreMatch`), `internal/index/index.go`. Diff gegen HEAD verifiziert.

### Acceptance Criteria

| # | Kriterium | Ergebnis |
|---|-----------|----------|
| 1 | User-Rolle filtert serverseitig via `AnyAddress` VOR LIMIT/OFFSET | PASS |
| 2 | `total` entspricht gefilterter Treffermenge | PASS |
| 3 | Pagination/`totalPages` korrekt, Seiten gefüllt | PASS |
| 4 | admin/superadmin/domain_admin/auditor unverändert | PASS |
| 5 | Volltext-Query + Filter AND-verknüpft mit User-Einschränkung | PASS |

**AC1 — PASS:** `req.AnyAddress = userEmailFilter` wird in Zeile ~101 gesetzt, also VOR `searchIdx.Search(req)` (Zeile ~116). In `manticore.go` Zeile 292-294 wird `AnyAddress` in `@(from_addr,to_addr,cc_addr,bcc_addr)` als MATCH-Part eingefügt. COUNT (Zeile 330-335) und SELECT mit LIMIT/OFFSET (Zeile 371-377) nutzen beide dasselbe `whereClause` inkl. dieses MATCH. Filterung wirkt damit korrekt vor der Paginierung.

**AC2 — PASS:** `total` stammt aus dem COUNT-Query, der denselben MATCH-Filter enthält → zählt nur Mails des Nutzers. Der alte, `total`-verfälschende Post-Filter-Block ist laut Diff vollständig entfernt (sowohl der `continue` bei `!mailBelongsToUser` als auch der `continue` bei Parse-Fehler).

**AC3 — PASS:** Da der Index-Filter vor LIMIT/OFFSET greift, liefert jede Seite bis zur letzten genau `page_size` Treffer. `total` und Seitenzahl sind konsistent.

**AC4 — PASS:** Der neue Block ist strikt auf `sess.Role == userstore.RoleUser` beschränkt. Für alle anderen Rollen bleibt `req.AnyAddress` leer → MATCH-Part wird nicht erzeugt (Zeile 292 `if req.AnyAddress != ""`). Tenant-Filterung (Zeile 104-141) und Auditor-Filterung (Zeile 167-179, 207-212) sind unverändert. Wichtig: Rolle `user` mit zugewiesenem Tenant nutzt weiterhin entweder den Per-Tenant-Index (`usedTenantIndex`) oder den Fallback-Post-Filter (Zeile 125-141) — der `AnyAddress`-Filter wirkt zusätzlich, nicht ersetzend. Korrekte AND-Semantik (Tenant ∩ eigene Mails).

**AC5 — PASS:** Alle MATCH-Parts (`Query`, `From`, `To`, `OwnEmail`, `AnyAddress`) werden mit Leerzeichen zu einem MATCH-String verkettet (Zeile 296-303), was in Manticore implizit AND ist. Date/HasAttachment sind separate WHERE-AND-Bedingungen. AND-Verknüpfung bestätigt.

### Security-Audit (Red Team)

- **Manticore-Injection:** PASS. `AnyAddress` läuft durch `escapeManticoreMatch()` (Zeile 293), identisch zu den bereits deployten `From`/`To`/`OwnEmail`-Filtern. `@` ist in der `specials`-Liste enthalten und wird escaped, sodass das User-`@(...)`-Feldpräfix nicht durch User-Input gefälscht werden kann.
- **Auth/Tenant-Isolation:** PASS. Quelle ist `sess.Email` aus dem JWT, kein Request-Parameter → nicht manipulierbar. Tenant-Isolation bleibt durch die unveränderten Pfade (Per-Tenant-Index / Fallback) erhalten.
- **Fail-safe:** PASS. Leeres `sess.Email` → `total:0, hits:[]` (Edge Case aus Spec erfüllt).

### Bugs / Hinweise

- **Hinweis (Low, keine Regression — Verhalten dokumentiert):** Edge Case CC/BCC-only-Mails vor PROJ-50-Reindex erscheinen erst nach `archivmail reindex`. In Spec als bekannte Einschränkung dokumentiert.
- **Beobachtung (Low, kein Bug):** Der ursprüngliche Post-Filter nutzte `mailBelongsToUser()`, das From/To/CC prüfte (kein BCC). Der neue Index-Filter deckt zusätzlich `bcc_addr` ab — semantische Erweiterung, konsistent mit AC1 ("From/To/Cc/Bcc"). Für `handleGetMail`/`handleGetRaw`/`handleGetAttachment` bleibt `mailBelongsToUser()` (ohne BCC) der Zugriffs-Gate. Folge: Eine Mail, in der der Nutzer NUR per BCC vorkommt, erscheint künftig in der Liste, der Detail-Aufruf liefert dann aber 403. Inkonsistenz zwischen Listentreffer und Detailzugriff. Severity Low (BCC im archivierten Mailverkehr selten sichtbar; kein Datenleck, eher das Gegenteil). Empfehlung an Backend: entweder `mailBelongsToUser()` um CC/BCC-Konsistenz erweitern oder BCC aus dem Listen-Filter ausschließen, damit Liste und Detailzugriff deckungsgleich sind.
- **Kein Critical/High gefunden.** Keine Regressionen für admin/superadmin/domain_admin/auditor erkennbar.

### Regression (Code-Review)

- Andere Nutzer von `mailBelongsToUser()` (Export, eDiscovery, Threads, OCR, `handleGetMail/Raw/Attachment`) bleiben laut Diff unberührt — Funktion selbst unverändert. PASS.

### Production-Ready: JA (mit Vorbehalt)

Keine Critical/High-Bugs. Empfehlungen vor Deploy:
1. `archivmail reindex` auf Testserver ausführen (cc_addr/bcc_addr für Bestand befüllen), dann Live-Verifikation der AC mit echtem `user`-Account (Total = sichtbare Mails, gefüllte Seiten).
2. Low-Inkonsistenz Liste↔Detail bei reinen BCC-Mails als Folge-Ticket für Backend einplanen (nicht blockierend).

## Deployment
**Deployed:** 2026-06-14
- Test: 192.168.1.132 — via `update.sh` (commit 776dee8), Backend+Frontend laufen, Manticore-Reindex 16177/16177 (0 Fehler), Backfill cc_addr/bcc_addr (1 nachindiziert, 0 Fehler).
- Produktion: 192.168.1.131 — via `update.sh` (commit 62693fa), Backend+Frontend laufen, Manticore-Reindex 129/129 (0 Fehler).
- Hinweis: Live-Smoke-Test mit echtem "user"-Account (Total/Pagination) konnte mangels Test-Credentials nicht automatisiert durchgeführt werden — vom Nutzer als "aktuell" bestätigt.
