Files
LehrerApp/LehrerApp.Api/UserStore.cs
adminandClaude Sonnet 5 ba8786906a 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>
2026-08-18 23:51:31 +02:00

62 lines
2.4 KiB
C#

using LiteDB;
namespace LehrerApp.Api;
public class UserStore(string dataPath) : IDisposable
{
private readonly LiteDatabase _db = new(Path.Combine(dataPath, "users.db"));
private ILiteCollection<UserEntry> Col
{
get
{
var col = _db.GetCollection<UserEntry>("users");
col.EnsureIndex(x => x.Username, unique: true);
return col;
}
}
public bool CreateUser(string username, string password)
{
if (Col.Exists(x => x.Username == username)) return false;
Col.Insert(new UserEntry { Username = username, PasswordHash = PasswordHasher.Hash(password) });
return true;
}
public bool SetPassword(string username, string password)
{
var user = Col.FindOne(x => x.Username == username);
if (user is null) return false;
user.PasswordHash = PasswordHasher.Hash(password);
Col.Update(user);
return true;
}
/// <summary>
/// Prüft die Anmeldedaten und liefert bei Erfolg den GESPEICHERTEN Nutzereintrag zurück -
/// nicht bloß true/false. Wichtig: LiteDBs Standard-Collation vergleicht Strings (und damit
/// auch den eindeutigen Index auf Username) standardmäßig case-insensitive, d.h. ein Login mit
/// abweichender Groß-/Kleinschreibung authentifiziert erfolgreich. Würde der Aufrufer daraufhin
/// den roh eingegebenen Benutzernamen als userId für den Server-seitigen Event-Speicher
/// verwenden (siehe EventStore.GetCol), würde ein einziger andersartig getippter Login auf
/// einem zweiten Gerät einen komplett getrennten, nicht überlappenden Datenbestand erzeugen,
/// obwohl es sich aus Nutzersicht um dasselbe Konto handelt. Der kanonisch gespeicherte Name
/// aus diesem Rückgabewert stellt sicher, dass die userId unabhängig von der beim Login
/// eingegebenen Schreibweise stets identisch ist.
/// </summary>
public UserEntry? Authenticate(string username, string password)
{
var user = Col.FindOne(x => x.Username == username);
return user is not null && PasswordHasher.Verify(password, user.PasswordHash) ? user : null;
}
public void Dispose() => _db.Dispose();
}
public class UserEntry
{
public ObjectId Id { get; set; } = ObjectId.NewObjectId();
public string Username { get; set; } = "";
public string PasswordHash { get; set; } = "";
}