fix: Login-userId war nicht stabil gegenüber Groß-/Kleinschreibung

/api/auth/login mintete die JWT-userId bisher aus dem roh eingegebenen Benutzernamen. LiteDBs
Standard-Collation vergleicht den eindeutigen Index auf Username aber case-insensitive - ein
Login mit nur einmal abweichender Schreibweise auf einem zweiten Gerät authentifiziert
erfolgreich, mintet aber eine andere userId. Da EventStore.GetCol(userId) diese direkt als
Dateiname für den Server-seitigen Event-Speicher nutzt, entstanden zwei komplett getrennte
Datenbestände für ein und dasselbe, aus Nutzersicht einzige Konto - Ursache dafür, dass ein
zweites Gerät trotz "since=0" durchgängig 0 Ereignisse erhielt.

UserStore.VerifyPassword(bool) durch Authenticate(UserEntry?) ersetzt, das bei Erfolg den
kanonisch gespeicherten Nutzereintrag liefert; /api/auth/login mintet Token und userId daraus
statt aus der Roheingabe.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-18 23:51:31 +02:00
co-authored by Claude Sonnet 5
parent e7e5faeba8
commit ba8786906a
4 changed files with 78 additions and 13 deletions
+9 -3
View File
@@ -19,9 +19,15 @@ public static class Endpoints
{
if (string.IsNullOrWhiteSpace(req.Username) || string.IsNullOrWhiteSpace(req.Password))
return Results.Unauthorized();
if (!store.VerifyPassword(req.Username, req.Password))
return Results.Unauthorized();
return Results.Ok(new { token = Jwt(req.Username, secret), userId = req.Username });
var user = store.Authenticate(req.Username, req.Password);
// Der kanonisch gespeicherte Username aus UserStore.Authenticate (nicht req.Username!)
// wird als JWT-userId verwendet - LiteDBs case-insensitive Standard-Collation lässt
// einen Login mit abweichender Groß-/Kleinschreibung erfolgreich durch; würde man
// stattdessen req.Username übernehmen, würde jede andersartig getippte Anmeldung einen
// eigenen, komplett getrennten Server-seitigen Event-Speicher erzeugen (siehe
// UserStore.Authenticate).
if (user is null) return Results.Unauthorized();
return Results.Ok(new { token = Jwt(user.Username, secret), userId = user.Username });
}).RequireRateLimiting("login");
}