feat(nextcloud-files): Anmeldung per Passwort und im Browser (Zwei-Faktor), Abmelden mit Widerruf

- Anmelde-Client (getapppassword, cloud/user, Widerruf, Login Flow v2 mit fester Abfrageadresse,
  Link aus Basis und Token neu gebaut), Anmeldebremse 3/15 min je Benutzer und 8/30 min je Server,
  Ablaufspeicher für Browser-Anmeldungen (20 min, höchstens 200, eine je Benutzer)
- Kontodienst: Verbinden, Trennen mit Widerruf, Sitzung mit Zugangsschlüssel-Sperre,
  frisch ausgestellte oder ersetzte App-Passwörter bleiben nie verwaist; jeder Kontozugriff
  über forTenant mit Mandant UND Benutzer aus dem Token
- Migration 20261008183000: Spalte ncLoginName (App-Passwort gilt nur für den Anmeldenamen der
  Ausstellung, gemessen mit E-Mail-Anmeldung gegen Nextcloud 34)
- Verbindungsbildschirm und Kontoleiste, Texte de/en, RLS-Inventar fortgeschrieben,
  E2E-Skript e2e-connect.sh

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 18:02:45 +02:00
parent 960696745f
commit d00b6ff79f
25 changed files with 3533 additions and 55 deletions
@@ -82,3 +82,49 @@ e2e_second_user() {
e2e_expect 200 "$code" "Passwortwechsel $name"
e2e_login "$jar" "$name" "$final"
}
# --- Aufgabe 2: Konten -------------------------------------------------------------------
NC_OCC() { docker exec -u www-data tessera-nc-test php occ "$@"; }
# e2e_nc_tokens <nc-benutzer> — Anzahl der Tessera-Zugaenge (Name enthaelt "Tessera") in der
# Nextcloud. Messweg: occ user:auth-tokens:list --output=json, Zeilen mit "Tessera" im Namen.
e2e_nc_tokens() {
NC_OCC user:auth-tokens:list "$1" --output=json 2>/dev/null \
| python3 -I -c 'import json,sys
d=json.load(sys.stdin)
print(len([t for t in d if "Tessera" in str(t.get("name",""))]))'
}
# e2e_connect_anna <jar> — verbindet anna (Passwort-Weg) im Tessera-Benutzer des <jar>.
# Gibt den HTTP-Status aus; der Antwortkoerper landet in $E2E_TMP/body.out.
e2e_connect_anna() {
e2e_status "$1" POST "$API/modules/nextcloud-files/connect/password" \
'{"loginName":"anna","password":"User1-Pass-12345"}'
}
# e2e_wait_health — wartet bis die API antwortet.
e2e_wait_health() {
local i
for i in $(seq 1 60); do
curl -sf "$API/health" >/dev/null 2>&1 && return 0
sleep 2
done
e2e_fail "API wurde nicht gesund"
}
# e2e_grant_use <admin-jar> <benutzer-jar> [slug] — gibt dem Benutzer des zweiten <jar> die
# Freigabestufe Benutzen fuer das Modul (idempotent: ein vorhandener Grant ist in Ordnung).
e2e_grant_use() {
local admin_jar=$1 user_jar=$2 slug=${3:-nextcloud-files} code user_id module_id
code=$(e2e_status "$user_jar" GET "$API/auth/me" "" "$E2E_TMP/me.out")
e2e_expect 200 "$code" "auth/me"
user_id=$(python3 -I -c 'import json,sys; print(json.load(open(sys.argv[1]))["id"])' "$E2E_TMP/me.out")
e2e_status "$admin_jar" GET "$API/modules/catalog" "" "$E2E_TMP/catalog.out" >/dev/null
module_id=$(python3 -I -c 'import json,sys
for m in json.load(open(sys.argv[2])):
if m["slug"]==sys.argv[1]: print(m["id"]); break' "$slug" "$E2E_TMP/catalog.out")
code=$(e2e_status "$admin_jar" POST "$API/module-grants" \
"{\"moduleId\":\"$module_id\",\"userId\":\"$user_id\",\"level\":\"USE\"}")
case "$code" in 200|201|409) ;; *) e2e_fail "Freigabe Benutzen -> $code" ;; esac
}