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>
This commit is contained in:
sysops
2026-03-14 11:43:19 +01:00
parent a893084a88
commit d360c9a5ba
68 changed files with 11938 additions and 435 deletions
+178
View File
@@ -0,0 +1,178 @@
# PROJ-13: REST API für externe CRM-Anbindung
## Status: In Progress
**Created:** 2026-03-13
**Last Updated:** 2026-03-13
## Dependencies
- Requires: PROJ-1 (Authentifizierung) API-Keys sind an Nutzer/Rollen gebunden
- Requires: PROJ-5 (Speicherung & Indexierung) Daten kommen aus dem Archiv
- Requires: PROJ-6 (Volltext-Suche) Suche über API nutzbar
## Hinweis
Externe Systeme (CRM, ERP, Helpdesk etc.) sollen über eine dokumentierte REST API auf das Archiv zugreifen können **ausschließlich lesend**. Schreiboperationen (Importieren, Löschen, Labeln etc.) sind über die API nicht möglich und werden nicht implementiert. Authentifizierung über API-Keys, nicht über Session-Cookies.
## User Stories
- Als CRM-Administrator möchte ich einen API-Key generieren, damit mein CRM-System auf das Archiv zugreifen kann.
- Als CRM-System möchte ich E-Mails eines bestimmten Kontakts (E-Mail-Adresse) abrufen, damit ich die Kommunikationshistorie im CRM anzeigen kann.
- Als CRM-System möchte ich E-Mails nach Datum, Absender oder Betreff durchsuchen, damit ich gezielt relevante Mails finden kann.
- Als Admin möchte ich API-Keys verwalten (anlegen, deaktivieren, löschen), damit ich den Zugriff kontrollieren kann.
- Als Admin möchte ich sehen, welcher API-Key wann welche Anfragen gestellt hat.
## Acceptance Criteria
- [ ] API-Key-Verwaltung im Admin-Bereich: anlegen, benennen, deaktivieren, löschen
- [ ] API-Keys haben eine konfigurierbare Rolle (`user` oder `auditor`) bestimmt Zugriffsumfang (Leserechte)
- [ ] Nur `GET`-Methoden erlaubt `POST`, `PUT`, `PATCH`, `DELETE` geben generisch 405 Method Not Allowed zurück
- [ ] Authentifizierung via HTTP-Header: `Authorization: Bearer <api-key>`
- [ ] Endpunkt: `GET /api/v1/mails?from=&to=&subject=&date_from=&date_to=` Suche/Filterung
- [ ] Endpunkt: `GET /api/v1/mails/{message_id}` einzelne E-Mail abrufen (Metadaten)
- [ ] Endpunkt: `GET /api/v1/mails/{message_id}/raw` Original-EML herunterladen
- [ ] Endpunkt: `GET /api/v1/mails?contact=email@firma.de` alle Mails eines Kontakts (From oder To)
- [ ] Antwortformat: JSON für Metadaten, `application/octet-stream` für Raw-EML
- [ ] Paginierung: `?page=1&limit=25` (max. 100 pro Anfrage)
- [ ] API-Zugriffe werden im Audit-Log erfasst (API-Key-Name, Endpunkt, Zeitstempel)
- [ ] OpenAPI/Swagger-Dokumentation unter `/api/v1/docs`
## Edge Cases
- Ungültiger oder deaktivierter API-Key → 401 Unauthorized
- API-Key mit `user`-Rolle fragt Mails ab, auf die er keinen Zugriff hat → 403
- Rate-Limiting: zu viele Anfragen pro API-Key → 429 Too Many Requests
- Sehr große Ergebnismengen (>10.000 Treffer) → Paginierung erzwingen, kein Full-Dump
- CRM fragt nicht existierende Message-ID ab → 404
## Technical Requirements
- **Reine Lese-API** ausschließlich `GET`-Endpunkte, keine Schreiboperationen
- Eigener Route-Prefix `/api/v1/` für externe API (getrennt von interner `/api/`)
- API-Keys: zufällig generiert (32 Byte, Base64), bcrypt-gehasht in der DB (nie im Klartext)
- Rate-Limiting pro API-Key konfigurierbar (Standard: 60 Anfragen/Minute)
- OpenAPI 3.0 Spec wird aus Code generiert oder manuell gepflegt
- Versionierung: `/api/v1/` spätere Versionen brechen bestehende Clients nicht
---
## Tech Design (Solution Architect)
### Systemübersicht
```
CRM / ERP / Helpdesk
│ GET /api/v1/mails?contact=kunde@example.com
│ Authorization: Bearer <api-key>
Go Backend Externer API-Router (/api/v1/*)
├── API-Key Middleware ← statt Session-Cookie
├── Rate Limiter
├── Shared Search Service ←──── dieselbe Logik wie interne Suche (PROJ-6)
└── Shared Mail Service ←──── dieselbe Logik wie Mail-Abruf (PROJ-7)
```
### Komponentenstruktur
**Go Backend:**
```
/api/v1/* (externer Prefix, getrennt von internem /api/*)
├── API-Key Middleware ← ersetzt Session-Middleware
│ ├── Bearer-Token aus Header lesen
│ ├── SHA-256-Hash → DB-Lookup ← schneller Lookup ohne bcrypt-Overhead
│ ├── Key deaktiviert? → 401
│ ├── Rolle laden (user/auditor)
│ └── Rate-Limit-Konfiguration laden
├── Rate Limiter ← Token-Bucket pro API-Key
│ └── Limit überschritten → 429 + Retry-After Header
├── Method Guard ← alles außer GET → 405
├── Shared Search Service ← identische Logik wie /api/search (PROJ-6)
│ ├── Xapian QueryParser
│ ├── Rollen-Filter (user/auditor)
│ └── PostgreSQL Metadaten-Lookup
├── Shared Mail Service ← identische Logik wie /api/mails (PROJ-7)
│ ├── .m-Datei lesen + entschlüsseln
│ ├── MIME-Parser
│ └── Anhang-Streaming
├── Audit Logger ← API-Key-Name + Endpunkt + Zeitstempel
└── API-Key-Verwaltung (Admin)
├── POST /api/admin/apikeys ← Key generieren (einmalige Anzeige)
├── GET /api/admin/apikeys ← Liste (Name + Rolle + letzter Zugriff)
└── DELETE /api/admin/apikeys/{id}
```
### API-Key Authentifizierungsfluss
```
CRM-System
│ Authorization: Bearer am_a1b2c3d4e5f6...
API-Key Middleware
├─ Präfix "am_" prüfen
├─ SHA-256(token) → DB-Lookup (indiziert)
├─ Key gefunden + aktiv? Nein → 401
└─ Ja → Rolle + Rate-Limit laden
Rate Limiter (Token-Bucket)
├─ Limit erreicht? → 429 + Retry-After: 30
└─ OK → weiter
Method Guard
├─ POST/PUT/DELETE? → 405
└─ GET → weiter
Shared Service Layer
Audit Logger → API-Key-Name + Endpunkt + Zeitstempel
```
### Datenmodell
**Tabelle `api_keys`:**
| Feld | Beschreibung |
|------|-------------|
| `id` | Interne ID |
| `name` | Bezeichnung (z.B. "CRM Salesforce") |
| `token_hash` | SHA-256 des Tokens (für schnellen Lookup, indiziert) |
| `role` | `user` oder `auditor` |
| `active` | `true` / `false` |
| `rate_limit` | Anfragen pro Minute (Standard: 60) |
| `created_at` | Erstellungszeitpunkt |
| `last_used_at` | Letzter erfolgreicher Zugriff |
**Key-Format:**
```
Generiert: am_<32-Byte-random-Base64>
Gespeichert: SHA-256(token) in DB
Angezeigt: einmalig im Admin-UI danach nicht mehr abrufbar
```
### Technische Entscheidungen
| Entscheidung | Begründung |
|---|---|
| **Shared Service Layer** | Suche und Mail-Abruf teilen dieselbe Go-Logik mit der internen API kein doppelter Code |
| **SHA-256 statt bcrypt** | API-Keys sind kryptografisch zufällig (32 Byte) SHA-256 reicht, bcrypt wäre bei jeder Anfrage zu langsam |
| **`am_`-Präfix** | Erkennungsmerkmal für archivmail-Keys einfach filterbar in Logs |
| **Token einmalig anzeigen** | Nur Hash gespeichert kein späteres Auslesen möglich (wie GitHub PAT) |
| **Token-Bucket Rate Limiter** | Gleichmäßige Anfragen erlaubt, kurze Bursts toleriert |
| **`/api/v1/` Prefix** | Klare Versionierung zukünftige `/api/v2/` bricht bestehende Clients nicht |
| **Audit-Log bei API-Zugriffen** | Externe Zugriffe werden geloggt (anders als interne Lesezugriffe) |
### Abhängigkeiten
Kein zusätzliches Paket Rate-Limiter und SHA-256 aus der Go-Stdlib (`crypto/sha256`, `sync`).
## QA Test Results
_To be added by /qa_
## Deployment
_To be added by /deploy_