feat(#150): 登录页客户端 auth.js(login/me + 路由守卫 requireLoginElseRedirect,PRD 8.2)

This commit is contained in:
2026-08-05 02:08:24 +00:00
parent 351f791f1a
commit 73056ed32e
+108
View File
@@ -0,0 +1,108 @@
/* 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<user|null>。
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 };