feat(satellite): gespeicherte Zugangsdaten anzeigen, Passwort per Auge, FritzBox-Benutzer ermitteln
Das Zugangsdaten-Formular war beim erneuten Oeffnen leer, obwohl der Satellit alles gespeichert hatte. Satellit: - sat_creds_list mit ip liefert je Typ die Felder ohne Geheimnisse (Benutzer, Port, SNMP-Version/v3-Stufe) und has_<feld> fuer Passwoerter; mit reveal (nur zusammen mit ip) auch die Geheimnisse dieses Geraets. - sat_creds_set behaelt ein gespeichertes Geheimnis, wenn das Feld nicht mitgeschickt wird — Benutzer aendern, ohne das Passwort neu zu tippen. Diagnostic-Server: Antworten mit Klartext (revealed) gehen nur an den Browser, der gefragt hat, nicht an alle offenen Tabs. Formular: Benutzer/Port/Version stehen beim Oeffnen drin, Passwoerter als "•••••• gespeichert"; 👁 holt sie im Klartext bzw. schaltet um. Leeres Passwortfeld = gespeichertes behalten. FritzBox: "Benutzer ermitteln" (nur bei als FritzBox erkannten Geraeten) liest login_sid.lua?version=2 — die Box nennt ihre Benutzer ohne Anmeldung; eingetragen wird der zuletzt angemeldete. Getestet: Satelliten-Logik per Unit-Test, Formular in headless Chromium (Anzeige, Speichern ohne Passwort, Auge, Benutzer ermitteln). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+116
-23
@@ -286,7 +286,7 @@
|
||||
<h3 style="margin:0;color:#fff;">🔑 Zugangsdaten: <span id="dev-creds-title">—</span></h3>
|
||||
<button onclick="closeDeviceCreds()" style="background:none;border:none;color:#8888AA;font-size:22px;cursor:pointer;">×</button>
|
||||
</div>
|
||||
<p style="color:#8888AA;font-size:11px;margin:0;">Wird verschluesselt auf dem Satelliten gespeichert. Leere Felder speichern nichts / loeschen den Typ. Der Satellit nutzt sie automatisch bei SNMP / HTTP / FritzBox.</p>
|
||||
<p style="color:#8888AA;font-size:11px;margin:0;">Wird verschlüsselt auf dem Satelliten gespeichert. Gespeicherte Passwörter stehen als •••••• da — 👁 zeigt sie an. Ein leeres Passwortfeld lässt das gespeicherte Passwort unverändert. Der Satellit nutzt die Daten automatisch bei SNMP / HTTP / FritzBox / SSH.</p>
|
||||
|
||||
<div style="border:1px solid #1E1E2E;border-radius:6px;padding:10px;">
|
||||
<div style="color:#E0E0F0;font-weight:600;margin-bottom:6px;">SNMP</div>
|
||||
@@ -299,7 +299,7 @@
|
||||
</select>
|
||||
<div id="dc-snmp-community-row">
|
||||
<label style="font-size:11px;color:#8888AA;">Community</label>
|
||||
<input id="dc-snmp-community" placeholder="public" style="width:100%;margin-bottom:4px;">
|
||||
<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>
|
||||
<div id="dc-snmp-v3" style="display:none;">
|
||||
<input id="dc-v3-user" placeholder="v3 User" style="width:100%;margin-bottom:4px;">
|
||||
@@ -310,11 +310,11 @@
|
||||
</select>
|
||||
<div style="display:flex;gap:4px;margin-bottom:4px;">
|
||||
<select id="dc-v3-authproto" style="flex:1;"><option value="SHA">SHA</option><option value="MD5">MD5</option><option value="SHA-256">SHA-256</option></select>
|
||||
<input id="dc-v3-authkey" placeholder="Auth-Passwort" style="flex:2;">
|
||||
<input id="dc-v3-authkey" type="password" placeholder="Auth-Passwort" style="flex:2;"><button type="button" class="btn secondary" onclick="dcEye('dc-v3-authkey')" title="Anzeigen / verbergen" style="padding:2px 8px;font-size:12px;">👁</button>
|
||||
</div>
|
||||
<div style="display:flex;gap:4px;">
|
||||
<select id="dc-v3-privproto" style="flex:1;"><option value="AES">AES</option><option value="DES">DES</option><option value="AES-256">AES-256</option></select>
|
||||
<input id="dc-v3-privkey" placeholder="Priv-Passwort" style="flex:2;">
|
||||
<input id="dc-v3-privkey" type="password" placeholder="Priv-Passwort" style="flex:2;"><button type="button" class="btn secondary" onclick="dcEye('dc-v3-privkey')" title="Anzeigen / verbergen" style="padding:2px 8px;font-size:12px;">👁</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -322,13 +322,14 @@
|
||||
<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;">
|
||||
<input id="dc-http-pass" type="password" placeholder="Passwort" style="width:100%;">
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<input id="dc-fb-user" placeholder="FritzBox-Benutzer (optional, leer wenn keiner)" style="width:100%;margin-bottom:4px;">
|
||||
<input id="dc-fb-pass" type="password" placeholder="FritzBox-Passwort" style="width:100%;">
|
||||
<div style="display:flex;gap:4px;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>
|
||||
|
||||
<div style="border:1px solid #1E1E2E;border-radius:6px;padding:10px;">
|
||||
@@ -337,8 +338,8 @@
|
||||
<input id="dc-ssh-user" placeholder="Benutzer" style="flex:2;">
|
||||
<input id="dc-ssh-port" placeholder="Port 22" style="flex:1;">
|
||||
</div>
|
||||
<input id="dc-ssh-pass" type="password" placeholder="Passwort (oder Key unten)" style="width:100%;margin-bottom:4px;">
|
||||
<textarea id="dc-ssh-key" placeholder="Privater SSH-Key (optional, PEM/OpenSSH)" style="width:100%;height:60px;font-family:monospace;font-size:10px;"></textarea>
|
||||
<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;"><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>
|
||||
|
||||
<div style="display:flex;gap:8px;justify-content:flex-end;">
|
||||
@@ -2695,9 +2696,20 @@
|
||||
if (msg.type === 'sat_creds_list_result') {
|
||||
const p = msg.payload || msg;
|
||||
const sat = p.satellite || '';
|
||||
const map = {};
|
||||
let map = {};
|
||||
if (p.ip) {
|
||||
// Antwort fuer EIN Geraet (Formular) — Liste der anderen behalten
|
||||
map = { ...(satCreds[sat] || {}) };
|
||||
delete map[p.ip];
|
||||
}
|
||||
(p.items || []).forEach(it => { map[it.ip] = it.types || []; });
|
||||
satCreds[sat] = map;
|
||||
const modalOpen = _dcEl('dev-creds-modal').style.display === 'flex';
|
||||
const it = (p.items || []).find(x => x.ip === credsModalIp);
|
||||
if (modalOpen && sat === credsModalSat && it && it.detail && (p.revealed || !dcFilled)) {
|
||||
dcFill(it.detail, !!p.revealed);
|
||||
_dcEl('dc-status').textContent = 'Hinterlegt: ' + (it.types || []).join(', ') + (p.revealed ? ' — im Klartext angezeigt.' : '');
|
||||
}
|
||||
renderSatellites();
|
||||
return;
|
||||
}
|
||||
@@ -6060,6 +6072,7 @@
|
||||
requestId: apkRequestId, params: { ip: apkTarget.ip } });
|
||||
}
|
||||
function onSatResult(p) {
|
||||
if (p && dcFbReq && p.requestId === dcFbReq) { dcOnFritzUsers(p); return; }
|
||||
if (!apkRequestId || !p || p.requestId !== apkRequestId) return;
|
||||
const status = document.getElementById('apk-status');
|
||||
const log = document.getElementById('apk-log');
|
||||
@@ -6252,7 +6265,8 @@
|
||||
'style="padding:1px 6px;font-size:11px;margin-left:6px;">✎</button>' : '';
|
||||
const hasCreds = d.ip && (satCreds[s.id] || {})[d.ip] && (satCreds[s.id][d.ip] || []).length;
|
||||
const keyBtn = d.ip ? '<button class="btn secondary" title="Zugangsdaten (SNMP / HTTP / FritzBox)" ' +
|
||||
'onclick="openDeviceCreds(\'' + escapeHtml(s.id) + '\',\'' + escapeHtml(d.ip) + '\',\'' + escapeHtml(d.name || d.ip) + '\')" ' +
|
||||
'onclick="openDeviceCreds(\'' + escapeHtml(s.id) + '\',\'' + escapeHtml(d.ip) + '\',\'' + escapeHtml(d.name || d.ip) + '\',' +
|
||||
(/fritz/i.test([d.name, d.model, d.type, d.service].join(' ')) ? 'true' : 'false') + ')" ' +
|
||||
'style="padding:1px 6px;font-size:11px;margin-left:8px;">' + (hasCreds ? '🔑 ✓' : '🔑') + '</button>' : '';
|
||||
// APK ausrollen: bei ADB-faehigen Geraeten (Fire TV & Co.).
|
||||
const canAdb = d.ip && (d.adbCapable || ['firetv', 'android', 'android-adb', 'androidtv', 'dial'].includes(d.type));
|
||||
@@ -6306,16 +6320,90 @@
|
||||
document.getElementById('dc-snmp-v3').style.display = (v === '3') ? 'block' : 'none';
|
||||
document.getElementById('dc-snmp-community-row').style.display = (v === '1' || v === '2c') ? 'block' : 'none';
|
||||
}
|
||||
function openDeviceCreds(satId, ip, name) {
|
||||
// Gespeichertes wird beim Oeffnen angezeigt: Benutzer/Port/Version im
|
||||
// Klartext, Passwoerter nur als "gespeichert". Das Auge holt sie im Klartext
|
||||
// (geht nur an diesen Browser, nicht an andere offene Diagnostic-Tabs).
|
||||
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'],
|
||||
};
|
||||
let dcStored = {}, dcRevealed = false, dcFilled = false, dcFbReq = '';
|
||||
const _dcEl = id => document.getElementById(id);
|
||||
function openDeviceCreds(satId, ip, name, isFritz) {
|
||||
credsModalSat = satId; credsModalIp = ip;
|
||||
document.getElementById('dev-creds-title').textContent = (name || ip) + ' (' + ip + ')';
|
||||
// Felder leeren — Secrets werden NIE zurueckgeliefert, nur neu gesetzt.
|
||||
['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 => document.getElementById(id).value = '');
|
||||
document.getElementById('dc-snmp-version').value = '';
|
||||
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 => {
|
||||
const el = _dcEl(id);
|
||||
el.value = '';
|
||||
if (el.dataset.ph == null) el.dataset.ph = el.placeholder;
|
||||
el.placeholder = el.dataset.ph;
|
||||
if (DC_SECRET_FIELDS[id] && el.tagName === 'INPUT') el.type = 'password';
|
||||
});
|
||||
_dcEl('dc-snmp-version').value = '';
|
||||
dcToggleV3();
|
||||
_dcEl('dc-fb-detect').style.display = isFritz ? '' : 'none';
|
||||
const set = ((satCreds[satId] || {})[ip]) || [];
|
||||
document.getElementById('dc-status').textContent = set.length ? ('Bereits hinterlegt: ' + set.join(', ')) : 'Noch keine Zugangsdaten hinterlegt.';
|
||||
document.getElementById('dev-creds-modal').style.display = 'flex';
|
||||
_dcEl('dc-status').textContent = set.length ? ('Bereits hinterlegt: ' + set.join(', ') + ' — lade …') : 'Noch keine Zugangsdaten hinterlegt.';
|
||||
_dcEl('dev-creds-modal').style.display = 'flex';
|
||||
if (set.length) send({ action: 'sat_creds_list', satellite: satId, ip });
|
||||
}
|
||||
function dcFill(detail, revealed) {
|
||||
const g = path => path.reduce((o, k) => (o ? o[k] : undefined), detail || {});
|
||||
if (!dcFilled) {
|
||||
const sn = detail.snmp || {};
|
||||
if (sn.version) { _dcEl('dc-snmp-version').value = sn.version; dcToggleV3(); }
|
||||
const v3 = sn.v3 || {};
|
||||
if (v3.user) _dcEl('dc-v3-user').value = v3.user;
|
||||
if (v3.level) _dcEl('dc-v3-level').value = v3.level;
|
||||
if (v3.authProto) _dcEl('dc-v3-authproto').value = v3.authProto;
|
||||
if (v3.privProto) _dcEl('dc-v3-privproto').value = v3.privProto;
|
||||
_dcEl('dc-http-user').value = (detail.http || {}).user || '';
|
||||
_dcEl('dc-fb-user').value = (detail.fritzbox || {}).user || '';
|
||||
_dcEl('dc-ssh-user').value = (detail.ssh || {}).user || '';
|
||||
_dcEl('dc-ssh-port').value = (detail.ssh || {}).port || '';
|
||||
}
|
||||
for (const [id, path] of Object.entries(DC_SECRET_FIELDS)) {
|
||||
const has = !!g([...path.slice(0, -1), 'has_' + path[path.length - 1]]);
|
||||
const el = _dcEl(id);
|
||||
dcStored[id] = has;
|
||||
if (!has) continue;
|
||||
if (revealed) { el.value = g(path) || ''; if (el.tagName === 'INPUT') el.type = 'text'; }
|
||||
else if (!el.value) el.placeholder = '•••••• gespeichert';
|
||||
}
|
||||
dcFilled = true;
|
||||
if (revealed) dcRevealed = true;
|
||||
}
|
||||
function dcEye(id) {
|
||||
const el = _dcEl(id);
|
||||
if (dcStored[id] && !dcRevealed && !el.value) {
|
||||
_dcEl('dc-status').textContent = 'Hole gespeicherte Zugangsdaten …';
|
||||
send({ action: 'sat_creds_list', satellite: credsModalSat, ip: credsModalIp, reveal: true });
|
||||
return;
|
||||
}
|
||||
if (el.tagName === 'INPUT') el.type = el.type === 'password' ? 'text' : 'password';
|
||||
}
|
||||
// FritzBox: login_sid.lua listet die Benutzer auch ohne Anmeldung. Wer sich
|
||||
// im Browser nur mit Passwort anmeldet, hat trotzdem einen (fritz1234).
|
||||
function dcDetectFritzUser() {
|
||||
dcFbReq = 'fb_' + Date.now();
|
||||
_dcEl('dc-status').textContent = 'Frage die FritzBox nach ihren Benutzern …';
|
||||
send({ action: 'sat_command', satellite: credsModalSat, cmd: 'http.get', requestId: dcFbReq,
|
||||
params: { url: 'http://' + credsModalIp + '/login_sid.lua?version=2' } });
|
||||
}
|
||||
function dcOnFritzUsers(p) {
|
||||
dcFbReq = '';
|
||||
const body = String(((p.result || {}).body) || '');
|
||||
const users = [...body.matchAll(/<User([^>]*)>([^<]+)<\/User>/g)].map(m => ({ name: m[2].trim(), last: /last="1"/.test(m[1]) }));
|
||||
const st = _dcEl('dc-status');
|
||||
if (!p.ok) { st.textContent = '⚠ FritzBox nicht erreichbar: ' + (p.error || 'Fehler'); return; }
|
||||
if (!users.length) { st.textContent = 'Die Box nennt keine Benutzer — dann reicht das Passwort, Benutzer leer lassen.'; return; }
|
||||
const pick = users.find(u => u.last) || users[0];
|
||||
_dcEl('dc-fb-user').value = pick.name;
|
||||
st.textContent = users.length > 1
|
||||
? `Benutzer auf der Box: ${users.map(u => u.name).join(', ')} — eingetragen: ${pick.name} (zuletzt angemeldet).`
|
||||
: `Benutzer ermittelt: ${pick.name}`;
|
||||
}
|
||||
function closeDeviceCreds() { document.getElementById('dev-creds-modal').style.display = 'none'; }
|
||||
function saveDeviceCreds() {
|
||||
@@ -6332,21 +6420,26 @@
|
||||
} else if (v === '1' || v === '2c') {
|
||||
const c = document.getElementById('dc-snmp-community').value.trim();
|
||||
if (c) creds.snmp = { version: v, community: c };
|
||||
else if (dcStored['dc-snmp-community']) creds.snmp = { version: v }; // Community bleibt
|
||||
}
|
||||
if (creds.snmp && creds.snmp.v3) { // leere Schluesselfelder = gespeicherte behalten
|
||||
if (!creds.snmp.v3.authKey) delete creds.snmp.v3.authKey;
|
||||
if (!creds.snmp.v3.privKey) delete creds.snmp.v3.privKey;
|
||||
}
|
||||
// Leeres Passwortfeld = gespeichertes Passwort behalten (Satellit fuellt es auf).
|
||||
// HTTP-Basic: Benutzer optional (manche Geraete nutzen Password-only).
|
||||
const hu = document.getElementById('dc-http-user').value.trim();
|
||||
const hp = document.getElementById('dc-http-pass').value;
|
||||
if (hu || hp) creds.http = { user: hu, pass: hp };
|
||||
// FritzBox: Benutzer optional (Password-only-Boxen haben keinen) — es reicht
|
||||
// ein Passwort. Gespeichert wird, sobald User ODER Passwort gesetzt ist.
|
||||
if (hu || hp || dcStored['dc-http-pass']) { creds.http = { user: hu }; if (hp) creds.http.pass = hp; }
|
||||
// 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) creds.fritzbox = { user: fu, pass: fp };
|
||||
if (fu || fp || dcStored['dc-fb-pass']) { creds.fritzbox = { user: fu }; if (fp) creds.fritzbox.pass = fp; }
|
||||
// SSH: Benutzer + (Passwort ODER Key). Port optional.
|
||||
const su = document.getElementById('dc-ssh-user').value.trim();
|
||||
const sp = document.getElementById('dc-ssh-pass').value;
|
||||
const sk = document.getElementById('dc-ssh-key').value.trim();
|
||||
if (su && (sp || sk)) {
|
||||
if (su && (sp || sk || dcStored['dc-ssh-pass'] || dcStored['dc-ssh-key'])) {
|
||||
creds.ssh = { user: su };
|
||||
if (sp) creds.ssh.pass = sp;
|
||||
if (sk) creds.ssh.key = sk;
|
||||
|
||||
Reference in New Issue
Block a user