109 lines
3.9 KiB
JavaScript
109 lines
3.9 KiB
JavaScript
/* 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 };
|