feat(satellite): Sicherungs-Kennwort je FritzBox — hinterlegt, stark erzwungen, ARIA kennt es nicht

- Zugangsdaten FritzBox: neues Feld "Kennwort fuer Sicherungen"
  (backupPass, verschluesselt auf dem Satelliten, mit 👁 wie die anderen).
- fritzbox.backup nimmt: ausdruecklich angegebenes > hinterlegtes
  Sicherungs-Kennwort > FritzBox-Login. Das Ergebnis nennt nur die Quelle,
  nie das Kennwort. ARIAs Beschreibung: kein 'password' uebergeben.
- Kennwort-Regel (Formular UND Satellit): mind. 12 Zeichen, Gross- und
  Kleinbuchstabe, Ziffer, Sonderzeichen. Schwaches Kennwort wird beim
  Speichern abgelehnt (Meldung im Formular), und eine Sicherung mit
  schwachem Kennwort (auch ueber das Login) bricht mit Hinweis ab.
- Formular: Eingabefelder mit Auge gleich hoch wie die anderen.

Getestet im Satelliten: Regel, Speichern schwach/stark (Login bleibt),
Sicherung ohne/mit hinterlegtem Kennwort; das Kennwort taucht im Ergebnis
nicht auf.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-31 17:42:24 +01:00
co-authored by Claude Opus 5.5
parent e91435a9f6
commit c545a1cd77
3 changed files with 73 additions and 14 deletions
+25 -9
View File
@@ -299,7 +299,7 @@
</select>
<div id="dc-snmp-community-row">
<label style="font-size:11px;color:#8888AA;">Community</label>
<div style="display:flex;gap:4px;margin-bottom:4px;"><input id="dc-snmp-community" type="password" placeholder="public" style="flex:1;"><button type="button" class="btn secondary" onclick="dcEye('dc-snmp-community')" title="Anzeigen / verbergen" style="padding:2px 8px;font-size:12px;">👁</button></div>
<div style="display:flex;gap:4px;align-items:center;margin-bottom:4px;"><input id="dc-snmp-community" type="password" placeholder="public" style="flex:1;"><button type="button" class="btn secondary" onclick="dcEye('dc-snmp-community')" title="Anzeigen / verbergen" style="padding:2px 8px;font-size:12px;">👁</button></div>
</div>
<div id="dc-snmp-v3" style="display:none;">
<input id="dc-v3-user" placeholder="v3 User" style="width:100%;margin-bottom:4px;">
@@ -322,14 +322,15 @@
<div style="border:1px solid #1E1E2E;border-radius:6px;padding:10px;">
<div style="color:#E0E0F0;font-weight:600;margin-bottom:6px;">HTTP-Basic</div>
<input id="dc-http-user" placeholder="Benutzer (optional)" style="width:100%;margin-bottom:4px;">
<div style="display:flex;gap:4px;"><input id="dc-http-pass" type="password" placeholder="Passwort" style="flex:1;"><button type="button" class="btn secondary" onclick="dcEye('dc-http-pass')" title="Anzeigen / verbergen" style="padding:2px 8px;font-size:12px;">👁</button></div>
<div style="display:flex;gap:4px;align-items:center;"><input id="dc-http-pass" type="password" placeholder="Passwort" style="flex:1;"><button type="button" class="btn secondary" onclick="dcEye('dc-http-pass')" title="Anzeigen / verbergen" style="padding:2px 8px;font-size:12px;">👁</button></div>
</div>
<div style="border:1px solid #1E1E2E;border-radius:6px;padding:10px;">
<div style="color:#E0E0F0;font-weight:600;margin-bottom:6px;">FritzBox (TR-064)</div>
<div style="display:flex;gap:4px;margin-bottom:4px;"><input id="dc-fb-user" placeholder="FritzBox-Benutzer (z.B. fritz1234)" style="flex:1;">
<div style="display:flex;gap:4px;align-items:center;margin-bottom:4px;"><input id="dc-fb-user" placeholder="FritzBox-Benutzer (z.B. fritz1234)" style="flex:1;">
<button type="button" id="dc-fb-detect" class="btn secondary" onclick="dcDetectFritzUser()" title="Fragt die FritzBox, welche Benutzer es gibt (geht ohne Anmeldung)" style="padding:2px 8px;font-size:11px;white-space:nowrap;">Benutzer ermitteln</button></div>
<div style="display:flex;gap:4px;"><input id="dc-fb-pass" type="password" placeholder="FritzBox-Passwort" style="flex:1;"><button type="button" class="btn secondary" onclick="dcEye('dc-fb-pass')" title="Anzeigen / verbergen" style="padding:2px 8px;font-size:12px;">👁</button></div>
<div style="display:flex;gap:4px;align-items:center;margin-bottom:4px;"><input id="dc-fb-pass" type="password" placeholder="FritzBox-Passwort" style="flex:1;"><button type="button" class="btn secondary" onclick="dcEye('dc-fb-pass')" title="Anzeigen / verbergen" style="padding:2px 8px;font-size:12px;">👁</button></div>
<div style="display:flex;gap:4px;align-items:center;"><input id="dc-fb-backuppass" type="password" placeholder="Kennwort für Sicherungen (mind. 12 Zeichen, Aa, 1, !)" title="Damit verschlüsselt ARIA Sicherungen der Box. ARIA nutzt es, kennt es aber nicht — zum Wiederherstellen brauchst du es." style="flex:1;"><button type="button" class="btn secondary" onclick="dcEye('dc-fb-backuppass')" title="Anzeigen / verbergen" style="padding:2px 8px;font-size:12px;">👁</button></div>
</div>
<div style="border:1px solid #1E1E2E;border-radius:6px;padding:10px;">
@@ -338,7 +339,7 @@
<input id="dc-ssh-user" placeholder="Benutzer" style="flex:2;">
<input id="dc-ssh-port" placeholder="Port 22" style="flex:1;">
</div>
<div style="display:flex;gap:4px;margin-bottom:4px;"><input id="dc-ssh-pass" type="password" placeholder="Passwort (oder Key unten)" style="flex:1;"><button type="button" class="btn secondary" onclick="dcEye('dc-ssh-pass')" title="Anzeigen / verbergen" style="padding:2px 8px;font-size:12px;">👁</button></div>
<div style="display:flex;gap:4px;align-items:center;margin-bottom:4px;"><input id="dc-ssh-pass" type="password" placeholder="Passwort (oder Key unten)" style="flex:1;"><button type="button" class="btn secondary" onclick="dcEye('dc-ssh-pass')" title="Anzeigen / verbergen" style="padding:2px 8px;font-size:12px;">👁</button></div>
<div style="display:flex;gap:4px;"><textarea id="dc-ssh-key" placeholder="Privater SSH-Key (optional, PEM/OpenSSH)" style="flex:1;height:60px;font-family:monospace;font-size:10px;"></textarea><button type="button" class="btn secondary" onclick="dcEye('dc-ssh-key')" title="Anzeigen / verbergen" style="padding:2px 8px;font-size:12px;">👁</button></div>
</div>
@@ -2716,7 +2717,7 @@
if (msg.type === 'sat_creds_result') {
const p = msg.payload || msg;
const el = document.getElementById('dc-status');
if (el) el.textContent = p.ok ? '✓ Gespeichert.' : '⚠ Fehlgeschlagen.';
if (el) el.textContent = p.ok ? '✓ Gespeichert.' : ('⚠ ' + (p.error || 'Fehlgeschlagen.'));
if (p.ok && p.satellite) requestSatCreds(p.satellite);
if (p.ok) setTimeout(closeDeviceCreds, 700);
return;
@@ -6326,7 +6327,8 @@
const DC_SECRET_FIELDS = {
'dc-snmp-community': ['snmp', 'community'], 'dc-v3-authkey': ['snmp', 'v3', 'authKey'],
'dc-v3-privkey': ['snmp', 'v3', 'privKey'], 'dc-http-pass': ['http', 'pass'],
'dc-fb-pass': ['fritzbox', 'pass'], 'dc-ssh-pass': ['ssh', 'pass'], 'dc-ssh-key': ['ssh', 'key'],
'dc-fb-pass': ['fritzbox', 'pass'], 'dc-fb-backuppass': ['fritzbox', 'backupPass'],
'dc-ssh-pass': ['ssh', 'pass'], 'dc-ssh-key': ['ssh', 'key'],
};
let dcStored = {}, dcRevealed = false, dcFilled = false, dcFbReq = '';
const _dcEl = id => document.getElementById(id);
@@ -6334,7 +6336,7 @@
credsModalSat = satId; credsModalIp = ip;
dcStored = {}; dcRevealed = false; dcFilled = false; dcFbReq = '';
_dcEl('dev-creds-title').textContent = (name || ip) + ' (' + ip + ')';
['dc-snmp-community','dc-v3-user','dc-v3-authkey','dc-v3-privkey','dc-http-user','dc-http-pass','dc-fb-user','dc-fb-pass','dc-ssh-user','dc-ssh-port','dc-ssh-pass','dc-ssh-key'].forEach(id => {
['dc-snmp-community','dc-v3-user','dc-v3-authkey','dc-v3-privkey','dc-http-user','dc-http-pass','dc-fb-user','dc-fb-pass','dc-fb-backuppass','dc-ssh-user','dc-ssh-port','dc-ssh-pass','dc-ssh-key'].forEach(id => {
const el = _dcEl(id);
el.value = '';
if (el.dataset.ph == null) el.dataset.ph = el.placeholder;
@@ -6434,7 +6436,21 @@
// FritzBox: Benutzer optional — es reicht ein Passwort.
const fu = document.getElementById('dc-fb-user').value.trim();
const fp = document.getElementById('dc-fb-pass').value;
if (fu || fp || dcStored['dc-fb-pass']) { creds.fritzbox = { user: fu }; if (fp) creds.fritzbox.pass = fp; }
const fbp = document.getElementById('dc-fb-backuppass').value;
if (fbp) {
const miss = [];
if (fbp.length < 12) miss.push('mindestens 12 Zeichen');
if (!/[A-Z]/.test(fbp)) miss.push('ein Großbuchstabe');
if (!/[a-z]/.test(fbp)) miss.push('ein Kleinbuchstabe');
if (!/\d/.test(fbp)) miss.push('eine Ziffer');
if (!/[^A-Za-z0-9]/.test(fbp)) miss.push('ein Sonderzeichen');
if (miss.length) { document.getElementById('dc-status').textContent = '⚠ Sicherungs-Kennwort zu schwach — es fehlt: ' + miss.join(', ') + '.'; return; }
}
if (fu || fp || fbp || dcStored['dc-fb-pass'] || dcStored['dc-fb-backuppass']) {
creds.fritzbox = { user: fu };
if (fp) creds.fritzbox.pass = fp;
if (fbp) creds.fritzbox.backupPass = fbp;
}
// SSH: Benutzer + (Passwort ODER Key). Port optional.
const su = document.getElementById('dc-ssh-user').value.trim();
const sp = document.getElementById('dc-ssh-pass').value;