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:
@@ -3268,6 +3268,11 @@ class Agent:
|
|||||||
if not sat or not action:
|
if not sat or not action:
|
||||||
return "FEHLER: satellite und action sind Pflicht."
|
return "FEHLER: satellite und action sind Pflicht."
|
||||||
params = arguments.get("params") if isinstance(arguments.get("params"), dict) else {}
|
params = arguments.get("params") if isinstance(arguments.get("params"), dict) else {}
|
||||||
|
# Kennwoerter im Klartext gehen nie ans Sprachmodell — die zeigt nur
|
||||||
|
# das Diagnostic (👁) in Stefans Browser.
|
||||||
|
if action in ("fritzbox.backup_password",):
|
||||||
|
return ("FEHLER: Kennwoerter zeige ich nicht an — Stefan sieht es im Diagnostic "
|
||||||
|
"(Satelliten → Geraet → 💾 → 👁 bei der Sicherung).")
|
||||||
# Langlaeufer brauchen mehr als den Bridge-Default (20s): eine APK ueber
|
# Langlaeufer brauchen mehr als den Bridge-Default (20s): eine APK ueber
|
||||||
# WLAN zu installieren dauert 30-60s. Ohne das meldet ARIA "Timeout",
|
# WLAN zu installieren dauert 30-60s. Ohne das meldet ARIA "Timeout",
|
||||||
# obwohl die Installation noch laeuft.
|
# obwohl die Installation noch laeuft.
|
||||||
|
|||||||
+45
-3
@@ -6349,7 +6349,7 @@
|
|||||||
m.innerHTML = `<div style="background:#12121E;border:1px solid #444466;border-radius:8px;padding:16px 18px;max-width:760px;width:100%;max-height:88vh;overflow-y:auto;color:#E0E0F0;font-size:13px;">${html}</div>`;
|
m.innerHTML = `<div style="background:#12121E;border:1px solid #444466;border-radius:8px;padding:16px 18px;max-width:760px;width:100%;max-height:88vh;overflow-y:auto;color:#E0E0F0;font-size:13px;">${html}</div>`;
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
function closeFbBackups() { const m = document.getElementById('fb-backups-modal'); if (m) m.remove(); fbB = null; }
|
function closeFbBackups() { const m = document.getElementById('fb-backups-modal'); if (m) m.remove(); fbB = null; } // vergisst auch angezeigte Kennwoerter
|
||||||
const _fbFmtDate = iso => { try { return new Date(iso).toLocaleString('de-DE', { dateStyle: 'medium', timeStyle: 'short' }); } catch (_) { return iso; } };
|
const _fbFmtDate = iso => { try { return new Date(iso).toLocaleString('de-DE', { dateStyle: 'medium', timeStyle: 'short' }); } catch (_) { return iso; } };
|
||||||
const _fbPwSrc = { backupPass: 'Sicherungs-Kennwort', login: 'FritzBox-Kennwort', angegeben: 'eigenes Kennwort' };
|
const _fbPwSrc = { backupPass: 'Sicherungs-Kennwort', login: 'FritzBox-Kennwort', angegeben: 'eigenes Kennwort' };
|
||||||
async function openFbBackups(sat, ip, name) {
|
async function openFbBackups(sat, ip, name) {
|
||||||
@@ -6368,12 +6368,19 @@
|
|||||||
<td style="padding:6px;">${escapeHtml(_fbFmtDate(b.erstellt) || b.datei)}</td>
|
<td style="padding:6px;">${escapeHtml(_fbFmtDate(b.erstellt) || b.datei)}</td>
|
||||||
<td style="padding:6px;color:#9a9ab5;">${escapeHtml(b.modell || '')}${b.firmware ? ' · FW ' + escapeHtml(b.firmware) : ''}</td>
|
<td style="padding:6px;color:#9a9ab5;">${escapeHtml(b.modell || '')}${b.firmware ? ' · FW ' + escapeHtml(b.firmware) : ''}</td>
|
||||||
<td style="padding:6px;color:#9a9ab5;">${_fmtBytes(b.bytes)}</td>
|
<td style="padding:6px;color:#9a9ab5;">${_fmtBytes(b.bytes)}</td>
|
||||||
<td style="padding:6px;color:#9a9ab5;" title="Verschlüsselt mit diesem Kennwort — zum Wiederherstellen nötig. „Wiederherstellen“ nimmt es automatisch; beim Einspielen von Hand in der FritzBox-Oberfläche musst du genau dieses eingeben.">🔒 ${escapeHtml(_fbPwSrc[b.kennwort] || b.kennwort || '')}</td>
|
<td style="padding:6px;color:#9a9ab5;" title="Verschlüsselt mit diesem Kennwort — zum Wiederherstellen nötig. „Wiederherstellen“ nimmt es automatisch; beim Einspielen von Hand in der FritzBox-Oberfläche musst du genau dieses eingeben.">🔒 ${escapeHtml(_fbPwSrc[b.kennwort] || b.kennwort || '')}
|
||||||
|
<button class="btn secondary" onclick="fbShowPw(${i})" title="Kennwort dieser Sicherung anzeigen" style="padding:1px 6px;font-size:11px;margin-left:4px;">👁</button></td>
|
||||||
<td style="padding:6px;white-space:nowrap;">
|
<td style="padding:6px;white-space:nowrap;">
|
||||||
<button class="btn secondary" onclick="fbViewBackup(${i})" title="Inhalt ansehen" style="padding:2px 8px;font-size:11px;">ℹ</button>
|
<button class="btn secondary" onclick="fbViewBackup(${i})" title="Inhalt ansehen" style="padding:2px 8px;font-size:11px;">ℹ</button>
|
||||||
<button class="btn secondary" onclick="fbDownloadBackup(${i})" title="Herunterladen (.export, verschlüsselt mit dem Sicherungs-Kennwort)" style="padding:2px 8px;font-size:11px;">⬇</button>
|
<button class="btn secondary" onclick="fbDownloadBackup(${i})" title="Herunterladen (.export, verschlüsselt mit dem Sicherungs-Kennwort)" style="padding:2px 8px;font-size:11px;">⬇</button>
|
||||||
<button class="btn secondary" onclick="fbRestoreBackup(${i})" title="Diese Sicherung einspielen" style="padding:2px 8px;font-size:11px;">Wiederherstellen</button>
|
<button class="btn secondary" onclick="fbRestoreBackup(${i})" title="Diese Sicherung einspielen" style="padding:2px 8px;font-size:11px;">Wiederherstellen</button>
|
||||||
<button class="btn secondary" onclick="fbDeleteBackup(${i})" title="Löschen" style="padding:2px 8px;font-size:11px;color:#FF6E6E;">🗑</button></td></tr>`).join('');
|
<button class="btn secondary" onclick="fbDeleteBackup(${i})" title="Löschen" style="padding:2px 8px;font-size:11px;color:#FF6E6E;">🗑</button></td></tr>`
|
||||||
|
+ (fbB.pw && fbB.pw[b.datei] ? `<tr><td colspan="5" style="padding:4px 6px 10px;">
|
||||||
|
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
|
||||||
|
<span style="color:#8888AA;font-size:11px;">Kennwort dieser Sicherung:</span>
|
||||||
|
<code style="background:#000;border:1px solid #333;border-radius:4px;padding:3px 8px;font-size:13px;color:#E0E0F0;user-select:all;">${escapeHtml(fbB.pw[b.datei].kennwort)}</code>
|
||||||
|
<button class="btn secondary" onclick="fbCopyPw('${escapeHtmlAttr(b.datei)}', this)" style="padding:2px 8px;font-size:11px;">Kopieren</button></div>
|
||||||
|
${fbB.pw[b.datei].hinweis ? `<div style="color:#FF9F0A;font-size:11px;margin-top:4px;">${escapeHtml(fbB.pw[b.datei].hinweis)}</div>` : ''}</td></tr>` : '')).join('');
|
||||||
_fbModal(`<div style="display:flex;justify-content:space-between;align-items:center;gap:10px;margin-bottom:8px;">
|
_fbModal(`<div style="display:flex;justify-content:space-between;align-items:center;gap:10px;margin-bottom:8px;">
|
||||||
<h3 style="margin:0;">💾 Sicherungen: ${escapeHtml(fbB.name)} <span style="color:#8888AA;font-size:12px;">(${escapeHtml(fbB.ip)})</span></h3>
|
<h3 style="margin:0;">💾 Sicherungen: ${escapeHtml(fbB.name)} <span style="color:#8888AA;font-size:12px;">(${escapeHtml(fbB.ip)})</span></h3>
|
||||||
<button onclick="closeFbBackups()" style="background:none;border:none;color:#8888AA;font-size:22px;cursor:pointer;">×</button></div>
|
<button onclick="closeFbBackups()" style="background:none;border:none;color:#8888AA;font-size:22px;cursor:pointer;">×</button></div>
|
||||||
@@ -6472,6 +6479,41 @@
|
|||||||
const r = await _fbRun('Wiederherstellen läuft', 'fritzbox.restore', { file: b.datei, confirmed: true });
|
const r = await _fbRun('Wiederherstellen läuft', 'fritzbox.restore', { file: b.datei, confirmed: true });
|
||||||
if (fbB) _fbShowResult(r, 'Sicherung eingespielt');
|
if (fbB) _fbShowResult(r, 'Sicherung eingespielt');
|
||||||
}
|
}
|
||||||
|
// Kopieren auch ohne https: navigator.clipboard gibt es nur in "sicheren
|
||||||
|
// Kontexten" — sonst ueber ein unsichtbares Textfeld + execCommand('copy').
|
||||||
|
function copyText(t) {
|
||||||
|
const fallback = () => {
|
||||||
|
const ta = document.createElement('textarea');
|
||||||
|
ta.value = t; ta.setAttribute('readonly', '');
|
||||||
|
ta.style.cssText = 'position:fixed;top:-1000px;left:0;opacity:0;';
|
||||||
|
document.body.appendChild(ta); ta.select();
|
||||||
|
let ok = false;
|
||||||
|
try { ok = document.execCommand('copy'); } catch (_) {}
|
||||||
|
ta.remove();
|
||||||
|
return ok;
|
||||||
|
};
|
||||||
|
if (navigator.clipboard && window.isSecureContext) return navigator.clipboard.writeText(t).then(() => true, fallback);
|
||||||
|
return Promise.resolve(fallback());
|
||||||
|
}
|
||||||
|
async function fbShowPw(i) {
|
||||||
|
const b = fbB.list[i];
|
||||||
|
if (!b) return;
|
||||||
|
fbB.pw = fbB.pw || {};
|
||||||
|
if (fbB.pw[b.datei]) { delete fbB.pw[b.datei]; _fbRender(''); return; } // zweiter Klick: verbergen
|
||||||
|
_fbRender('Hole Kennwort …');
|
||||||
|
const r = await fbCmd('fritzbox.backup_password', { file: b.datei });
|
||||||
|
if (!fbB) return;
|
||||||
|
if (!r.ok) { _fbRender('⚠ ' + (r.error || 'Kennwort nicht abrufbar')); return; }
|
||||||
|
fbB.pw[b.datei] = { kennwort: r.result.kennwort, hinweis: r.result.hinweis || '' };
|
||||||
|
_fbRender('');
|
||||||
|
}
|
||||||
|
async function fbCopyPw(datei, btn) {
|
||||||
|
const e = fbB && fbB.pw && fbB.pw[datei];
|
||||||
|
if (!e) return;
|
||||||
|
const ok = await copyText(e.kennwort);
|
||||||
|
btn.textContent = ok ? '✓ kopiert' : 'Kopieren ging nicht — markieren + Strg+C';
|
||||||
|
setTimeout(() => { if (btn.isConnected) btn.textContent = 'Kopieren'; }, 2500);
|
||||||
|
}
|
||||||
async function fbDownloadBackup(i) {
|
async function fbDownloadBackup(i) {
|
||||||
const b = fbB.list[i];
|
const b = fbB.list[i];
|
||||||
if (!b) return;
|
if (!b) return;
|
||||||
|
|||||||
+13
-1
@@ -1415,7 +1415,15 @@ function connectRVS(forcePlain) {
|
|||||||
broadcast({ type: "llm_openai_config_result", payload: msg.payload || {} });
|
broadcast({ type: "llm_openai_config_result", payload: msg.payload || {} });
|
||||||
} else if (msg.type === "sat_result") {
|
} else if (msg.type === "sat_result") {
|
||||||
// Ergebnis einer Satelliten-Aktion → an den Browser (z.B. APK-Rollout).
|
// 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") {
|
} else if (msg.type === "agent_history" || msg.type === "agent_history_list") {
|
||||||
// Live-Event (agent_history) bzw. Abfrage-Antwort (agent_history_list)
|
// Live-Event (agent_history) bzw. Abfrage-Antwort (agent_history_list)
|
||||||
// der Bridge → an den Browser (Agent-Historie).
|
// der Bridge → an den Browser (Agent-Historie).
|
||||||
@@ -3370,6 +3378,10 @@ wss.on("connection", (ws) => {
|
|||||||
params.rvs_tls = RVS_TLS === "true";
|
params.rvs_tls = RVS_TLS === "true";
|
||||||
params.rvs_token = RVS_TOKEN;
|
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({
|
sendToRVS_raw({
|
||||||
type: "sat_command",
|
type: "sat_command",
|
||||||
payload: {
|
payload: {
|
||||||
|
|||||||
+45
-4
@@ -1524,7 +1524,11 @@ def _fb_backup(ip: str, user: str, pw: str, params: dict) -> dict:
|
|||||||
"bytes": len(d.content), "model": fi.get("NewModelName", ""), "firmware": fi.get("NewSoftwareVersion", ""),
|
"bytes": len(d.content), "model": fi.get("NewModelName", ""), "firmware": fi.get("NewSoftwareVersion", ""),
|
||||||
"serial": fi.get("NewSerialNumber", ""),
|
"serial": fi.get("NewSerialNumber", ""),
|
||||||
# nur die QUELLE des Kennworts — zum Wiederherstellen noetig, nie der Wert
|
# nur die QUELLE des Kennworts — zum Wiederherstellen noetig, nie der Wert
|
||||||
"kennwort": "angegeben" if params.get("password") else ("backupPass" if stored else "login")}
|
"kennwort": "angegeben" if params.get("password") else ("backupPass" if stored else "login"),
|
||||||
|
# Das benutzte Kennwort selbst, verschluesselt wie der Zugangsdaten-
|
||||||
|
# Speicher: passt auch noch, wenn das Sicherungs-Kennwort spaeter
|
||||||
|
# geaendert wird (Wiederherstellen + 👁 im Diagnostic).
|
||||||
|
"kennwort_enc": _creds_cipher().encrypt(export_pw.encode()).decode()}
|
||||||
with open(path[:-len(".export")] + ".json", "w", encoding="utf-8") as fh:
|
with open(path[:-len(".export")] + ".json", "w", encoding="utf-8") as fh:
|
||||||
json.dump(meta, fh, ensure_ascii=False, indent=1)
|
json.dump(meta, fh, ensure_ascii=False, indent=1)
|
||||||
keep = _fb_backup_keep(ip)
|
keep = _fb_backup_keep(ip)
|
||||||
@@ -1545,7 +1549,7 @@ def _fb_backup(ip: str, user: str, pw: str, params: dict) -> dict:
|
|||||||
return {"ok": True, "result": {
|
return {"ok": True, "result": {
|
||||||
"ip": ip, "datei": path, "bytes": len(d.content), "auf": "Satellit",
|
"ip": ip, "datei": path, "bytes": len(d.content), "auf": "Satellit",
|
||||||
**({"file_b64": file_b64, "filename": fname} if file_b64 else {}),
|
**({"file_b64": file_b64, "filename": fname} if file_b64 else {}),
|
||||||
"kennwort": ("das angegebene Kennwort" if params.get("password")
|
"verschluesselt_mit": ("das angegebene Kennwort" if params.get("password")
|
||||||
else "das im Satelliten hinterlegte Sicherungs-Kennwort" if stored
|
else "das im Satelliten hinterlegte Sicherungs-Kennwort" if stored
|
||||||
else "das FritzBox-Login-Kennwort"),
|
else "das FritzBox-Login-Kennwort"),
|
||||||
"hinweis": f"Liegt auf dem Satelliten, die letzten {keep} je Box bleiben. "
|
"hinweis": f"Liegt auf dem Satelliten, die letzten {keep} je Box bleiben. "
|
||||||
@@ -1591,12 +1595,44 @@ def _fb_backup_path(name: str) -> str:
|
|||||||
return path if os.path.isfile(path) else ""
|
return path if os.path.isfile(path) else ""
|
||||||
|
|
||||||
|
|
||||||
def _fb_backup_meta(path: str) -> dict:
|
def _fb_backup_meta(path: str, with_secret: bool = False) -> dict:
|
||||||
try:
|
try:
|
||||||
with open(path[:-len(".export")] + ".json", encoding="utf-8") as fh:
|
with open(path[:-len(".export")] + ".json", encoding="utf-8") as fh:
|
||||||
return json.load(fh)
|
m = json.load(fh)
|
||||||
except Exception:
|
except Exception:
|
||||||
return {}
|
return {}
|
||||||
|
if not with_secret:
|
||||||
|
m.pop("kennwort_enc", None)
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
def _fb_backup_password(ip: str, path: str) -> tuple:
|
||||||
|
"""(kennwort, hinweis) fuer genau diese Sicherung. Aeltere Sicherungen ohne
|
||||||
|
gespeichertes Kennwort: das AKTUELL hinterlegte der Quelle (mit Hinweis)."""
|
||||||
|
meta = _fb_backup_meta(path, with_secret=True)
|
||||||
|
if meta.get("kennwort_enc"):
|
||||||
|
try:
|
||||||
|
return _creds_cipher().decrypt(meta["kennwort_enc"].encode()).decode(), ""
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
fb = _creds_for(ip).get("fritzbox", {})
|
||||||
|
src = meta.get("kennwort", "")
|
||||||
|
if src == "backupPass" and fb.get("backupPass"):
|
||||||
|
return fb["backupPass"], ("Aeltere Sicherung: das ist das AKTUELL hinterlegte Sicherungs-Kennwort — "
|
||||||
|
"wurde es seitdem geaendert, passt es nicht.")
|
||||||
|
if src == "login" and fb.get("pass"):
|
||||||
|
return fb["pass"], "Aeltere Sicherung: das ist das aktuelle FritzBox-Kennwort."
|
||||||
|
return "", "Fuer diese Sicherung ist kein Kennwort bekannt (eigens genanntes Kennwort)."
|
||||||
|
|
||||||
|
|
||||||
|
def _fb_backup_pw_action(ip: str, params: dict) -> dict:
|
||||||
|
path = _fb_backup_path(params.get("file") or params.get("datei") or "")
|
||||||
|
if not path:
|
||||||
|
return {"ok": False, "error": "Sicherung nicht gefunden."}
|
||||||
|
pw, hint = _fb_backup_password(ip, path)
|
||||||
|
if not pw:
|
||||||
|
return {"ok": False, "error": hint}
|
||||||
|
return {"ok": True, "result": {"datei": os.path.basename(path), "kennwort": pw, "hinweis": hint}}
|
||||||
|
|
||||||
|
|
||||||
def _fb_backups(ip: str, params: dict) -> dict:
|
def _fb_backups(ip: str, params: dict) -> dict:
|
||||||
@@ -1724,8 +1760,11 @@ def _fb_restore(ip: str, user: str, pw: str, params: dict) -> dict:
|
|||||||
"Stefan fragen; nur nach seinem ausdruecklichen Ja erneut mit confirmed=true.")}
|
"Stefan fragen; nur nach seinem ausdruecklichen Ja erneut mit confirmed=true.")}
|
||||||
src = meta.get("kennwort", "")
|
src = meta.get("kennwort", "")
|
||||||
stored = _creds_for(ip).get("fritzbox", {}).get("backupPass")
|
stored = _creds_for(ip).get("fritzbox", {}).get("backupPass")
|
||||||
|
saved_pw, _hint = _fb_backup_password(ip, path)
|
||||||
if params.get("password"):
|
if params.get("password"):
|
||||||
export_pw = str(params["password"])
|
export_pw = str(params["password"])
|
||||||
|
elif saved_pw and _fb_backup_meta(path, with_secret=True).get("kennwort_enc"):
|
||||||
|
export_pw = saved_pw # genau das Kennwort dieser Sicherung
|
||||||
elif src == "angegeben":
|
elif src == "angegeben":
|
||||||
return {"ok": False, "error": "Diese Sicherung wurde mit einem eigens genannten Kennwort erstellt — "
|
return {"ok": False, "error": "Diese Sicherung wurde mit einem eigens genannten Kennwort erstellt — "
|
||||||
"Stefan muss es fuer das Wiederherstellen angeben (password)."}
|
"Stefan muss es fuer das Wiederherstellen angeben (password)."}
|
||||||
@@ -1878,6 +1917,8 @@ def _do_fritzbox(action: str, params: dict) -> dict:
|
|||||||
return _fb_backup_delete(params)
|
return _fb_backup_delete(params)
|
||||||
if action == "fritzbox.backup_get":
|
if action == "fritzbox.backup_get":
|
||||||
return _fb_backup_get(params)
|
return _fb_backup_get(params)
|
||||||
|
if action == "fritzbox.backup_password": # nur fuers Diagnostic (Brain sperrt es)
|
||||||
|
return _fb_backup_pw_action(ip, params)
|
||||||
fb = _creds_for(ip).get("fritzbox", {})
|
fb = _creds_for(ip).get("fritzbox", {})
|
||||||
user = str(params.get("user") or fb.get("user") or "")
|
user = str(params.get("user") or fb.get("user") or "")
|
||||||
pw = str(params.get("pass") or params.get("password") or fb.get("pass") or "")
|
pw = str(params.get("pass") or params.get("password") or fb.get("pass") or "")
|
||||||
|
|||||||
Reference in New Issue
Block a user