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
@@ -0,0 +1,94 @@
|
||||
import { request } from "./core";
|
||||
|
||||
// PROJ-43: Tenant-Routing-Regeln — automatische Mandanten-Zuordnung nach
|
||||
// Domain/Absender-Mustern mit Priorität + Dry-Run.
|
||||
// CRUD unter /api/admin/routing-rules (domain_admin+; superadmin = alle Tenants,
|
||||
// domain_admin = eigener Tenant, serverseitig erzwungen).
|
||||
|
||||
// Match-Typen — müssen mit den Backend-Konstanten übereinstimmen.
|
||||
export type RoutingMatchType =
|
||||
| "from_domain"
|
||||
| "to_domain"
|
||||
| "from_addr"
|
||||
| "to_addr";
|
||||
|
||||
export interface RoutingRule {
|
||||
id: number;
|
||||
tenant_id: number;
|
||||
match_type: RoutingMatchType;
|
||||
pattern: string;
|
||||
priority: number;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface RoutingRulesResponse {
|
||||
rules: RoutingRule[] | null;
|
||||
}
|
||||
|
||||
// tenant_id optional: nur superadmin muss ihn setzen; domain_admin bekommt
|
||||
// serverseitig seinen eigenen Tenant erzwungen.
|
||||
export interface RoutingRuleInput {
|
||||
tenant_id?: number;
|
||||
match_type: RoutingMatchType;
|
||||
pattern: string;
|
||||
priority: number;
|
||||
}
|
||||
|
||||
export interface RoutingDryRunSample {
|
||||
id: string;
|
||||
mail_from: string;
|
||||
mail_to: string;
|
||||
subject: string;
|
||||
received_at: string | null;
|
||||
}
|
||||
|
||||
export interface RoutingDryRunResult {
|
||||
match_type: RoutingMatchType;
|
||||
pattern: string;
|
||||
match_count: number;
|
||||
sample_limit: number;
|
||||
sample: RoutingDryRunSample[] | null;
|
||||
}
|
||||
|
||||
// Dry-Run kann entweder gegen eine bestehende Regel (rule_id) oder gegen ein
|
||||
// noch nicht gespeichertes Muster (match_type + pattern) laufen.
|
||||
export type RoutingDryRunInput =
|
||||
| { rule_id: number; limit?: number }
|
||||
| { match_type: RoutingMatchType; pattern: string; limit?: number };
|
||||
|
||||
export async function getRoutingRules(): Promise<RoutingRule[]> {
|
||||
const data = await request<RoutingRulesResponse>("/api/admin/routing-rules");
|
||||
return data.rules ?? [];
|
||||
}
|
||||
|
||||
export async function createRoutingRule(
|
||||
input: RoutingRuleInput
|
||||
): Promise<{ id: number }> {
|
||||
return request<{ id: number }>("/api/admin/routing-rules", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateRoutingRule(
|
||||
id: number,
|
||||
input: RoutingRuleInput
|
||||
): Promise<void> {
|
||||
await request<void>(`/api/admin/routing-rules/${id}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteRoutingRule(id: number): Promise<void> {
|
||||
await request<void>(`/api/admin/routing-rules/${id}`, { method: "DELETE" });
|
||||
}
|
||||
|
||||
export async function dryRunRoutingRule(
|
||||
input: RoutingDryRunInput
|
||||
): Promise<RoutingDryRunResult> {
|
||||
return request<RoutingDryRunResult>("/api/admin/routing-rules/dry-run", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user