Commit Graph
5 Commits
Author SHA1 Message Date
Florian WildeandClaude Opus 5 6dad3331e2 feat: Zammad, Telegram und den Spiegel anbinden
Zammad liest ueber die Suche und schreibt nichts zurueck. Telegram holt
per getUpdates statt per Webhook — der Planer soll auch hinter einem
Anschluss ohne feste Adresse laufen. Der Spiegel schickt Belegungen ohne
Titel und nur die ausdruecklich markierten Aufgaben.

Wer dem Telegram-Bot schreibt, ohne im Adressbuch zu stehen, bekommt
keine Antwort. Die einzige Ausnahme ist ein gueltiger Kopplungscode: Er
ist der Beweis, dass jemand mit Zugang zur Anwendung diesen Chat will.
Ohne ihn muesste man die Chatkennung von Hand eintragen — und um sie zu
erfahren, muesste der Bot Unbekannten antworten.

Jede Sendung des Spiegels traegt eine Signatur ueber den Rumpf und gilt
fuenf Minuten. Der Empfaenger antwortet auf jeden Fehlschlag gleich:
falsches Geheimnis, verstellte Uhr oder Unsinn im Inhalt sind von aussen
nicht zu unterscheiden.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 23:04:03 +02:00
Florian WildeandClaude Opus 5 e0e11c85b2 feat: Versand per Web-Push und E-Mail
Push ueber VAPID, Mail ueber SMTP, beide unabhaengig zuschaltbar. Die
Schluessel eines Push-Geraets liegen verschluesselt in der Datenbank.

Ein Geraet, das der Push-Dienst mit 404 oder 410 ablehnt, wird sofort
entfernt; andere Fehler zaehlen hoch und fuehren nach fuenf Versuchen
zum selben Ergebnis. Eine Liste toter Abos putzt sich so von allein.

Der Nachweis wird vor dem Senden geschrieben, nicht danach: lieber eine
verpasste Nachricht als eine, die im Minutentakt erneut versucht wird.

Weder Endpunkt noch Schluessel noch Mailinhalt landen im Protokoll.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 08:59:31 +02:00
Florian WildeandClaude Opus 5 9946e7c308 feat(kalender): OAuth und Abgleich für Graph und Google
Verbinden, Token erneuern und seitenweise abrufen — mit blankem fetch,
ohne SDK. Es geht um drei Aufrufe je Anbieter, und die Antworten werden
ohnehin mit zod geprüft; ein SDK brächte vor allem Abhängigkeiten und
blinde Flecken.

Entscheidungen:
- Der Zustandswert im OAuth-Ablauf ist mit AUTH_SECRET signiert und trägt
  die Benutzerkennung. Ohne das ließe sich ein fremdes Kalenderkonto an
  ein beliebiges Konto hängen.
- Die Berechtigungen schließen das Schreiben ein, obwohl M3 nur liest.
  Sonst müsste ab M4 jedes Konto neu zustimmen — und die Zustimmung ist
  genau der Schritt, an dem Einrichtung scheitert.
- Läuft der Abgleichverweis ab (beide Anbieter antworten mit 410), wird er
  verworfen und von vorn gelesen. Der Ablauf ist so gebaut, dass ein
  vollständiger Durchlauf jederzeit möglich ist.
- Googles syncToken trägt die Zeitgrenzen nicht mit; Folgeabgleiche
  liefern also auch Änderungen außerhalb unseres Fensters. Was daneben
  liegt, wird verworfen — sonst wüchse der Spiegel unbegrenzt.

Die Instanzregel greift serverseitig: Google gibt es nur daheim, in der
Firmeninstanz liefert die Route 404 statt eines Hinweises.

Fehlermeldungen nennen nie den Antwortkörper des Token-Endpunkts — der
enthält das Client-Secret oft im Echo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 18:05:06 +02:00
Florian WildeandClaude Opus 5 1876ae4c6b feat(betrieb): Health-Endpunkt und Seed für eine frische Datenbank
/api/health meldet Status, Version, Instanz und Erreichbarkeit der
Datenbank – ohne Anmeldung, deshalb ohne Zählstände, Namen oder Adressen.
update.sh wird sich später darauf stützen.

Der Seed legt die vier Bereiche an und ein erstes Verwaltungskonto aus
ADMIN_EMAIL und ADMIN_PASSWORT. Er läuft mehrfach ohne Schaden; ohne
ADMIN_PASSWORT entsteht ein Konto, das sich nur über OIDC oder Passkey
anmelden kann.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 13:02:49 +02:00
Florian WildeandClaude Opus 5 cc013fd6a1 feat(anmeldung): OIDC, Passkeys und Passwort mit Auth.js v5
Drei Wege hinein, je Instanz zuschaltbar: generischer OIDC-Anbieter
(Entra ID beziehungsweise authentik), Passkeys und wahlweise E-Mail mit
Passwort.

Entscheidungen:
- Passkeys laufen direkt über SimpleWebAuthn 14, nicht über den
  experimentellen WebAuthn-Provider von Auth.js, der noch auf Fassung 9
  festgelegt ist.
- Eigener Prisma-Adapter, weil der mitgelieferte fest auf prisma.user und
  prisma.account zugreift, unsere Modelle aber Benutzer und Konto heißen.
- Sitzungen als JWT, weil Credentials-Provider das verlangen. Damit eine
  Sperre trotzdem sofort wirkt, liest der session-Callback Rolle und
  Status bei jeder Anfrage frisch aus der Datenbank. Die Tabellen sitzung
  und verifizierungs_token entfallen deshalb wieder.
- allowDangerousEmailAccountLinking ist bewusst an: je Instanz gibt es
  genau einen vertrauenswürdigen Anbieter, der die Adresse selbst prüft.

Anmeldung, Passwort und Passkey antworten bei Fehlschlag immer gleich,
damit sich nicht abfragen lässt, wer hier ein Konto hat. Die
WebAuthn-Herausforderung wird nach einem Versuch verworfen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 13:02:34 +02:00