Files
tessera-ctl/.planning/quick/261008-mzu-modul-nextcloud-dateien-eigenstaendiger-/261008-mzu-VERIFICATION.md
T
schalli 71b3f32c98
Tessera CI/CD / Lint & Type Check (push) Successful in 50s
Tessera CI/CD / Tests (push) Successful in 1m46s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 20s
Tessera CI/CD / Build & Publish Images (push) Successful in 3m44s
docs(quick-261008-mzu): Modul Dateien (Nextcloud-Client)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 22:59:16 +02:00

121 lines
12 KiB
Markdown

---
phase: quick-261008-mzu
verified: 2026-10-08T20:20:00Z
status: human_needed
score: 9/9 must-haves verified
covered_files:
- ".planning/quick/261008-mzu-modul-nextcloud-dateien-eigenstaendiger-/261008-mzu-PLAN.md"
- ".planning/quick/261008-mzu-modul-nextcloud-dateien-eigenstaendiger-/261008-mzu-SUMMARY.md"
- "apps/api/prisma/migrations/20261008180000_nextcloud_files/migration.sql"
- "apps/api/src/nextcloud-files/nextcloud-files-account.service.ts"
- "apps/api/src/nextcloud-files/nextcloud-files.controller.ts"
- "apps/api/src/nextcloud-files/nextcloud-http.ts"
- "apps/api/src/nextcloud-files/nextcloud-login-guard.ts"
- "apps/api/src/nextcloud-files/nextcloud-upstream.ts"
- "apps/web/src/app/(portal)/modules/nextcloud-files/components/FileBrowser.tsx"
covered_digest: "v3:sha256:324f20c51c56325030b045e7413ef53d5aa7c3d742f7aac80c85bcbe0aa91f6a"
behavior_unverified: 0
overrides_applied: 0
human_verification:
- test: "Desktop-App: Link 'Anmeldung bei Nextcloud öffnen' (target=_blank) und Rückkehr nach 'Zugriff gewähren'"
expected: "System-Browser öffnet sich, danach zeigt die Desktop-App die Dateiansicht"
why_human: "Tauri-Desktop-App (Opener-Skript) ist im lokalen Stack nicht prüfbar"
- test: "Nginx Proxy Manager vor alpha/live: client_max_body_size >= 10m und Zeitlimits >= 120 s, dann Datei > 10 MB über die echte Tessera-Adresse hochladen"
expected: "8-MiB-Stücke laufen durch, kein 413"
why_human: "NPM-Grenzen existieren nur in der echten Umgebung; lokal läuft der Upload nur durch den Next.js-Proxy (belegt)"
- test: "Echte Firmen-Nextcloud: Brute-Force-Ausnahme für die Tessera-Server-IP, interne CA (NODE_EXTRA_CA_CERTS), Adresse speichern, 'Verbindung prüfen', Passwort-Anmeldung und Zwei-Faktor-Anmeldung, Abmelden und Eintrag 'Tessera' in der Nextcloud-Geräteliste prüfen"
expected: "Anmeldung klappt, Abmelden entfernt den App-Passwort-Eintrag, Name/Logo/Farbe der echten Nextcloud stimmen auf der Anmeldekarte"
why_human: "Echte Nextcloud-Instanz (Version, Theming, Zertifikat, Absenderadresse) ist nur beim Benutzer verfügbar"
---
# Quick 261008-mzu: Modul Dateien (nextcloud-files), Etappe 1 - Verification Report
**Goal:** Neues Modul "Dateien" (Nextcloud, Etappe 1): eine Firmen-Nextcloud, eigenes Konto je Benutzer (Passwort -> App-Passwort verschlüsselt, oder Login Flow v2 bei Zwei-Faktor), Abmelden widerruft, Dateien durchblättern/Vorschau/Hoch- und Herunterladen/ZIP/Ordner-Operationen/Mehrfachauswahl/Quota/Overwrite-Schutz/Brute-Force-Schutz/Benutzertrennung, ausgeprägtes Design, Doku + CHANGELOG.
**Verified:** 2026-10-08
**Status:** human_needed (alle automatisch prüfbaren Punkte erfüllt; drei Punkte brauchen die echte Umgebung)
**Re-verification:** Nein, Erstprüfung
## Goal Achievement
### Observable Truths
| # | Truth | Status | Evidence |
|---|-------|--------|----------|
| 1 | Eine Adresse je Organisation unter Verwalten; Prüfen per status.php; Adresswechsel mit Bestätigung lässt Konten ablaufen; Benutzen-only bekommt 403 | VERIFIED | Controller: `GET/PUT settings`, `POST settings/test` mit `@ModuleManage`, kein Rollen-Decorator. `settings.service.ts` Z.117-141: `confirmReconnect` Pflicht bei verbundenen Konten, danach `status: 'EXPIRED'`. `e2e-settings.sh` läuft grün (frisch ausgeführt). |
| 2 | Passwort-Anmeldung: einmaliger Austausch gegen App-Passwort, nur dieses AES-verschlüsselt (`crypto.encrypt`), echtes Passwort verworfen, nichts in Antwort/Log | VERIFIED | `account.service.ts` `connectWithPassword`/`storeAppPassword` (Z.154-194, 366-390); Spalte `encryptedAppPassword`, kein Passwortfeld im Schema. Logs enthalten nur Kennungen/Mandant. e2e-connect grün (Anna per Passwort und per E-Mail). |
| 3 | 2FA: `credentialsOrTwoFactor`, Login Flow v2, Poll nur gegen `{Basis}/index.php/login/v2/poll`, Wartezustand mit Abbrechen, Wechsel auf verbunden | VERIFIED | `auth-client.ts` `pollLoginFlow` mit festem Präfix (Z.317-328, `poll.endpoint` verworfen). e2e-connect: zoe -> 422 `credentialsOrTwoFactor`, Flow Start (loginUrl auf konfigurierte Basis), pending, Abbruch. Screenshots `t2-flow-waiting.png`, `t2-flow-connected.png`, `t6-dark-connect-waiting.png` zeigen Wartezustand und verbundenen Zustand gegen die Test-Nextcloud. |
| 4 | Brute-Force-Schutz: 3/15 min je Benutzer, 8/30 min je Server; 429 pausiert alle Aufrufe; erstes 401 tötet Zugangsschlüssel inkl. laufender Aufrufe; Whitelist-Hinweis in Einstellungen/Handbuch | VERIFIED | `login-guard.ts` Z.26-29 (3/15, 8/30). `http.ts` `ncRequest`: `gate.isDead/isPaused` vor Transport, 429 -> `gate.pause`, 401 mit Schlüssel -> `markDead`, `signalFor` bricht laufende Aufrufe ab. Live bestätigt: wiederholte e2e-Läufe lieferten HTTP 429 `tooManyAttempts` (retryAfter 817 s). Handbuch Administration Z.359ff mit occ-Befehl. Call-Gate/Guard-Specs grün. |
| 5 | Abmelden widerruft App-Passwort und löscht Zeile; Wiederverbinden widerruft altes; frisch ausgestelltes, nicht speicherbares wird widerrufen; nie an fremden Host | VERIFIED | `disconnect` (nur wenn ACTIVE und Adresse gleich), `revokePrevious`, `revokeFresh` im catch von `storeAppPassword`. e2e-connect belegt Token-Zählung in der echten Nextcloud: 1 nach Wiederverbinden, 0 nach Trennen (auch bei E-Mail-Anmeldename). |
| 6 | Benutzertrennung: Mandant UND Benutzer (RLS + forTenant mit Token-User), zweiter Benutzer 409 notConnected | VERIFIED | Migration: `FORCE RLS`, Policy `tenantId = current_tenant_id() AND (current_user_id() IS NULL OR userId = current_user_id())`. Jede Methode nutzt `forTenant(this.prisma, tenantId, userId)`, `userId` nur aus `req.user.id`. e2e-connect: e2euser sieht `account:null`, Trennen -> 409, Flow-IDs fremder Benutzer -> 404. rls-coverage-Spec grün, Inventar-Doku Z.185 nachgeführt. |
| 7 | Durchblättern (Breadcrumb, Liste/Raster pro Benutzer gemerkt), Typkacheln, Vorschau, Quota (-3 = unbegrenzt), Ordner anlegen/umbenennen/verschieben/löschen (Papierkorb), Mehrfachauswahl, Maus/Menü/Rechtsklick/Tastatur | VERIFIED | `FileBrowser.tsx`: localStorage je `userId`, Tasten Enter/Backspace/Delete/F2/Strg+A (Z.607-714). `QuotaMeter` zeigt bei unbegrenzt nur belegten Platz (Screenshot "3,6 MB belegt"). e2e-files grün. 169 Web-Tests grün (FileBrowser, TransferBar, Seite, Upload). Screenshots dunkel/hell: Liste, Raster, Auswahl, Verschieben-Dialog, Löschen-Dialog, leer, mobil. |
| 8 | Upload per Drag&Drop/Dateiwahl auch > 10 MiB in 8-MiB-Stücken durch /api-proxy, Fortschritt, Abbruch, Fehlertext, nie stilles Überschreiben ("Ersetzen", "Beide behalten", "Überspringen") | VERIFIED | `dav-transfer.ts`: `If-None-Match: *`, Ersetzen nur mit `If-Match`-Etag. e2e-transfer grün: 30 MB in Stücken durch Next.js-Proxy (:3000/api-proxy), byteidentischer Download, 409 `nameTaken`, 409 `changedMeanwhile`, 413 `chunkTooLarge`, 411, Abbruch räumt Upload-Ordner auf. Screenshot `t6-dark-transfers.png`: Übertragungsleiste mit Fortschritt und den drei Konfliktknöpfen. |
| 9 | Downloads ungepuffert (nosniff, ZIP für Ordner/Auswahl); jede Nextcloud-Antwort wird vor dem Pipen abgebildet, nie 401/403; nur Basis-URL, feste Pfade, keine Weiterleitung, keine Cookies, nie URL aus Antwort | VERIFIED | `upstream.ts`: `sendUpstreamStream` ruft `mapNcFailure` vor `pipeline`. `http.ts`: `ALLOWED_PREFIXES`, `buildNcUrl` mit Segment-Kodierung, 3xx -> `redirect`-Fehler, Header `cookie/host/authorization` gesperrt. e2e-transfer: Range 206, ZIP (Ordner, Auswahl, zwei Namen), `..` -> 400 `invalidPath`, fehlende Datei -> 404 `notFound`, ohne Anmeldung 401 nur von Tessera. |
**Score:** 9/9 Truths verified (0 behavior-unverified)
### Required Artifacts
| Artifact | Status | Details |
|----------|--------|---------|
| `migration.sql` (+ `..._login_name`) | VERIFIED | zwei Tabellen, ENABLE/FORCE RLS, Policy mit `current_user_id()` |
| `nextcloud-http.ts` / `nextcloud-call-gate.ts` | VERIFIED | alle genannten Exporte, im Transport verdrahtet |
| `nextcloud-auth-client.ts`, `...account.service.ts` | VERIFIED | substantiell, in Modul und Controller verdrahtet |
| `nextcloud-upstream.ts`, `...transfer.service.ts` | VERIFIED | `mapNcFailure`, `sendUpstreamStream`, 593 Zeilen Transferdienst |
| `nextcloud-files-upload.ts` (Web) | VERIFIED | Uploader mit Tests (503 Zeilen), von `use-transfers.ts` genutzt |
| `FileBrowser.tsx`, `ConnectPanel.tsx` | VERIFIED | in `page.tsx` eingebunden, Screenshots belegen Rendering mit echten Daten |
| Registrierung (app.module, module-loader, nav-store, module-identity, Seed) | VERIFIED | per grep bestätigt |
### Key Link Verification
| From | To | Status |
|------|----|--------|
| account.service -> `crypto.encrypt(` | nur App-Passwort | WIRED |
| auth-client `pollLoginFlow` -> `/index.php/login/v2/poll` | fester Pfad | WIRED |
| `ncRequest` -> `NextcloudCallGate` | isPaused/isDead/pause/markDead | WIRED |
| `getSession` -> `forTenant(this.prisma, tenantId, userId)` | via `findAccount` | WIRED |
| transfer.service -> `mapNcFailure(` | vor dem Pipen | WIRED |
| dav `move` -> `Overwrite: F` | außer Etag-geprüftes Ersetzen | WIRED |
### Behavioral Spot-Checks (frisch ausgeführt)
| Check | Ergebnis | Status |
|-------|----------|--------|
| API-Tests `src/nextcloud-files` + `src/module-registry` | 19 Dateien, 513 Tests grün | PASS |
| Web-Tests Modul, Lib, Komponenten, Layouts | 11 Dateien, 169 Tests grün | PASS |
| `tsc --noEmit` apps/api und apps/web | ohne Fehler | PASS |
| rls-coverage-Spec | 5 Tests grün | PASS |
| `e2e-settings.sh`, `e2e-connect.sh`, `e2e-files.sh`, `e2e-transfer.sh` gegen Test-Nextcloud 34.0.4 | alle "ok" (nach API-Neustart) | PASS |
### Anti-Patterns Found
Keine TBD/FIXME/XXX/TODO/HACK in den Modulverzeichnissen. Keine Stubs: Dateigrößen und Datenfluss (echte PROPFIND/DAV-Antworten bis in die Oberfläche, Screenshots mit echten Nextcloud-Dateien) belegen es.
| Beobachtung | Schwere | Auswirkung |
|-------------|---------|------------|
| `e2e-connect.sh` ist innerhalb von 15 Minuten nach einem früheren Lauf nicht wiederholbar: der Zwei-Faktor-Versuch zählt als Fehlversuch für `admin`, der nächste Lauf scheitert schon beim Schritt "anna verbinden" mit 429 (Neustart der API dann nötig). Das Skript startet die API nur beim 2FA-Schritt neu. Die Aussage der SUMMARY, die Skripte seien "zweimal hintereinander wiederholbar", gilt deshalb nur mit dazwischenliegendem Neustart. | Info | Nur Testwerkzeug; es zeigt zugleich, dass der Brute-Force-Schutz wirkt. |
### Requirements Coverage
QUICK-261008-mzu: SATISFIED für alle im Zielsatz genannten Punkte (Konto, Dateien, Transfer, Schutzmaßnahmen, Design, Doku). CHANGELOG (vier Einträge unter "Neu"), Anwender-, Administrations- und Betriebshandbuch vorhanden und inhaltlich passend.
### Design
Screenshots (dunkel und hell, desktop und mobil) zeigen ein eigenständiges Erscheinungsbild: Typkacheln je Dateifamilie mit Farbcodierung, Kennungskopf der Nextcloud (Name, Logo, Themenfarbe) auf der Anmeldekarte, angedockte Übertragungsleiste, Mosaik-Gelb als Akzent. Nicht generisch. Die subjektive Bewertung bleibt beim Benutzer.
### Human Verification Required
Diese Punkte brauchen die echte Umgebung und sind keine Lücken:
1. **Desktop-App:** Link "Anmeldung bei Nextcloud öffnen" öffnet den System-Browser, nach "Zugriff gewähren" erscheint die Dateiansicht. Grund: Tauri-Opener nur in der installierten App prüfbar.
2. **Nginx Proxy Manager:** `client_max_body_size` >= 10m, Zeitlimits >= 120 s für alpha/live, dann Datei > 10 MB hochladen. Grund: NPM-Grenzen nur in der echten Umgebung.
3. **Echte Firmen-Nextcloud:** Brute-Force-Ausnahme für die Tessera-IP, ggf. `NODE_EXTRA_CA_CERTS`, Adresse eintragen, Passwort- und 2FA-Anmeldung, Abmelden und Geräteliste prüfen, Name/Logo/Farbe der Anmeldekarte beurteilen.
### Gaps Summary
Keine Lücken. Alle neun Truths sind gegen den Code, frisch ausgeführte Tests und die End-zu-End-Skripte gegen eine echte Test-Nextcloud belegt. Der Gesamtstatus ist `human_needed` nur wegen der drei Punkte, die die Produktionsumgebung erfordern.
---
_Verified: 2026-10-08_
_Verifier: Claude (gsd-verifier)_