feat: Phase 1 前端认证接入 FBA JWT——三轨会话 + 图形验证码(Epic #159)
This commit is contained in:
+75
-4
@@ -138,6 +138,24 @@
|
||||
}
|
||||
.field .pwd-toggle:hover { color: var(--ant-primary); }
|
||||
|
||||
/* ---- FBA 图形验证码(Epic #159 Phase 1) -------------------------------- */
|
||||
.captcha-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: stretch;
|
||||
}
|
||||
.captcha-row .input-wrap { flex: 1; }
|
||||
.captcha-img {
|
||||
height: 40px;
|
||||
min-width: 110px;
|
||||
border: 1px solid var(--ant-border);
|
||||
border-radius: var(--ant-radius);
|
||||
cursor: pointer;
|
||||
object-fit: cover;
|
||||
background: #fafafa;
|
||||
}
|
||||
.captcha-img:hover { border-color: var(--ant-primary-hover); }
|
||||
|
||||
.btn-login {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
@@ -210,11 +228,13 @@
|
||||
</head>
|
||||
<body>
|
||||
<!--
|
||||
iAOP 登录页(issue #150/#134 + 2026-08 Ant Design Pro 风格重构)。
|
||||
认证双轨(shared/session.js):
|
||||
· 后端 core/auth 可达 → POST /auth/login(HMAC 会话,HttpOnly cookie);
|
||||
iAOP 登录页(issue #150/#134 + 2026-08 Ant Design Pro 风格重构 + Epic #159 Phase 1)。
|
||||
认证三轨(shared/session.js):
|
||||
· FBA 可用(/fba/api/v1 探测通过)→ FBA JWT 登录(默认强制图形验证码);
|
||||
· 旧后端 core/auth 可达 → POST /auth/login(HMAC 会话,HttpOnly cookie);
|
||||
· 纯静态部署 → UserStore 本地账号(首次访问自动播种演示账号)。
|
||||
角色三级(PRD 8.2):readonly 只读 / engineer 可配置 / admin 可发布回滚,
|
||||
FBA 侧角色按 session.js 的 FBA_ROLE_MAP 命名即可映射到三级角色,
|
||||
登录后门户按角色过滤可见模块。
|
||||
-->
|
||||
<div class="brand">
|
||||
@@ -250,6 +270,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FBA 图形验证码:仅当 FBA 认证可用且开启验证码时显示(session.js fbaStatus) -->
|
||||
<div class="field" id="captchaField" hidden>
|
||||
<label for="captcha">图形验证码</label>
|
||||
<div class="captcha-row">
|
||||
<div class="input-wrap">
|
||||
<span class="input-icon">🛡</span>
|
||||
<input id="captcha" name="captcha" type="text"
|
||||
autocomplete="off" placeholder="请输入验证码" maxlength="8">
|
||||
</div>
|
||||
<img class="captcha-img" id="captchaImg" alt="点击刷新验证码"
|
||||
title="看不清?点击刷新">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-login" id="loginBtn">登 录</button>
|
||||
|
||||
<!-- issue #152:OIDC SSO 双轨入口——配置了 issuer/client_id 时显示 -->
|
||||
@@ -299,6 +333,30 @@
|
||||
// 首次访问播种本地演示账号(幂等;后端模式不影响)
|
||||
UserStore.seedDefaults().catch(function () {});
|
||||
|
||||
/* Epic #159 Phase 1:FBA 可用且开启图形验证码时显示验证码字段 */
|
||||
var captchaState = { enabled: false, uuid: null };
|
||||
function refreshCaptcha() {
|
||||
IAOP_SESSION.getCaptcha().then(function (c) {
|
||||
if (c) {
|
||||
captchaState.uuid = c.uuid;
|
||||
$("captchaImg").src = c.imageSrc;
|
||||
$("captcha").value = "";
|
||||
}
|
||||
});
|
||||
}
|
||||
IAOP_SESSION.fbaStatus().then(function (st) {
|
||||
if (st.available && st.captchaEnabled) {
|
||||
captchaState.enabled = true;
|
||||
$("captchaField").hidden = false;
|
||||
refreshCaptcha();
|
||||
}
|
||||
if (st.available) {
|
||||
// FBA 模式下隐藏本地演示账号提示,避免误导
|
||||
document.querySelector(".demo-accounts").hidden = true;
|
||||
}
|
||||
});
|
||||
$("captchaImg").addEventListener("click", refreshCaptcha);
|
||||
|
||||
// 已登录直接跳转
|
||||
IAOP_SESSION.currentUser().then(function (u) {
|
||||
if (u) location.href = nextUrl();
|
||||
@@ -310,6 +368,7 @@
|
||||
var p = $("password").value;
|
||||
$("username").removeAttribute("aria-invalid");
|
||||
$("password").removeAttribute("aria-invalid");
|
||||
$("captcha").removeAttribute("aria-invalid");
|
||||
if (!u) {
|
||||
$("username").setAttribute("aria-invalid", "true");
|
||||
showAlert("请输入用户名", "error");
|
||||
@@ -320,15 +379,27 @@
|
||||
showAlert("密码不少于 8 位", "error");
|
||||
return;
|
||||
}
|
||||
var captchaOpt = null;
|
||||
if (captchaState.enabled) {
|
||||
var c = $("captcha").value.trim();
|
||||
if (!c) {
|
||||
$("captcha").setAttribute("aria-invalid", "true");
|
||||
showAlert("请输入图形验证码", "error");
|
||||
return;
|
||||
}
|
||||
captchaOpt = { uuid: captchaState.uuid, captcha: c };
|
||||
}
|
||||
$("loginBtn").disabled = true;
|
||||
clearAlert();
|
||||
IAOP_SESSION.login(u, p).then(function (user) {
|
||||
IAOP_SESSION.login(u, p, captchaOpt).then(function (user) {
|
||||
showAlert("登录成功,欢迎 " + user.username + "(" +
|
||||
(IAOP_SESSION.ROLE_LABELS[user.role] || user.role) + ")", "ok");
|
||||
setTimeout(function () { location.href = nextUrl(); }, 400);
|
||||
}).catch(function (err) {
|
||||
$("password").setAttribute("aria-invalid", "true");
|
||||
showAlert(err.message || "登录失败", "error");
|
||||
// FBA 验证码一次性有效,失败后必须刷新
|
||||
if (captchaState.enabled) refreshCaptcha();
|
||||
}).finally(function () {
|
||||
$("loginBtn").disabled = false;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user