Tenant-User (tenant_id IS NOT NULL) melden sich künftig per E-Mail an statt per Username — behebt Verwechslungen wie im Support-Fall vom 2026-06-13 (Login schlug trotz Passwort-Reset fehl, weil E-Mail statt Username verwendet wurde). Nicht-Tenant-User (Superadmin/System) können weiterhin Username ODER E-Mail nutzen. Neue Store.VerifyLogin() prüft erst per E-Mail (alle User), fällt dann auf Username zurück (nur tenant_id IS NULL). VerifyPassword() bleibt für den IMAP-Server-Login-Pfad (PROJ-26) unverändert. Bewusster Breaking Change für Tenant-User, Datenqualität vorab geprüft (0 Kollisionen). Security-Nachtrag: bcrypt-Dummy-Compare im "user not found"-Pfad ergänzt, um Timing-basierte Identifier-Enumeration zu verhindern. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
113 lines
6.0 KiB
Markdown
113 lines
6.0 KiB
Markdown
---
|
|
id: PROJ-43
|
|
title: Automatische Archivierungsregeln (by Domain/Sender)
|
|
status: In Review
|
|
created: 2026-04-05
|
|
---
|
|
|
|
## Kontext
|
|
|
|
Das SMTP Domain-Routing (PROJ-21 Phase 5) ist bereits implementiert:
|
|
- `tenant_domains`-Tabelle ordnet Domains Mandanten zu
|
|
- `resolveTenantFromRcpts()` im SMTP-Daemon weist Mails automatisch zu
|
|
- IMAP/POP3-Importer unterstützen ebenfalls TenantID
|
|
|
|
PROJ-43 **erweitert** diese Basis um flexiblere Muster-Regeln und eine GUI.
|
|
|
|
## Ziel
|
|
|
|
Admins können über die Web-Oberfläche Regeln verwalten die über einfache Domain-Zuordnung
|
|
hinausgehen — z.B. Wildcard-Domains, Absender-Adressen, Betreff-Muster.
|
|
|
|
## User Stories
|
|
|
|
- Als Admin möchte ich alle Mails von @kunde.de automatisch dem Mandanten "Kunde GmbH" zuweisen
|
|
- Als Admin möchte ich Mails an archiv@firma.de einem bestimmten Tenant zuordnen
|
|
|
|
## Namenskonflikt (Nachtrag 2026-07-03)
|
|
|
|
PROJ-51 (Retention-Kategorien) hat bereits eine Tabelle `archiving_rules` angelegt
|
|
(`internal/storage/retention_rules.go`, Spalten `condition_type`, `pattern`,
|
|
`priority`, `retention_days`) — andere Bedeutung (Aufbewahrungsfrist statt
|
|
Tenant-Zuordnung). Um Kollision zu vermeiden, heißt die neue Tabelle für PROJ-43
|
|
**`tenant_routing_rules`**, nicht `archiving_rules` wie ursprünglich benannt.
|
|
|
|
## Acceptance Criteria
|
|
|
|
- [ ] Tabelle `tenant_routing_rules (id, tenant_id, match_type [from_domain|to_domain|from_addr|to_addr], pattern, priority, created_at)`
|
|
- [ ] SMTP-Daemon und IMAP-Import prüfen Regeln nach jedem eingehenden Mail
|
|
- [ ] API: CRUD für tenant_routing_rules (Admin only)
|
|
- [ ] Frontend: Regel-Verwaltung im Admin-Bereich
|
|
- [ ] Priorität: höhere Priorität gewinnt bei mehreren Treffern
|
|
- [ ] Dry-Run: zeigt welche bestehenden Mails von einer Regel betroffen wären
|
|
|
|
## Betroffene Dateien
|
|
|
|
- `internal/storage/storage.go` bzw. neues `internal/storage/tenant_routing_rules.go` (DB-Schema + ApplyRules-Methode, analog `retention_rules.go`)
|
|
- `internal/smtpd/smtpd.go` (Regel-Check nach Save, ergänzt bestehendes `resolveTenantFromRcpts()` aus PROJ-21 Phase 5)
|
|
- `internal/imap/importer.go` (Regel-Check nach Import)
|
|
- `internal/api/rules_handlers.go` (neu)
|
|
- `src/app/admin/` (Regel-UI)
|
|
|
|
## Implementierungsnotizen (2026-07-03, Status: In Review)
|
|
|
|
Kein lokaler `go build` möglich (kein Go-Toolchain im Arbeitsverzeichnis) — Build/Tests
|
|
laufen separat auf dem Testserver.
|
|
|
|
### Neue/geänderte Dateien
|
|
- `internal/storage/tenant_routing_rules.go` (NEU) — Tabelle `tenant_routing_rules`,
|
|
CRUD, Matching-Engine `ResolveTenantByRoutingRules()`, Dry-Run `DryRunRoutingRule()`.
|
|
Stil analog `retention_rules.go`. Match-Typen: `from_domain`, `to_domain`,
|
|
`from_addr`, `to_addr`. Wildcard-Domains via `*.kunde.de` (matcht Sub-Domains).
|
|
- `internal/storage/storage.go` — `initTenantRoutingRulesSchema(ctx)` in Init-Kette.
|
|
- `internal/smtpd/smtpd.go` — `resolveTenantByRules()` neu; in `resolveTenant()` als
|
|
Stufe 0 VOR der `tenant_domains`-Logik (Regeln sind expliziter → Vorrang).
|
|
- `internal/imap/importer.go` — `storeAndIndex()`: Mail wird jetzt früh geparst,
|
|
Regel-Match kann die Account-Default-TenantID vor dem Save überschreiben.
|
|
- `internal/api/rules_handlers.go` (NEU) — CRUD + Dry-Run, `authAdmin` (domain_admin+),
|
|
Tenant-Scoping + IDOR-Check (`tenantAccessAllowed`) bei jedem `{id}`.
|
|
- `internal/api/server.go` — Routen registriert.
|
|
|
|
### Tabelle
|
|
`tenant_routing_rules (id, tenant_id [NOT NULL, FK tenants ON DELETE CASCADE],
|
|
match_type, pattern, priority, created_at)`. Höhere `priority` gewinnt, bei
|
|
Gleichstand niedrigste `id`.
|
|
|
|
### API-Endpunkte (alle domain_admin+; superadmin = alle Tenants, domain_admin = eigener)
|
|
- `GET /api/admin/routing-rules` → `{ "rules": RoutingRule[] }`
|
|
- `POST /api/admin/routing-rules` Body `{tenant_id?, match_type, pattern, priority}`
|
|
→ `201 {"id": <int>}` (superadmin muss `tenant_id` setzen; domain_admin bekommt
|
|
eigenen Tenant erzwungen)
|
|
- `PUT /api/admin/routing-rules/{id}` gleicher Body → `200 {"ok": true}`
|
|
- `DELETE /api/admin/routing-rules/{id}` → `200 {"ok": true}`
|
|
- `POST /api/admin/routing-rules/dry-run` Body entweder `{rule_id}` ODER
|
|
`{match_type, pattern}` (+ optional `limit`, default 20, max 100) →
|
|
`200 {match_type, pattern, match_count, sample_limit, sample: [{id, mail_from,
|
|
mail_to, subject, received_at}]}`. Dry-Run ist ILIKE-Näherung gegen `emails`,
|
|
LIMIT-begrenzt (kein Vollscan-Timeout); domain_admin sieht nur eigene Tenant-Mails.
|
|
|
|
RoutingRule-Shape: `{id, tenant_id, match_type, pattern, priority, created_at}`.
|
|
|
|
### Frontend (2026-07-03, Status: In Review — QA offen)
|
|
- `src/lib/api/routing_rules.ts` (NEU) — TS-Typen (`RoutingRule`, `RoutingRuleInput`,
|
|
`RoutingMatchType`, `RoutingDryRunResult/Input/Sample`) + API-Funktionen
|
|
`getRoutingRules`, `createRoutingRule`, `updateRoutingRule`, `deleteRoutingRule`,
|
|
`dryRunRoutingRule`. Nutzt zentralen `request()`-Wrapper aus `core.ts`.
|
|
- `src/lib/api/index.ts` — Re-exports der neuen Typen + Funktionen ergänzt.
|
|
- `src/components/admin/tabs/RoutingRulesTab.tsx` (NEU) — Tab „Routing-Regeln":
|
|
Tabelle (Prio, Typ, Muster, Angelegt; bei superadmin zusätzlich Tenant-Spalte),
|
|
CRUD via Dialog (match_type-Select, pattern-Input mit typabhängigem Placeholder,
|
|
priority-Input, bei superadmin Tenant-Auswahl-Dropdown), Löschen mit Bestätigung.
|
|
Dry-Run-Button im Dialog ruft `dryRunRoutingRule({match_type, pattern})` und zeigt
|
|
`match_count` + Stichproben-Tabelle. Prioritäts-Erklärung + Hinweis „keine
|
|
rückwirkende Umroutung" als Alert. Loading/Error/Empty-States implementiert.
|
|
- `src/app/admin/page.tsx` — Tab-Trigger + `<TabsContent value="routing-rules">`
|
|
eingebunden; Tab für alle Admin-Seiten-Besucher sichtbar (Seite ist bereits per
|
|
`useAuth("domain_admin")` auf domain_admin+ gegated). `isSuperAdmin`-Prop steuert
|
|
Tenant-Spalte/-Dropdown; serverseitiges Scoping bleibt maßgeblich.
|
|
- Typecheck: `npx tsc --noEmit` → sauber (Exit 0).
|
|
|
|
### Offen / Handoff
|
|
- QA gegen Acceptance Criteria (CRUD, Dry-Run, Rollen-Gate) auf Testserver.
|
|
- Bereits archivierte Mails werden NICHT rückwirkend umgeroutet (nur neue Ingests).
|