Commit Graph

72 Commits

Author SHA1 Message Date
sysops 7ac0391205 fix(PROJ-51): retain_until_source nicht für Endbenutzer, WARN-Status in CLI
- search_handlers.go: retain_until_source wird nur noch an Rollen != user
  ausgegeben, um interne Archivierungsregel-IDs nicht an normale
  Endbenutzer zu exponieren
- cmd_status.go: archivmail status zeigt [WARN] statt [OK] wenn Detail
  mit "WARNUNG" beginnt (z.B. PROJ-51 Retention-Check); Exit-Code/r.OK
  bleibt unverändert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 21:05:07 +02:00
sysops 507dee6431 feat(PROJ-51): Aufbewahrungsfristen nach Dokumentenart (Retention-Kategorien)
Fuehrt archiving_rules ein (PROJ-43-Basis: Tabelle + CRUD-API + Admin-UI) und
erweitert die Retention-Logik (PROJ-34) um Regel-basierte Fristen, eine
globale Mindestfrist (min_retention_days) sowie Nachvollziehbarkeit der
Frist-Quelle (retain_until_source) in API und Mail-Detailansicht.
2026-06-13 20:48:16 +02:00
sysops 46096db802 feat(PROJ-49): Verschlüsselungspflicht at-rest sichtbar machen (Healthcheck & Warnung)
storage.loadKey() startet bei fehlendem/unlesbarem/ungültigem Keyfile
weiterhin unverschlüsselt (kein Hard-Fail), aber:
- einmalige WARN-Logzeile beim Start mit konkretem Grund
- neuer Healthcheck-Prüfpunkt "Encryption" in archivmail status
- Dashboard-API liefert encryption.enabled
- README: GoBD-Hinweis zu storage.keyfile

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 20:01:38 +02:00
sysops 501ee8f7ea fix(sec): Cross-Tenant-IDOR bei POP3-Konten schließen
Gleiches Muster wie bei IMAP (730099d): domain_admin konnte POP3-Konten
fremder Tenants auflisten, löschen und Importe/Progress fremder Tenants
ansehen, da pop3_accounts keine tenant_id hatte und Store.List() für
Admins ungefiltert alle Konten lieferte.

- pop3_accounts: neue Spalte tenant_id (ALTER TABLE ADD COLUMN IF NOT EXISTS)
- Store.List() filtert nach tenant_id, außer für superadmin
- Store.Create() setzt tenant_id beim Anlegen
- delete/start-import/progress prüfen zusätzlich tenantAccessAllowed()
2026-06-12 23:31:56 +02:00
sysops 730099d2aa fix(sec): Cross-Tenant-IDOR bei IMAP-Konten schließen
domain_admin sah und konnte IMAP-Konten (inkl. Credentials) fremder
Tenants auflisten, löschen, synchronisieren und umkonfigurieren, da
Store.List() für Admins ungefiltert alle Konten lieferte und die
Einzelhandler nur den Owner, nicht den Tenant prüften.

- Store.List() filtert jetzt nach tenant_id, außer für superadmin
- Store.Create() setzt tenant_id beim Anlegen
- Alle Einzelhandler (delete/start-import/progress/sync/update)
  prüfen zusätzlich tenantAccessAllowed()
2026-06-12 23:26:31 +02:00
sysops f32f83ff8e fix(PROJ-28): Invite-Token Pflicht bei Signup — TOCTOU + Enumeration-Leak schließen
- Signup ohne Invite-Token gibt 400 zurück (war: optional)
- Use() statt Peek() vor User-Erstellung: verhindert TOCTOU bei parallelen
  Requests mit demselben Token und Enumeration via "Token noch gültig?"
- invite_used Audit-Eintrag ergänzt
- Doppeltes IsConfigured()-Check entfernt
- Frontend: ohne ?invite= im URL wird Formular nicht gerendert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 18:20:07 +02:00
sysops 15a5da33fd feat(PROJ-13): OpenAPI 3.0 Spec + GET /api/v1/docs Endpoint
Serves the static OpenAPI YAML via go:embed. Completes the last
open acceptance criterion for PROJ-13. PROJ-44 marked Deployed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 18:03:58 +02:00
sysops fa9f77782c fix(sec): Authorization-Bypässe und Path-Traversal schließen, Xapian-Doku bereinigen
- SEC: requireMailAccess auf GET /api/threads/{threadID} — superadmin/domain_admin konnten Mail-Metadaten lesen
- SEC: requireMailAccess auf POST /api/export/ediscovery — superadmin/domain_admin konnten bis zu 10k EML exportieren
- SEC: V1-API user-role Keys müssen 'contact=' angeben — verhindert vollständige Tenant-Enumeration
- SEC: Domain-Regex-Validierung in handleCertACME vor filepath.Join und certbot-Aufruf
- docs: README und config.test.yml auf Manticore Search aktualisiert (kein Xapian mehr)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 23:55:31 +02:00
sysops a1c4e59fff fix: Date-Parsing-Fallback für nicht-standard MTA-Datumsformate
mailparser: weitere Layouts (Timezone +02:00 mit Doppelpunkt, ohne Sekunden)
storage: GetReceivedAts() für Batch-Lookup von received_at
search_handlers: received_at als Fallback wenn pm.Date.IsZero()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 23:36:18 +02:00
sysops 16013e8b66 fix(PROJ-44): OCR-Tenant-Routing nutzt kanonische DB-tenant_id
Strukturbug auf 132 gefunden: Tenant-User (Rolle user) sahen ihren
OCR-Text nicht, obwohl ocr_chars>0 in PostgreSQL stand. Ursache:

- OCR-Worker hat in den per Job.TenantID gewaehlten Index geschrieben.
  Beim Reprocess via CLI kam TenantID aus dem Submitter-Kontext und
  konnte vom in emails.tenant_id gespeicherten Wert abweichen.
- /ocr-text-Endpoint hat fuer die Index-Auswahl session.TenantID
  benutzt. Bei Admin/Auditor (nil Session-Tenant) wurde immer global
  gelesen, auch wenn die Mail einem Tenant gehoert.

Fix: Beide Stellen lesen jetzt die TenantID **immer** aus
storage.GetTenantForMail(emails.tenant_id) und routen den
Manticore-Index entsprechend. ACL-Check im Endpoint bleibt
unveraendert auf session.TenantID == mail.tenant_id — die
Tenant-Isolation wird nicht aufgeweicht.

Edge cases:
- Mail mit tenant_id NULL: GetTenantForMail liefert nil -> globaler
  Index (vorher und nachher gleich).
- DB-Fehler beim Lookup: faellt auf nil zurueck -> globaler Index,
  liefert leeren Text fuer Tenant-Mails -> 404. Safe (keine
  Querleckage zwischen Tenants).
2026-05-10 23:13:57 +02:00
sysops bb71ef2fd1 fix(PROJ-44): snippet + match_field in enrichedHit-Struct und JSON-Response verdrahten
Die enrichedHit-Struct in search_handlers.go fehlten die PROJ-44-Felder
Snippet und MatchField, sodass die vom Index berechneten Snippets in der
API-Response verworfen wurden.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 22:48:20 +02:00
sysops a44fd1ae44 feat(PROJ-44): ocr_status/ocr_chars in MailDetail + v1 API
GET /api/mails/{id} liefert jetzt ocr_status + ocr_chars fuer das GUI-
Badge und den konditionalen Download-Button. Die externe v1-API
(/api/v1/mails/{id}) bekommt zusaetzlich ocr_status, damit CRM-
Integrationen entscheiden koennen, ob ein OCR-Text-Pull lohnt.
2026-05-10 22:21:05 +02:00
sysops 62a130d208 feat(PROJ-44): GET /api/mails/{id}/ocr-text + Audit-Event
Neuer Endpoint liefert den OCR-extrahierten Reintext als text/plain
mit Content-Disposition. ACL identisch zu /raw (Tenant-Isolation,
Auditor-Regeln, User-Ownership-Check). Status-Mapping:
  done + chars>0 -> 200, attachment "<id16>.ocr.txt"
  pending        -> 202 JSON {"error":"ocr_pending"}
  skipped/failed/disabled/empty -> 404 JSON {"error":"ocr_not_available"}
Jeder erfolgreiche Download landet im Audit-Log als mail:ocr_download.
2026-05-10 22:20:59 +02:00
sysops 2a91f6e249 fix: IMAP-Serveradresse dynamisch aus Backend laden
Hardcodierte 192.168.1.131 in settings/page.tsx ersetzt durch
dynamischen API-Call GET /api/system/info → {fqdn, imap_port}.
Fallback auf window.location.hostname wenn API nicht antwortet.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 11:06:58 +02:00
sysops 3b05e949dd feat(PROJ-13,PROJ-42): REST API v1 + Gespeicherte Suchanfragen
PROJ-13: Externe REST API für CRM/ERP-Anbindung
- API-Key Middleware mit SHA-256-Hash-Lookup + Token-Bucket Rate-Limiter
- GET /api/v1/mails — Suche mit Paginierung (max 100/Seite)
- GET /api/v1/mails/{id} — Mail-Metadaten als JSON
- GET /api/v1/mails/{id}/raw — Original-EML Download
- Admin-Endpoints: POST/GET/DELETE /api/admin/apikeys
- Tenant-Isolation, Audit-Log, 405 für non-GET Methoden

PROJ-42: Gespeicherte Suchanfragen
- Tabelle saved_searches (user_id, tenant_id, name, query_json)
- GET/POST/DELETE /api/searches/saved mit Ownership-Check
- Frontend: "Suche speichern"-Button + Popover mit gespeicherten Suchen
- shadcn/ui Komponenten, Loading/Empty States

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 10:54:26 +02:00
sysops 9298216ce0 feat(PROJ-40,PROJ-41): Prometheus Metriken + Dashboard Zeitreihe
- PROJ-40: /api/health mit Version+Uptime, /metrics Prometheus-Format
  (mails_last_60min/24h/7d/30d, mails_total, storage_bytes, tenants_total,
   users_total, uptime_seconds) — Token-Schutz optional konfigurierbar
- PROJ-41: GET /api/admin/stats/timeseries (30-Tage tagesgenau, Tenant-scoped)
  + SVG-Balkendiagramm im Dashboard (Mail-Eingang letzte 30 Tage)
- storage.DBQueryRow() Helper für Metrics-Queries ohne Pool-Exposition
- config.MetricsConfig (enabled, token) in config.go

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 21:10:42 +02:00
sysops 963a324476 fix(PROJ-39): sanitizeFilename-Konflikt beheben — in ediscovery.go umbenannt
sanitizeFilename war doppelt deklariert (server.go + ediscovery.go).
Funktion in ediscovery.go zu sanitizeExportFilename umbenannt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 20:56:27 +02:00
sysops a82e854cfc feat(PROJ-39): eDiscovery Export + Feature-Specs PROJ-40–43
- Neuer POST /api/export/ediscovery Handler (internal/api/ediscovery.go)
- Route in server.go registriert
- Feature-Specs PROJ-39 bis PROJ-43 angelegt
- INDEX.md aktualisiert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 20:55:37 +02:00
sysops 2bab61209c chore: Modulname github.com/archivmail → archivmail
Go-Modul in go.mod und allen 45 Go-Dateien umbenannt.
2026-04-05 20:37:35 +02:00
sysops b252172cc7 feat(PROJ-26,PROJ-38): IMAP LDAP-Auth + Mail-Threading 2026-04-05 20:17:41 +02:00
sysops fdb25cb16a feat: Labels-Feature vollständig entfernen (PROJ-9)
Backend:
- internal/labelstore/ gelöscht (Store, Schema, CRUD)
- internal/api/label_handlers.go gelöscht (alle Label-Routen)
- internal/api/server.go: labels-Feld + SetLabels() entfernt
- internal/api/search_handlers.go: label_id-Filter + Enrichment entfernt
- internal/index/index.go: LabelID aus SearchRequest entfernt
- internal/imapserver/server.go: labels-Feld + labelbasierte Mailboxen entfernt
- cmd/archivmail/main.go: labelstore-Init + SetLabels() entfernt
- cmd/archivmail/version.go: labelstore-Modul entfernt, index-Kommentar korrigiert

Frontend:
- LabelList.tsx, LabelPicker.tsx, LabelsTab.tsx gelöscht
- src/lib/api/system.ts: MailLabel/LabelRule-Typen + alle Label-Funktionen entfernt
- src/lib/api/index.ts: Label-Exports entfernt
- src/app/search/page.tsx: LabelList + selectedLabelId State entfernt
- src/app/mail/[id]/page.tsx: LabelPicker + Labels-State entfernt
- src/app/admin/page.tsx: LabelsTab + alle Label-Handler/State entfernt

Docs:
- features/PROJ-9: Status auf Removed gesetzt
- features/INDEX.md: PROJ-9 auf Removed gesetzt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 21:32:13 +02:00
sysops 48cfb7cfa6 fix: auditor immer globalen Index nutzen, tenant_id ignorieren
auditor-Rolle hat evtl. tenant_id gesetzt (historisch), soll aber
trotzdem immer den globalen Index durchsuchen und nur No-Tenant-Mails
sehen. tenant_id auf auditor-User per DB-Migration auf NULL gesetzt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 19:51:42 +02:00
sysops 994e5d16fc feat: auditor sieht Mails ohne Tenant-Zuordnung
- auditor-Rolle sieht jetzt Mails wo tenant_id IS NULL und kein
  email_refs-Eintrag existiert (statt nur eigene Mails)
- Neues storage.IsWithoutTenant() für effizienten Direktzugriff
- Neues storage.GetAllIDsWithoutTenant() für Suche + ZIP-Export
- Konsistente Prüfung in Search, GetMail, GetAttachment, GetRaw, Export

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 19:34:45 +02:00
sysops 030752157b fix(PROJ-9): Superadmin-Tenant-NULL, GET admin/labels, from_domain-Allowlist
- labelTenantID() returns *int64 (nil for superadmin) statt int64(0)
  → verhindert FK-Constraint-Fehler bei tenant_id = 0
- CreateAdminLabel/CreateLabelRule: nil-Check, 400 wenn kein Tenant
- GET /api/admin/labels Route + handleGetAdminLabels Handler ergänzt
- from_domain in condition_field Allowlist für Label-Regeln hinzugefügt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 10:42:06 +02:00
sysops e1f25f2287 fix(security): emailsFromHeader fail-closed, domain_auditor-Block, Manticore-Tabellenvalidierung
- emailsFromHeader gibt bei Parse-Fehler nil zurück (fail-closed) statt raw-Header-String;
  verhindert Authorization-Bypass via malformiertem From-Header
- mailBelongsToUser: strings.Contains-Fallback entfernt (war dead code nach dem fix-closed-Fix)
- handleSearch: domain_auditor ohne TenantID wird mit 403 abgewiesen, bevor der globale Index
  abgefragt wird
- manticoreTableName: Regex-Validierung ^emails_(global|tenant_\d+)$ mit panic bei Abweichung

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 02:01:50 +02:00
sysops 4ef5897e68 feat(PROJ-29): Tenant-Quotas & Usage-Limits vollständig implementiert
- storage/quota.go: SQL-Bug gefixt (emails.size → size_bytes, email_refs JOIN)
- tenantstore/quota.go: GetUsage nutzt jetzt email_refs JOIN für korrekte Tenant-Isolation
- smtpd: ErrQuotaExceeded → SMTP 452 statt 554 (MTA-retry statt permanent reject)
- admin_handlers: handleCreateUser prüft max_users-Quota → HTTP 402 bei Überschreitung
- quota_handlers: handleGetTenantUsage gibt jetzt warnings-Feld mit soft-limit-Prozenten zurück
- server.go: spec-konforme Alias-Route GET /api/admin/tenants/{id}/usage registriert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 01:27:59 +02:00
sysops 22cbfb5df6 fix(security): Email-Matching, LDAP-Validierung, Auditor-Isolation
- mailBelongsToUser: net/mail.ParseAddressList statt strings.Contains
  verhindert False-Positives durch Display-Namen in Mail-Headern
- LDAP mail-Attribut: net/mail.ParseAddress-Validierung vor Übernahme,
  Fallback auf username / username@ldap.local bei ungültiger Adresse
- handleSearch: Auditor-Rolle in userEmailFilter-Check eingeschlossen,
  sodass Auditoren im Search-Pfad dieselbe Mail-Isolation erhalten wie User

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 01:18:34 +02:00
sysops 825e4db7c1 fix(PROJ-30): ListFolders — c.Client statt c uebergeben (imap.Conn vs *imapclient.Client)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 21:21:20 +02:00
sysops a93a843506 feat(PROJ-30): Xapian → Manticore Search Migration
- internal/index/manticore.go: ManticoreTenantManager + manticoreIndex (RT-Indizes, CGO-frei)
- internal/index/index.go: TenantIndexer Interface (Xapian + Manticore)
- internal/index/tenant_worker.go: mgr-Typ auf TenantIndexer Interface
- internal/api/server.go: idxMgr auf TenantIndexer Interface
- config/config.go: IndexConfig.ManticoreDSN Feld
- cmd/archivmail/cmd_reindex.go: reindex Subkommando
- cmd/archivmail/main.go: Manticore-Branch + reindex Case
- go.mod: github.com/go-sql-driver/mysql v1.8.1
- update.sh: Manticore auto-install, CGO_ENABLED=0, config.yml migration, auto-reindex

fix(IMAP): TCP-Deadline-Wrapper für steckengebliebene Imports
fix(auth): Email-Claim in JWT für User-Isolation
fix(search): User-Isolation via sess.Email (fail-safe)
fix(ui): Admin-Login Auth-Cache, Logout-Redirect, IMAP-Polling-Resilienz

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 21:19:36 +02:00
sysops c1a9004720 feat(PROJ-28): SMTP-Out Relay — DB-Konfiguration + Admin-Tab
- smtpoutconfig.Store: AES-256-GCM verschlüsseltes Passwort in DB (id=1 Singleton)
- Mailer: Reload() für Runtime-Konfigurationswechsel (sync.RWMutex)
- API: GET/PUT/DELETE /api/admin/smtp-out + POST /api/admin/smtp-out/test
- Admin-Tab: Host, Port, User, Passwort, TLS-Switch, From, Test-Button, Status-Badge
- Startup: Lädt DB-Konfiguration und aktiviert Mailer ohne Restart

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 22:36:57 +02:00
sysops 7371a73b3e fix(SEC): Signup-Enumeration durch Always-Send-Email schließen
Bei doppeltem Signup wird eine "bereits registriert"-Mail gesendet,
sodass jeder Signup-Versuch eine ausgehende E-Mail erzeugt.
Side-Channel-Angriff zur Account-Enumeration nicht mehr möglich.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 22:00:43 +02:00
sysops 4583262ea4 feat(PROJ-28): Self-Service Onboarding — Signup, Verify, Password Reset, Invites
- internal/mailer: SMTP-Out via net/smtp (TLS + STARTTLS), HTML+Text-Templates
- internal/tokenstore: auth_tokens Tabelle, SHA-256-Hash, TTL, einmalig verwendbar
- userstore: CreateInactive(), Activate(), GetByEmail(), SetPassword()
- API: POST /signup, GET /verify, POST /forgot-password, POST /reset-password
- API: POST /admin/invite (domain_admin+), GET /auth/invite?token (check)
- Login-Seite: Links zu "Passwort vergessen" und "Registrieren"
- Frontend: /signup, /verify, /forgot-password, /reset-password Seiten
- server.fqdn nicht konfiguriert → Startup-Warnung, Self-Service deaktiviert
- LDAP-Nutzer: Passwort-Reset abgewiesen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 21:54:11 +02:00
sysops 7930b85cde feat(PROJ-29): Tenant-Quotas & Usage-Limits
- DB: max_storage_bytes, max_users, max_emails per Tenant (NULL = unlimited)
- storage/quota.go: CheckQuota() mit 60s-Cache, ErrQuotaExceeded
- Save() prüft Quota vor dem Schreiben — Ablehnung bei Hard-Limit
- tenantstore/quota.go: SetQuota(), GetQuota(), GetUsage()
- API: GET/PUT /api/admin/tenant/{id}/quota, GET /api/admin/quotas
- QuotaTab: Usage-Balken (Speicher/Nutzer/Mails), Edit-Dialog, Warnung ab 80%
- InvalidateQuotaCache() nach Quota-Änderung für sofortige Wirkung

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 21:21:11 +02:00
sysops 4aadf7a4d2 fix: retention_handlers.go — getSession/audlog.Log-Signatur korrigiert
Ersetzt nicht-existente getSession(r) durch sessionFromCtx(r.Context()) und
passt den audlog.Log-Aufruf auf die korrekte audit.Entry-Struct-Signatur an.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 10:47:18 +02:00
sysops 58bcfb1586 feat: Dashboard-Metriken nach Mailpiler-Vorbild (Uptime, Aktivität, Prognose)
- storage_stats.go (neu): MailActivityStats (60min/24h/7d/30d), StorageEstimateStats
- dashboard_handlers.go: Uptime (/proc/uptime), activity + estimate in System-Stats-Response
- DashboardTab: Uptime in API-Kachel, neue Kacheln "Mail-Eingang" + "Speicherprognose"
- Warnung (Badge "Knapp!") wenn Partition in <90 Tagen voll

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 10:44:02 +02:00
sysops 5bbf6d0ff3 feat(PROJ-34): Retention-Tab + pro-Mandant Aufbewahrungsfristen
- tenantstore: retention_days Spalte, GetRetentionDays/SetRetentionDays
- storage.Save(): per-tenant retention überschreibt globale config
- API: GET /api/admin/retention, PUT /api/admin/tenant/{id}/retention
- Frontend: RetentionTab mit globaler Policy-Anzeige, Mandanten-Tabelle,
  Bearbeiten-Dialog und Purge-Button (superadmin only)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 10:37:15 +02:00
sysops 8d0f685fc9 feat(PROJ-33): IMAP UID-Stabilität + Shared/Personal-Modus
Backend:
- storage: uid BIGSERIAL Migration, MailWithUID, GetMailsWithUID, GetMailsByRecipient
- tenantstore: imap_mode Spalte, GetIMAPMode, SetIMAPMode
- imapserver: stable UIDs aus DB, personal/shared Modus, userEmail in session
- api: GET/PUT /api/admin/settings/imap-mode (domain_admin only, double opt-in)

Frontend:
- IMAPSettingsTab: Modus-Anzeige + Toggle mit Double-Opt-In Dialog
- Admin-Panel: IMAP-Tab für domain_admin

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 09:46:52 +02:00
sysops b6856af2eb feat(PROJ-32): Message-ID-basierte Duplikatserkennung
- message_id Spalte + UNIQUE-Index in emails-Tabelle
- Save() prüft Message-ID vor SHA-256-Flow (kein Disk-I/O bei Duplikat)
- lookupByMessageID() als private Hilfsfunktion
- insertMeta() schreibt message_id, gibt error zurück (Race-safe)
- SaveMeta() schreibt message_id idempotent (Backfill)

feat(PROJ-34): Retention-Policy + Löschsperre (GoBD)

- retain_until TIMESTAMPTZ Spalte in emails-Tabelle
- ErrRetentionLock typed error
- Delete() prüft Retention-Frist vor Löschung
- Purge() löscht alle Mails mit abgelaufener Retention
- POST /api/admin/purge Endpunkt (superadmin only)
- config: storage.retention_days

fix: Superadmin-Benutzerübersicht zeigt Mandant-Spalte

- UsersTab: Mandant-Spalte wenn isSuperAdmin
- domain_auditor Rolle im Create-Dialog ergänzt
- storage Modulversion → 1.6

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 01:29:25 +02:00
sysops 4f7a7d6946 feat: App-Version 0.9.1 + Modulversionsnummern
- version.go: AppVersion + Modules-Map (pro Modul interne Versionsnummer)
- GET /api/version: liefert App- und Modulversionen (ohne Auth)
- archivmail version: zeigt App- und Modulversionen in CLI
- version-Konstante aus cmd_import.go entfernt (war falsche Stelle)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 01:12:51 +02:00
sysops 64433aa847 fix(SEC-29): Rollen-Trennung Admins/Auditoren, domain_auditor Rolle
- superadmin + domain_admin haben keinen Mail-Zugriff mehr (requireMailAccess)
- Neue Rolle domain_auditor: alle Tenant-Mails, kein Admin-Zugriff
- auditor + user: nur eigene Mails
- ZIP-Export: kein separates Attachment-Entpacken mehr, nur EML
- roleLevel() um domain_auditor (Level 3) erweitert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 01:09:19 +02:00
sysops a6a66beaa8 chore: weitere Code-Aufteilung (api.ts, hooks, ldap_sync)
- src/lib/api.ts (1085 Zeilen) → 5 thematische Module unter src/lib/api/
  (core, users, ldap, tenants, mail, system) + index.ts Re-Export
- useLDAPConfig / useTenantLDAPConfig / useTenantUsers Hooks extrahiert;
  admin/page.tsx nutzt diese statt roher useState-Blöcke
- handleSyncTenantLDAP, handleAdminSyncTenantLDAP, doSyncTenantLDAP,
  buildTenantTestConfig, syncResult aus ldap_tenants.go in ldap_sync.go verschoben

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 13:05:19 +01:00
sysops 72b023b598 feat: LDAP-User-Sync in Mandanten-Benutzerliste
Neuer Endpoint POST /api/admin/tenants/{id}/ldap/sync importiert alle
LDAP-User (source=ldap) per UpsertLDAPUser in die Tenant-Benutzerliste.
Im Nutzer-Dialog erscheint ein "LDAP-Benutzer synchronisieren"-Button
wenn LDAP für den Mandanten aktiv ist. Unterstützt Univention UCS
(mailPrimaryAddress, inetOrgPerson). Benutzertabelle zeigt jetzt auch
die Quelle (local/ldap).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 11:35:02 +01:00
sysops 30c6694dff feat(PROJ-24): Mandanten-Logo Upload
- DB: logo_data (BYTEA) + logo_content_type Spalten in tenants-Tabelle
- Backend: SetLogo/GetLogo/DeleteLogo im tenantstore
- API: Logo-Endpunkte für superadmin (beliebiger Mandant) und
  domain_admin (eigener Mandant), max. 2 MB, PNG/JPEG/GIF/WebP/SVG
- Frontend: Logo-Dialog in Mandantentabelle (superadmin),
  Logo-Upload-Sektion im LDAP-Tab (domain_admin)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 03:15:34 +01:00
sysops 9db433c4c1 fix: Mandantenverwaltung LDAP-Status und Nutzer-Listing
- tenantstore.List(): LEFT JOIN tenant_ldap hinzugefügt — ldap_enabled + ldap_url
  werden jetzt im GET /api/tenants Response mitgeliefert
- Tenant-Struct: Felder LDAPEnabled *bool + LDAPURL string ergänzt
- Neuer Endpunkt GET /api/tenants/{id}/users → listet Nutzer eines Mandanten
- api.ts: getTenantUsers() Funktion + tenant_id Feld im User Interface
- Admin-Panel: "Nutzer"-Button im Mandanten-Tab öffnet Dialog mit Nutzerliste

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 02:01:12 +01:00
sysops c59cad92be fix: IMAP-Konto bearbeiten + Löschen auch bei sync_running
- Store: UpdateCredentials() — Zugangsdaten + Passwort neu verschlüsseln,
  setzt status='idle', error_msg='', sync_running=false zurück
- Handler: PATCH /api/imap/{id} unterstützt nun Credential-Update
  (name/host/username vorhanden = Credential-Update, sonst sync_interval)
- Frontend: "Bearbeiten"-Button öffnet Edit-Dialog mit allen Feldern;
  Passwort-Feld leer = unverändertes Passwort
- Frontend: Löschen-Button nicht mehr durch sync_running blockiert
  (nur noch bei status=running gesperrt)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 00:49:37 +01:00
sysops 9e71af104f feat: Dark Mode + Zertifikat-Verwaltung im Superadmin
- Dark Mode: ThemeProvider (next-themes), ThemeToggle in Navbar (Hell/Dunkel/System)
- Zertifikat-Tab (superadmin only): aktuelles Zertifikat anzeigen, Upload (cert+key),
  Self-Signed ausstellen, Let's Encrypt/ACME
- Backend: /api/admin/cert/* Endpunkte (info, upload, self-signed, acme), nginx reload
- HTTPS bereits live auf Server (self-signed RSA-4096, Port 443)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 00:14:43 +01:00
sysops d79e334029 refactor: server.go in separate Handler-Dateien aufgeteilt
server.go (2357 -> 391 Zeilen) enthaelt nur noch Server-Struct,
Konstruktor, Router, Middleware und Hilfsfunktionen.

Neue Dateien:
- auth_handlers.go: Login, Logout, Me
- search_handlers.go: Suche, Mail-Anzeige, Anhaenge, Raw-Download
- admin_handlers.go: User-CRUD, SMTP/Storage-Stats, Services, Security
- import_handlers.go: IMAP + POP3 Account-Verwaltung und Import
- dashboard_handlers.go: System-Stats, Audit-Log

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 11:55:21 +01:00
sysops 5a6289c83d feat(PROJ-9): implement labels backend - DB schema, labelstore, API handlers, Xapian integration
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 08:32:30 +01:00
sysops 0fbb1924bb fix(security): W-02 Secure-Cookie-Flag + W-03 TrustedProxies für X-Forwarded-For
W-02: Cookie Secure-Flag ist nun über config.yml steuerbar.
      api.secure_cookies: true/false — default false (kein Breaking Change).
      Alle 3 SetCookie-Aufrufe (Login, Logout, TOTP) nutzen s.cfg.SecureCookies.

W-03: remoteIP() ist jetzt eine Methode und prüft api.trusted_proxies.
      X-Forwarded-For wird nur ausgewertet wenn der direkte Peer in der
      trusted_proxies-Liste steht (IP oder CIDR). Sonst wird r.RemoteAddr
      verwendet — kein Spoofing mehr möglich.
      Neue Hilfsfunktion: isTrustedProxy(ip, proxies).

config.go: APIConfig um SecureCookies bool + TrustedProxies []string erweitert.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 01:10:24 +01:00
sysops 280034679e feat(PROJ-25): User-Profil & Einstellungen — Passwort, E-Mail, 2FA
Backend:
- PATCH /api/auth/password — Passwort ändern (bcrypt, LDAP-Guard, Audit-Log)
- PATCH /api/auth/email — E-Mail ändern (Unique-Check, LDAP-Guard, Audit-Log)
- userstore: UpdatePassword, UpdateEmail, GetPasswordHash

Frontend:
- UserNav.tsx: Dropdown-Menü (Profil & Einstellungen, Abmelden)
- navbar.tsx: UserNav eingebunden
- /settings: Passwort ändern, E-Mail ändern, 2FA verwalten (QR-Code + Deaktivieren)
- api.ts: changePassword, changeEmail, getTOTPSetup, confirmTOTPSetup, disableTOTP

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 01:05:33 +01:00