zcbot/web/static/js/auth.js

218 lines
7.8 KiB
JavaScript

// 认证:登录(邮箱密码 / 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;