diff --git a/desktop/index.html b/desktop/index.html index 40be90c..ea99676 100644 --- a/desktop/index.html +++ b/desktop/index.html @@ -151,6 +151,28 @@ + + diff --git a/desktop/src/main.ts b/desktop/src/main.ts index 9a73c85..4abde04 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -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 { + 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 diff --git a/desktop/src/style.css b/desktop/src/style.css index 02d3436..5d03216 100644 --- a/desktop/src/style.css +++ b/desktop/src/style.css @@ -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;