# 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) - [x] `src/app/api/auth/login/route.ts` löschen - [x] 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) - [x] Entscheiden: Jest raus oder Jest einrichten - [x] `package.json`: `"test"` auf das zeigen lassen, was wirklich existiert - [x] `"test:e2e"` für Playwright ergänzen - [x] Debug-Skripte in `tests/` aussortieren - [x] **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) - [x] Sliding-Window-Limiter in `src/lib/rate-limit.ts` anlegen - [x] Einbinden in `signup` - [x] Einbinden in `resend-verification` - [x] Einbinden in `reset-password` - [x] Einbinden in `verify-email` - [x] 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) - [x] Beim Erzeugen: Klartext per Mail, **Hash** in die DB - [x] 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) - [x] `.env.production.save` und `.env.local` gelöscht (mit `shred` überschrieben) - [x] `.gitignore` auf `.env*` erweitert (Ausnahme `.env.example`) - [x] `NEXTAUTH_SECRET` neu erzeugt — 44 Zeichen, geprüft - [x] `SMTP_PASSWORD` neu gesetzt - [x] `deployment-notes.md` und `INFRASTRUCTURE_REQUIREMENTS.md` aus dem Index entfernt - [x] Klartext-Werte in beiden Dateien durch Platzhalter ersetzt - [x] Passwort aus `tests/debug/db-connection-test.spec.ts` entfernt - [x] 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`](docs/secret-rotation.md). --- ## Block C — Build und Typsicherheit ### C1. Build darf Fehler nicht mehr verschlucken (Audit 1) - [x] **Schritt 1:** `eslint.ignoreDuringBuilds` in `next.config.js` auf `false` - [x] Gemeldete ESLint-Fehler abarbeiten — es waren nur **2** (`prefer-const` in `SettingsSidebar.tsx`), verifiziert: `next lint` endet jetzt mit Exit 0 - [x] **Schritt 2:** `npm run type-check` — es waren **7 Fehler**, alle behoben - [x] **Schritt 3:** `typescript.ignoreBuildErrors` auf `false` — Build prüft jetzt Typen - [x] `tsconfig.json`: `include` von `**/*` auf `src/**/*` eingegrenzt. **Das war die Ursache der Langsamkeit**, nicht die Dateigröße: Der Lauf dauert jetzt **19 Sekunden** statt über 30 Minuten. Meine gegenteilige Vermutung war falsch. - [x] **3 Dateien mit `@ts-nocheck`** entschärft — im Projekt gibt es jetzt **kein einziges `@ts-nocheck` mehr**. `calendar-cache.ts` brauchte es gar nicht (0 Fehler), in `microsoft-todo.ts` fehlten Typannotationen, und in `outlook-calendar.ts` steckte ein möglicher Laufzeitfehler dahinter. - [ ] `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 - [x] Typen nach `src/lib/weekly/types.ts` (7 Typen, 97 Zeilen) — Build verifiziert - [ ] `src/components/weekly/` anlegen - [x] Datums-/Zeitraster-Helfer nach `src/lib/weekly/date-utils.ts` (13 Funktionen, 202 Zeilen) - [x] Cookie-/Geräteeinstellungen nach `src/lib/weekly/device-settings.ts` - [x] 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 - [ ] `calendar-cache.ts` hat mit `getWeekStart` eine **vierte** eigene Wochenlogik — ebenfalls gegen `date-utils.ts` ersetzen - [ ] 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) - [x] SonarQube-Analyse gegen https://sq.martin-bierschenk.de einrichten - [x] Regelmäßigen Lauf aufsetzen — nightly 03:00, `todo-sonar.timer` (systemd user unit) - [x] `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`](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: ```bash 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 H — Aus dem scharfen Typecheck ### H1. Notion-Anbindung war defekt *(migriert, aber ungetestet)* - [ ] Migration praktisch prüfen (braucht Notion-Zugang) - [ ] Betroffene Nutzer informieren: Notion-Verbindung muss neu eingerichtet werden **Warum:** Der Code rief `notion.databases.query()` auf. Die Methode gibt es in `@notionhq/client` v5 nicht mehr (nur noch retrieve/create/update) — der Aufruf lief zur Laufzeit in ein "is not a function". **Die Notion-Anbindung war damit schlicht kaputt**, und das ist niemandem aufgefallen, weil der Build Typfehler verschluckte. Umgestellt auf `dataSources.query()`; die Suche filtert jetzt auf `data_source` statt `database`. Das ist in sich konsistent, aber **mangels Notion-Zugang nicht erprobt**. ⚠️ **Gespeicherte Verbindungen enthalten alte `database_id`s. Das sind keine `data_source_id`s** — betroffene Nutzer müssen Notion einmal neu verbinden. --- ## Block I — UI-Wünsche ### I1. Aktuellen Tag auch im Spaltenkopf hervorheben - [ ] Hervorhebungsfarbe zusätzlich hinter Wochentagsname und Datum legen **Wunsch:** Der laufende Tag soll deutlicher auffallen. Aktuell ist nur der Spaltenkörper eingefärbt, der Kopfbereich mit "SEP 7 / MONDAY" bleibt weiß. **Wo:** `globals.css:666` färbt `.weekly-day-column.is-today` mit `var(--weekly-today-highlight)`. Der Kopf ist ein eigenes `
` ([WeeklyView.tsx:7089](src/components/WeeklyView.tsx#L7089)) und übermalt die Farbe offenbar mit eigenem Hintergrund. Der Wochentagsname hat bereits eine eigene Regel (`globals.css:708`, `.weekly-day-name.is-today` setzt nur die Schriftfarbe). **Zu klären:** Der Kopf ist vermutlich `sticky` und braucht deshalb einen deckenden Hintergrund — die Farbe muss also gesetzt, nicht nur geerbt werden. ### I2. Symbole in Kalenderterminen nach oben rechts - [ ] `.event-icons-row` von unten rechts nach oben rechts verschieben **Wunsch:** Die kleinen Symbole (Wiederholung, Anbieter) sitzen unten rechts im Termin und sollen nach oben rechts. **Wo:** `globals.css:3183` — `.event-icons-row` hat `bottom: 2px; right: 4px;`. Für oben rechts wird daraus `top: 2px; right: 4px;`. **Achtung:** Der Titel beginnt oben links; bei kurzen Terminen könnten sich Symbole und Titel überlappen. Vermutlich braucht die Titelzeile rechts etwas Freiraum (`padding-right`), damit nichts verdeckt wird. --- ## 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://: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)