feat: replace browser confirm with proper key-change warning dialog
Some checks failed
Mirror to GitHub / mirror (push) Failing after 35s
Build Release Binaries / build-amd64 (push) Failing after 1m57s

When the relay's server key changes (e.g. after restart), show a
styled in-app warning dialog instead of the ugly browser confirm().
The dialog shows old vs new fingerprints and lets the user accept
the new key or cancel. Accepting updates the saved fingerprint and
refreshes the relay button state.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-04-07 18:19:53 +04:00
parent 369347ce54
commit ded49bdb7b
3 changed files with 136 additions and 3 deletions

View File

@@ -76,6 +76,13 @@ const sFingerprint = document.getElementById("s-fingerprint")!;
const sRecentRooms = document.getElementById("s-recent-rooms")!;
const sClearRecent = document.getElementById("s-clear-recent")!;
// Key warning dialog
const keyWarning = document.getElementById("key-warning")!;
const kwOldFp = document.getElementById("kw-old-fp")!;
const kwNewFp = document.getElementById("kw-new-fp")!;
const kwAccept = document.getElementById("kw-accept")!;
const kwCancel = document.getElementById("kw-cancel")!;
let statusInterval: number | null = null;
let myFingerprint = "";
let userDisconnected = false;
@@ -377,6 +384,28 @@ connectBtn.addEventListener("click", doConnect);
el.addEventListener("keydown", (e) => { if (e.key === "Enter") doConnect(); })
);
function showKeyWarning(oldFp: string, newFp: string): Promise<boolean> {
return new Promise((resolve) => {
kwOldFp.textContent = oldFp;
kwNewFp.textContent = newFp;
keyWarning.classList.remove("hidden");
const cleanup = () => {
keyWarning.classList.add("hidden");
kwAccept.removeEventListener("click", onAccept);
kwCancel.removeEventListener("click", onCancel);
keyWarning.removeEventListener("click", onBackdrop);
};
const onAccept = () => { cleanup(); resolve(true); };
const onCancel = () => { cleanup(); resolve(false); };
const onBackdrop = (e: Event) => { if (e.target === keyWarning) { cleanup(); resolve(false); } };
kwAccept.addEventListener("click", onAccept);
kwCancel.addEventListener("click", onCancel);
keyWarning.addEventListener("click", onBackdrop);
});
}
async function doConnect() {
const relay = getSelectedRelay();
if (!relay) { connectError.textContent = "No relay selected"; return; }
@@ -384,13 +413,13 @@ async function doConnect() {
// Warn on fingerprint mismatch
const ls = lockStatus(relay);
if (ls === "changed") {
if (!confirm(`Server fingerprint has changed!\n\nKnown: ${relay.knownFingerprint}\nNew: ${relay.serverFingerprint}\n\nThis could indicate a man-in-the-middle attack. Continue?`)) {
return;
}
const accepted = await showKeyWarning(relay.knownFingerprint || "", relay.serverFingerprint || "");
if (!accepted) return;
// User accepted — update known fingerprint
const s = loadSettings();
s.relays[s.selectedRelay].knownFingerprint = relay.serverFingerprint;
saveSettingsObj(s);
renderRelayButton();
}
// Don't block connect on offline — ping may have failed transiently

View File

@@ -652,6 +652,88 @@ button.primary:disabled { opacity: 0.5; cursor: not-allowed; }
.secondary-btn:hover { border-color: var(--accent); color: var(--text); }
/* ── Key warning dialog ── */
#key-warning {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(6px);
display: flex;
align-items: center;
justify-content: center;
z-index: 300;
padding: 20px;
}
.key-warning-card {
max-width: 360px;
text-align: center;
gap: 16px;
}
.key-warning-icon {
font-size: 48px;
color: var(--yellow);
line-height: 1;
}
.key-warning-card h2 {
font-size: 18px;
font-weight: 600;
}
.key-warning-text {
font-size: 13px;
color: var(--text-dim);
line-height: 1.5;
}
.key-warning-fps {
display: flex;
flex-direction: column;
gap: 8px;
background: var(--surface);
border-radius: 8px;
padding: 12px;
}
.key-fp-row {
display: flex;
flex-direction: column;
gap: 2px;
text-align: left;
}
.key-fp-label {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--text-dim);
}
.key-fp {
font-family: monospace;
font-size: 11px;
word-break: break-all;
color: var(--text);
}
.key-warning-actions {
display: flex;
gap: 10px;
}
.key-warning-actions .primary {
flex: 1;
background: var(--yellow);
color: #000;
font-weight: 600;
}
.key-warning-actions .secondary-btn {
flex: 1;
}
/* ── Quality slider ── */
.quality-control {
display: flex;