feat(PROJ-46): E-Mail als primärer Login-Identifier für Tenant-User
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>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
804cd62201
commit
767373b206
@@ -243,6 +243,22 @@ func (imp *Importer) fetchBatch(ctx context.Context, c *imapclient.Client, uids
|
||||
// accountID identifies the IMAP account for PROJ-52 source tracking.
|
||||
func (imp *Importer) storeAndIndex(raw []byte, tenantID *int64, accountID int64, log *slog.Logger) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Parse early: needed both for PROJ-43 routing-rule resolution (before Save,
|
||||
// so the mail is stored under the correct tenant) and for indexing below.
|
||||
pm, parseErr := mailparser.Parse(raw)
|
||||
|
||||
// PROJ-43: pattern routing rules may override the account's default tenant.
|
||||
// They are more specific than the per-account TenantID, so a match wins.
|
||||
if parseErr == nil {
|
||||
recipients := append(append([]string{}, pm.To...), pm.CC...)
|
||||
if tid, err := imp.mailStore.ResolveTenantByRoutingRules(ctx, pm.From, recipients); err != nil {
|
||||
log.Warn("routing rule lookup failed", "err", err)
|
||||
} else if tid != nil {
|
||||
tenantID = tid
|
||||
}
|
||||
}
|
||||
|
||||
// Save to file storage (deduplicates by SHA256 automatically)
|
||||
id, err := imp.mailStore.Save(ctx, raw, time.Now(), tenantID)
|
||||
if err != nil {
|
||||
@@ -255,10 +271,9 @@ func (imp *Importer) storeAndIndex(raw []byte, tenantID *int64, accountID int64,
|
||||
log.Warn("failed to tag source", "id", id, "err", err)
|
||||
}
|
||||
|
||||
// Parse for indexing
|
||||
pm, err := mailparser.Parse(raw)
|
||||
if err != nil {
|
||||
log.Warn("failed to parse mail for indexing", "id", id, "err", err)
|
||||
// Parse for indexing (reuse the early parse from routing-rule resolution).
|
||||
if parseErr != nil {
|
||||
log.Warn("failed to parse mail for indexing", "id", id, "err", parseErr)
|
||||
// Store succeeded, just skip indexing for unparseable mails
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user