fix(PROJ-54): Listenansicht/Pagination für Rolle "user" korrigieren
Filtert die Mails des Nutzers (From/To/Cc/Bcc) jetzt serverseitig via req.AnyAddress vor LIMIT/OFFSET im Index, statt wie bisher per Post-Filter nach der Paginierung. total und totalPages stimmen damit mit den tatsächlich sichtbaren Treffern überein.
This commit is contained in:
+2
-1
@@ -70,7 +70,8 @@
|
|||||||
| PROJ-51 | Aufbewahrungsfristen nach Dokumentenart (Retention-Kategorien) | Deployed | [PROJ-51](PROJ-51-retention-kategorien.md) | 2026-06-13 |
|
| PROJ-51 | Aufbewahrungsfristen nach Dokumentenart (Retention-Kategorien) | Deployed | [PROJ-51](PROJ-51-retention-kategorien.md) | 2026-06-13 |
|
||||||
| PROJ-52 | Vollständigkeits-Reconciliation (Zähl-Report) | Planned | [PROJ-52](PROJ-52-vollstaendigkeits-reconciliation.md) | 2026-06-13 |
|
| PROJ-52 | Vollständigkeits-Reconciliation (Zähl-Report) | Planned | [PROJ-52](PROJ-52-vollstaendigkeits-reconciliation.md) | 2026-06-13 |
|
||||||
| PROJ-53 | Konfigurierbare Listenanzahl pro Seite | In Review | [PROJ-53](PROJ-53-konfigurierbare-listenanzahl.md) | 2026-06-14 |
|
| PROJ-53 | Konfigurierbare Listenanzahl pro Seite | In Review | [PROJ-53](PROJ-53-konfigurierbare-listenanzahl.md) | 2026-06-14 |
|
||||||
|
| PROJ-54 | Fix Listenansicht/Pagination für Rolle "user" (Nachbesserung PROJ-6/PROJ-21) | In Review | [PROJ-54](PROJ-54-fix-listenansicht-total.md) | 2026-06-14 |
|
||||||
|
|
||||||
<!-- Add features above this line -->
|
<!-- Add features above this line -->
|
||||||
|
|
||||||
## Next Available ID: PROJ-54
|
## Next Available ID: PROJ-55
|
||||||
|
|||||||
@@ -87,6 +87,20 @@ func (s *Server) handleSearch(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PROJ-54: For user role, restrict results to mails the user is involved
|
||||||
|
// in (From/To/CC/BCC) at the INDEX level — before LIMIT/OFFSET — so that
|
||||||
|
// Total and pagination reflect the user's own mails, not the full
|
||||||
|
// tenant/global result set. Email comes from the JWT session — no DB
|
||||||
|
// lookup needed. If email is missing, block all results (fail-safe).
|
||||||
|
if sess.Role == userstore.RoleUser {
|
||||||
|
userEmailFilter := strings.ToLower(sess.Email)
|
||||||
|
if userEmailFilter == "" {
|
||||||
|
writeJSON(w, http.StatusOK, map[string]interface{}{"total": 0, "hits": []interface{}{}})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.AnyAddress = userEmailFilter
|
||||||
|
}
|
||||||
|
|
||||||
// PROJ-21 Phase 4: Use per-tenant index when available; fall back to
|
// PROJ-21 Phase 4: Use per-tenant index when available; fall back to
|
||||||
// global index + post-filter when the tenant index manager is not wired.
|
// global index + post-filter when the tenant index manager is not wired.
|
||||||
// auditor always uses the global index — they see no-tenant mails only,
|
// auditor always uses the global index — they see no-tenant mails only,
|
||||||
@@ -164,18 +178,6 @@ func (s *Server) handleSearch(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SEC: For user role, restrict results to mails the user is involved in
|
|
||||||
// (From, To, or CC). Email comes from the JWT session — no DB lookup needed.
|
|
||||||
// If email is missing, block all results (fail-safe).
|
|
||||||
var userEmailFilter string
|
|
||||||
if sess.Role == userstore.RoleUser {
|
|
||||||
userEmailFilter = strings.ToLower(sess.Email)
|
|
||||||
if userEmailFilter == "" {
|
|
||||||
writeJSON(w, http.StatusOK, map[string]interface{}{"total": 0, "hits": []interface{}{}})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Batch-load thread info and received_at fallback for all hits
|
// Batch-load thread info and received_at fallback for all hits
|
||||||
hitIDs := make([]string, len(result.Hits))
|
hitIDs := make([]string, len(result.Hits))
|
||||||
for i, h := range result.Hits {
|
for i, h := range result.Hits {
|
||||||
@@ -201,14 +203,6 @@ func (s *Server) handleSearch(w http.ResponseWriter, r *http.Request) {
|
|||||||
eh.Date = t.UTC().Format(time.RFC3339)
|
eh.Date = t.UTC().Format(time.RFC3339)
|
||||||
}
|
}
|
||||||
eh.HasAttachments = len(pm.Attachments) > 0
|
eh.HasAttachments = len(pm.Attachments) > 0
|
||||||
|
|
||||||
// User isolation: skip mails the user is not involved in.
|
|
||||||
if userEmailFilter != "" && !mailBelongsToUser(pm, userEmailFilter) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else if userEmailFilter != "" {
|
|
||||||
// If mail can't be parsed, deny access to user role.
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
// Auditor isolation: skip mails that belong to a tenant.
|
// Auditor isolation: skip mails that belong to a tenant.
|
||||||
if auditorAllowedIDs != nil {
|
if auditorAllowedIDs != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user