feat: replace browser confirm with proper key-change warning dialog
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:
@@ -151,6 +151,28 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Key changed warning dialog -->
|
||||
<div id="key-warning" class="hidden">
|
||||
<div class="settings-card key-warning-card">
|
||||
<div class="key-warning-icon">⚠</div>
|
||||
<h2>Server Key Changed</h2>
|
||||
<p class="key-warning-text">The relay's identity has changed since you last connected. This usually happens when the server was restarted, but could also indicate a security issue.</p>
|
||||
<div class="key-warning-fps">
|
||||
<div class="key-fp-row">
|
||||
<span class="key-fp-label">Previously known</span>
|
||||
<code id="kw-old-fp" class="key-fp"></code>
|
||||
</div>
|
||||
<div class="key-fp-row">
|
||||
<span class="key-fp-label">New key</span>
|
||||
<code id="kw-new-fp" class="key-fp"></code>
|
||||
</div>
|
||||
</div>
|
||||
<div class="key-warning-actions">
|
||||
<button id="kw-accept" class="primary">Accept New Key</button>
|
||||
<button id="kw-cancel" class="secondary-btn">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user