From c24c4acaec2c7544dc8a16270d29b9289ec47ef8 Mon Sep 17 00:00:00 2001 From: Ben Miller Date: Tue, 23 Jun 2026 12:22:06 -0500 Subject: [PATCH] feat: implement client-side room listing UI and server-side authentication blueprint --- blueprints/auth.py | 28 ++++++++++++++++++++ static/assets/js/server_list.js | 45 +++++++++++++++++++++++++++++++- transient_chat.db | Bin 40960 -> 40960 bytes 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/blueprints/auth.py b/blueprints/auth.py index 01a9d08..89f0371 100644 --- a/blueprints/auth.py +++ b/blueprints/auth.py @@ -69,3 +69,31 @@ def validate(): return jsonify({"error": "Invalid credentials"}), 403 + +@auth_bp.route("/logout", methods=["POST"]) +def logout(): + data = request.get_json() + username = data.get("username") + key = data.get("key") + + if not username or not key: + return jsonify({"error": "Missing credentials"}), 400 + + if verify_user_key(username, key): + # Remove from active sessions + user_sessions.pop(username, None) + # Remove from user_ips in memory and SQLite to free up the username immediately + user_ips.pop(username, None) + from models.database import db_delete_user_ip + db_delete_user_ip(username) + + # Remove from all rooms + from models.state import room_users + for room in room_users: + room_users[room].pop(username, None) + + return jsonify({"status": "logged_out"}) + + return jsonify({"error": "Invalid credentials"}), 403 + + diff --git a/static/assets/js/server_list.js b/static/assets/js/server_list.js index 014b469..a962b97 100644 --- a/static/assets/js/server_list.js +++ b/static/assets/js/server_list.js @@ -25,8 +25,51 @@ document.addEventListener("DOMContentLoaded", async () => { return; } + function escapeHtml(text) { + if (!text) return text; + return text + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + } + const userInfoEl = document.getElementById('user-info'); - userInfoEl.textContent = `Logged in as: ${username}`; + userInfoEl.style.display = 'flex'; + userInfoEl.style.alignItems = 'center'; + userInfoEl.style.justifyContent = 'flex-start'; + userInfoEl.style.gap = '15px'; + userInfoEl.style.marginBottom = '20px'; + + const logoutBtn = document.createElement('button'); + logoutBtn.textContent = 'Logout'; + logoutBtn.style.backgroundColor = '#d9534f'; + logoutBtn.style.color = 'white'; + logoutBtn.style.border = 'none'; + logoutBtn.style.padding = '6px 12px'; + logoutBtn.style.borderRadius = '4px'; + logoutBtn.style.cursor = 'pointer'; + logoutBtn.onclick = async () => { + if (confirm("Are you sure you want to logout? This will release your username immediately.")) { + try { + await fetch('/logout', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username, key: userKey }) + }); + } catch (e) { + console.error("Logout request failed:", e); + } + localStorage.clear(); + window.location.href = '/'; + } + }; + + const userSpan = document.createElement('span'); + userSpan.innerHTML = `Logged in as: ${escapeHtml(username)}`; + + userInfoEl.append(logoutBtn, userSpan); document.querySelectorAll('.collapsible').forEach(heading => { heading.addEventListener('click', () => { diff --git a/transient_chat.db b/transient_chat.db index e908d3376c7c1832c02a53cdfa3685410897e6fe..b4a5b2b3aacb2973e216d4bf666db4e8b4cf0081 100644 GIT binary patch delta 162 zcmZoTz|?SnX@WGP_(U0JM)8daOX9hh_zyGiKj1&i|9-Qez;u3V85U+;`I6M)lK7nb z^!(D2_|oFkB10o{Jp&*#lxAkumQPGi%`1rqt3i>R{2*TiXx@DW{?GjP`R@bG+rzIZ e!mP^(H;$cIn9(UUk42bSn-R?v=FOk$c@+Q{{4(+Y delta 56 zcmZoTz|?SnX@WGPz(g5mMuCk9OX4{g`5!RwKj43`Sy12#|KtbxDnOCX4E&$@KLbUc L^KbrK&#M3cAFLD<