feat: Ein Benutzername laesst sich aendern – er ist ein Etikett, kein Schluessel

Aendert der Anmeldedienst den Namen eines Nutzers, muss Certain folgen
koennen. Bisher ging das gar nicht: Umbenennen war nirgends vorgesehen.

Die gute Nachricht vorweg – am Fundament war nichts zu richten. Buchungen,
Konten, Vertraege, Sitzungen und Geraeteschluessel haengen alle an der NUMMER
des Nutzers; der Name ist nirgends ein Fremdschluessel, und auch das
Wiedereinspielen einer Sicherung ordnet nicht ueber ihn zu. Umbenennen zieht
also nichts mit. Ein Test haelt das jetzt fest: Nach dem Umbenennen sind es
dieselbe Nummer, dasselbe Konto und derselbe Saldo.

Zwei Wege gibt es jetzt.

Der Admin kann umbenennen (PATCH /api/users/{id}). Eindeutig bleiben muss der
Name trotzdem – er ist das, was beim Passwort-Login eingetippt wird.

Und der Anmeldedienst zieht bei jeder Anmeldung nach. Dabei zaehlt, WESSEN
Name das jeweils ist: Der Anzeigename gehoert dem Anbieter – heiratet jemand,
soll der neue Name auch hier stehen. Der Benutzername gehoert bei einem Konto
MIT Passwort dem Nutzer; ihn hinter dessen Ruecken zu aendern hiesse, ihm
seine Anmeldung wegzunehmen. Nur bei einem reinen SSO-Konto gibt es nichts
einzutippen – dort darf der Anbieter bestimmen.

Ist der neue Name schon vergeben, bleibt der alte stehen. Eine Anmeldung
daran scheitern zu lassen waere die schlechtere Antwort.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-10 06:00:52 +02:00
co-authored by Claude Opus 5
parent 62cb6a017c
commit 41db67f53e
4 changed files with 203 additions and 0 deletions
+5
View File
@@ -57,6 +57,11 @@ class UserCreate(BaseModel):
class UserUpdate(BaseModel):
# Umbenennen ist erlaubt: Der Benutzername ist ein ETIKETT, kein Schluessel.
# Alles Fachliche haengt an der Nummer des Nutzers – aendert sich der Name
# beim Anmeldedienst, muss Certain folgen koennen, ohne dass Daten
# umziehen.
username: str | None = Field(default=None, min_length=1, max_length=64)
password: str | None = Field(default=None, min_length=1, max_length=1024)
role: Role | None = None
display_name: str | None = Field(default=None, max_length=120)