/login, /register, /login/extern, /forgotpw, /resetpw, /mailbestaetigen, /post/abbestellen — Anmeldung
Hinweis: Rahmen: nicht der von 00-rahmen.md — diese Seiten stehen vor der Anmeldung und haben keine Leiste. Code:
frontend/src/components/menus/auth/· Nachweisnpm run verify:registration.
Wofür
Der Eingang. Anmelden und Registrieren sind seit dem 10.10.2026 neu gebaut (ES-261009-alte-anmeldeschirme, Guide §21 Zeile 6): Styled Components mit Werten aus @/theme, das Formular als Blatt Papier auf dem Grund der gewählten Szene, die Hauptaktion in Beere, Glas nur an der Datumsauswahl und an den Passwortregeln, die über dem Formular schweben. Die Zeichen kommen aus dem eigenen Satz (Zeichen). Hell und dunkel folgen der Wahl des Nutzers wie auf jedem anderen Schirm.
Seit dem 15.10.2026 unter der Kuppel (E03, Entwurf 2 „Aufstieg“, N489): /login, /register und /login/extern stehen auf dem ganzen Himmel der Szene zur Stunde — Verlauf, Atmosphäre, Wetter, Land —, das Formular schwebt als Scheibe aus Glas davor, und auf ihrer Oberkante sitzt die Eule, neutral, im Kostüm der Szene. Die Marke wählt ihre Fassung nach der Tinte des Himmels (schriftAufHimmel), der Fuß wird eine Leiste aus Stein, weil das Land unter ihm mit Szene und Stunde die Farbe wechselt. Wer den Himmel ausgeschaltet hat (theme.himmel = aus), bekommt das Blatt auf dem Grund wie vorher.
Die Eule spricht zweimal (seit 15.10.2026, nur unter der Kuppel): beim ersten Besuch dieses Geräts („Neu hier? Unter dem Knopf geht es zur Registrierung.“) und nach einer Anmeldung, auf die der Server LOGIN_FAILED_ERROR antwortet („Das passte nicht. Im Passwort zählt die Groß- und Kleinschreibung.“) — dann schaut sie überlegend. Ihr Satz steht als Glasblase im Fluss über der Scheibe, rechts bis an ihren Kopf, und wird über aria-live vorgelesen. Sobald jemand in Name oder Passwort tippt, schweigt sie. Ihr Fehlersatz sagt nicht mehr als die Meldung des Servers (N91, C03): kein Wort darüber, ob es den Namen gibt. Den ersten Besuch merkt denotato.tuer.gesehen im localStorage (handlers/tuerbesuch.ts, GERAETESPUREN), ein Merker ohne Namen und Zeit, der beim Abmelden liegen bleibt. Im zweiten Schritt und beim Hinweis auf ein häufiges Passwort schweigt sie.
/forgotpw, /resetpw und /mailbestaetigen stehen seit dem 15.10.2026 unter derselben Kuppel (E03): Marke, Scheibe mit Eule und Anmeldefuss wie beim Anmelden, „Zur Anmeldung“ als Verweis unter der Scheibe statt als Nebenknopf. Die Eule schweigt dort; ihre Sätze gehören der Anmeldung. Die Infoseite trägt seither nur noch Rechtstexte, die 404-Seite und /post/abbestellen. Ein Formblatt ohne onSubmit ist ein <section> statt eines Formulars — so steht die Meldung „kein Schlüssel“ auf derselben Scheibe, ohne ein leeres Formular zu sein.
Der Schlüssel verlässt die Adresse, sobald er gelesen ist (handlers/merkmalAusAdresse.ts): /resetpw und /mailbestaetigen lesen ?merkmal= einmal und nehmen es mit history.replaceState heraus. Sonst stünde er im Verlauf des Browsers und in jedem Bildschirmfoto der Adresszeile. Wer danach neu lädt, braucht den Link aus der Mail noch einmal — das ist gewollt.
Kein Rahmen der Anwendung auf den Seiten aus einer Mail, auch mit Sitzung (F-261015-mailbestaetigen-telefon): /forgotpw, /resetpw, /mailbestaetigen und /post/abbestellen nennt istMailseite in config/frontendPaths.ts. Dort zeichnen die Leiste unten am Telefon (mit ihr die Kapsel „↩ vorher“) und der schwebende Rückmeldeknopf nichts, auch wenn im selben Browser jemand angemeldet ist. Am iPhone lagen beide sonst über dem einzigen Knopf der Seite. „Zur Anmeldung“ heißt englisch „Sign in“.
Noch offen in E03: Willkommen unter die Kuppel, und das Telefon in der Hand.
Der Alpha-Stand mit Nachthimmel, Glaskarte, Ionicons und den beiden CSS-Wurzeln liegt nach Grundsatz 55 in archiv/2026-10-10-legacy-anmeldung/ (mit WARUM.md). Die Bausteine der Darstellung stehen in menus/auth/Anmeldeseite.tsx; LoginMenu.tsx und RegisterMenu.tsx tragen dieselbe Logik wie vorher, Zeile für Zeile.
Weg und Rahmen
| Routen | /login, /register, /login/extern, /forgotpw, /resetpw, /mailbestaetigen, /post/abbestellen — je ein <Route> in packages/frontend/src/App.tsx; die Pfade stehen in config/frontendPaths.ts |
| Anmeldung | keine nötig. Die sieben Routen liegen nicht unter der Sitzungswache, und frontendPaths.ts führt sie unter den offenen Wegen, die nie einen Raumpräfix tragen |
| Rahmen | keiner aus 00-rahmen.md, keine Leiste. /login, /register und /login/extern zeichnen LoginScreen, RegisterScreen und ExternalLoginScreen in einem Schirmwechsel (bis zum 10.10.2026 darin Nachtfarben, siehe RL02 unten); die Seite ist ihr eigener Scrollbehälter. /forgotpw, /resetpw und /mailbestaetigen nehmen seit dem 15.10.2026 dieselben Teile aus Anmeldeseite.tsx (E03); nur /post/abbestellen ist noch eine Infoseite (components/pages/) |
| Laden | LoginScreen ist ein fester Import, weil er der erste Schirm ohne Sitzung ist; RegisterScreen, ExternalLoginScreen, PasswortVergessen, PasswortSetzen, MailBestaetigen und PostAbbestellen laden per React.lazy nach (B69) |
| Merkmal | keines — die Schirme stehen vor jedem Konto und gehören zum Gerüst |
| Endpunkte | siehe „Woher die Daten kommen“ unten |
Was man sieht
/login — Anmelden
| Feld | |
|---|---|
| Benutzername oder E-Mail | |
| Passwort | mit Auge zum Anzeigen |
| Angemeldet bleiben | Kästchen, steht auf aus; angehakt legt die Anmeldung einen Geräteschlüssel an, der dreißig Tage gilt (Punkt 7 unter „Was fehlt“) |
| Element | Was passiert |
|---|---|
| Anmelden | startet die Sitzung und führt danach zurück auf den Schirm, der die Anmeldung verlangt hat (zehn Minuten gemerkt, nur eigene Wege) — sonst auf die gewählte Startseite (B-16, 23.09.2026; F53, 24.09.2026) |
| Über Google anmelden, Mit Apple anmelden | über den Feldern, nur für die Anbieter, die der Server unter GET /api/auth/oidc/providers nennt; ohne eingerichteten Anbieter steht nichts davon da, auch nicht der Satz darunter. Führt zum Anbieter und von dort über /login/extern zurück (siehe unten). Das Kästchen Angemeldet bleiben gilt auch für diesen Weg. Darunter: Wer noch kein Konto hat, bekommt dabei eins und nimmt damit die Nutzungsbedingungen an (Verweis auf /terms), danach ein Strich mit „oder“ vor den Feldern (D-261008-google-apple) |
| Passwort vergessen? | neben dem Kästchen; führt auf /forgotpw |
| Registrieren | hinter „Noch kein Konto?“; führt auf /register |
| Ohne Konto ausprobieren | nur, wenn ein Admin gast.erlaubt eingeschaltet hat (öffentliche Frage GET /api/auth/gast/erlaubt): legt ein Gastkonto an und meldet es an wie „Angemeldet bleiben“, mit Geräteschlüssel im Browser; danach dasselbe Ziel wie Anmelden. Darunter ein Satz: Damit gelten die Nutzungsbedingungen (Verweis auf /terms), und das Konto hängt an diesem Browser. Absagen (abgeschaltet, zu viele Gäste von diesem Anschluss, VPN oder Rechenzentrum) kommen als Meldung des Servers (D-261008-gastkonto, Schritt 5). Bei der Absage über VPN bleibt unter dem Knopf ein Satz mit dem Grund und dem Verweis Registrieren auf /register, weil die Meldung verschwindet, bevor man den Ausweg gefunden hat (D-261008-gaeste-bremse) |
| Etwas geht nicht? | im Fuß neben Sprache, Impressum und Datenschutz; öffnet das Rückmeldeformular über RUECKMELDUNG_RUF, dasselbe wie der schwebende Knopf auf den übrigen Schirmen |
| Sprache | Deutsch / English |
| Enter | meldet an |
Geht es schief, kommt ein Satz für beide Fälle: „Name oder Passwort stimmt nicht." Seit dem 20.09.2026 (N91) unterscheidet die Anmeldung nicht mehr zwischen „den Namen gibt es nicht" und „das Passwort stimmt nicht" — weder im Text noch im Status (beide 401), und auch nicht in der Dauer: Der Weg ohne Konto rechnet dieselbe scrypt-Ableitung und wirft sie weg, sonst verriete die Uhr, was der Text verschweigt.
Am 21.09.2026 war das nur die halbe Wahrheit, und die Uhr hat es gesagt. Gemessen am laufenden Dienst, dreimal je Fall, immer mit einem absichtlich falschen Passwort:
| Fall | Antwort | Dauer (Median) |
|---|---|---|
| Den Namen gibt es nicht | 401 login_failed_error | 287 ms |
| Konto mit altem SHA-256-Abdruck | 401 login_failed_error | 20 ms (Minimum 3 ms) |
| Konto mit heutigem scrypt-Abdruck | 401 login_failed_error | 210 ms |
N91 hatte die Dauer nur an einer der beiden Stellen angeglichen. Der unbekannte Name rechnet seither ein scrypt und wirft es weg; der bekannte rechnet, was sein Abdruck verlangt — und ein Konto, das noch den alten Abdruck trägt (password_verfahren = 'sha256', D21), war in Mikrosekunden fertig. Eine Antwort unter fünfzig Millisekunden hieß damit: dieses Konto gibt es. Kein Durchprobieren nötig, ein Versuch je Name genügte, und die Bremse half nicht — sie zählt fünf Versuche, die Auskunft gab schon der erste. Zur Größe: an dem Tag trugen sechs Konten auf dem Server noch den alten Abdruck.
Behoben am selben Tag in plugins/src/auth/server/konten.ts: Scheitert das Passwort und hat das Konto den alten Abdruck, rechnet auch dieser Weg die scrypt-Ableitung, die seine Prüfung gespart hat — und wirft sie weg. Nur dann: Ein Konto mit heutigem Abdruck hat sie schon gerechnet, und ein zweites obendrauf wäre dasselbe Fenster andersherum. An der Passwortablage ändert das nichts; der Umstieg auf scrypt passiert weiterhin allein nach einem richtigen Passwort. Nachgemessen: die drei Fälle liegen seither bei 317 / 375 / 296 ms — der Unterschied ist Maschinenrauschen und kein Merkmal mehr.
Der zweite Schritt — der Code (seit dem 21.09.2026, D07)
Wer einen zweiten Faktor eingerichtet hat, sieht nach dem richtigen Passwort nicht den Schreibtisch, sondern ein zweites Formular. Der Schirm bleibt derselbe; die Felder werden ausgetauscht.
| Feld | |
|---|---|
| Code aus deiner App | sechs Ziffern aus der App — oder einer der zehn Wiederherstellungscodes; darunter steht „Kein Telefon zur Hand? Ein Wiederherstellungscode geht auch.“ |
| Element | Was passiert |
|---|---|
| Weiter | löst den Code ein und meldet an; Enter tut dasselbe |
| Zurück zur Anmeldung | stellt die Felder von oben wieder her — kein Seitenwechsel, die Sitzung bleibt |
Was dabei auf dem Server geschieht, ist der eigentliche Punkt. Nach dem richtigen Passwort trägt die Sitzung eine Wartemarke (offenerZweiterFaktor) — und nicht das Konto. Solange sie dort steht, sagt jeder geschützte Endpunkt ab, denn getSessionUser findet niemanden. Der zweite Faktor ist damit ein Riegel und kein Schirm, den man wegklickt; npm run verify:zweiterfaktortor misst genau das.
Die Marke verfällt nach fünf Minuten (AUTH_ZWEITER_FAKTOR_FRIST_S). Ohne die Frist bliebe ein Rechner, an dem jemand kurz das Passwort eingab und wegging, eine Stunde lang sechs Ziffern von der Anmeldung entfernt. Sie wird auch nicht mitgeschrieben: Eine halbe Anmeldung überlebt keinen Neustart, und wer wiederkommt, fängt beim Passwort an.
Zehn Codeversuche je Konto (AUTH_CODEVERSUCHE_JE_KONTO), dann dieselbe Antwort wie bei jeder anderen Bremse. Eine neue Anmeldung — auch eine gescheiterte — räumt die Wartemarke von vorhin weg; sonst käme, wem der Code später einfällt, doch noch in das erste Konto.
Der Schlüsselweg (/api/auth/token) fragt nicht nach dem Code, und das ist Absicht: Ein Wiedererkennungsschlüssel ist ein zweiter Faktor — etwas, das man hat, widerrufbar, an ein Gerät gebunden. Ein zweiter Code davor hieße, denselben Beweis zweimal zu verlangen.
Der Hinweis auf ein häufiges Passwort (seit dem 23.09.2026, D30)
Steht das eingegebene Passwort auf der Liste häufiger Passwörter (siehe „Die Passwortregel" unten), ist die Anmeldung trotzdem durch — aber der Schirm geht nicht sofort weiter. Er hält einen dritten Schritt an, wieder im selben Formular:
Hinweis: Dein Passwort steht auf einer Liste häufig benutzter Passwörter. Solche Passwörter werden bei Angriffen zuerst ausprobiert. Ein anderes schützt dein Konto besser.
| Element | Was passiert |
|---|---|
| Passwort ändern | führt auf /settings?a=konto&g=passwortwechsel: die Kategorie Konto der Einstellungen, die Gruppe „Passwort ändern" schon aufgeklappt (N143); Enter tut dasselbe |
| Später | führt dorthin, wohin es ohne Hinweis gegangen wäre — der gemerkte Weg oder die gewählte Startseite |
Entschieden wird auf dem Server. login in konten.ts fragt nach dem Abdruck und der Sperre stehtAufListe und gibt das Ergebnis als passwortHaeufig mit der Antwort von /api/auth/login hinaus. Mit zweitem Faktor wartet das Merkmal an der Wartemarke und kommt erst mit der Antwort von /api/auth/zweiter-faktor — die Antwort auf das Passwort allein trägt es nicht. Der Schlüsselweg (/api/auth/token) prüft nicht, denn dort liegt kein Passwort vor.
Gespeichert wird nichts — keine Spalte, kein Vermerk. Wer „Später" wählt, sieht den Hinweis bei der nächsten Anmeldung mit demselben Passwort wieder, und nur dann. Nachweise: npm run verify:passworthinweis (die Leitung, fünf Wege, und dass keine Spalte in auth das Merkmal trägt) und LoginMenu.test.tsx (der Schritt im Formular, jsdom).
Der Probebereich eines neuen Gastes (seit dem 10.10.2026, D-261010-probebereich-gaeste)
Ein neues Gastkonto beginnt nicht leer. legeGastAn legt nach dem Konto in einer Transaktion unter dem Gast (withUserScope) an, was in shared/src/config/probebereich.ts steht: das Dokument „Beispiel: Ableitungen“ mit zwölf Blöcken (Hinweis, Überschriften, Formel, Aufzählung), fünf Lernkarten dazu, die sofort fällig sind („Was ist die Ableitung von sin x?“ und vier weitere), und die Aufgabe „Die fünf Beispielkarten lernen“, fällig heute um 23:59 in der Zone des Kontos. Die Sätze sind Oberflächentext (satz) und werden beim Anlegen in die Sprache des Gastes übersetzt (kontosprache: Kontowahl, sonst die Sprache des Browsers, sonst Deutsch). Ob es geschieht, stellt ein Admin mit gast.probe (Vorgabe 1). Scheitert das Anlegen, steht das Gastkonto trotzdem, und das Protokoll sagt [Gastkonto] Probebereich nicht angelegt. Die Registrierung legt keinen Probebereich an.
Die Kennungen leitet der Server aus der Kennung des Gastes ab (probekennungen.ts); ein eigenes Merkmal gibt es nicht. Unberührt heißt: Das Dokument trägt noch Titel, Symbol, Freigabe und Blöcke einer Sprachfassung, ohne Unterseiten und ohne Karte aus einem Block; eine Karte wurde nie geändert und nie gelernt; die Aufgabe hat keinen Fortschritt, keine Unteraufgabe und wurde nie geändert. Was unberührt ist, zählt nicht im Angebot unten und wird vor dem Übernehmen in derselben Transaktion gelöscht; Angefasstes kommt mit wie alles andere. Wer sich aus der Gastsitzung registriert, behält den Probebereich, wie er ist. Nachweise: npm run verify:gastkonto, Abschnitt 2b (Deutsch, Englisch, Schalter aus), und npm run verify:registration (nur der Probebereich bringt kein Angebot; eine gelernte Beispielkarte kommt mit, der Rest nicht; nach der Registrierung ist er unverändert).
Das Angebot an den Gast (seit dem 10.10.2026, D-261008-gast-uebertrag)
Wer als Gast unterwegs war und sich auf /login mit einem bestehenden Konto anmeldet, tut das in der Sitzung des Gastes. Hat der Gast Seiten, Karten, Aufgaben, Termine, Personen, Tagebucheinträge, Erinnerungen oder LaTeX-Texte angelegt (ohne den unberührten Probebereich und ohne Termine aus einem Kalenderabo), hält das Formular nach der Anmeldung einen Schritt an, vor dem Passworthinweis:
Hinweis: Was du als Gast angelegt hast, in dieses Konto übernehmen?
| Element | Was passiert |
|---|---|
| Übernehmen | hängt alles, was dem Gast gehörte, in das Konto um und führt weiter wie Anmelden |
| Nicht übernehmen | lässt das Gastkonto stehen, bis es an seinem Gastende verfällt, und führt ebenso weiter |
Die beiden Knöpfe stehen untereinander, gleich breit und gleich gefärbt; keiner ist vorgewählt, und Enter tut nichts. Beides ist eine gute Wahl, und welche, weiß nur, wer als Gast etwas angelegt hat.
Den Gast kennt nur der Server. login in konten.ts (mit zweitem Faktor zweiterFaktorRouten.ts) merkt sich vor der Neuanmeldung, welcher Gast die Sitzung hielt, und legt es fünfzehn Minuten lang in die neue Sitzung (gastangebot in sitzungen.ts, nicht abgelegt). Die Antwort trägt nur die Zahl der Dinge als gastangebot, nie die Kennung des Gastes. Beantwortet wird über POST /api/auth/gast/uebertragen mit { uebernehmen }: Das Angebot verlässt die Sitzung vor dem ersten Warten, also gilt höchstens eine Antwort, nur in der Sitzung, die es bekam, und nicht unter Impersonation. Annehmen ruft auth.gast_uebertragen (Wanderung 277) in einer Transaktion: Jede Tabelle, die shared/src/config/gastuebertrag.ts als „umhängen“ führt, wechselt den Eigentümer unter derselben Kennung; bei einem Zusammenstoß gewinnt der jüngere FSRS-Stand. Danach ist der Gast abgelaufen, seine Schlüssel sind widerrufen und seine übrigen Sitzungen beendet.
Gilt das Angebot nicht mehr (Frist vorbei, schon beantwortet), sagt ein Hinweis das, und der Schirm geht trotzdem weiter: Die Anmeldung ist durch. Scheitert nur die Anfrage, bleibt der Schritt stehen, und der Server hat das Angebot behalten. Der Schlüsselweg (/api/auth/token) bietet nichts an. Nachweise: npm run verify:registration, Abschnitt „Ein Gast meldet sich in ein bestehendes Konto an“ (nachweisGastuebernahme.ts), verify:gastuebertrag (die Funktion kennt jede Tabelle der Liste) und LoginMenu.test.tsx (der Schritt im Formular, jsdom).
Die Absage einer beta-API (seit dem 01.10.2026, P07)
Läuft die API als beta (DENOTATO_STAND=beta), lässt sie nur Konten mit der Fähigkeit beta herein (N443, Form 1). Ein Konto ohne sie kommt durch das Passwort, den zweiten Faktor oder den Schlüsselweg bis ans Tor und bekommt dort beta_access_denied (403). Der Schirm zeigt den Satz aus de.json als Fehlermeldung und bleibt auf /login:
Hinweis: dieses Konto hat keinen Zugang zum Arbeitsstand (beta) — Denotato selbst läuft unter der gewohnten Adresse
Es ist nicht „Anmeldung fehlgeschlagen", weil das Passwort stimmt; ein zweiter Versuch oder ein Zurücksetzen änderte nichts.
Mitten in der Arbeit — die Fähigkeit wurde entzogen, oder die Sitzung kam mit der Abschrift aus prod herüber — beendet der Server die Sitzung nicht, sondern weist jede Anfrage ab. requestHandler.ts nimmt die Absage deshalb selbst als Ende (beta-abgewiesen), außer auf den drei Anmeldewegen, deren Aufrufer sie deuten. IdentityContext meldet dann ohne Wiedererkennungsversuch ab, mit demselben Satz statt „die Sitzung ist abgelaufen"; trifft eine Wiedererkennung aufs Tor, ebenso. In prod und ohne gesetzten Stand fragt niemand nach der Fähigkeit. Nachweise: npm run verify:betator (Server, alle Türen, drei Stände), LoginMenu.test.tsx, betaAbsage.test.tsx und requestHandler.test.ts (jsdom).
/register — Konto anlegen
| Feld | Hinweis |
|---|---|
| Benutzername | gegen SQL-Zeichen, XSS-Vektoren, gesperrte Namen und SQL-Schlüsselwörter geprüft |
| Anzeigename | „Wie sollen dich andere sehen?" |
| E-Mail-Adresse | |
| Geburtsdatum | eigener Datumswähler („Ein Monat vor / zurück"), Kalenderzeichen links; Monat, Wochentage, Anzeige und Platzhalter folgen der Sprache des Geräts (tt.mm.jjjj, mm/dd/yyyy), die Woche beginnt am Montag; das Fenster schwebt über einem Schleier, ein Klick daneben oder Escape schließt es |
| Passwort | „Passwort ausdenken", mit Anforderungsliste und Stärkeanzeige darunter |
| Ich stimme den Nutzungsbedingungen zu | Kästchen, Pflicht; „Nutzungsbedingungen“ öffnet /terms in einem neuen Reiter |
| Freiwillig: Der Begleiter darf Tagebuch und Gedächtnis mitnehmen | eigenes Kästchen, steht auf aus, keine Bedingung für das Konto (Art. 7 Abs. 4 DSGVO); darunter aufklappbar der Wortlaut der Einwilligung „Tagebuch und Gedächtnis im Begleiter“. Mit Haken erteilt die Seite die Einwilligung gleich nach der Anmeldung in der neuen Sitzung; scheitert das oder bleibt die Anmeldung aus (Tor vor beta, Riegel), fragt der Begleiter beim ersten Öffnen selbst. Nur, wenn das Begleiter-Plugin da ist (F-261011-begleiter-einwilligung) |
| Alle akzeptieren | setzt beide Haken, schickt nichts ab; Christian am 11.10.2026: zustimmen „mit einem Klick“, wer ablehnt, wird beim Öffnen des Begleiters gefragt |
Fehlt das Geburtsdatum, sagt der Schirm das — ist es unlesbar, sagt er etwas anderes. Bis zum 21.09.2026 gab es für beides denselben Satz („das Geburtsdatum fehlt"), gemessen mit birthDate: '' und birthDate: 'quatsch': zweimal birth_date_missing_error. Der zweite Satz schickte jemanden suchen, der das Feld ausgefüllt hatte. Jetzt antwortet ein unlesbares Datum mit birth_date_format_error — „das Format des Geburtsdatums ist ungültig". Die Kennung gab es seit jeher in messages.ts; benutzt hat sie niemand.
Das Feld bleibt Pflicht, obwohl Denotato es nirgends liest (Punkt 13 unten). auth.users.birth_date ist NOT NULL — es fallen zu lassen ist eine Wanderung und keine Zeile.
| Element | Was passiert |
|---|---|
| Konto anlegen | bleibt gesperrt, bis alle fünf Felder gefüllt sind und das Kästchen angehakt ist; legt das Konto an, meldet die Sitzung gleich darin an — ohne „Angemeldet bleiben“ — und führt in die Anwendung wie Anmelden (D-261008-registrierung-gleich, 10.10.2026). Dort steht über jedem Schirm, dass die Bestätigungsmail unterwegs ist (Mailhinweis, siehe 00-rahmen.md). Hält das Tor vor beta die Anmeldung auf, steht das Konto trotzdem, und es bleibt beim Satz „Konto angelegt: …“ (Punkt 30 unter „Was fehlt“). In einer Gastsitzung macht der Knopf das Gastkonto zum Konto, statt ein zweites anzulegen (siehe unten) |
| Mit Google registrieren, Mit Apple registrieren | über den Feldern, unter denselben Bedingungen wie auf /login; derselbe Weg wie dort, nur anders beschriftet. Ohne „Angemeldet bleiben“. In einer Gastsitzung wird wie beim Formular das Gastkonto zum Konto |
| Passwortanforderungen anzeigen | klappt die Liste auf: mindestens 8, höchstens 100 Zeichen; jede Zusammensetzung; nicht unter den häufigsten Passwörtern (geprüft beim Absenden) |
| Schon ein Konto? | führt auf /login |
| Escape | schließt das Anforderungsfenster |
Aus einer Gastsitzung (seit dem 10.10.2026, D-261008-gast-uebertrag)
Wer als Gast unterwegs ist und über Konto anlegen in der Leiste (Gastzeile) oder im Kontomenü hierher kommt, sieht unter der Überschrift den Satz „Was du als Gast angelegt hast, kommt mit in dein Konto.“ Die Felder und Prüfungen sind dieselben wie für jeden anderen: Bremse, Passwortregel, Namensprüfung und Quote je Adresse.
Hinter dem Knopf legt registerUser dann kein neues Konto an, sondern schreibt Name, Anzeigename, Adresse, Geburtsdatum und Passwort in die Zeile des Gastes und löscht guest_until (wandleGastUm in plugins/src/auth/server/konten.ts). Die Kontokennung bleibt, und mit ihr alles, was daran hängt: Dokumente, Karten mit ihrem FSRS-Stand, Aufgaben, Einstellungen. Kopiert wird nichts. In derselben Transaktion werden die Geräteschlüssel des Gastes widerrufen, denn sie öffneten ein Konto ohne Passwort. Danach geht die Bestätigungsmail hinaus wie bei jeder Registrierung, und die Sitzung wird über schliesseAnmeldungAb neu angemeldet: neue Sitzungskennung (N137, N161) und ein neuer Geräteschlüssel, weil der Gast vorher angemeldet blieb. Die Oberfläche tauscht keine Schlüssel neu aus, übernimmt die neue Kennung und lädt den Zugriff neu, damit die Gastzeile verschwindet.
Hält ein Riegel die Anmeldung auf (Tor vor beta, zweiter Faktor), endet die Gastsitzung: Sie trägt noch den widerrufenen Schlüssel, und ein Konto mit Passwort soll nicht über eine Sitzung erreichbar bleiben, die nie eines gezeigt hat. Das Formular sagt dann „Konto angelegt: …“ und führt auf /login. Nachweis: npm run verify:registration, Abschnitt „Ein Gast registriert sich“ (nachweisGastumwandlung.ts).
Die Passwortregel (seit dem 23.09.2026, D30)
Länge statt Zeichenklassen, dazu eine Liste der häufigsten Passwörter — so, wie ASVS 5.0.0 es in Stufe 1 verlangt (V6.2.1, V6.2.4, V6.2.5). Dieselbe Regel gilt auf allen drei Wegen, auf denen ein Mensch ein Passwort setzt: hier, bei /resetpw und beim Wechsel unter /settings. Die drei rufen dafür eine Funktion, pruefeNeuesPasswort in plugins/src/auth/server/passwortregel.ts.
- 8 bis 100 Zeichen, gezählt in Codepunkten. Ein Emoji ist ein Zeichen, nicht zwei;
😀😀Aa1!hat sechs und wird abgewiesen. - Jede Zusammensetzung. Keine Ziffer, kein Großbuchstabe, kein Sonderzeichen ist Pflicht, und keines ist mehr verboten —
correct horse battery staple,Passwort(1)AundGroot2026!xgehen. Gesperrt bleiben nur Steuer- und Richtungszeichen; sie lassen sich nicht tippen und können ein Passwort anders aussehen lassen, als es ist. - Nicht auf der Liste. 49 343 häufige Passwörter aus SecLists, ohne Rücksicht auf Groß- und Kleinschreibung verglichen (
plugins/src/auth/server/haeufigePasswoerter.txt, Herkunft und Lizenz im Kopf der Datei).Password1!,P@ssw0rdund12345678werden mit „dieses Passwort gehört zu den häufigsten und wird als Erstes durchprobiert — bitte ein anderes wählen" abgewiesen.
Die Liste liegt nur auf dem Server. Ein halbes Megabyte gehört nicht ins Bündel; der Browser prüft Länge und Zeichen vorab und erfährt erst beim Absenden, dass ein Passwort zu häufig ist. Die Stärkeanzeige unter dem Feld misst seitdem nach Länge (8–11 schwach, ab 12 mittel, ab 16 stark, ab 20 sehr stark) und zieht für bekannte Muster ab — password, 1234, qwert, Folgen wie abcd, dreimal dasselbe Zeichen, höchstens vier verschiedene. Zeichenklassen zählt sie nicht mehr.
Wer schon ein Passwort von der Liste hat, kommt weiter herein. Die Anmeldung weist nichts ab; seit dem 23.09.2026 zeigt sie danach aber den Hinweis oben (D30). Nachweis: npm run verify:passwortregel.
Was „legt das Konto an" hinter dem Knopf bedeutet
provisionNewUser stattet das frische Konto aus: Bereiche in auth.feature_grants, die ausgeschalteten davon in auth.user_feature_settings, dazu eine Zeile in personal.settings und eine in personal.customizations. Welche Bereiche, steht nicht im Quelltext, sondern als Instanzvorgabe in personal.preference_defaults unter plugins.verfuegbar.
Seit dem 22.09.2026 bekommt das Konto davon nicht mehr alles (F22). Fünf Bereiche — admin, journal, memories, personalities, moodle — hängen an einer Fähigkeit (FEATURE_CAPABILITY), und wer sie nicht trägt, bekommt den Bereich nicht gewährt. Gemessen am 22.09.2026 stand plugins.verfuegbar auf allen dreizehn, und ein frisch registriertes Konto bekam damit admin: POST /api/access/feature schaltete die Verwaltung ein, POST /api/verwaltung gab den Baum heraus. Heute antworten beide mit 404.
Was ein neues Konto hier und heute sieht, sind die acht Bereiche ohne Fähigkeit: documents, events, flashcards, gamification, people, study, stundenplan, tasks — gamification davon ausgeschaltet. Wer mehr bekommen soll, bekommt die Fähigkeit dazu; die Grantzeile allein öffnet nichts mehr. Nachweis: npm run verify:registration, Abschnitt „Kein Bereich ohne seine Faehigkeit (F22)".
Seit dem 27.09.2026 schreibt provisionNewUser je gewährtem Bereich auch den Vermerk „einmal angeboten" in auth.feature_seed_offers (N265, F08). Ohne ihn bot db:seed-capabilities einem Konto, das nach Wanderung 146 entstand, jeden Bereich noch einmal an und gab einen im Meta-Server entzogenen zurück. Kommt ein Bereich später in die Vorgabe, geschieht das über den Meta-Server, Reiter „Permissions" (N306): Er zeigt, was im Code steht und in plugins.verfuegbar fehlt, und ein Klick nimmt es auf und bietet es jedem vorhandenen Konto einmal an.
/forgotpw — Passwort vergessen
Seit dem 18.09.2026 ein Ablauf und keine Infoseite mehr (D01). Code: frontend/src/components/pages/PasswortZuruecksetzen.tsx · Nachweis npm run verify:ruecksetzung.
| Feld | |
|---|---|
| Kontoname oder E-Mail-Adresse | eines von beiden genügt — wer vergisst, weiß oft nicht mehr, welches |
| Element | Was passiert |
|---|---|
| Zur Anmeldung | Verweis unter der Scheibe, führt auf /login (RL18; bis zum 15.10.2026 ein Nebenknopf) |
| Mail anfordern | schickt eine Mail mit einem Link, der eine Stunde und einmal gilt — in der Sprache des Kontos, sonst in der, die dieser Browser eingestellt hat (N377; die Regel steht in 00-rahmen.md bei Sprache) |
Die Meldung trägt kein Zeichen. Ein haken stand hier kurz (N134), zeigte sich aber auch vor dem Fehlschlag (ergebnis.success === false) — und behauptete damit einen Erfolg, den diese Seite absichtlich nie zusichert: Die Antwort ist immer dieselbe, gleich ob es das Konto gibt. Ein Prüfer hat das am 23.09.2026 gemeldet; das Zeichen ist seither wieder weg, der Satz bleibt Text in der Grundfarbe (text.default), ohne Farbe und ohne Zeichen. Er trägt role="status" (RL18), damit ein Bildschirmleser ihn ansagt, ohne dass der Fokus dorthin wandert.
Die Antwort ist immer dieselbe — auch bei einem Tippfehler, auch bei einer erfundenen Adresse: „Wenn es dazu ein Konto gibt, ist eine Mail unterwegs." Eine Seite, die „unbekannt" sagt, wäre eine Auskunft darüber, wer hier ein Konto hat. Weil das unbequem ist, steht der Hinweis darauf über dem Feld, bevor jemand etwas eintippt — nicht als Fehlermeldung danach.
Höchstens fünf Anforderungen je Konto und Stunde. Das ist nicht gegen Übernahme gerichtet (dagegen hilft der Ablauf), sondern dagegen, dass jemand ein fremdes Postfach flutet, bis der Besitzer die echte Mail übersieht.
Und seit dem 22.09.2026 braucht die Antwort auch immer gleich lang. Am 21.09. stand hier ein Verdacht mit zu kleiner Stichprobe (Punkt 19). Nachgemessen am 22.09.2026, fünfzehn Läufe je Fall an der Funktion selbst:
| Fall | Dauer (Median) |
|---|---|
| Den Namen gibt es nicht | 0,6 ms |
| Konto vorhanden, Adresse unbestätigt | 0,6 ms |
| Konto vorhanden, Adresse bestätigt | 8,2 ms |
Der Verdacht stimmte, und der Faktor war dreizehn: Jede Antwort über drei Millisekunden hieß „dieses Konto gibt es, und seine Adresse ist bestätigt." Der Grund steht im Code und ist genau der vermutete — eine unbestätigte Adresse bricht früh ab, eine bestätigte schreibt eine Zeile und schickt eine Mail.
Es ist derselbe Fehler wie bei der Anmeldung, nur an der anderen Tür. Angeglichen werden konnte er hier aber nicht wie dort: Bei der Anmeldung rechnen alle Wege dieselbe scrypt-Ableitung, hier tun die drei Wege wirklich Verschiedenes — einer schreibt eine Zeile, zwei schreiben keine. Also zwei Griffe statt einer:
- Die Antwort wartet nicht mehr auf den Mailserver. Der Versand lief unter
await; damit hing die Dauer an einem SMTP-Gespräch, und zwar nur auf dieser einen Bahn. Eine Untergrenze allein hätte das nicht geheilt — sie macht die schnellen Bahnen langsamer, nicht die langsame schneller. - Eine gesetzte Untergrenze von 250 ms (
ANTWORT_MINDESTENS_MS), bequem über allem, was die langsamste Bahn an Datenbankarbeit braucht (gemessen 14,7 ms im Höchstfall), und in derselben Größenordnung wie eine gescheiterte Anmeldung: Die beiden Türen, an denen jemand einen Kontonamen ausprobiert, sollen sich auch nicht daran unterscheiden, wie schnell sie zumachen.
Nachgemessen: 250,4 / 250,4 / 250,5 ms. Für den Menschen davor ist es ein Viertel einer Sekunde, das er nicht bemerkt; er wartet ohnehin auf eine Mail. Festgehalten wird es von npm run verify:ruecksetzung — in Verhältnissen und nicht in Millisekunden („kein Fall unter der Hälfte des langsamsten"), weil eine Dauer auf einer geteilten Maschine verrauscht ist. Die Gegenprobe am selben Tag: Untergrenze auf null gesetzt, und der Nachweis wird rot mit 0,3 ms gegen 4,5 ms.
Scheitert die Anfrage, bleibt „Mail anfordern" bedienbar (F28b, 23.09.2026): Der Knopf setzt seinen Laufmerker über useBusyLock, das ihn in einem try/finally wieder freigibt, statt bei „einen Moment…" gesperrt stehen zu bleiben.
/resetpw — Neues Passwort setzen
Der zweite Schritt. Der Schlüssel steht in der Adresse (?merkmal=…), aus dem Link der Mail; er wird nicht angezeigt und nirgends gespeichert.
| Feld | |
|---|---|
| Neues Passwort | dieselbe Regel wie bei der Registrierung — 8 bis 100 Zeichen, jede Zusammensetzung, nicht unter den häufigsten (siehe „Die Passwortregel" oben) |
| Noch einmal | müssen gleich sein, sonst bleibt der Knopf aus |
| Element | Was passiert |
|---|---|
| Passwort setzen | setzt es und macht den Schlüssel unbrauchbar |
| Zur Anmeldung | Verweis unter der Scheibe, führt auf /login (bis zum 15.10.2026 ein Nebenknopf, nach dem Setzen der Hauptknopf) |
Ohne ?merkmal= in der Adresse sagt die Seite genau das und zeigt kein Formular. Abgelaufen, verbraucht und erfunden geben dieselbe Meldung: Für den, der davorsteht, sind die drei dasselbe — er braucht einen neuen Link.
Beide Meldungen — die ungleichen Eingaben und die Antwort des Servers — tragen seit dem 23.09.2026 das Zeichen warnung in status.danger (N134), der Satz bleibt neutral. Die Erfolgsmeldung nach dem Setzen ist eine eigene Ansicht (fertig) ohne Formular und trägt seit dem 15.10.2026 den haken in status.success, wie die Bestätigung.
Was das Einlösen beendet (seit dem 22.09.2026)
Wer hier ankommt, tut das oft, weil er jemanden vermutet. Deshalb endet mit dem Einlösen alles, was am alten Passwort hing — und zwar zweierlei, denn eines allein wäre Theater gewesen:
| Was | Womit |
|---|---|
| jede laufende Anmeldung | beendeAlle(userId) — ohne Ausnahme, anders als beim Passwortwechsel. Hier gibt es kein eigenes Gerät: Wer zurücksetzt, ist nicht angemeldet, und die Sitzung, die man verschonte, wäre genau die des Fremden |
| jeder „Angemeldet bleiben"-Schlüssel | widerrufeAlleSchluessel(userId) — markiert, nicht gelöscht |
Warum der zweite Griff dazugehört. Gemessen am 22.09.2026: Nach einem erfolgreichen Zurücksetzen stand die fremde Sitzung noch, und ein Zugriffsschlüssel löste weiterhin ein. Ein solcher Schlüssel trägt dreißig Tage, /api/auth/token fragt weder nach dem Passwort noch nach dem zweiten Faktor — wer nur die Sitzungen beendet, hat den Hinausgeworfenen mit einer einzigen Anfrage zurück.
Und der MCP-Schlüssel geht mit. „Jeder Zugriffsschlüssel" heißt wirklich jeder — auch der, mit dem sich ein Programm anmeldet (Ben, ein Skript, ein zweites Gerät). Das ist kein Nebeneffekt, sondern der Zweck: Ein Schlüssel, den man ausnähme, wäre genau der, den ein Fremder hat. Wer nach einer Rücksetzung wieder verbunden sein will, legt unter /settings einen neuen an — derselbe Handgriff wie beim ersten Mal.
Nach dem Festschreiben und nicht darin. Schlüge das Hinauswerfen fehl, wäre das Passwort trotzdem gesetzt; es wird deshalb auch nicht zum Fehlschlag, sondern zu einer Zeile im Protokoll. Wer hier „hat nicht geklappt" läse, forderte einen neuen Link an und käme mit dem alten Passwort nicht mehr hinein. Ein abgewiesenes Passwort (zu kurz, zu häufig) wirft dagegen niemanden hinaus — ein Tippfehler ist kein Grund für einen Rauswurf. Beides hält npm run verify:ruecksetzung fest.
Scheitert die Anfrage, bleibt „Passwort setzen" bedienbar (F28b, 23.09.2026): Derselbe Riegel (useBusyLock) wie beim Anfordern gibt den Knopf in einem try/finally wieder frei, statt bei „einen Moment…" gesperrt stehen zu bleiben.
/mailbestaetigen — Adresse bestätigen
Der Klick aus der Bestätigungsmail (D02, seit dem 18.09.2026). Ohne Anmeldung, und das ist kein Versehen: Wer den Link anklickt, sitzt oft an einem anderen Gerät als dem, auf dem er sich registriert hat — im Postfach auf dem Telefon. Eine Bestätigung, die eine Anmeldung verlangt, wird genau dort nicht angeklickt.
Der Schlüssel steht in der Adresse (?merkmal=…), wird nicht angezeigt und nirgends gespeichert.
| Element | Was passiert |
|---|---|
| (kein Knopf) | Der Schirm löst die Bestätigung beim Laden aus — ein „Jetzt bestätigen"-Knopf hinter einem Link, den man zum Bestätigen angeklickt hat, ist ein Schritt zu viel |
| Zur Anmeldung | führt auf /login |
Ohne ?merkmal= sagt die Seite genau das. Abgelaufen, verbraucht und erfunden geben dieselbe Meldung — wie bei /resetpw, aus demselben Grund. Links gelten 48 Stunden und nicht eine Stunde: Eine Bestätigungsmail kommt an, während man etwas anderes tut, und wird am Abend gelesen.
Die Meldung trägt seit dem 23.09.2026 ein Zeichen (N134): haken bei Erfolg, warnung sonst — beide in der passenden Statusfarbe, der Satz bleibt neutral. Vorher stand hier nur roter oder normaler Text.
Was daran hängt: Solange die Adresse nicht bestätigt ist, schickt /forgotpw keinen Rücksetzlink dorthin — die Antwort bleibt trotzdem dieselbe. Einen Schlüssel zum Konto an eine Adresse zu schicken, von der niemand weiß, wem sie gehört, ist der Angriff, gegen den die Bestätigung gebaut ist.
/post/abbestellen — Post abbestellen (D25)
Der öffentliche Schirm liest das signierte Merkmal ausschließlich aus dem Fragment der Adresse. Ein Aufruf des Schirms ändert noch nichts: Erst der Knopf „Post abbestellen“ sendet den von RFC 8058 vorgesehenen Körper List-Unsubscribe=One-Click an /api/post/abbestellen.
Ist die Sorte noch bekannt, stehen ihr Titel und ihr erklärender Satz vor dem Knopf. Danach nennt der Schirm jeden Ausgang ausdrücklich: abbestellt, ungültiger Link, nicht mehr vorhandene Sorte, Bremse, fehlende Einrichtung oder Zeitgrenze. Vorgangs-Mails zum eigenen Konto bleiben davon unberührt.
/login/extern — Rückkehr von Google und Apple (D-261008-google-apple)
Hierher schickt der Server den Browser, nachdem Google oder Apple ihn zurückgegeben haben, mit code und state im Fragment der Adresse. Die Seite liest das Fragment, bevor sie irgendetwas anderes tut, und ersetzt die Adresse ohne es: Weder der Verlauf noch ein Lesezeichen behält den Code. Danach nimmt sie den Ablauf, den der Knopf im selben Reiter in sessionStorage abgelegt hat (Schlüssel denotato.externalLogin, mit dem Geheimnis des Ablaufs), und löscht ihn im selben Zug. Fehlt er, oder passt state nicht, ruft die Seite nichts auf und sagt: „Dieser Anmeldeversuch gilt nicht mehr. Fang bitte noch einmal an.“
Solange sie wartet, steht unter „Anmelden“ ein Kreisel und „Die Anmeldung über Google wird abgeschlossen.“ Danach:
| Antwort | Was man sieht |
|---|---|
| angemeldet | geht weiter wie Anmelden auf /login, mit einer kurzen Meldung „Angemeldet als …“. Mit „Angemeldet bleiben“ oder als umgewandelter Gast bleibt die Anmeldung im Browser |
| angemeldet, Verknüpfung aber nicht angenommen | dasselbe, dazu ein Warnhinweis: Das Konto beim Anbieter gehört schon zu einem anderen Denotato-Konto oder hat eine andere Adresse als dieses |
| verknüpft aus den Einstellungen | Meldung „Verknüpft …“ und zurück nach /settings?a=konto&g=anmeldung |
| Adresse hat schon ein Konto | Schirm Konto verknüpfen: der Satz des Servers, Benutzername oder E-Mail, Passwort, Anmelden und verknüpfen; leiser darunter Doch ein neues Konto anlegen, ganz unten Zurück zur Anmeldung. Ein falsches Passwort lässt den Schirm stehen (die Bremse der Anmeldung zählt mit); eine abgelaufene Verknüpfung (fünf Minuten) führt zur Fehlerseite |
| zweiter Faktor verlangt | derselbe Codeschritt wie auf /login, auch nach Anmelden und verknüpfen |
| Konto angelegt, Tor vor beta hält die Anmeldung auf | Konto angelegt mit dem Satz, dass man sich auf der Anmeldeseite noch einmal über den Anbieter anmelden soll, und Zur Anmeldung. Kam man aus einer Gastsitzung, ist die Sitzung beendet und man steht abgemeldet mit „Dein Konto ist angelegt.“ da |
| abgebrochen beim Anbieter | kurze Meldung und zurück: aus den Einstellungen dorthin, sonst nach /login |
| alles andere | der Satz zum Fehler und Zurück zur Anmeldung oder, bei einer Verknüpfung, Zurück zu den Einstellungen |
Hält der Browser nichts in sessionStorage (ein privates Fenster mit gesperrtem Speicher etwa), geht der Knopf gar nicht erst los und sagt, dass es so nicht geht und mit Passwort schon.
/oauth/authorize — Zugang für einen Verbinder (A-261011-oauth-denotato-mcp)
Hierher schickt claude.ai ein Mitglied, das bei einem Verbinder auf Connect klickt. Ohne Sitzung führt die Sitzungswache erst auf /login und danach mit der vollen Adresse zurück. Der Schirm nennt den Namen, den der Client sich selbst gegeben hat, das Konto, als das er arbeiten wird, und — als das, worauf es ankommt — den Rechner, an den der Zugang geht. Eine Anfrage, die nicht stimmt (unbekannter Client, fremder Rückweg, kein PKCE), zeigt nur den Grund und keinen Knopf.
Was man tun kann
| Element | Wo | Was passiert |
|---|---|---|
| Anmelden | /login | startet die Sitzung und führt zurück auf den verlangten Schirm (zehn Minuten, nur eigene Wege), sonst auf die gewählte Startseite (F53); Enter tut dasselbe |
| Weiter | /login, zweiter Schritt | löst den Code ein und führt danach genauso zurück (D07); Enter tut dasselbe |
| Zurück zur Anmeldung | /login, zweiter Schritt | stellt die Felder von oben wieder her |
| Passwort ändern | /login, Hinweis bei häufigem Passwort | führt auf /settings?a=konto&g=passwortwechsel, Gruppe aufgeklappt (D30, N143); Enter tut dasselbe |
| Später | /login, Hinweis bei häufigem Passwort | führt dorthin, wohin es ohne Hinweis gegangen wäre |
| Übernehmen | /login, nach der Anmeldung aus einer Gastsitzung | hängt das als Gast Angelegte in das Konto um und führt weiter (D-261008-gast-uebertrag) |
| Nicht übernehmen | /login, nach der Anmeldung aus einer Gastsitzung | lässt das Gastkonto verfallen und führt weiter |
| Über Google anmelden, Mit Apple anmelden | /login, nur für eingerichtete Anbieter | führt zum Anbieter und über /login/extern zurück; danach wie Anmelden |
| Mit Google registrieren, Mit Apple registrieren | /register, nur für eingerichtete Anbieter | derselbe Weg; ein neues Konto entsteht, wenn es zur Adresse keins gibt |
| Anmelden und verknüpfen | /login/extern, Adresse hat schon ein Konto | meldet mit dem Passwort an und hängt Google oder Apple an dieses Konto; Enter tut dasselbe |
| Doch ein neues Konto anlegen | /login/extern, Adresse hat schon ein Konto | legt ein eigenes Konto an; das bestehende bleibt unberührt |
| Zurück zur Anmeldung, Zurück zu den Einstellungen | /login/extern, nach einem Fehler | führt auf /login oder, bei einer Verknüpfung, auf /settings?a=konto&g=anmeldung |
| Passwort anzeigen | /login, /register | das Auge im Feld |
| Angemeldet bleiben | /login | Kästchen; mit Haken legt die Anmeldung einen Geräteschlüssel an |
| Passwort vergessen? | /login | führt auf /forgotpw |
| Registrieren | /login | hinter „Noch kein Konto?“; führt auf /register |
| Ohne Konto ausprobieren | /login, nur mit gast.erlaubt | legt ein Gastkonto an, meldet es dauerhaft an und führt wie Anmelden weiter (D-261008-gastkonto). Seit dem Neubau (10.10.2026) steht das Logo im Fluss über dem Blatt und scrollt mit; die Messung --logo-unterkante, die es vorher vom Formular freihielt, liegt im Archiv |
| Konto anlegen | /register | legt das Konto an, meldet gleich an und führt wie Anmelden weiter; nur wenn die Anmeldung ausbleibt (beta), „Konto angelegt: …“ |
| Passwortanforderungen anzeigen | /register | klappt die Liste auf; Escape schließt |
| Geburtsdatum wählen | /register | eigener Datumswähler, „Ein Monat vor / zurück" |
| Schon ein Konto? | /register | führt auf /login |
| Sprache | /login, /register | Deutsch / English |
| Mail anfordern | /forgotpw | fordert eine Rücksetzung an; die Antwort ist immer dieselbe |
| Passwort setzen | /resetpw | löst den Schlüssel aus der Mail ein |
| Zur Anmeldung | /resetpw, /mailbestaetigen | führt auf /login |
| (kein Knopf) | /mailbestaetigen | bestätigt die Adresse beim Laden |
| Zulassen | /oauth/authorize | gibt einen Code aus und führt zurück zum Verbinder; der tauscht ihn gegen einen Zugriffsschlüssel „OAuth: …“, der unter Einstellungen → Zugangsdaten → Nach innen steht |
| Ablehnen | /oauth/authorize | führt zurück zum Verbinder mit error=access_denied |
| Fehler melden | alle fünf | auf /login und /register der Verweis „Etwas geht nicht?“ im Fuß, auf den übrigen der schwebende Knopf „Rückmeldung“; beide öffnen dasselbe Formular |
Woher die Daten kommen
/oauth/authorize prüft die Anfrage über /api/oauth/request und gibt sie über /api/oauth/approve frei, beide verschlüsselt; der Code lebt eine Minute im Speicher der API (oauth.ts).
Die Schirme zeigen keine gespeicherten Inhalte, sie schicken Eingaben ab. Fast alles läuft als verschlüsselte Route über die Sitzung, die schon vor der Anmeldung besteht (SESSION_START, KEY_EXCHANGE; siehe „Die Sitzung“ unten) und noch kein Konto trägt. Ohne Sitzung und ohne Verschlüsselung gehen nur AUTH_GAST_ERLAUBT, AUTH_OIDC_PROVIDERS, POST_ABBESTELLEN und die beiden Rückwege der Anbieter, die nur weiterleiten.
| Schirm | Endpunkt | Handler im Backend | Tabellen |
|---|---|---|---|
/login, Passwort | AUTH_LOGIN | login in plugins/src/auth/server/konten.ts | auth.users; die Sitzung in auth.sessions |
/login, Code | AUTH_ZWEITER_FAKTOR | plugins/src/auth/server/zweiterFaktorRouten.ts | auth.zweiter_faktor |
/login, gemerkter Schlüssel | AUTH_LOGIN_TOKEN | loginMitSchluessel in konten.ts, Schlüssel in zugangsschluessel.ts | auth.access_tokens |
/login und /, ohne Konto ausprobieren | AUTH_GAST_ERLAUBT, AUTH_GAST | gastschalter.ts und legeGastAn in konten.ts, der Probebereich in legeProbebereichAn (packages/backend/src/database/access/probebereich.ts) | auth.users und die Ausstattung wie bei der Registrierung; mit gast.probe dazu personal.documents, personal.blocks, study.flashcards, study.flashcard_states, game.tasks |
/register | AUTH_REGISTER, mit anmelden: true | registerUser in konten.ts, danach provisionNewUser in packages/backend/src/database/access/provisioning.ts; mit anmelden meldet meldeNeuesKontoAn die Sitzung über schliesseAnmeldungAb an wie login (neue Kennung, Tor vor beta, zweiter Faktor) | auth.users, auth.feature_grants, auth.user_feature_settings, auth.feature_seed_offers, personal.settings, personal.customizations; der Bestätigungsschlüssel in auth.email_bestaetigungen. Aus einer Gastsitzung stattdessen wandleGastUm in konten.ts: dieselbe Zeile in auth.users, die Geräteschlüssel des Gastes in auth.access_tokens widerrufen, ein neuer angelegt |
/login, /register, welche Anbieter | AUTH_OIDC_PROVIDERS (offen, ohne Sitzung) | plugins/src/auth/server/oidcRouten.ts, eingerichtet über die Umgebung (oidcProviders.ts) | keine |
/login, /register, Google oder Apple | AUTH_OIDC_START | startExternal in plugins/src/auth/server/externalLogin.ts; der Ablauf liegt zehn Minuten im Speicher der API (oidcFlows.ts) | keine |
| (Rückweg vom Anbieter) | AUTH_OIDC_GOOGLE_CALLBACK, AUTH_OIDC_APPLE_CALLBACK | oidcCallback.ts; leitet nur auf /login/extern mit dem Fragment weiter | keine |
/login/extern | AUTH_OIDC_FINISH | finishExternal in externalLogin.ts: löst den Code beim Anbieter ein, prüft das ID-Token (oidcRedeem.ts, oidcToken.ts) und meldet an, legt an oder lässt verknüpfen | auth.external_identities, auth.users, auth.sessions; ein neues Konto wie bei /register |
/login/extern, Konto verknüpfen | AUTH_OIDC_LINK | confirmPendingLink in externalLogin.ts, das Passwort über login in konten.ts | auth.external_identities, auth.users, auth.sessions |
/login/extern, Code | AUTH_ZWEITER_FAKTOR | wie auf /login; hängt danach eine wartende Verknüpfung an (linkAfterSecondFactor) | auth.zweiter_faktor, auth.external_identities |
/forgotpw | AUTH_PASSWORT_VERGESSEN | fordereRuecksetzungAn in plugins/src/auth/server/passwortRuecksetzung.ts | auth.users (lesen), auth.password_resets |
/resetpw | AUTH_PASSWORT_SETZEN | loeseRuecksetzungEin in passwortRuecksetzung.ts | auth.password_resets, auth.users; beendet Sitzungen (auth.sessions) und Schlüssel (auth.access_tokens) |
/mailbestaetigen | AUTH_MAIL_BESTAETIGEN | bestaetigeMail in plugins/src/auth/server/mailBestaetigung.ts | auth.email_bestaetigungen, auth.users |
/post/abbestellen | POST_ABBESTELLEN | bestelleAb in packages/backend/src/database/controllers/postController.ts, Route in packages/backend/src/routes/api/post.ts | personal.post_wahl |
Ob der Gastzugang angeboten wird, entscheidet der Instanzwert gast.erlaubt, den gaesteErlaubt in plugins/src/auth/server/gastwerte.ts liest. Die beiden Mails — Rücksetzlink und Bestätigung — gehen über verschicke in packages/backend/src/utils/mailVersand.ts hinaus; die Schlüssel in ihren Links liegen nur als Abdruck in den Tabellen auth.password_resets und auth.email_bestaetigungen (plugins/src/auth/server/merkmale.ts). Ein GET auf /api/post/abbestellen leitet nur auf /post/abbestellen weiter (303) und bestellt nichts ab; geschrieben wird allein vom POST.
Die Gastbremse (seit dem 10.10.2026, D-261008-gaeste-bremse). Je Anschluss entstehen höchstens gast.jeAdresseUndTag Gastkonten am Tag (Vorgabe 3), aus einem Hochschul- oder Forschungsnetz gast.jeHochschuladresseUndTag (Vorgabe 50). Gezählt wird nicht die Adresse, sondern ihr HMAC mit einem Schlüssel, der nur im Arbeitsspeicher liegt und täglich wechselt (Adresseimer in shared/src/utils/adresseimer.ts, adresskennung.ts im Backend). Aus welchem Netz eine Adresse kommt, sagt die Tabelle admin.gastnetze (Wanderung 276), die der Zeitgeber denotato-gastnetze täglich aus freien Listen füllt (runGastnetze.ts): Rechenzentren und VPN-Ausgänge von X4BNet, die Präfixe der Forschungsnetze aus RIPEstat. Keine Adresse eines Gastes geht dabei hinaus. Aus einem Rechenzentrum gibt es kein Gastkonto (guest_vpn_error, 403), und ein bestehender Gast darf von dort nicht schreiben (gastVpnSperre in backend/src/utils/gastsperre.ts). Fehlt die Liste der Rechenzentren, legt die Anmeldung gar kein Gastkonto an. Die KI-Aufrufe aller Gäste eines Anschlusses teilen sich das Tageskontingent eines Gratis-Kontos mal gast.kiJeAdresse (Vorgabe 1), aus Hochschulnetzen mal gast.kiJeHochschuladresse (Vorgabe 10) (gastkontingent.ts im Begleiter).
Die Sitzung
GET /api/session/start → POST /api/session/exchange → verschlüsselte Anfragen. Diffie-Hellman (Modp14, 2048 Bit) plus AES-256-GCM.
Die Sitzungen stehen seit dem 21.09.2026 in auth.sessions (B01, Wanderung 58) — der Schlüssel mit CREDENTIAL_KEY verschlüsselt (C02, Weg 1), so wie ein hinterlegtes Zugangsmerkmal. Die Abbildung im Arbeitsspeicher bleibt der Zwischenspeicher, wird aber vor dem ersten Zuhören aus der Tabelle gefüllt. Hier stand bis zum 22.09.2026 noch „liegen im Arbeitsspeicher … gebaut ist es nicht"; Grundsatz 70 ist eingelöst.
Was das ändert und was nicht. Ein Neustart der API meldet niemanden mehr ab — das war BE01, Bens erster Punkt, und am 22.09.2026 am laufenden Dienst nachgemessen: Sitzung angelegt, Prozess neu gestartet, dieselbe Kennung mit demselben abgeleiteten Schlüssel gerufen, und die Antwort kam weiter entschlüsselt zurück. Die Stunde bleibt: Nach sechzig Minuten ohne Anfrage läuft eine Sitzung weiterhin ab, und dann entscheidet der Wiedererkennungsschlüssel („Angemeldet bleiben"), ob jemand es merkt.
Wo eine Sitzung seit dem 23.09.2026 endet (D29). Die ASVS-Selbstprüfung (D24) fand fünf Stellen, an denen eine Sitzung weiter galt, obwohl sie hätte enden müssen. Gebaut ist, was ohne Christians Wahl ging; verify:sitzungen hält jeden Punkt fest.
- Die Kennung trägt 128 Zufallsbits statt 122 (
neueKennunginplugins/src/session/server/sitzungen.ts). Die Form bleibt die einer UUID, damit der Typ und das Antwortschema von/encrypted/getUser_idstehen bleiben. Am Eingang prüft die Form niemand:parseRequestschlägt die Kennung nur nach, und eine unbekannte ist „nicht gefunden". Das gilt seit dem 23.09.2026 auch für Kennungen wieconstructor, dieObject.prototypekennt — die Dateiwege antworteten darauf bis dahin mit 500 statt 401. - Eine Anmeldung nimmt eine laufende Impersonation mit hinaus — auf allen drei Wegen: Passwort, Schlüssel und zweiter Faktor laufen über
meldeSitzungAn. Vorher arbeitete, wer sich auf einer Sitzung anmeldete, auf der zuvor impersoniert wurde, als das Ziel weiter. - Der Ablauf gilt auch auf den Dateiwegen — beim Hochladen, beim Export und bei
/files/:id, die nur die Kennung tragen. Dort wurde eine abgelaufene Sitzung bis zum nächsten Kehrbesen weiter angenommen, und ein langes Hochladen hielt die Sitzung nicht am Leben. Beides gilt nicht mehr. - Ein gesperrtes Konto ist ab seiner nächsten Anfrage draußen, mit
ACCOUNT_LOCKEDund dem Grund, und alle seine Sitzungen enden — im Speicher und inauth.sessions. Vorher griff die Sperre nur beim Anmelden (Punkt 6). Das gilt auch für die vier Griffe des zweiten Faktors und für das Tor zwischen Passwort und Code: Wer in diesen fünf Minuten gesperrt wird, kommt mit dem Code nicht mehr herein. Auch die Schlüsselverwaltung (plugins/src/auth/server/schluesselRouten.ts) fragt seitdemgetSessionOwnerstatt das Konto selbst aus der Sitzung zu lesen: Ein gesperrtes Konto kann dort keine Schlüssel mehr sehen, anlegen, drehen oder widerrufen.verify:sitzungenhält fest, dass kein Serverteil das Konto einer Sitzung an diesem Weg vorbei liest. - Abmelden geht auch im Nur-Lesen-Modus einer Impersonation, und die Antwort darauf ist kein 500 mehr. Bis zum 23.09.2026 löschte die Route den Sitzungsschlüssel, bevor die Antwort damit verschlüsselt war.
- Der MCP-Server meldet sich bei Denotato ab, wenn seine eigene Sitzung endet — wenn der Client sie schließt, wenn ihr Transport zugeht und wenn der Kehrbesen sie wegräumt. Scheitert der Aufbau, bleibt ebenfalls keine Denotato-Sitzung zurück. Vorher stand sie bis zu einer Stunde in der Geräteliste.
- Beim Abmelden und beim Ablauf räumt die Oberfläche die Spuren des Kontos aus dem Browser (00-rahmen.md, Zeile „Abmelden").
Was davon noch nicht gilt: Die Kennung wird beim Anmelden nicht erneuert, der „Angemeldet bleiben"-Schlüssel überlebt das Abmelden, und ein widerrufener Schlüssel beendet die Sitzungen nicht, die er geöffnet hat. Alle drei warten auf eine Entscheidung (D29, „Was zu bedenken ist"). — erledigt (N156, N161): Jede Anmeldung gibt der Sitzung eine neue Kennung (meldeSitzungAn in plugins/src/session/server/sitzungen.ts, beim zweiten Faktor erst nach dem Code). Die Sitzung führt den Schlüssel mit, aus dem sie entstanden ist (accessTokenId); Abmelden widerruft ihn, wenn er ein Geräteschlüssel ist, ein Zugangsschlüssel für MCP bleibt stehen (widerrufeGeraeteschluessel). Und sessionIsLoggedIn in plugins/src/session/server/helfer.ts fragt je Anfrage, ob der Schlüssel einer Sitzung noch gilt; ein widerrufener beendet sie mit „Sitzung abgelaufen", auch nach einem Widerruf von der Kommandozeile. Und der laufende Dienst trägt das alles erst nach einem Neustart der API.
Was fehlt
- Wer sich bei der Adresse vertippt und sein Passwort vergisst, ist draußen. Die Bestätigungsmail erreicht ihn nicht, und einen Rücksetzlink bekommt eine unbestätigte Adresse nicht. Der Weg zurück führt über den Betreiber — für den ersten Fremden ist das genau die Sackgasse, gegen die D01 gebaut wurde. Was fehlt: eine zweite Adresse, oder ein Weg, die Adresse vor dem ersten Anmelden zu berichtigen.
Die Registrierung sagt nicht, dass eine Mail unterwegs ist.Nach dem Absenden kommt „Konto angelegt: …“ als Toast; angemeldet wird nicht, das Formular bleibt gefüllt stehen.— gebaut am 10.10.2026 (D-261008-registrierung-gleich, Christians Wahl vom 08.10.2026: „Gleich anmelden“). Das Formular schicktanmelden: true; der Server meldet die Sitzung über denselben Abschluss an wielogin(schliesseAnmeldungAbinkonten.ts: neue Kennung, kein Geräteschlüssel), nach denselben Riegeln — Tor vor beta, zweiter Faktor. Eine bestätigte Adresse ist keine Bedingung der Anmeldung (D02 hält nur den Rücksetzlink zurück). In der Anwendung sagtMailhinweisüber jedem Schirm, dass die Bestätigungsmail unterwegs ist, mit einem Verweis auf die Adresse in den Einstellungen. Der Toast entfällt — außer im Fall von Punkt 30. Nachweise:verify:registration(„Gleich angemeldet“),RegisterMenu.test.tsx,loginHandler.test.ts,Mailhinweis.test.tsx.Der Rücksetzweg beendet keine fremden Anmeldungen.— gebaut am 22.09.2026. Der Satz stand hier zwei Tage mit der Begründung „es geht erst mit B01", obwohl B01 seit dem 21.09.2026 stand: keine fehlende Grundlage mehr, sondern eine nicht nachgezogene Zeile. Sie wog schwerer als vorher — früher endete die fremde Sitzung spätestens beim nächsten Neustart, seit B01 überlebt sie ihn. Jetzt endet mit dem Einlösen jede Anmeldung und jeder „Angemeldet bleiben"-Schlüssel (siehe/resetpwoben). Was offen bleibt:Punkt 23 undPunkt 24 unten —der Passwortwechselunter/settingswirft die Schlüssel nicht mit hinaus, undein widerrufener Schlüssel sagt der Geräteliste nicht, warum er es ist. Punkt 23 ist erledigt, siehe dort.Keine E-Mail-Bestätigung— gebaut am 18.09.2026 (D02). Nach der Registrierung geht eine Mail mit einem Link hinaus, der 48 Stunden gilt;/mailbestaetigenlöst ihn ein, ohne Anmeldung. Solange die Adresse unbestätigt ist, schickt/forgotpwkeinen Rücksetzlink dorthin. Was fehlt: Punkt 1 und 2 oben.Keine Ratenbegrenzung.— gebaut am 17.09.2026, erweitert am 18.09.2026, zuletzt D31 am 26.09.2026. Getrennte Eimer inauth/server/bremse.ts: je Konto, je Absenderadresse, über alle Konten, für Registrierungen, Zugriffsschlüssel, Zweitfaktorcodes und die fünf erneuten Passwortnachfragen hinter einer Sitzung. Gezählt werden Versuche, nicht Fehlschläge — sonst lässt sie alle gleichzeitigen Anfragen durch. Dazu seit D03 eine Quote je Mailadresse: zwei neue Konten am Tag, mit derselben Antwort wie die allgemeine Bremse (ein eigener Satz wäre eine Auskunft darüber, welche Adressen hier ein Konto haben). Die Eimer liegen im Speicher; ein Neustart leert sie. Seit dem 26.09.2026 (D31, N313) sperrt ein Fremder den Besitzer nicht mehr aus: Der enge Eimer zählt je Konto und Adresse, darüber liegt ein Dach je Konto (zwanzig), das nur Unbekannte sperrt. Wer sich mit dem richtigen Passwort anmeldet, bekommt ein Gerätemerkmal, ein signiertesHttpOnly-Cookie (auth/server/geraetemerkmal.ts). Ein Gerät, das es für das angefragte Konto vorzeigt, zählt nur in seinem eigenen Eimer — fünf Versuche je Viertelstunde, auch wenn Adresse, Dach oder der Eimer über alle Konten voll sind. OhneGERAETEMERKMAL_SCHLUESSELauf dem Server gibt es keine Merkmale; dann gilt jedes Gerät als unbekannt. Der Schirm selbst ändert sich dadurch nicht: Die Meldung bei vollem Eimer bleibt „Zu viele Versuche“.- Ein gesperrtes Konto (D03, 18.09.2026) kommt mit dem richtigen Passwort nicht herein — weder über
/loginnoch über einen Zugriffsschlüssel — und erfährt den Grund. Die Prüfung steht hinter der Passwortprüfung: Wer einen Namen durchprobiert, soll nicht erfahren, dass es das Konto gibt. Gesperrt und freigegeben wird mitnpm run sperren -- <name> "<grund>". Seit dem 23.09.2026 (D29) wirkt die Sperre auch auf ein Konto, das schon angemeldet ist: Seine nächste Anfrage bekommtACCOUNT_LOCKED, und alle seine Sitzungen enden. Vorher blieb es drin, solange es aktiv war — also genau in dem Fall, für den es die Sperre gibt. Ein Konto, das gerade nichts tut, merkt die Sperre allerdings erst bei seiner nächsten Anfrage;npm run sperrenist ein eigener Prozess und kann die Sitzungen im Speicher des Servers nicht beenden. Was fehlt ist ein Schirm dafür, und der gehört in den Meta-Server, nicht in/settings— eine Sperre gehört dem Betreiber. Kein „angemeldet bleiben"— gebaut. Der Schalter legt den Wiedererkennungsschlüssel in denlocalStoragestatt in densessionStorage(handlers/identityStorage.ts), und der Server führt dazu einen Zugriffsschlüssel inauth.access_tokens, dreißig Tage gültig — derselbe Weg, über den sich der MCP-Server anmeldet.Was fehlt:eine Übersicht der angemeldeten Geräte— die gibt es seit dem 21.09.2026 (B26):/session/listeund die Geräteliste unter/settings, mit Merkmal („Firefox auf Linux"), Beginn und letzter Nutzung. Was fehlt: ein Knopf, der ein einzelnes Gerät abmeldet — heute geht nur „alle anderen" (/session/abmelden-andere).Und auch das hält ein Gerät mit „Angemeldet bleiben" nicht draußen: Die Route widerruft keinen Schlüssel, und die Oberfläche dort meldet sich beim nächsten 401 mit dem gespeicherten Schlüssel ohne Zutun wieder an. Aus dem Code gelesen, nicht im Browser gesehen; der Weg dorthin ist dieselbe Wahl wie in Punkt 23 und steht beiD29.— erledigt (N156):/session/abmelden-anderewiderruft die Geräteschlüssel aller anderen Geräte (widerrufeAndereGeraeteschluessel), bis auf den der eigenen Sitzung, und beendet danach deren Sitzungen. Zugangsschlüssel für MCP und Werkzeuge bleiben stehen.Keine Zwei-Faktor-Anmeldung— gebaut am 21.09.2026 (D07). TOTP nach RFC 6238, eingerichtet unter/settings, zehn Wiederherstellungscodes, das Geheimnis verschlüsselt inauth.zweiter_faktor. Was fehlt: ein QR-Bild — heute steht das Geheimnis zum Abtippen da und daneben einotpauth:-Verweis, den das Telefon öffnen kann; eine Bibliothek dafür ist Grundsatz 17 und braucht eine Freigabe. Ebenso fehlt ein Weg, den Satz Codes zu erneuern, ohne den Riegel abzuschalten, und eine Erinnerung, wenn nur noch wenige offen sind.Der Neubau fehlt(Guide §21 Zeile 6).— gebaut am 10.10.2026 (ES-261009-alte-anmeldeschirme): Anmelden und Registrieren stehen untermenus/auth/, der Alpha-Stand inarchiv/2026-10-10-legacy-anmeldung/mitWARUM.md;INLINE_BESTANDist leer, die Ausnahme inFARBE_AUSNAHMENund die inverify:sprachesind weg. Der alte Wortlaut: („und damit aucharchiv/"war falsch: das Archiv gibt es seit Längerem und führt bereits mehrere abgelöste Stellen. Was fehlt, ist allein der Neubau — die Legacy-Schirme stehen noch untermenus/auth/legacy/.) B09 ist damit abgeschlossen und der Umzug an S-anmeldung übergeben: dort steht, was dazugehört — eine Ablage mitWARUM.md, die Obergrenze inverify:archauf null, und die beiden AusnahmenINLINE_BESTANDundFARBE_AUSNAHMENersatzlos weg.- Keine Willkommensführung. N61·16 entscheidet sie: ein Walkthrough, der Interessenbaum vollständig aber unbewertet, keine Beispieldaten.
Die Registrierung hat nur eine halbe Bremse— die andere Hälfte ist am 18.09.2026 dazugekommen. Von den fünf Dingen, die hier am 17.09. noch fehlten, gibt es jetzt vier: die Bestätigung der Adresse (D02), die Quote je Mailadresse und den Sperrgrund (D03), und den Weg, ein Konto wieder zu entfernen (D04). Offen bleibt die Quote je Absenderadresse — das ist C04 und liegt bei Christian. Der Satz „wer die Adresse kennt, kann beliebig viele Konten anlegen" stimmt nicht mehr: zwei am Tag je Adresse, fünfzig je Stunde über alle.- Kein Hinweis auf den Verbindungszustand. Fällt der Server aus, scheitert die Anmeldung ohne Erklärung.
- Das Geburtsdatum wird erfragt und in Denotato nirgends benutzt. Gelesen am 21.09.2026:
auth.users.birth_datewird beim Anlegen geschrieben, und weder Backend noch Frontend noch ein Plugin liest es wieder — die Geburtstage inshared/handlers/life/geburtstag.tsgehören zupeopleund nicht zum Konto. Einer liest es doch: Der Meta-Server führt die Spalte in seiner Kontenliste (packages/meta/src/services/accounts.ts, Zeile 120 und 160) und zeigt sie an (packages/meta/src/web/app.js:1458). Wer das Feld abschafft, muss also auch dorthin. Die Spalte istNOT NULL, das Feld bleibt deshalb Pflicht. Was fehlt: die Entscheidung, ob es einen Zweck bekommt (Altersgrenze? Glückwunsch?) oder mit einer Wanderung verschwindet. Gemessen wurde zugleich, dass die API ein Datum in der Zukunft annimmt — der Schirm prüft das (birthDateObj > today), der Server nicht; wer nicht über den Schirm kommt, trägt ein, was er will. Keine Anmeldung über ein fremdes Konto(SSO) — bewusst, aber nirgends gesagt.— gebaut am 13.10.2026 für Google und Apple (D-261008-google-apple). Siehe/login/externoben und Einstellungen → Konto → Anmeldung.Kein Gerätename.Wer wo angemeldet ist, sieht niemand.— gebaut am 21.09.2026 (B26). Die Sitzung merkt sich beim Handschlag ein gekürztes Merkmal („Firefox auf Linux"), nicht den vollen User-Agent: Der sähe genauer aus, als er ist, und wäre ein Fingerabdruck, den Denotato über Monate aufhöbe. Ohne User-Agent wird nichts geraten, sondern unbekannt eingetragen. Was fehlt: ein selbst vergebener Name je Gerät — „Laptop", „Telefon" —, damit die Liste auf einen Blick lesbar ist, wenn zweimal dasselbe Merkmal darin steht.- Die Registrierung verrät weiterhin, welche Namen vergeben sind. Sie antwortet „Nutzername * existiert bereits", und das ist bei N91 ausdrücklich so entschieden worden: Wer sich anmeldet, soll den Bestand nicht erfahren; wer sich registriert und auf einen vergebenen Namen stößt, soll den Grund erfahren. Die Lücke bleibt damit bestehen — sie ist nur keine unbeabsichtigte mehr.
- Sechs Konten tragen noch den alten Abdruck. Gemessen am 21.09.2026 in
auth.users:password_verfahren = 'sha256'bei sechs Zeilen. Wie viele Zeilen es insgesamt sind, ist keine Angabe wert — am Nachmittag zehn, am Abend zwölf, weil die Nachweisläufe fortwährend Prüfkonten anlegen und wegräumen; die Sechs blieb. Der Umstieg passiert nur beim nächsten erfolgreichen Anmelden (D21) — ein Konto, das sich nie wieder anmeldet, behält den Abdruck ohne Aufwandsfaktor für immer. Das Zeitfenster darüber ist zu; die schwache Ablage selbst ist es nicht. Was fehlt: eine Entscheidung, was mit Konten geschieht, die lange nicht kommen — Christians Sache, denn der einzige saubere Weg ist ein erzwungenes Zurücksetzen, und das sieht aus wie ein Angriff. - Kein Nachweis hält die Gleichdauer der Anmeldung fest. Die Angleichung von N91 war an einer von zwei Stellen gebaut und ist zwei Tage lang niemandem aufgefallen, weil nichts sie misst. Das Rezept dagegen gibt es seit dem 22.09.2026 — nur an der anderen Tür:
verify:ruecksetzungmisst die drei Bahnen von/forgotpwje dreimal und prüft Verhältnisse statt Millisekunden („kein Fall unter der Hälfte des langsamsten"), weil eine Dauer auf einer geteilten Maschine verrauscht ist. Die Gegenprobe zeigt, dass er greift. Was fehlt: dasselbe für/loginmit seinen drei Fällen (Name unbekannt, alter Abdruck, heutiger Abdruck). Der Aufbau ist teurer als bei/forgotpw— er braucht ein Konto mitpassword_verfahren = 'sha256', also genau das, was D21 abschafft. /forgotpwkönnte dieselbe Uhr haben.— gemessen und behoben am 22.09.2026. Der Verdacht vom 21.09. stimmte, und der Faktor war größer als gedacht: fünfzehn Läufe je Fall ergaben 0,6 ms (Name unbekannt), 0,6 ms (Adresse unbestätigt) und 8,2 ms (Adresse bestätigt) — jede Antwort über drei Millisekunden war eine Auskunft. Behoben mit zwei Griffen (Versand nicht mehr abgewartet, Untergrenze 250 ms), nachgemessen 250,4 / 250,4 / 250,5 ms, festgehalten vonverify:ruecksetzung. Der Abschnitt/forgotpwoben steht ausführlich dazu. Was offen bleibt: Punkt 25 — die Untergrenze deckt den Mehrkonten-Fall (D20) nicht sicher ab.- Der Anmeldeschirm zeigt nicht, dass er wartet. Seit der Angleichung oben kostet jede gescheiterte Anmeldung ihre scrypt-Ableitung — gemessen rund 300 ms auf der geteilten Maschine, auf einem kleinen Server mehr. Der Knopf bleibt in dieser Zeit unverändert und lädt zum zweiten Klick ein, der einen zweiten Versuch aus der Bremse nimmt. Was fehlt: ein gesperrter Knopf mit sichtbarem Zustand, solange die Antwort unterwegs ist — und das gehört in den Neubau (Punkt 9), nicht in die Legacy-Schirme.
- „Angemeldet bleiben" ist nach B01 der einzige Grund, es noch anzuhaken — und der Haken steht weiter auf aus. Der Neustart wirft seit dem 21.09.2026 niemanden mehr heraus, die Stunde aber schon: Wer ohne Haken sechzig Minuten nichts tut, findet danach den Anmeldeschirm, weil kein Wiedererkennungsschlüssel da ist. Das ist kein Fehler, sondern die Voreinstellung — BE01 hat drei Wege dagegen gestellt, und Christian hat am 20.09.2026 Weg 3 gewählt: auf B01 warten statt den Haken vorzubelegen. Was offen ist: ob die Stunde nach B01 noch die richtige Zahl ist. Sie war bemessen für einen Speicher, den ohnehin jeder Neustart leerte; jetzt ist sie die einzige Grenze, die es noch gibt. Das ist Christians Entscheidung, keine Nachlässigkeit.
- Die alten Grantzeilen stehen noch da — sie öffnen nur nichts mehr. Gemessen am 22.09.2026 in
auth.feature_grantsdieser Maschine: acht Konten aus früheren Prüfläufen tragen elf oder dreizehn Bereiche bei null Fähigkeiten; 36 Zeilen nennen einen der fünf Bereiche, die eine Fähigkeit verlangen. Seit F22 lesenloadEffectiveAccessund der Rechtebaum (vergabenFuer) die Fähigkeit mit — die Zeilen sind damit wirkungslos. Der zweite Leser kam erst am Abend des 22.09.2026 dazu: Bis dahin sagte der Baumgold.adminerlaubt, wo der Zugriff den Bereich gar nicht mehr kannte, und dieser Satz hier war nur zur Hälfte wahr. Gelöscht wurden die Zeilen nicht, weil eine löschende Wanderung über eine Rechtetabelle auch einen echten Eigner aus seinen eigenen Bereichen aussperren kann; entschieden ist das am 22.09.2026 so, sie bleiben als halbe Aussage stehen. Was fehlt: ein Weg, einem bestehenden Konto Bereich und Fähigkeit zusammen nachzureichen — der Meta-Server schreibt bis heute nur die Grantzeile (F08). Der Passwortwechselunter/settingswirft die Schlüssel nicht mit hinaus.Gemessen am 22.09.2026: Konto mit zwei Anmeldungen und einem „Angemeldet bleiben"-Schlüssel, Passwort über/settingsgewechselt — die fremde Anmeldung endete, der Schlüssel blieb offen und löste danach weiter ein. Der Satz indocs/seiten/settings.md(„Wer das alte Passwort kannte, ist sofort draußen") ist damit die halbe Wahrheit; dreißig Tage später steht derselbe Fremde wieder drin. Der Rücksetzweg hat es seit dem 22.09.2026 richtig, der Wechselweg nicht.Warum es dort nicht einfach nachgezogen wurde:Beim Zurücksetzen gibt es kein eigenes Gerät zu verschonen, beim Wechseln schon — und die Sitzung weiß nicht, aus welchem Schlüssel sie entstanden ist (sessionTypeführt keine Schlüsselkennung). Es sind also zwei Wege: alle widerrufen, auch den eigenen (ein Satz, und das Gerät hakt „Angemeldet bleiben" neu an), oder die Kennung in der Sitzung mitführen (sauberer, greift aber insession/). Das ist eine Entscheidung und keine Zeile.Seit dem 23.09.2026 steht sie in D29(„Was zu bedenken ist"), zusammen mit „andere abmelden" (Punkt 7), dem eigenen Abmelden und dem Schlüsselwiderruf — alle vier hängen an derselben Frage, woher der Server weiß, welcher Schlüssel eine Sitzung geöffnet hat.— erledigt (N156):passwortAendern.tswiderruft vor dem Hinauswerfen die Geräteschlüssel der anderen Geräte (widerrufeFremdeGeraete), der eigene bleibt stehen. Möglich ist das, weil die Sitzung ihren Schlüssel seitdem kennt (accessTokenId, gesetzt vonmeldeSitzungAn). Zugangsschlüssel für MCP und Werkzeuge widerruft der Wechsel nicht; das hat N156 so entschieden.- Ein widerrufener Schlüssel sagt nicht, warum er es ist. Seit dem 22.09.2026 kann eine Rücksetzung auf einen Schlag alle Schlüssel eines Kontos widerrufen. In der Liste unter
/settingsstehen sie danach als „widerrufen" da — ohne Grund, ohne Datum im Text, ohne Bezug zur Rücksetzung. Wer sie ansieht, weiß nicht, ob er selbst das war, ein anderes Gerät oder der Vorgang von vorhin.auth.access_tokenshat keine Spalte dafür; das ist eine additive Wanderung und eine kleine Anzeige. - Die Untergrenze von 250 ms ist eine gesetzte Zahl und wird nur auf dieser Maschine gemessen. Sie hält nur, solange die langsamste Bahn darunter bleibt: gemessen am 22.09.2026 höchstens 14,7 ms an Datenbankarbeit, und selbst der Mehrkonten-Fall aus D20 — fünf Konten unter einer Adresse, fünf Zeilen — kam auf 249,6 bis 250,9 ms. Auf dem Hetzner-Server ist nichts davon nachgemessen, und der Nachweis prüft Verhältnisse zwischen den drei Bahnen, nicht den Abstand zur Untergrenze. Was fehlt: eine Messung dort, und ein Nachweis, der rot wird, wenn die langsamste Bahn der Untergrenze zu nahe kommt — sonst merkt es niemand, wenn sie eines Tages darüber hinauswächst.
- Der zweite Faktor überlebt das Zurücksetzen, und das ist beides — richtig und eine Falle. Gelesen am 22.09.2026: Weder
passwortRuecksetzung.tsnochpasswortAendern.tsrührtauth.zweiter_faktoran. Richtig, weil sonst der Zugriff auf ein Postfach das ganze Konto öffnete und der zweite Faktor nichts mehr wäre. Eine Falle, weil ein Fremder, der ihn einrichtet, den Besitzer dauerhaft aussperrt: Der kommt über seine Adresse an ein neues Passwort und steht danach vor einem Code, den er nicht hat. Die zehn Wiederherstellungscodes helfen nur dem, der sie vorher hatte. Was fehlt: eine Entscheidung — ein Weg über den Betreiber (wie beim gesperrten Konto, Punkt 6), oder eine Frist, nach der ein frisch eingerichteter zweiter Faktor durch eine Rücksetzung fällt. Zwei der aufgerufenen Ionicons-Namen fehlen im eigenen Symbolsatz— erledigt mit dem Neubau am 10.10.2026 (ES-261009-alte-anmeldeschirme). Das Feld für den zweiten Faktor trugphone-portrait-outline, der Kalenderknopf beim Geburtsdatumcalendar-outline; beide Namen kannte die selbst gezeichnete Ionicons-Datei nicht, und sie zeichnete dafür nichts. Seit dem Neubau stehen alle Zeichen aus dem eigenen Satz (schluessel,person,steckbrief,brief,kalender,gesperrt); die Ionicons-Datei liegt im Archiv.- Der Hinweis auf ein häufiges Passwort steht nur auf dem Anmeldeschirm (D30).
Er führte nur bis zur Tür— seit dem 23.09.2026 (N143) öffnet „Passwort ändern"/settings?a=konto&g=passwortwechsel, und die Gruppe ist schon aufgeklappt. Offen bleibt: Wer „Später" wählt und angemeldet bleibt, sieht den Hinweis erst bei der nächsten Anmeldung mit Passwort wieder, weil nichts gespeichert wird; und nach dem Passwortwechsel sagt nichts, dass das neue Passwort nicht mehr auf der Liste steht. Was fehlt: nichts Eiliges — ein Merkmal zu speichern, widerspräche N139 (Ableitung 3); ein Satz nach dem Wechsel wäre möglich. - Die Anforderungsliste und die Stärkeanzeige kennen die Liste nicht. Beide laufen im Browser, die Liste nur auf dem Server.
Schmetterlingsteht auf der Liste, die Anzeige nennt es „medium", und abgewiesen wird es erst beim Absenden (gemessen am 23.09.2026). Umgekehrt stehtSommer2026!nicht auf der Liste und geht durch; die Anzeige nennt es „weak".Die Stufen sind außerdem englisch („very weak" bis „very strong"), gegen Grundsatz 1.— erledigt mit F67: Die Anzeige nennt sie „Sehr schwach“ bis „Sehr stark“ (PASSWORTSTAERKE_TEXTEinRegisterMenu.tsx). Was fehlt:deutsche Stufen undeine Anzeige, die ein abgewiesenes Passwort nicht mehr „Mittel" nennt. - In beta meldet die Registrierung nicht an, und der Mailhinweis merkt eine Bestätigung erst nach dem Neuladen (D-261008-registrierung-gleich, aus dem Code gelesen, nicht im Browser gesehen). Ein Konto, das eben entstanden ist, hat die Fähigkeit
betanicht;meldeNeuesKontoAninkonten.tsfragtbetatorwieloginund lässt die Sitzung dann ohne Konto. Das Formular sagt „Konto angelegt: …“, und die Anmeldung auf/loginendet mit der Absage von beta, bis jemandbetagewährt.Mailhinweisfragt den Adressstand einmal je Sitzungskennung; wer in einem anderen Reiter bestätigt, sieht die Zeile bis zum Neuladen oder zur nächsten Anmeldung weiter. Was fehlt: nichts Eiliges — für beta eine Zeile im Formular, dass der Zugang freigeschaltet werden muss; für den Hinweis ein Anstoß aus/mailbestaetigenan die anderen Reiter. - Die Gastbremse vergisst bei jedem Neustart. Die Zählung je Anschluss liegt nur im Arbeitsspeicher, damit keine Adresse und kein beständiger Abdruck davon gespeichert wird (D-261008-gaeste-bremse). Nach einem Neustart der API beginnt der Tag für jeden Anschluss neu, für Konten wie für das gemeinsame KI-Kontingent. Was fehlt: nichts, solange Neustarts selten sind; sonst ein Abdruck mit Tagesschlüssel in der Datenbank, der mit dem Schlüssel verfällt.
- Ein VPN über IPv6 erkennt die Bremse nicht. X4BNet liefert seine Listen nur für IPv4; wer über IPv6 aus einem Rechenzentrum kommt, gilt als gewöhnlicher Anschluss. Und die Schreibsperre für Gäste aus einem Rechenzentrum sitzt an den verschlüsselten Routen; die wenigen unverschlüsselten Schreibwege prüfen sie nicht. Was fehlt: eine freie IPv6-Liste der Rechenzentren und die Sperre an der gemeinsamen Stelle aller Schreibrouten.
- Die Quote je Adresse zählt einen umgewandelten Gast nur am ersten Tag. Sie zählt Konten mit der Adresse, deren
created_atjünger als ein Tag ist (D03). Ein Gast, der älter ist und sich registriert, behält seincreated_at(D-261008-gast-uebertrag) und zählt danach nicht mit; zwei solche Umwandlungen und zwei frische Konten am selben Tag ergeben vier Konten an einer Adresse statt zwei. Gemessen nicht, aus dem Code gelesen. Was fehlt: nichts Eiliges, die Bremse über den ganzen Dienst greift weiter; sonst ein Zeitpunkt der Registrierung inauth.users, nach dem die Quote zählt. Wer schon ein Konto hat und sich aus einer Gastsitzung anmeldet, bekommt die Gastdaten noch nicht angeboten— gebaut am 10.10.2026 (D-261008-gast-uebertrag, Sonderfall). Nach der Anmeldung fragt/login„Was du als Gast angelegt hast, in dieses Konto übernehmen?“ mit zwei gleichen Knöpfen; der Server merkt sich den Gast in der Sitzung, und das Annehmen hängt überauth.gast_uebertragen(Wanderung 277) jede Tabelle ausshared/src/config/gastuebertrag.tsin einer Transaktion um (siehe „Das Angebot an den Gast“ oben). Was offen bleibt: Wanderung 277 liegt erst in der Datenbank, wenn C-261010-wanderung-277-beta-prod erledigt ist; bis dahin endet das Annehmen mit einem internen Fehler, und das Angebot bleibt stehen.- Der Satz unter „Ohne Konto ausprobieren“ trägt noch seinen Schleier (ES-261009-alte-anmeldeschirme, 10.10.2026).
Satzincomponents/auth/Gasteinstieg.tsxlegt einen halbdurchsichtigen Grund unter sich, damit er auf dem alten Nachthimmel lesbar blieb. Auf dem Blatt Papier ist das nur noch ein schwacher Ton. Was fehlt: den Grund streichen, sobald jemand den Gasteinstieg angeschaltet ansieht; beim Neubau standgast.erlaubtaus. - Einen Schalter zurück zum alten Schirm gibt es nicht. Grundsatz 55 hält den Alpha-Stand „als Rückfall“ im Archiv; laufen kann er dort nicht mehr, weil Theme-Ausnahmen,
Nachtfarbenund die CSS-Wurzeln mit ihm gegangen sind. N489 sah/login?alt=1für etwa vier Wochen vor; am 11.10.2026 hat Christian auf die Frage, ob er wieder eingehängt wird, „Archiv bleibt“ gewählt. Was fehlt: nichts. - Datumsauswahl und Passwortregeln gehen ohne Bewegung. Beide kommen mit einer Feder (Schleier und Fenster, Regeln vom Knopf herab), beim Schließen verschwinden sie sofort, weil kein
AnimatePresenceüber ihnen steht. Das Verhalten ist das alte. Was fehlt: der Ausgang mit derselben Feder. - Der Platzhalter wiederholt die Beschriftung (Grundsatz 75): „Benutzername oder E-Mail“ steht über dem Feld und darin, ebenso „Passwort“, „Benutzername“ und „E-Mail-Adresse“. Beim Neubau belassen, weil Tests und Übersetzungen an den Platzhaltern hängen. Was fehlt: im Feld nur, was die Beschriftung nicht sagt (ein Beispiel oder nichts).
- Zwei gemeinsame Knöpfe messen am Daumen 40 statt 44 px: „Passwort zeigen“ im Eingabefeld (
components/ui/Input.tsx) und der schwebende Rückmeldeknopf auf/forgotpw. Beide gehören nicht diesem Schirm, sondern jedem, der sie trägt. Was fehlt: beide auflayout.zielflaeche. /forgotpw,/resetpwund die übrigen Infoseiten haben einen anderen Fuß als Anmelden und Registrieren. Diese tragen denRechtsfussausAnmeldeseite.tsxmit Rückmeldung, Sprache, Impressum und Datenschutz; die Infoseiten nur, was dieInfoseitezeichnet. Beim Neubau unverändert, weil sie schon aus dem Theme zeichneten. Behoben am 15.10.2026 (E03):/forgotpw,/resetpwund/mailbestaetigentragen denAnmeldefuss. Was fehlt: nichts./oauth/authorizeist vor dem ersten echten Durchlauf gebaut. Am 11.10.2026 mit Tests für Code, PKCE und Rückweg, aber ohne Bildschirmfoto: Hier liefen weder API noch Datenbank. Was fehlt:/ansehenvorher und nachher und ein Durchlauf mit claude.ai.- Das Apple-Zeichen ist nachgezeichnet, nicht Apples eigene Vorlage. Die Human Interface Guidelines verlangen das Logo aus den Apple Design Resources. Was fehlt: die Vorlage einsetzen, bevor Apple eingeschaltet wird.
- Die Farben des Google-Knopfs sind aus dem Gedächtnis. Am 13.10.2026 war die Seite mit Googles Gestaltungsregeln von hier nicht erreichbar; die Werte in
theme/providerBrands.tsfolgen dem Stand, den das Modell kannte. Googles Schrift Roboto liefert Denotato nicht mit; der Knopf trägt die Schrift des Themes. Was fehlt: ein Abgleich mit den aktuellen Regeln. /login/externhat keinen eigenen Titel. Die Seite steht nicht unter den öffentlichen Seiten inconfig/oeffentlicheSeiten.tsund heißt deshalb nur „Denotato“, ohne Eintrag für Suchmaschinen; für eine Seite, die niemand von außen aufruft, ist das Absicht.- Kein Durchlauf mit echten Anbietern. Gebaut und angesehen am 13.10.2026 mit erfundenen Zugangsdaten; die Knöpfe stehen, der Weg zu Google und Apple selbst ist ungeprüft.
Lesbarkeit auf dem Anmeldeverlauf (F67, 25.09.2026)
Fußzeile, Rechtsverweise, Sprachwahl und die Hinweise im Formular stehen deckend weiß. Die freie Fußzeile trägt eine dunkle Fläche, sodass der wechselnde rosa-violette Verlauf ihren Kontrast nicht mehr bestimmt. Die Passwortstärke verwendet übersetzte deutsche Schlüssel; ein zu kurzes Passwort heißt „Zu kurz“ beziehungsweise „Too short“.
Farben der Nacht, Fokus und Daumenziele (RL02, 27.09.2026)
Die Durchsicht nach Apples Richtlinien und dem Style-Guide (RL02) fand auf Anmeldung und Registrierung vier kritische Stellen; behoben sind sie so:
- Die Eingabefelder schrieben in jeder Szene in den Farben der Nacht (Geschichte: seit dem Neubau am 10.10.2026 folgen beide Schirme der gewählten Szene,
Nachtfarbenliegt im Archiv). Beide Schirme zeichnen einen festen, dunklen Himmel, die Bausteine darin nahmen aber die Neutralen der gewählten Szene: In der Tagszene stand dunkle Schrift auf dem Himmel, die Eingabe mit 1,06 bis 2,56 : 1, der Platzhalter bis 1,84 : 1. Der RahmenNachtfarben(components/screens/Nachtfarben.tsx) umLoginScreenundRegisterScreentauscht Neutrale, Farbschichten und Farbschema gegen die der Nacht; Bewegung und Dichte bleiben, wie sie gewählt sind (NachweisNachtfarben.test.tsx). - Die Rechtszeile verdeckt die Registrierung nicht mehr. Ab 700 px Höhe wird sie unten festgehalten; auf der fast doppelt so hohen Registrierung lag sie am Schreibtisch (1440 × 900) als dunkles Band über dem Ende der Karte und verdeckte „Schon ein Konto? Anmelden". Auf
/registersteht sie unter 1000 px Höhe im Fluss, unter der Karte. - Der Fokus ist sichtbar an den Links im Formular, an den Links der Rechtszeile und am Kästchen „Angemeldet bleiben" — ein weißer Ring mit 2 px, wie ihn die Knöpfe der Rechtszeile schon hatten.
- Am Daumen 40 px: „Passwort zeigen" wächst auf groben Zeigern von 22 auf 40 px und gibt den Platz mit negativen Rändern an das Feld zurück; die Zeile des Kästchens ist mindestens 40 px hoch. Seit dem 08.10.2026 gilt 44 (Marke
layout.zielflaeche, Guide §16.1); seit dem Neubau misst das „?" neben der Passwortstärke am Daumen 44, „Passwort zeigen" kommt aus dem gemeinsamen Eingabefeld und misst seit dem 15.10.2026 dort ebenfalls 44 (Punkt 39, E30).
Nachgearbeitet am 28.09.2026 (RL02):
- Das Geburtsdatum steht wie die Felder darüber: Kalenderzeichen links (das Zeichen
kalender;calendar-outlinegab es in den IonIcons nie), die Eingabe über die ganze Höhe des Feldes, in der Schrift der übrigen Felder mit 16 px. Die Auswahl öffnet das Feld selbst, beim Fokus oder Tippen. - Am Daumen die Zielfläche (44 px, seit E30) tragen auch das „?" neben der Passwortstärke und das Logo über der Anmeldung; „Passwort zeigen" misst wirklich 44 × 44 (die Höchstbreite aus
.login-menu buttonhielt es bei 22). - „Zu kurz" steht erst, wenn getippt wurde. Vorher sagte der Schirm es, bevor jemand das Feld berührt hatte.
- Fehler stehen am Feld. Eine abgelehnte Eingabe (Benutzername zu kurz, keine Adresse, Datum in der Zukunft …) zeigt ihren Satz rot unter dem Feld; das Feld ist rot umrandet,
aria-invalidund mit dem Satz verbunden, der Satz wird vorgelesen, und der Fokus springt ins Feld — nur nicht ins Datum, dessen Auswahl den Satz verdecken würde. Wer das Feld ändert, nimmt den Fehler weg. Der Feldname im Satz ist deutsch („Der Benutzername liegt unter der Mindestlänge 4"). Antworten des Servers kommen weiter als Meldung. - Die Telefontastatur passt: Benutzername und Adresse ohne Großschreibung, Autokorrektur und Rechtschreibprüfung; die Adresse als
type="email"mit @ auf der Tastatur. - Nichts läuft mehr rechts aus dem Bild. Die Grid-Spalte des Schirms wuchs am Telefon auf 419 px; sie ist jetzt
minmax(0, 1fr). - Ein Fokusring, nicht zwei: Im Feld lag der allgemeine Ring als zweiter Rahmen versetzt; den Fokus zeigt jetzt allein die Hülle.
Offen: Hauptaktion und Navigationsknopf tragen vertauschte Rollenfarben (RL05); weiße klickbare Wörter auf dem rosa Himmel stehen bei 3,55–4,4 : 1 (als Nebentext erlaubt). Die Liste steht in RL02.
Seit dem 29.09.2026 (RL18) ist „Nutzungsbedingungen“ im Satz des Kästchens unterstrichen und öffnet in einem neuen Reiter: Wer den Verweis trifft, verliert das halb ausgefüllte Formular nicht mehr. Auf /forgotpw und /resetpw ist „Zur Anmeldung“ ein Nebenknopf, solange das Formular dort die Hauptaktion ist.
Rollen bis zum letzten Knopf (14.10.2026, F-261013-registrierung-ohne)
Rückmeldung vom 13.10.2026: „Im Signup kann man nicht scrollen, obwohl manche Geräte nicht den ganzen Screen sehen.“ Die Rollfläche Seite (Anmeldeseite.tsx) trägt height: 100% und overflow-y: auto; die Zeile darüber in .App war aber 1fr und wuchs mit dem Inhalt mit. So war die Rollfläche so hoch wie die Registrierung selbst, rollte nie, und .App schnitt unten ab. Seit dem 14.10.2026 steht dort minmax(0, 1fr) (GlobalStyles.tsx); nachgestellt in Chromium bei 360 × 640: die Rollfläche 640 Pixel hoch, der Inhalt 1518 und erreichbar. Dasselbe gilt für Anmelden und Anmelden mit fremdem Konto.