My-Weekly-ToDo-List/TODO.md
mARTin-B78 5c5f2a284e
Some checks are pending
Playwright Tests / test (push) Waiting to run
Test and SonarQube / quality (push) Waiting to run
feat: 63 Unit-Tests fuer die ausgelagerten Funktionen
Nodes eingebauter Test-Runner mit --experimental-strip-types: keine neue
Abhaengigkeit, keine Browser, keine Datenbank, Laufzeit rund 170 ms.
npm test fuehrt sie aus, npm run test:e2e startet weiterhin Playwright.

Die Tests haben sofort zwei echte Fehler gefunden, die ich beim Auslagern
in v1.117.7 selbst eingebaut hatte:

- isAllDayEvent rief parseCalendarDate ohne Import auf
- resolveViewDays nutzte clampViewDays ohne Import

Beides haette zur Laufzeit einen ReferenceError gegeben. Der Build meldete
nichts, weil typescript.ignoreBuildErrors noch aktiv ist -- genau der Fall,
gegen den C1 laeuft. isAllDayEvent liegt jetzt in date-utils.ts, wo es
thematisch hingehoert; clampViewDays und MAX_VIEW_DAYS in
device-settings.ts, DaysSelector re-exportiert sie.

Geprueft werden ISO-Kalenderwochen gegen bekannte Kalenderdaten, die
Wochenstart-Berechnung inklusive Sonntag-Sonderfall, das Zeitraster, die
12/24-Stunden-Anzeige und der Round-Trip formatDateToISO <-> parseCalendarDate
-- die Bruecke zwischen Datenbank und Anzeige, aus der Ein-Tag-Verschiebungen
entstehen.

Mit npm run build verifiziert (Exit 0).

v1.118.0

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 09:19:41 +02:00

16 KiB

To-do — Repository-Audit vom 2026-09-06

Abarbeitungsliste aus dem Repository-Audit. Reihenfolge ist bewusst gewählt: schnelle Sicherheitsgewinne zuerst, das große Refactoring zuletzt.

Abhaken mit [x]. Die Nummern in Klammern entsprechen den Punkten aus dem Audit.


Block A — Schnell erledigt, sofortiger Gewinn

A1. Tote Mock-Login-Route entfernen (Audit 3)

  • src/app/api/auth/login/route.ts löschen
  • Prüfen, dass niemand sie aufruft (grep -rn "api/auth/login" src/)
  • Commit + Version bumpen

Warum: Altcode aus der Anfangszeit. Vergleicht gegen ein hartcodiertes Fake-Passwort ($2b$10$examplehashedpassword), erzeugt ein bedeutungsloses mock-jwt-token-… und setzt es als Cookie. Weil die Middleware /api/auth/* bewusst durchlässt, ist die Route ohne Login von außen erreichbar und schreibt jede probierte E-Mail per console.log ins Log. Kein Login-Bypass (der Vergleich schlägt immer fehl), aber unnötige Angriffsfläche.

A2. npm test reparieren (Audit 6)

  • Entscheiden: Jest raus oder Jest einrichten
  • package.json: "test" auf das zeigen lassen, was wirklich existiert
  • "test:e2e" für Playwright ergänzen
  • Debug-Skripte in tests/ aussortieren
  • 63 Unit-Tests für die ausgelagerten reinen Funktionen (tests/unit/), laufen in ~170 ms ohne Browser und ohne Datenbank über Nodes eingebauten Test-Runner
  • E2E-Tests mit echtem Login schreiben (braucht laufende App + Datenbank)
  • Testlauf in den nächtlichen Qualitätslauf aufnehmen (scripts/quality-check.sh)

Warum: "test": "jest" — aber es gibt keine Jest-Config und keinen einzigen Jest-Test. Die 15 Dateien in tests/ sind Playwright-Specs, und die Namen verraten den Charakter: login-debug.spec.ts, css-debug-test.spec.ts, login-simple-debug.spec.ts. Wegwerf-Skripte, keine Regressionstests. npm test läuft ins Leere.

Aufräumen: Die reinen Debug-Skripte löschen, die brauchbaren behalten und benennen.

A3. dev-Branch auflösen (Audit 5)

  • Die vier Maßnahmen aus f6c4a21 frisch auf main anwenden (siehe A1, B1, B2)
  • dev auf main zurücksetzen oder löschen
  • CLAUDE.md: Branch-Strategie an die gelebte Praxis anpassen

Warum: Commit f6c4a21 vom 04.07.2026 behebt bereits Rate-Limiting, Token-Hashing, die Mock-Route und die Secret-Datei — wurde aber nie gemerged. dev fehlen inzwischen 70 Commits von main, ein Merge würde alte Dateistände zurückholen. Deshalb: Commit als Vorlage nehmen, Änderungen neu anwenden, dev danach entsorgen.

CLAUDE.md beschreibt einen dev → main-Workflow, der faktisch nicht gelebt wird (alles läuft direkt auf main). Doku an die Realität anpassen ist ehrlicher.


Block B — Sicherheit

B1. Rate-Limiting auf den Auth-Endpunkten (Audit 4)

  • Sliding-Window-Limiter in src/lib/rate-limit.ts anlegen
  • Einbinden in signup
  • Einbinden in resend-verification
  • Einbinden in reset-password
  • Einbinden in verify-email
  • Limits dokumentieren (Vorschlag: 5 Versuche / 15 Minuten je IP)

Warum: Im gesamten src/ ist kein Limiter vorhanden. Signup, Passwort-Reset und Verification-Mails sind unbegrenzt aufrufbar — Einladung zum Missbrauch deines SMTP-Zugangs.

B2. Passwort-Reset-Token nur als SHA-256-Hash speichern (Audit 4)

  • Beim Erzeugen: Klartext per Mail, Hash in die DB
  • Beim Einlösen: eingehenden Token hashen, dann vergleichen
  • Migration für bestehende Tokens (einfachster Weg: alle invalidieren)

Warum: src/app/api/auth/reset-password/route.ts:28 legt passwordResetToken im Klartext ab. Wer Lesezugriff auf die Datenbank hat, kann fremde Konten übernehmen. Der Token selbst kommt aus crypto.randomUUID() — das ist in Ordnung, nur die Ablage nicht.

B3. Secrets aus dem Git entfernen und rotieren (Audit 2)

  • .env.production.save und .env.local gelöscht (mit shred überschrieben)
  • .gitignore auf .env* erweitert (Ausnahme .env.example)
  • NEXTAUTH_SECRET neu erzeugt — 44 Zeichen, geprüft
  • SMTP_PASSWORD neu gesetzt
  • deployment-notes.md und INFRASTRUCTURE_REQUIREMENTS.md aus dem Index entfernt
  • Klartext-Werte in beiden Dateien durch Platzhalter ersetzt
  • Passwort aus tests/debug/db-connection-test.spec.ts entfernt
  • Nach dem Wechsel neu eingeloggt
  • Entscheiden: Historie umschreiben oder belassen (Empfehlung: belassen)
  • Prüfen, ob eines der geleakten DB-Passwörter noch aktiv ist (siehe F1)

Warum: .env.production.save ist eingecheckt und enthält echte Werte — DATABASE_URL (70 Zeichen), NEXTAUTH_SECRET (20 Zeichen), SMTP_PASSWORD (20 Zeichen). .gitignore listet .env.production, die .save-Endung fällt durchs Raster. Drin seit Commit da8d019.

Entwarnung: Das GitHub-Repo ist privat — nicht öffentlich abgreifbar. Die Werte stehen aber in der kompletten Historie, in jedem Klon und auf Gitea.

Anleitung siehe docs/secret-rotation.md.


Block C — Build und Typsicherheit

C1. Build darf Fehler nicht mehr verschlucken (Audit 1)

  • Schritt 1: eslint.ignoreDuringBuilds in next.config.js auf false
  • Gemeldete ESLint-Fehler abarbeiten — es waren nur 2 (prefer-const in SettingsSidebar.tsx), verifiziert: next lint endet jetzt mit Exit 0
  • Schritt 2: npm run type-check laufen lassen, Fehlerzahl notieren
  • Fehler abarbeiten — modulweise, nicht auf einmal
  • Schritt 3: typescript.ignoreBuildErrors auf false
  • 3 Dateien mit @ts-nocheck aufspüren und entschärfen — die überspringen den Typecheck vollständig, ignoreBuildErrors: false würde sie gar nicht erfassen
  • any-Stellen reduzieren — ESLint zählt 470 no-explicit-any — fortlaufend, kein Blocker
  • 209 ungenutzte Variablen (no-unused-vars) aufräumen — fortlaufend
  • 11 unvollständige Hook-Abhängigkeiten (react-hooks/exhaustive-deps) prüfen — die sind die gefährlichsten der Warnungen, weil daraus echte Bugs entstehen

Warum: next.config.js hat ignoreDuringBuilds: true und ignoreBuildErrors: true. tsconfig.json sagt zwar strict: true, das ist aber wirkungslos, weil der Build die Fehler gar nicht meldet. Ein Typfehler fällt nicht beim Bauen auf, sondern erst als kaputte Seite im Browser. Ein erfolgreicher Build ist derzeit kein Beweis, dass der Code korrekt ist.

Achtung Reihenfolge: Erst ESLint, dann TypeScript. Beides gleichzeitig abzuschalten blockiert jeden Deploy, bis alles behoben ist.


Block D — Struktur

D1. Dateien über 4000 Zeilen aufteilen (Audit 7)

Betroffen sind genau zwei Dateien:

Datei Zeilen
src/components/WeeklyView.tsx 11.556 (war 11.906)
src/components/SettingsSidebar.tsx 5.445

Voraussetzung: C1 muss laufen — ohne Typecheck ist ein Refactoring dieser Größe Blindflug. Ebenso hilfreich: A2, damit es überhaupt Tests gibt.

WeeklyView.tsx → Zielstruktur

  • Typen nach src/lib/weekly/types.ts (7 Typen, 97 Zeilen) — Build verifiziert
  • src/components/weekly/ anlegen
  • Datums-/Zeitraster-Helfer nach src/lib/weekly/date-utils.ts (13 Funktionen, 202 Zeilen)
  • Cookie-/Geräteeinstellungen nach src/lib/weekly/device-settings.ts
  • Formatierungs-Helfer nach src/lib/weekly/format.ts
  • getWeekNumber existiert dreimal im Projekt (WeeklyView, WeekPrintModal.tsx, api/user/export-week-view/route.ts) — Duplikate gegen date-utils.ts ersetzen, vorher die drei Implementierungen auf Gleichheit prüfen
  • Drag & Drop in einen Hook useTaskDragDrop.ts
  • Someday-Listen in SomedayLists.tsx
  • All-Day-Leiste in AllDayBar.tsx
  • Zeitraster + Tagesspalten in TimeGrid.tsx / DayColumn.tsx
  • Einstellungs-Flyouts in QuickSettingsPanel.tsx
  • Daten-Laden in useWeeklyData.ts
  • WeeklyView.tsx bleibt als Orchestrator übrig

SettingsSidebar.tsx → Zielstruktur

  • src/components/settings/ anlegen
  • Ein Panel pro Themenbereich als eigene Datei
  • Gemeinsame Bausteine (Farbwähler, Schalterzeile) in settings/controls/

Vorgehen: In kleinen Schritten, jeder Schritt ein eigener Commit, nach jedem Schritt npm run build. Kein "großer Wurf" — die Datei ist zu zentral.

Zwischenstand nach drei Schnitten: 11.906 → 11.556 Zeilen, also rund 3 %. Damit sind die reinen Funktionen ohne Komponenten-State weitgehend heraus — das war der leichte Teil.

Die verbleibenden ~11.500 Zeilen sind die Komponente selbst: JSX und Event-Handler, die auf gemeinsamen State zugreifen. Jeder weitere Schnitt braucht echtes Props-Design statt Verschieben. Realistisch pro Schnitt ein eigener Durchgang, und erst mit Tests (A2) oder zumindest laufendem Typecheck sinnvoll — ein Fehler beim Herauslösen von Drag & Drop fällt sonst wieder erst im Browser auf.

Warum: WeeklyView.tsx ist ein Viertel des gesamten Quellcodes. Der Weekday-Bug von Anfang September saß genau dort und war nur deshalb schwer zu finden.


Block E — Dauerhafte Absicherung

E1. Regelmäßige automatische Prüfung (Audit 8)

  • SonarQube-Analyse gegen https://sq.martin-bierschenk.de einrichten
  • Regelmäßigen Lauf aufsetzen — nightly 03:00, todo-sonar.timer (systemd user unit)
  • npm audit in denselben Lauf aufnehmen (scripts/quality-check.sh)
  • todo-sonar.timer auf scripts/quality-check.sh umstellen statt nur todo-sonar-scan
  • Playwright-Tests in den nächtlichen Lauf aufnehmen (braucht laufende App — erst nach A2)
  • Quality Gate im SonarQube definieren
  • Ergebnis irgendwohin melden, wo es auffällt (bisher nur Konsolenausgabe)
  • Entscheiden: .github/workflows/sonarqube.yml löschen oder behalten

Stand: Die Analyse läuft bereits — nächtlich um 03:00 auf dem Spark, per systemd-Timer, Ergebnis auf https://sq.martin-bierschenk.de. Was fehlte, war npm audit (Sicherheitslücken in Abhängigkeiten findet SonarQube nicht) und ein Bündel-Skript. Beides ist jetzt da.

Zur Entscheidung: GitHub Actions wurde am 02.09. bewusst verworfen — das Repo ist privat, Actions-Minuten kosten, und der SonarQube-Server läuft ohnehin selbst gehostet. Trotzdem liegt seit dem 03.09. ein Workflow im Repo, der bei jedem Push auf main anspringt. Er ruft npm run type-check und npm run lint auf — beides schlägt derzeit fehl (siehe C1), der Workflow wäre also dauerhaft rot. Sein Projekt-Key war zudem falsch und hätte in ein zweites, leeres SonarQube-Projekt geschrieben; das ist korrigiert. Entweder löschen (passt zur Entscheidung vom 02.09.) oder bewusst behalten und C1 vorziehen.

Warum: Bisher fallen Regressionen erst in Produktion auf. In Kombination mit C1 (Build meldet Fehler) und A2 (Tests laufen) entsteht daraus ein echtes Netz.

E2. Abhängigkeiten auf Sicherheitslücken prüfen

  • npm audit auf dem Container ausführen
  • Kritische Funde beheben

Warum: Konnte im Audit nicht geprüft werden — node_modules ist auf dem CIFS-Share nicht installierbar (keine Symlinks). Zu bekannten Lücken in den Abhängigkeiten liegt daher keine Aussage vor. Muss nachgeholt werden.


Block F — Zurückgestellt

F1. Datenbank-Passwort rotieren (bewusst nach hinten geschoben)

  • Prüfen, ob eines der geleakten Passwörter noch aktiv ist
  • Falls ja: rotieren nach docs/secret-rotation.md, Schritt 2
  • Falls nein: nichts zu tun, Haken dran

Warum zurückgestellt: Von den drei Zugangsdaten ist dieses das am wenigsten dringende. Die Datenbank hört auf einer privaten LAN-Adresse, nicht am Internet. Wer das Passwort aus dem Git holen will, braucht Zugriff auf das private Repository — und ist damit ohnehin schon im Netz. NEXTAUTH_SECRET und SMTP_PASSWORD waren die kritischen, beide sind erledigt.

Und es ist riskant: Supabase läuft selbst gehostet per Docker. Dasselbe Passwort steckt in supabase/docker/.env und in Postgres. Ein halb durchgeführter Wechsel legt die Datenbank und damit die App lahm. Das gehört an einen ruhigen Abend, nicht nebenbei.

Zwei verschiedene Passwörter liegen im Git (Fingerabdrücke, keine Klartexte):

Fingerabdruck Wo Stand
59fb583eaae9 ehem. deployment-notes.md, INFRASTRUCTURE_REQUIREMENTS.md altes Docker-Compose-Setup (@db:5432); Dateien bereinigt
9dbb40b14c4d ehem. .env.production.save Supabase-Zugang, Status offen — Datei gelöscht, Wert steht noch in der Historie

Vom Nutzer bestätigt: das alte NEXTAUTH_SECRET (20 Zeichen) und das alte SMTP_PASSWORD sind nicht mehr in Verwendung, beide wurden rotiert. Offen ist allein das Datenbank-Passwort.

Prüfen lässt sich das auf dem Container, ohne einen Wert auszugeben:

cd /root/My-Weekly-ToDo-List
grep '^DATABASE_URL=' .env | sed -E 's|.*://[^:]+:([^@]+)@.*|\1|' | tr -d '\n' | sha256sum | cut -c1-12

Ergibt der Befehl 9dbb40b14c4d, ist das geleakte Passwort noch in Benutzung — dann lohnt F1. Kommt etwas anderes heraus, wurde es längst getauscht und du kannst F1 abhaken.


Block G — Infrastruktur (Supabase-LXC 201)

G1. Container läuft privilegiert (Sicherheitsthema)

  • Entscheiden, ob unprivileged: 0 so bleiben soll
  • Falls nicht: Container neu aufsetzen und Daten übernehmen

Warum: pct config 201 meldet unprivileged: 0. Wer aus diesem Container ausbricht, ist root auf dem NUC — bei einem Container, der die Datenbank betreibt, die relevantere Baustelle als ein fehlendes Root-Passwort. Meist wird das umgestellt, weil Docker im unprivilegierten Container zickt; nachvollziehbar, aber ein bewusster Kompromiss.

G2. Besitzverhältnisse im Dateisystem sind schief

  • Kurzfix, falls SSH-Zugang zum Container gebraucht wird: pct exec 201 -- chown 0:0 /etc/passwd und pct exec 201 -- chown 0:42 /etc/shadow
  • Langfristig: Container sauber neu aufsetzen

Warum: Das Dateisystem stammt aus einem unprivilegierten Container (alles liegt mit UID 100000 auf der Platte), läuft aber jetzt privilegiert, also ohne UID-Mapping. root besitzt dadurch praktisch keine Datei im System. Zeitstempel belegen es: alles vom 26.02.2026 mit UID 100000, nur /etc/hostname vom 06.09. mit 0:0 — das schreibt Proxmox von außen.

Symptom war passwd: Authentication token manipulation error. Das System läuft trotzdem, weil root per CAP_DAC_OVERRIDE die Rechteprüfung überspringt; Werkzeuge wie passwd, die strenger prüfen, fallen darüber. Weitere solche Merkwürdigkeiten sind zu erwarten.

⚠️ Kein chown -R über das ganze Dateisystem — nicht am laufenden Container und schon gar nicht mit Docker darin. Postgres und die Supabase-Container haben eigene UIDs (999, 70, …), die dabei zerschossen würden.

Kein Blocker: pct enter 201 gibt jederzeit eine Root-Shell ohne Passwort. Ein Root-Passwort wird nur für direkten SSH-Zugang gebraucht.

G3. Supabase Studio ist nicht erreichbar

  • Auf 201 prüfen: docker compose ps — laufen die Container?
  • Port klären: Studio hängt hinter Kong auf 8000, nicht 8001
  • scripts/supabase-setup.sh korrigieren: falsche Port-Ausgabe und Shebang #!/bash

Warum: http://<IP>:8001 antwortet nicht. Die Zahl stammt aus einer echo-Zeile in scripts/supabase-setup.sh, die nirgends eine Portfreigabe setzt — beim offiziellen supabase/docker-Stack ist Studio nur über Kong auf 8000 erreichbar. Zusätzlich hat das Skript #!/bash statt #!/bin/bash und ist so gar nicht direkt ausführbar.


Kleinkram (ohne Eile)

  • 114 console.log aus dem Produktionscode entfernen (die 275 console.error sind in Ordnung)
  • NEXTAUTH_SECRET auf 32 Byte verlängern (erledigt sich mit B3)
  • API-Routen: JSON-401 statt HTML-Redirect für nicht angemeldete Aufrufe
  • deploy.sh und docker-compose.yml löschen oder als veraltet markieren — sie beschreiben ein Portainer-Setup, das nicht mehr verwendet wird
  • Eigener Auth-Check in apple/sync und weather/geocode (aktuell nur durch die Middleware geschützt — zusätzliche Absicherung schadet nicht)