merge: 合并 #150 认证后端/登录页,落地 #134 用户/角色管理+OIDC 预留+配置台接入

This commit is contained in:
2026-08-05 10:20:09 +08:00
16 changed files with 1263 additions and 418 deletions
+107 -104
View File
@@ -1,118 +1,121 @@
/* iAOP 用户/角色管理页逻辑(issue #134)。
* 仅 admin(Publisher + MANAGE 权限)可访问;其余角色跳回登录/配置台。
* 会话守卫复用 #150 的 IAOP_AUTH.requireLoginElseRedirect(core/auth 后端 /auth/me);
* 仅 admin(manage 权限)可访问;CRUD/审计/OIDC 走 user_store.js(Demo 存储)。
*/
"use strict";
(function () {
var session = Auth.requireAuth("login.html");
if (!session) return;
if (!Auth.can(session.role, "manage")) {
alert("仅 Publisher(admin)可访问用户管理");
location.href = "../studio/index.html";
return;
}
document.getElementById("who").textContent =
session.username + " · " + Auth.ROLE_LABELS[session.role];
IAOP_AUTH.requireLoginElseRedirect("login.html").then(function (user) {
if (!user) return; // 正在跳转登录页
if (user.role !== "admin") {
alert("仅 Publisher(admin)可访问用户管理");
location.href = "../studio/index.html";
return;
}
document.getElementById("who").textContent =
user.username + " · " + UserStore.ROLE_LABELS[user.role];
function el(tag, text) {
var n = document.createElement(tag);
if (text !== undefined) n.textContent = text;
return n;
}
function el(tag, text) {
var n = document.createElement(tag);
if (text !== undefined) n.textContent = text;
return n;
}
function renderUsers() {
var tbody = document.getElementById("user-tbody");
tbody.innerHTML = "";
Auth.listUsers().forEach(function (u) {
var tr = el("tr");
if (!u.enabled) tr.className = "disabled-user";
tr.appendChild(el("td", u.username));
// 角色分配(下拉即改)
var tdRole = el("td");
var sel = el("select");
Auth.ROLES.forEach(function (r) {
var opt = el("option", Auth.ROLE_LABELS[r]);
opt.value = r;
if (r === u.role) opt.selected = true;
sel.appendChild(opt);
function renderUsers() {
var tbody = document.getElementById("user-tbody");
tbody.innerHTML = "";
UserStore.list().forEach(function (u) {
var tr = el("tr");
if (!u.active) tr.className = "disabled-user";
tr.appendChild(el("td", u.username));
// 角色分配(下拉即改)
var tdRole = el("td");
var sel = el("select");
UserStore.ROLES.forEach(function (r) {
var opt = el("option", UserStore.ROLE_LABELS[r]);
opt.value = r;
if (r === u.role) opt.selected = true;
sel.appendChild(opt);
});
sel.className = "role-" + u.role;
sel.onchange = function () {
UserStore.update(u.username, { role: sel.value }, user.username).then(renderUsers);
};
tdRole.appendChild(sel);
tr.appendChild(tdRole);
tr.appendChild(el("td", u.active ? "启用" : "禁用"));
tr.appendChild(el("td", (u.created_at || "").slice(0, 10)));
var tdOps = el("td");
var toggle = el("button", u.active ? "禁用" : "启用");
toggle.onclick = function () {
UserStore.update(u.username, { active: !u.active }, user.username).then(renderUsers);
};
var reset = el("button", "重置密码");
reset.onclick = function () {
var pwd = prompt("为 " + u.username + " 设置新密码(≥6 位)");
if (pwd) UserStore.update(u.username, { password: pwd }, user.username).then(renderUsers);
};
var del = el("button", "删除");
del.className = "danger";
del.onclick = function () {
if (u.username === user.username) { alert("不能删除当前登录账号"); return; }
if (confirm("确认删除用户 " + u.username + "?")) {
UserStore.remove(u.username, user.username).then(renderUsers);
}
};
[toggle, reset, del].forEach(function (b) {
b.style.marginRight = "4px"; tdOps.appendChild(b);
});
tr.appendChild(tdOps);
tbody.appendChild(tr);
});
sel.className = "role-" + u.role;
sel.onchange = function () {
Auth.updateUser(u.username, { role: sel.value }).then(renderUsers);
};
tdRole.appendChild(sel);
tr.appendChild(tdRole);
tr.appendChild(el("td", u.enabled ? "启用" : "禁用"));
tr.appendChild(el("td", (u.created_at || "").slice(0, 10)));
var tdOps = el("td");
var toggle = el("button", u.enabled ? "禁用" : "启用");
toggle.onclick = function () {
Auth.updateUser(u.username, { enabled: !u.enabled }).then(renderUsers);
};
var reset = el("button", "重置密码");
reset.onclick = function () {
var pwd = prompt("为 " + u.username + " 设置新密码(≥6 位)");
if (pwd) Auth.updateUser(u.username, { password: pwd }).then(renderUsers);
};
var del = el("button", "删除");
del.className = "danger";
del.onclick = function () {
if (u.username === session.username) { alert("不能删除当前登录账号"); return; }
if (confirm("确认删除用户 " + u.username + "?")) {
Auth.deleteUser(u.username).then(renderUsers);
}
};
[toggle, reset, del].forEach(function (b) {
b.style.marginRight = "4px"; tdOps.appendChild(b);
renderAudit();
}
function renderAudit() {
var box = document.getElementById("audit-list");
box.innerHTML = "";
UserStore.auditLog().slice().reverse().forEach(function (e) {
box.appendChild(el("div",
e.time.slice(0, 19).replace("T", " ") + " | " + e.actor + " | " +
e.action + " | " + e.resource + " | " + e.reason));
});
tr.appendChild(tdOps);
tbody.appendChild(tr);
});
renderAudit();
}
}
function renderAudit() {
var box = document.getElementById("audit-list");
box.innerHTML = "";
Auth.auditLog().slice().reverse().forEach(function (e) {
box.appendChild(el("div",
e.time.slice(0, 19).replace("T", " ") + " | " + e.actor + " | " +
e.action + " | " + e.resource + " | " + e.reason));
});
}
document.getElementById("btn-add").onclick = function () {
var err = document.getElementById("form-err");
err.textContent = "";
UserStore.create(document.getElementById("new-username").value.trim(),
document.getElementById("new-password").value,
document.getElementById("new-role").value)
.then(function () {
document.getElementById("new-username").value = "";
document.getElementById("new-password").value = "";
renderUsers();
})
.catch(function (e) { err.textContent = e.message; });
};
document.getElementById("btn-add").onclick = function () {
var err = document.getElementById("form-err");
err.textContent = "";
Auth.createUser(document.getElementById("new-username").value.trim(),
document.getElementById("new-password").value,
document.getElementById("new-role").value)
.then(function () {
document.getElementById("new-username").value = "";
document.getElementById("new-password").value = "";
renderUsers();
})
.catch(function (e) { err.textContent = e.message; });
};
// OIDC 配置点
var cfg = UserStore.oidcConfig() || {};
document.getElementById("oidc-issuer").value = cfg.issuer || "";
document.getElementById("oidc-client").value = cfg.client_id || "";
document.getElementById("oidc-redirect").value = cfg.redirect_uri || "";
document.getElementById("btn-oidc").onclick = function () {
UserStore.configureOidc({
issuer: document.getElementById("oidc-issuer").value.trim(),
client_id: document.getElementById("oidc-client").value.trim(),
redirect_uri: document.getElementById("oidc-redirect").value.trim()
}, user.username);
alert("OIDC 配置已保存;登录页将出现 SSO 入口");
};
// OIDC 配置点
var cfg = Auth.oidcConfig() || {};
document.getElementById("oidc-issuer").value = cfg.issuer || "";
document.getElementById("oidc-client").value = cfg.client_id || "";
document.getElementById("oidc-redirect").value = cfg.redirect_uri || "";
document.getElementById("btn-oidc").onclick = function () {
Auth.configureOidc({
issuer: document.getElementById("oidc-issuer").value.trim(),
client_id: document.getElementById("oidc-client").value.trim(),
redirect_uri: document.getElementById("oidc-redirect").value.trim()
});
alert("OIDC 配置已保存;登录页将出现 SSO 入口");
};
document.getElementById("logout-btn").onclick = function () {
fetch((IAOP_AUTH.AUTH_BASE || "") + "/auth/logout",
{ method: "POST", credentials: "include" })
.finally(function () { location.href = "login.html"; });
};
document.getElementById("logout-btn").onclick = function () {
Auth.logout();
location.href = "login.html";
};
renderUsers();
renderUsers();
});
})();