Das Audit hatte nur src/ nach Passwoertern durchsucht -- zu eng. Betroffen sind vier getrackte Dateien, nicht eine: - INFRASTRUCTURE_REQUIREMENTS.md fuehrt das DB-Passwort unverschleiert als "**Password**: ..." auf, dazu SMTP und NEXTAUTH_SECRET - deployment-notes.md enthaelt DATABASE_URL und NEXTAUTH_SECRET - tests/debug/db-connection-test.spec.ts eine Connection-URL samt echter IP - .env.production.save, der urspruengliche Fund Die ersten beiden und .env.production.save sind aus dem Index genommen (lokal bleiben sie), im Testskript ist das Passwort ersetzt. .gitignore greift jetzt auf .env* statt auf feste Namen -- genau daran war .env.production.save vorbeigerutscht. Im Git liegen zwei verschiedene DB-Passwoerter: eines aus dem alten Docker-Compose-Setup (@db:5432, vermutlich tot), eines aus dem Supabase-Zugang. Ob letzteres noch aktiv ist, klaert der Pruefbefehl in TODO.md Block F. v1.117.2 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
236 lines
11 KiB
Markdown
236 lines
11 KiB
Markdown
# 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 (Playwright)
|
|
- [x] `"test:e2e"` für Playwright ergänzen
|
|
- [x] Debug-Skripte in `tests/` aussortieren (siehe unten)
|
|
|
|
**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` aus dem Git-Index entfernt (Datei bleibt lokal)
|
|
- [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] 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)
|
|
- [ ] **Schritt 1:** `eslint.ignoreDuringBuilds` in `next.config.js` auf `false`
|
|
- [ ] Gemeldete ESLint-Fehler abarbeiten
|
|
- [ ] **Schritt 2:** `npm run type-check` laufen lassen, Fehlerzahl notieren
|
|
- [ ] Fehler abarbeiten — modulweise, nicht auf einmal
|
|
- [ ] **Schritt 3:** `typescript.ignoreBuildErrors` auf `false`
|
|
- [ ] `any`-Stellen reduzieren (aktuell **452**) — fortlaufend, kein Blocker
|
|
|
|
**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.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
|
|
- [ ] `src/components/weekly/` anlegen
|
|
- [ ] Reine Helfer nach `src/lib/weekly/` (Datum, Slots, Formatierung)
|
|
- [ ] 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
|
|
Typecheck. Kein "großer Wurf" — die Datei ist zu zentral.
|
|
|
|
**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 | Vermutung |
|
|
|---|---|---|
|
|
| `59fb583eaae9` | `deployment-notes.md`, `INFRASTRUCTURE_REQUIREMENTS.md` | altes Docker-Compose-Setup (`@db:5432`), vermutlich tot |
|
|
| `9dbb40b14c4d` | `.env.production.save` | Supabase-Zugang, **möglicherweise noch aktiv** |
|
|
|
|
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.
|
|
|
|
---
|
|
|
|
## 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)
|