// 认证:登录(邮箱密码 / UUID+PLATFORM_KEY 两 tab)、管理员加用户、改密码。 // 各入口在本模块顶层自绑 onclick;只 logout / closeChpwModal 对外 // (logout 供全局 401 处理,closeChpwModal 供 main 的 Esc 统一关弹窗栈)。 // 反向依赖 main 的 glue:enterApp(登录成功进入)、embedPostToParent/embedShowWaiting // (logout 在 embed 模式通知父页面)——均运行时(点击/401)才调,ES 环 live binding 安全。 import { state, LS_TOKEN, LS_UID, LS_NAME, EMBED } from "./state.js"; import { $ } from "./dom.js"; import { api } from "./api.js"; import { enterApp, embedPostToParent, embedShowWaiting } from "./main.js"; // ───── login ───── let loginTab = "pw"; // "pw" | "key";持久化 last-used tab 在 LS,刷新后默认那个 const LS_TAB = "zcbot_login_tab"; function switchLoginTab(name) { loginTab = name; document.querySelectorAll("#login .tabs button").forEach(b => { b.classList.toggle("active", b.dataset.tab === name); }); document.querySelectorAll("#login .tab-body").forEach(b => { b.classList.toggle("active", b.id === "body-" + name); }); localStorage.setItem(LS_TAB, name); $("li-err").textContent = ""; // 自动 focus 第一个空 input,Enter 直接登 const firstInput = document.querySelector("#body-" + name + " input"); if (firstInput) firstInput.focus(); } document.querySelectorAll("#login .tabs button").forEach(b => { b.addEventListener("click", () => switchLoginTab(b.dataset.tab)); }); const savedTab = localStorage.getItem(LS_TAB); if (savedTab === "key") switchLoginTab("key"); $("li-go").onclick = doLogin; // 任意 input 上回车都触发登录 document.querySelectorAll("#login input").forEach(i => { i.addEventListener("keydown", (e) => { if (e.key === "Enter") doLogin(); }); }); async function doLogin() { $("li-err").textContent = ""; let url, body, displayLabel; if (loginTab === "pw") { const email = $("li-email").value.trim(); const password = $("li-password").value; if (!email || !password) { $("li-err").textContent = "请填邮箱和密码"; return; } url = "/v1/auth/login_password"; body = { email, password }; displayLabel = "email"; } else { const uid = $("li-uid").value.trim(); const pkey = $("li-pkey").value; if (!uid || !pkey) { $("li-err").textContent = "请填 user_id 和 PLATFORM_KEY"; return; } url = "/v1/auth/login"; body = { user_id: uid, platform_key: pkey }; displayLabel = null; // 这条路径不返显示名,顶栏只显 uid 前 8 位 } try { const r = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (!r.ok) { const d = await r.json().catch(() => ({})); throw new Error(d.detail || (r.status + " login failed")); } const data = await r.json(); state.token = data.token; state.userId = data.user_id; state.userName = displayLabel ? (data[displayLabel] || "") : ""; localStorage.setItem(LS_TOKEN, state.token); localStorage.setItem(LS_UID, state.userId); if (state.userName) { localStorage.setItem(LS_NAME, state.userName); } else { localStorage.removeItem(LS_NAME); } enterApp(); } catch (e) { $("li-err").textContent = e.message; } } export function logout() { state.token = ""; state.userId = ""; state.userName = ""; localStorage.removeItem(LS_TOKEN); localStorage.removeItem(LS_UID); localStorage.removeItem(LS_NAME); if (state.evtSrc) state.evtSrc.close(); if (EMBED) { embedPostToParent({ type: "zcbot-401" }); embedShowWaiting("登录已失效,等待父页面重新签发…", false); document.body.classList.add("embed-waiting"); return; } location.reload(); } $("hd-logout").onclick = logout; // ───── admin add-user ───── // 入口在登录页右下角链接;弹窗收 email/password/admin_token 三项,POST /v1/auth/admin/create_user。 // 成功后不自动登录(让用户自己用新账号登,逻辑清晰),只回填邮箱到登录表单 + 提示。 function openAdminModal() { $("ad-email").value = ""; $("ad-password").value = ""; $("ad-token").value = ""; $("ad-err").textContent = ""; $("admin-modal").classList.add("show"); $("ad-email").focus(); } function closeAdminModal() { $("admin-modal").classList.remove("show"); } $("open-admin-add").onclick = (e) => { e.preventDefault(); openAdminModal(); }; $("ad-cancel").onclick = closeAdminModal; $("admin-modal").addEventListener("click", (e) => { if (e.target.id === "admin-modal") closeAdminModal(); // 点遮罩关闭 }); // 任一 input 上回车触发提交 document.querySelectorAll("#admin-modal input").forEach(i => { i.addEventListener("keydown", (e) => { if (e.key === "Enter") doAdminAdd(); }); }); async function doAdminAdd() { $("ad-err").textContent = ""; const email = $("ad-email").value.trim(); const password = $("ad-password").value; const admin_token = $("ad-token").value; if (!email || !password || !admin_token) { $("ad-err").textContent = "请填邮箱、密码、管理员口令"; return; } if (password.length < 6) { $("ad-err").textContent = "密码至少 6 字符"; return; } try { const r = await fetch("/v1/auth/admin/create_user", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, password, admin_token }), }); if (!r.ok) { const d = await r.json().catch(() => ({})); throw new Error(d.detail || (r.status + " create failed")); } const data = await r.json(); closeAdminModal(); // 切到邮箱密码 tab,回填邮箱,提示一下 switchLoginTab("pw"); $("li-email").value = data.email || email; $("li-password").value = ""; $("li-password").focus(); $("li-err").style.color = "var(--muted)"; // 临时降级为提示色 $("li-err").textContent = `已创建 ${data.email},请登录`; setTimeout(() => { $("li-err").style.color = ""; }, 4000); } catch (e) { $("ad-err").textContent = e.message; } } $("ad-go").onclick = doAdminAdd; // ───── 改密码(顶栏入口,需已登录)───── // 旧/新/确认三项;user_id 不传,后端从 JWT 取。成功后关弹窗,提示一下(不登出)。 function openChpwModal() { $("cp-old").value = ""; $("cp-new").value = ""; $("cp-new2").value = ""; $("cp-err").textContent = ""; $("chpw-modal").classList.add("show"); $("cp-old").focus(); } export function closeChpwModal() { $("chpw-modal").classList.remove("show"); } $("hd-chpw").onclick = openChpwModal; $("cp-cancel").onclick = closeChpwModal; $("chpw-modal").addEventListener("click", (e) => { if (e.target.id === "chpw-modal") closeChpwModal(); // 点遮罩关闭 }); document.querySelectorAll("#chpw-modal input").forEach(i => { i.addEventListener("keydown", (e) => { if (e.key === "Enter") doChangePassword(); }); }); async function doChangePassword() { $("cp-err").textContent = ""; const oldPw = $("cp-old").value; const newPw = $("cp-new").value; const newPw2 = $("cp-new2").value; if (!oldPw || !newPw || !newPw2) { $("cp-err").textContent = "请填旧密码和新密码"; return; } if (newPw.length < 6) { $("cp-err").textContent = "新密码至少 6 字符"; return; } if (newPw !== newPw2) { $("cp-err").textContent = "两次输入的新密码不一致"; return; } try { await api("POST", "/v1/auth/change_password", { old_password: oldPw, new_password: newPw }); closeChpwModal(); // 不登出:同一会话 JWT 仍有效,下次登录用新密码即可 alert("密码已修改,下次登录请用新密码"); } catch (e) { if (e.status === 401) { closeChpwModal(); logout(); return; } $("cp-err").textContent = e.message; } } $("cp-go").onclick = doChangePassword;