/* iAOP 登录页客户端(issue #150 / PRD 8.2)。 * 对接 core/auth/auth_api.py: * POST /auth/login {username,password} → {token,user}(后端 Set-Cookie iaop_session) * GET /auth/me 凭 cookie 校验当前登录态(路由守卫用) * 写操作守卫:未登录跳登录页;readonly 角色写按钮置灰(见 studio/cockpit 的 applyRbac)。 */ "use strict"; // 认证服务基址:独立运行 auth_api 时指向它;由统一网关接入时留空(同源)。 var AUTH_BASE = (function () { try { if (localStorage.getItem("iaop_auth_base")) return localStorage.getItem("iaop_auth_base"); } catch (e) {} return ""; // 生产同源,留空 })(); function $(id) { return document.getElementById(id); } function showAlert(msg, kind) { var el = $("alert"); el.textContent = msg || ""; el.className = "alert show " + (kind === "ok" ? "alert-ok" : "alert-error"); } function clearAlert() { $("alert").className = "alert"; } // 检查当前登录态(路由守卫复用)。返回 Promise。 function currentUser() { return fetch(join(AUTH_BASE, "/auth/me"), { credentials: "include" }) .then(function (r) { return r.ok ? r.json() : null; }) .then(function (d) { return (d && d.user) ? d.user : null; }) .catch(function () { return null; }); } // 写操作守卫:未登录跳登录页(PRD 8.2)。 function requireLoginElseRedirect(loginUrl) { return currentUser().then(function (u) { if (!u) { var next = encodeURIComponent(location.pathname + location.search); location.href = (loginUrl || "login.html") + "?next=" + next; return false; } return u; }); } function join(base, path) { return (base || "") + path; } function login(username, password) { $("loginBtn").disabled = true; clearAlert(); return fetch(join(AUTH_BASE, "/auth/login"), { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username: username, password: password }) }).then(function (resp) { return resp.json().then(function (d) { if (!resp.ok) throw new Error((d && d.error) || ("登录失败 HTTP " + resp.status)); return d; }); }).then(function (d) { // 后端已 Set-Cookie(HttpOnly),同时返回 token 供 Bearer 场景(如 SPA fetch 显式带) showAlert("登录成功,欢迎 " + (d.user && d.user.username) + "(" + (d.user && d.user.role) + ")", "ok"); var next = new URLSearchParams(location.search).get("next") || "../cockpit/index.html"; setTimeout(function () { location.href = next; }, 500); }).catch(function (e) { $("password").setAttribute("aria-invalid", "true"); showAlert(e.message || "登录失败", "error"); }).finally(function () { $("loginBtn").disabled = false; }); } document.addEventListener("DOMContentLoaded", function () { var form = $("loginForm"); if (!form) return; // 已登录直接跳转 currentUser().then(function (u) { if (u) { var next = new URLSearchParams(location.search).get("next") || "../cockpit/index.html"; location.href = next; } }); form.addEventListener("submit", function (e) { e.preventDefault(); var u = $("username").value.trim(); var p = $("password").value; $("username").removeAttribute("aria-invalid"); $("password").removeAttribute("aria-invalid"); if (!u || !p || p.length < 8) { $("password").setAttribute("aria-invalid", String(!p || p.length < 8)); showAlert("用户名不能为空且密码不少于 8 位", "error"); return; } login(u, p); }); var tog = $("togglePwd"); if (tog) tog.addEventListener("click", function () { var pwd = $("password"); pwd.type = pwd.type === "password" ? "text" : "password"; }); }); // 导出给其它页面复用(路由守卫) window.IAOP_AUTH = { currentUser: currentUser, requireLoginElseRedirect: requireLoginElseRedirect, AUTH_BASE: AUTH_BASE };