Commit Graph
5 Commits
Author SHA1 Message Date
Florian Wilde cba2e49211 Anleitung: Jahresabschluss und was aussieht wie ein Konto
Zwei Fragen aus der Praxis, die die Anleitung nicht beantwortete.

## Der Jahresabschluss

Sie beschrieb, was in jedem Bericht steht, aber nicht die Reihenfolge und
nicht, was man vorher besorgen muss. Der neue Abschnitt beginnt deshalb
sechs Wochen vor dem Stichtag: Rueckkaufswerte und Bescheinigungen
brauchen Wochen, und wer erst am Stichtag daran denkt, geraet in Verzug.
Danach die Staende mit Quellenangabe, dann die Berichte in der
Reihenfolge, in der sie aufeinander aufbauen.

## Genossenschaftsanteile

Das Gericht fragt einen "Kontoauszug" an, den es nicht gibt: Ein
Geschaeftsguthaben ist kein Zahlungskonto, sondern eine Forderung gegen
die Genossenschaft. Es laufen keine Buchungen darueber, und ausgezahlt
wird erst nach Kuendigung der Mitgliedschaft.

Der Fall fuehrt zu einem stillen Fehler: Wer die Anteile als Konto
anlegt, kann die Bescheinigung nicht am Wert hinterlegen - genau das
Blatt, das das Gericht sehen will. Ein Saldostand traegt eine
Quellenangabe, aber kein Dokument; ein Wertstand am Vermoegensposten
schon. Deshalb steht im Text nicht nur, was zu tun ist, sondern warum -
sonst legt der naechste Leser es wieder als Konto an.

Dazu der Hinweis, dass "der Wert bleibt doch gleich" nicht traegt: Wird
eine Dividende dem Geschaeftsguthaben gutgeschrieben statt ausgezahlt,
waechst es. Genau deshalb fragt man jaehrlich nach, statt den
Vorjahreswert fortzuschreiben.

Eine Uebersicht behandelt die verwandten Faelle mit - Bausparvertrag,
Depot, Verbindlichkeit. Das Depot ist die Ausnahme: Dort laufen Umsaetze,
es gibt einen Auszug, es ist ein Konto. Versicherungen gehoeren an den
Vertrag, nicht ins Vermoegen.

Zehn Pruefungen in tests/anleitung.test.ts, gegen vier Mutationen
geprueft: als Konto geraten, Dividendenhinweis entfernt, Vorlauf
entfernt, Abschnitt entfernt - jede wird rot.

Refs #55
2026-09-02 22:52:41 +02:00
Florian Wilde c38c988c3c feat: Anmelden mit Passkey
Passkeys (WebAuthn/FIDO2) als zweiter Anmeldeweg - auf der Anmeldeseite
und bei der erneuten Bestaetigung vor sensiblen Aktionen. Verwaltet unter
"Mein Konto".

Der Passkey ERSETZT den zweiten Faktor, statt zu ihm hinzuzukommen. Das
ist tragbar, weil er zwei Faktoren in sich vereint: Besitz des Geraets
und dessen Entsperrung. Damit das keine Annahme bleibt, wird die
Nutzerverifikation angefordert (userVerification: 'required') und
serverseitig nachgeprueft - eine Antwort ohne sie wird abgewiesen.

TOTP muss vorher eingerichtet sein. Ein Passkey haengt an einem Geraet;
waere er der einzige Weg hinein, koennte ein verlorenes Handy die Akte
schliessen.

Aufbau:
- Die Pruefung laeuft vollstaendig im Credentials-Provider "passkey",
  nicht in der Server Action davor. Ein "war schon geprueft" ueber die
  Grenze zu reichen waere die Stelle, an der sich Anmeldungen faelschen
  lassen.
- Challenges liegen serverseitig und werden per DELETE genau einmal
  verbraucht - die Datenbank entscheidet, wer sie bekommt.
- Bereichskennung und Herkunft kommen aus AUTH_URL, nicht aus einer
  zweiten Konfigurationsquelle.
- Ohne https wird der Knopf gar nicht erst angeboten.
- Rate-Limiting, Kontosperre und Audit-Log gelten unveraendert; die
  Anmeldung nennt den verwendeten Passkey namentlich.

Zwei Anmerkungen zur Migration:
- "prisma migrate diff" wollte zusaetzlich die Trigramm-Indizes aus
  prisma/sql/suche.sql loeschen, die Prisma nicht kennt. Die Zeilen sind
  entfernt und im Migrationskopf vermerkt - ein DROP haette die unscharfe
  Suche stillgelegt, ohne dass etwas fehlgeschlagen waere.
- "prisma format" hat das Schema durchgaengig neu ausgerichtet. Ohne
  Whitespace betrachtet sind es 88 hinzugefuegte Zeilen und keine
  geloeschte.

Geprueft: 21 Einheitstests, drei End-to-End-Tests gegen Chromiums
virtuellen Authenticator (einrichten, anmelden, bestaetigen, entfernen,
Geraet ohne Verifikation abgewiesen), beide Geraeteprofile. 496 Tests,
null Typfehler, Produktionsbau steht.

Nebenbei: Die Zahlen im README waren veraltet (24 statt 33 Modelle, 46
statt 79 Seiten). Sie stehen jetzt mit dem Befehl daneben, mit dem man
sie nachzaehlt.
2026-08-22 18:44:56 +02:00
Florian Wilde 5ee3275112 fix: Betriebsskripte fehlten im Docker-Abbild
Das Verzeichnis scripts/ wurde beim Bauen nie in die Laufzeitstufe
uebernommen. Der in der Anleitung dokumentierte Befehl

    docker compose exec app node scripts/totp-geheimnis.mjs ...

endete deshalb mit "Cannot find module". Betroffen war nicht nur dieses
eine Skript, sondern alle, die im laufenden Behaelter gedacht sind - auch
wartungsschluessel.mjs und schluessel-erzeugen.mjs.

Kopiert wird jetzt das ganze Verzeichnis, nicht einzelne Dateien. Sonst
faellt beim naechsten Betriebsskript wieder auf, dass es fehlt - und zwar
erst dann, wenn man es braucht. Die Entwicklungsskripte darin laufen dort
mangels Werkzeugen nicht, kosten aber nur ein paar Kilobyte.

Geprueft im fertigen Abbild und gegen eine echte Datenbank: Das Skript
liegt unter /app/scripts, laeuft als Benutzer cockpit und gibt dasselbe
Geheimnis aus wie auf dem Entwicklungsrechner.

Die Anleitung nennt jetzt zusaetzlich den Weg fuer alle, die noch eine
aeltere Fassung laufen haben und nicht sofort aktualisieren wollen:
docker compose cp legt die Datei einmalig in den Behaelter.

Etappe: 8
Phase: Haertung
2026-08-22 12:02:16 +02:00
Florian Wilde 9df6419a68 feat: TOTP-Geheimnis auslesen, um ein zweites Geraet einzurichten
Die Anwendung zeigt das Geheimnis genau einmal an, beim Einrichten - das
ist richtig so. Wer den Zugang aber auf ein zweites Geraet legen will,
Handy und Tablet oder ein Ersatzgeraet im Schrank, braucht es noch
einmal. Bisher blieb dafuer nur, den zweiten Faktor zurueckzusetzen und
alle Wiederherstellungscodes neu zu erzeugen.

Das Skript laeuft nur auf dem Server und nur mit Zugriff auf die
Datenbank UND auf ENC_KEY_V1. Wer beides hat, kann ohnehin alles lesen -
genau deshalb liegt der Schluessel nicht in der Datenbank. Es ist also
keine Hintertuer, sondern erspart eine unnoetige Neueinrichtung.

Es gibt den otpauth-URI mit aus, aber mit dem ausdruecklichen Hinweis,
ihn in einen QR-Erzeuger auf demselben Rechner zu geben und nicht in
einen Dienst im Netz - sonst haette man das Geheimnis gerade dorthin
gegeben, wovor der zweite Faktor schuetzen soll.

Geprueft gegen den Entwicklungszugang: der ausgelesene Wert stimmt mit
SEED_DEV_TOTP_SECRET ueberein.

Etappe: 8
Phase: Haertung
2026-08-22 11:44:29 +02:00
Florian Wilde 0e7da42f27 docs: Bedienungsanleitung und Roadmap
Bis jetzt gab es Code und Betriebsdokumentation, aber nichts, was einem
beim Arbeiten hilft.

Die Anleitung ist kein Handbuch zum Durchlesen, sondern die zwoelf
Handgriffe, die im Alltag vorkommen - vom Behoerdenschreiben aufnehmen
bis zum Vertrag kuendigen. Dazu der Jahresrhythmus, die Berichte, was am
Handy anders ist, und ein Abschnitt "wenn etwas schiefgeht" mit den
Faellen, die wirklich passieren: Handy verloren, Code wird nicht
angenommen, Frist stimmt nicht, Bericht zeigt eine Differenz.

Ein eigener Abschnitt sagt, was die Anwendung NICHT tut. Wer sich auf
etwas verlaesst, das sie nicht leistet, faellt sonst genau dann darauf
herein, wenn es darauf ankommt.

Sie ist unter /anleitung auch in der Anwendung lesbar - dieselbe Datei,
kein zweiter Text, der auseinanderlaeuft. Wer im Heim steht und nicht
weiterweiss, hat sie auf dem Telefon dabei, und der Druckknopf legt sie
zu den Betreuungsunterlagen.

Dafuer ein schlichter Markdown-Baustein statt einer Bibliothek: die
Anwendung zeigt genau zwei solche Dokumente, beide schreibe ich selbst.
Rohes HTML wird nicht durchgereicht. Die Datenschutzseite benutzt ihn
jetzt ebenfalls, statt eine zweite Umsetzung zu pflegen.

Beide Dateien werden ueber outputFileTracingIncludes in den
Standalone-Build gezogen - der Pfad entsteht erst zur Laufzeit, die
Dateiverfolgung von Next.js kann ihn nicht erkennen. Ohne den Eintrag
waeren beide Seiten im Behaelter leer.

Die Roadmap sortiert die 44 Schema-Wuensche der Etappen nach einer
einzigen Frage zuerst: Kann daraus ein falscher Wert in einem Bericht
ans Gericht werden? Was das kann, steht in 1.1. Dazu ein Abschnitt
"bewusst nicht geplant" mit Begruendung - damit klar ist, worauf
niemand wartet.

Etappe: 8
Phase: Haertung
2026-08-22 10:51:28 +02:00