--- 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)_