feat: Kennwort je FritzBox-Sicherung — gespeichert, per 👁 anzeigen, kopieren

Bisher merkte sich der Satellit nur die QUELLE des Kennworts. Wurde das
Sicherungs-Kennwort spaeter geaendert, passte es nicht mehr zu aelteren
Sicherungen — auch "Wiederherstellen" haette das falsche genommen.

- Satellit: das benutzte Kennwort liegt verschluesselt (Fernet, wie der
  Zugangsdaten-Speicher) in der Begleitdatei; Liste/Ansicht geben es nie
  heraus. Wiederherstellen nimmt genau dieses. fritzbox.backup_password
  liefert es (aeltere Sicherungen: aktuelles der Quelle + Hinweis).
  Feld "kennwort" im Sicherungs-Ergebnis heisst jetzt "verschluesselt_mit".
- Diagnostic-Server: Ergebnisse mit Kennwort gehen nur an den Browser,
  der gefragt hat; ohne Fragenden werden sie verworfen, nie an alle.
- Brain: fritzbox.backup_password ist fuer ARIA gesperrt (Kennwoerter
  gehen nicht ans Sprachmodell).
- Diagnostic: 👁 in der Kennwort-Spalte → Zeile mit dem Kennwort und
  "Kopieren" — klappt auch ohne https (Fallback ueber execCommand).

Getestet: Satellit (Kennwort nicht im Klartext gespeichert, nach
Kennwort-Aenderung zeigt 👁 und nimmt Wiederherstellen das alte), Fenster
in headless Chromium ohne Clipboard-API.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-31 18:26:37 +01:00
co-authored by Claude Opus 5.5
parent e44e2c2a70
commit 2cd8f1a0de
4 changed files with 108 additions and 8 deletions
+13 -1
View File
@@ -1415,7 +1415,15 @@ function connectRVS(forcePlain) {
broadcast({ type: "llm_openai_config_result", payload: msg.payload || {} });
} else if (msg.type === "sat_result") {
// Ergebnis einer Satelliten-Aktion → an den Browser (z.B. APK-Rollout).
broadcast({ type: "sat_result", payload: msg.payload || {} });
// Klartext-Kennwoerter (fritzbox.backup_password) nur an den Fragenden.
const p = msg.payload || {};
const asker = credsRevealWaiters.get(p.requestId);
if (asker) {
credsRevealWaiters.delete(p.requestId);
if (asker.readyState === WebSocket.OPEN) asker.send(JSON.stringify({ type: "sat_result", payload: p }));
} else if (!(p.result && typeof p.result === "object" && "kennwort" in p.result)) { // Kennwort ohne Fragenden: verwerfen
broadcast({ type: "sat_result", payload: p });
}
} else if (msg.type === "agent_history" || msg.type === "agent_history_list") {
// Live-Event (agent_history) bzw. Abfrage-Antwort (agent_history_list)
// der Bridge → an den Browser (Agent-Historie).
@@ -3370,6 +3378,10 @@ wss.on("connection", (ws) => {
params.rvs_tls = RVS_TLS === "true";
params.rvs_token = RVS_TOKEN;
}
if (String(msg.cmd || "") === "fritzbox.backup_password" && msg.requestId) {
credsRevealWaiters.set(String(msg.requestId), ws);
setTimeout(() => credsRevealWaiters.delete(String(msg.requestId)), 60000);
}
sendToRVS_raw({
type: "sat_command",
payload: {