Files
dogfather-universe/cloudflare-worker/migrations/0019_webdesign_push.sql
T
DogFatherGitandClaude Opus 5 f435d89a16 Benachrichtigung bei neuer Anfrage: Push aufs Handy
BEFUND ZUERST, DENN ER WAR ANDERS ALS ERWARTET

Es gab sehr wohl eine Benachrichtigung -- ueber einen Discord-Webhook,
sauber gebaut, bewusst ohne Kundendaten im Text. Sie begann aber mit:

    const url = process.env.DISCORD_WEBHOOK_WEBDESIGN;
    if (!url) return;

Und diese Variable ist in .env.example nirgends aufgefuehrt. Eine
Variable, die niemand kennt, wird nicht gesetzt; dann kehrte die
Funktion wortlos zurueck. Kein Fehler, keine Protokollzeile. Es gab
eine Benachrichtigung, die nur im Quelltext existierte -- und niemand
konnte das bemerken, weil "funktioniert" und "kaputt" identisch
aussehen, solange nichts passiert.

WAS JETZT DA IST

Push aufs Handy, ohne Fremdpaket. Der interne Dienst liegt unter
/home/dogiintern mit Rechten 700 -- dort ist kein npm erreichbar, jede
Abhaengigkeit haette dauerhafte Handarbeit bedeutet. Node bringt P-256,
HKDF und AES-128-GCM selbst mit.

Die Schluessel erzeugt der Server beim ersten Start selbst und legt den
privaten Teil verschluesselt in der Datenbank ab (derselbe Weg wie die
PayPal-Zugangsdaten). Damit gibt es keinen Einrichtungsschritt, der
vergessen werden kann.

GEPRUEFT

Gegen RFC 8291 statt gegen ein Bauchgefuehl: alle fuenf Zwischenwerte
aus Anhang A stimmen (ECDH-Geheimnis, PRK_key, IKM, CEK, NONCE), und
die fertige Nachricht ist Byte fuer Byte die aus Abschnitt 5 der Norm.
Bei Kryptographie erzeugt ein Ableitungsfehler keinen Absturz, sondern
Bytes, die genauso zufaellig aussehen wie richtige.

Dazu ein Kettentest mit einem echten Empfaenger, der entschluesselt:
Kopfzeilen, Inhalt, keine Kundendaten in der Meldung, 410 loescht das
Geraet, 500 loescht es NICHT (sonst kostet eine einzelne Stoerung die
Anmeldung). 50 Pruefungen, alle gruen.

DREI ENTSCHEIDUNGEN

1. In der Meldung stehen nur Nummer und Paket. Sie erscheint auf einem
   Sperrbildschirm, den auch jemand sieht, der zufaellig danebensteht.

2. Ist der Schluessel unlesbar, wird KEIN neuer erzeugt. Das waere der
   bequeme Weg und der schlimmste: Ein neuer oeffentlicher Schluessel
   macht schlagartig jede Anmeldung wertlos, ohne dass jemand erfaehrt,
   warum nichts mehr ankommt.

3. Die Verwaltung zeigt den Zustand an und hat einen Testknopf. Genau
   das fehlte dem Discord-Weg. Bei verweigerter Erlaubnis erscheint
   kein Knopf, der nichts bewirkt, sondern der Weg ueber die
   Browsereinstellungen.

Das stille "if (!url) return;" ist ersetzt: Jeder Weg wird einzeln
protokolliert -- auch und gerade, wenn er uebersprungen wird.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-26 10:50:33 +02:00

55 lines
2.2 KiB
SQL

-- =====================================================================
-- Push-Benachrichtigungen: angemeldete Geraete
--
-- WARUM EINE EIGENE TABELLE UND KEIN FELD AM BENUTZER
--
-- Ein Mensch hat mehrere Geraete -- Handy, Rechner, vielleicht ein
-- Tablet -- und jedes meldet sich einzeln an. Jedes bekommt eine eigene
-- Adresse und eigene Schluessel. Ein einzelnes Feld am Benutzer koennte
-- immer nur das zuletzt angemeldete Geraet halten, und die Anmeldung am
-- Rechner wuerde die am Handy stillschweigend loeschen.
--
-- WAS HIER STEHT UND WAS NICHT
--
-- endpunkt, p256dh und auth kommen vom Browser. Sie sind keine
-- Geheimnisse im engeren Sinn, aber sie erlauben es, an genau dieses
-- Geraet zu senden -- deshalb liegen sie nicht offener als der Rest der
-- Datenbank.
--
-- ZAEHLER STATT SOFORT LOESCHEN
--
-- Meldet der Push-Dienst 404 oder 410, ist das Geraet endgueltig weg und
-- der Eintrag wird geloescht. Bei allen anderen Fehlern (Netz gestoert,
-- Dienst kurz nicht erreichbar) waere Loeschen falsch -- dann wuerde
-- eine einzelne Stoerung die Anmeldung kosten. Deshalb ein Zaehler:
-- voruebergehende Fehler heilen sich, dauerhafte fallen auf.
-- =====================================================================
CREATE TABLE IF NOT EXISTS wd_push_geraete (
id TEXT PRIMARY KEY,
-- Die Adresse beim Push-Dienst. Eindeutig: Meldet sich dasselbe
-- Geraet erneut an, soll der alte Eintrag ersetzt werden und kein
-- zweiter danebenstehen -- sonst kaeme jede Meldung doppelt.
endpunkt TEXT NOT NULL UNIQUE,
p256dh TEXT NOT NULL,
auth TEXT NOT NULL,
-- Wem gehoert das Geraet. Verweist auf die Team-Anmeldung, damit beim
-- Entzug eines Zugangs auch dessen Geraete verschwinden koennen.
benutzer_id TEXT,
-- Etwas Wiedererkennbares fuer die Liste in der Verwaltung ("Chrome
-- auf Android"). Ohne das steht dort nur eine 200 Zeichen lange
-- Adresse, und niemand weiss, welches Geraet er gerade abmeldet.
bezeichnung TEXT,
erstellt_am TEXT NOT NULL,
zuletzt_ok_am TEXT,
fehler_zaehler INTEGER NOT NULL DEFAULT 0,
letzter_fehler TEXT
);
CREATE INDEX IF NOT EXISTS idx_wd_push_benutzer ON wd_push_geraete (benutzer_id);