dsh-webui-oauth 0.7.6 → 0.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/index.js +55 -26
- package/lib/client.js +39 -56
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
本文件记录每个版本的**用户可见**变更。破坏性变更与升级动作单独标注。
|
|
4
4
|
|
|
5
|
+
## 0.7.7
|
|
6
|
+
|
|
7
|
+
设置页再收敛:移除冗余密码框、保存按钮移到顶部、SSO 复验改为会话级。
|
|
8
|
+
|
|
9
|
+
### 调整:账号卡片不再有「当前密码」输入框
|
|
10
|
+
|
|
11
|
+
保存/禁用已经统一走验证弹窗,账号卡片里的「当前密码」输入框是冗余的,已移除。
|
|
12
|
+
禁用认证改为与保存一致的流程:顶部按钮 → 弹窗验证(密码或 SSO)。
|
|
13
|
+
|
|
14
|
+
### 调整:「保存设置」从右下角悬浮改为顶部卡片按钮
|
|
15
|
+
|
|
16
|
+
- 移除右下角悬浮的「保存设置」按钮;
|
|
17
|
+
- 保存按钮放到「概览」卡片的操作区(与退出登录、禁用认证并列);
|
|
18
|
+
- 首次初始化(未启用认证)直接保存,无需验证;
|
|
19
|
+
- 已启用时点击弹窗验证:当前密码,或 SSO 验证。
|
|
20
|
+
|
|
21
|
+
### 调整:SSO 复验改为**会话级**,不再签发独立令牌
|
|
22
|
+
|
|
23
|
+
- 复验回调仍走统一的 `/dsh-webui-oauth/oidc/callback`;
|
|
24
|
+
- 发起复验时把当前会话 token 记进 OIDC state(服务端);
|
|
25
|
+
- 回调校验 sub 通过后,直接给该会话写 `reauthAt = now`(2 分钟有效),
|
|
26
|
+
**不再生成令牌、也不再往 URL 里放令牌**;
|
|
27
|
+
- `configure` / `disable` / `oidc/unbind` 通过 `sessionReauthOk(req)` 判断;
|
|
28
|
+
成功后清除 `reauthAt`(一次性);
|
|
29
|
+
- `status` 回显 `reauthActive`,前端据此显示「已通过 SSO 验证」;
|
|
30
|
+
- 回调回执只保留 `oidcreauth=1` 状态标记。
|
|
31
|
+
|
|
32
|
+
### 测试
|
|
33
|
+
|
|
34
|
+
- `test/oidc-bind.test.mjs` 的复验用例改为会话级断言(回调只带状态标记、
|
|
35
|
+
同会话无密码保存成功、复验不可重放);
|
|
36
|
+
- 全量 22 个测试文件、599 项断言通过。
|
|
37
|
+
|
|
5
38
|
## 0.7.6
|
|
6
39
|
|
|
7
40
|
把 DeepSeek 登录回调输入**合并进官方「等待登录」对话框**,不再单独占一个悬浮入口。
|
package/index.js
CHANGED
|
@@ -2691,6 +2691,31 @@ export async function apply(ctx) {
|
|
|
2691
2691
|
|
|
2692
2692
|
const SESSION_TTL_MS = SESSION_BROWSER_TTL_MS
|
|
2693
2693
|
|
|
2694
|
+
/** 取当前请求对应的会话记录(不做续期),无效/过期返回 null。 */
|
|
2695
|
+
function sessionOf(req) {
|
|
2696
|
+
const token = cookieOf(req, COOKIE_NAME)
|
|
2697
|
+
if (!token) return null
|
|
2698
|
+
const s = sessions.get(token)
|
|
2699
|
+
if (!s) return null
|
|
2700
|
+
if (s.expiresAt <= Date.now()) {
|
|
2701
|
+
sessions.delete(token)
|
|
2702
|
+
return null
|
|
2703
|
+
}
|
|
2704
|
+
return s
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
/** 该会话是否在 2 分钟内通过过 SSO 复验。 */
|
|
2708
|
+
function sessionReauthOk(req) {
|
|
2709
|
+
const s = sessionOf(req)
|
|
2710
|
+
return !!(s && typeof s.reauthAt === 'number' && Date.now() - s.reauthAt < REAUTH_WINDOW_MS)
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
/** 复验是一次性的:敏感操作成功后清掉时间戳。 */
|
|
2714
|
+
function clearSessionReauth(req) {
|
|
2715
|
+
const s = sessionOf(req)
|
|
2716
|
+
if (s) delete s.reauthAt
|
|
2717
|
+
}
|
|
2718
|
+
|
|
2694
2719
|
function createSession(username, ttl) {
|
|
2695
2720
|
const token = randomBytes(24).toString('hex')
|
|
2696
2721
|
const ttlMs = ttl > 0 ? ttl * 3600 * 1000 : SESSION_TTL_MS
|
|
@@ -2832,30 +2857,20 @@ export async function apply(ctx) {
|
|
|
2832
2857
|
// 必须在后台定时器使用 purge 之前定义。
|
|
2833
2858
|
const oidcStateStore = makeOidcStateStore()
|
|
2834
2859
|
|
|
2835
|
-
//
|
|
2836
|
-
//
|
|
2837
|
-
const
|
|
2838
|
-
const REAUTH_TTL_MS = 2 * 60 * 1000
|
|
2839
|
-
const mintReauthToken = (username) => {
|
|
2840
|
-
const token = randomBytes(16).toString('hex')
|
|
2841
|
-
reauthTokens.set(token, { username, expiresAt: Date.now() + REAUTH_TTL_MS })
|
|
2842
|
-
return token
|
|
2843
|
-
}
|
|
2844
|
-
const consumeReauthToken = (token) => {
|
|
2845
|
-
if (typeof token !== 'string' || !token) return null
|
|
2846
|
-
const rec = reauthTokens.get(token)
|
|
2847
|
-
if (!rec) return null
|
|
2848
|
-
reauthTokens.delete(token)
|
|
2849
|
-
if (rec.expiresAt <= Date.now()) return null
|
|
2850
|
-
return rec
|
|
2851
|
-
}
|
|
2860
|
+
// SSO 复验通过后,直接在**当前会话**上记一个时间戳(2 分钟有效),
|
|
2861
|
+
// 不再签发独立令牌、也不经过 URL 传递。敏感操作直接检查会话。
|
|
2862
|
+
const REAUTH_WINDOW_MS = 2 * 60 * 1000
|
|
2852
2863
|
const purgeExpiredOidcStates = oidcStateStore.purge
|
|
2853
2864
|
|
|
2854
2865
|
// 后台任务:过期会话清理 + enabled 状态刷新
|
|
2855
2866
|
const bgTimer = setInterval(async () => {
|
|
2856
2867
|
const now = Date.now()
|
|
2857
|
-
for (const [k, s] of sessions.live)
|
|
2858
|
-
|
|
2868
|
+
for (const [k, s] of sessions.live) {
|
|
2869
|
+
if (s.expiresAt <= now || (typeof s.reauthAt === 'number' && now - s.reauthAt >= REAUTH_WINDOW_MS)) {
|
|
2870
|
+
if (s.expiresAt <= now) sessions.delete(k)
|
|
2871
|
+
else delete s.reauthAt
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2859
2874
|
purgeExpiredOidcStates()
|
|
2860
2875
|
try {
|
|
2861
2876
|
const creds = await readCredentials(ctx)
|
|
@@ -3124,6 +3139,8 @@ export async function apply(ctx) {
|
|
|
3124
3139
|
sessionsPersisted: sessions.ok,
|
|
3125
3140
|
// 远程访问信任(可选):是否已注入 ownsHost。
|
|
3126
3141
|
remoteTrust: remoteTrustFlag,
|
|
3142
|
+
// 当前会话是否在 2 分钟内通过过 SSO 复验(保存/禁用/解绑可直接进行)。
|
|
3143
|
+
reauthActive: sessionReauthOk(req),
|
|
3127
3144
|
// 核心兜底席位形态(handler | resource | absent | unknown):核心升级后
|
|
3128
3145
|
// 如果这里变成 unknown,文档请求会 404 —— 排查时先看这个字段。
|
|
3129
3146
|
fallback: fallbackShape(ctx.webServer),
|
|
@@ -3243,8 +3260,8 @@ export async function apply(ctx) {
|
|
|
3243
3260
|
}
|
|
3244
3261
|
}
|
|
3245
3262
|
if (wasEnabled) {
|
|
3246
|
-
// 支持 SSO
|
|
3247
|
-
const reauthOk =
|
|
3263
|
+
// 支持 SSO 复验(「仅允许 SSO」时密码登录入口不可用):复验结果记在会话上
|
|
3264
|
+
const reauthOk = sessionReauthOk(req)
|
|
3248
3265
|
const curValid = reauthOk || (current && typeof creds.hash === 'string' && await verifyPassword(current, creds.hash))
|
|
3249
3266
|
if (!curValid) {
|
|
3250
3267
|
await auditLog(ctx, 'configure_failure', { username, ip: meta.ip, ua: meta.ua, detail: '当前密码不正确' })
|
|
@@ -3383,6 +3400,7 @@ export async function apply(ctx) {
|
|
|
3383
3400
|
if (!keepPassword) detailParts.push('密码已修改')
|
|
3384
3401
|
if (body.ttl !== undefined) detailParts.push('有效期已修改')
|
|
3385
3402
|
await auditLog(ctx, 'configure_success', { username, ip: meta.ip, ua: meta.ua, detail: detailParts.length ? detailParts.join(',') : null })
|
|
3403
|
+
clearSessionReauth(req)
|
|
3386
3404
|
sendJson(res, 200, { ok: true })
|
|
3387
3405
|
} catch (e) {
|
|
3388
3406
|
sendJson(res, 500, { error: e && e.message ? e.message : String(e) })
|
|
@@ -3412,7 +3430,7 @@ export async function apply(ctx) {
|
|
|
3412
3430
|
sendJson(res, 200, { ok: true })
|
|
3413
3431
|
return
|
|
3414
3432
|
}
|
|
3415
|
-
const reauthOk =
|
|
3433
|
+
const reauthOk = sessionReauthOk(req)
|
|
3416
3434
|
const curValid = reauthOk || (current && typeof creds.hash === 'string' && await verifyPassword(current, creds.hash))
|
|
3417
3435
|
if (!curValid) {
|
|
3418
3436
|
await auditLog(ctx, 'disable_failure', { username: creds.username, ip: meta.ip, ua: meta.ua, detail: '当前密码不正确' })
|
|
@@ -3498,7 +3516,10 @@ export async function apply(ctx) {
|
|
|
3498
3516
|
const state = oidcRandom(24)
|
|
3499
3517
|
const nonce = oidcRandom(24)
|
|
3500
3518
|
const verifier = oidcRandom(32)
|
|
3501
|
-
|
|
3519
|
+
// 复验要绑定到发起它的浏览器的**会话**:回调时直接把 reauthAt 记在该会话上,
|
|
3520
|
+
// 不再签发独立令牌、也不把令牌放进 URL。
|
|
3521
|
+
const sessionToken = purpose === 'reauth' ? cookieOf(req, COOKIE_NAME) : null
|
|
3522
|
+
oidcStateStore.put(state, { nonce, verifier, redirectUri, purpose, sessionToken })
|
|
3502
3523
|
const authUrl = new URL(disc.metadata.authorization_endpoint)
|
|
3503
3524
|
authUrl.searchParams.set('response_type', 'code')
|
|
3504
3525
|
authUrl.searchParams.set('client_id', o.clientId)
|
|
@@ -3629,7 +3650,7 @@ export async function apply(ctx) {
|
|
|
3629
3650
|
const creds = await readCredentials(ctx)
|
|
3630
3651
|
const meta = requestMeta(req)
|
|
3631
3652
|
const current = String(body.current || '')
|
|
3632
|
-
const reauthOk =
|
|
3653
|
+
const reauthOk = sessionReauthOk(req)
|
|
3633
3654
|
const curValid = reauthOk || (current && creds && typeof creds.hash === 'string' && await verifyPassword(current, creds.hash))
|
|
3634
3655
|
if (!curValid) {
|
|
3635
3656
|
await auditLog(ctx, 'oidc_unbind_failure', { username: creds && creds.username ? creds.username : null, ip: meta.ip, ua: meta.ua, detail: '当前密码不正确' })
|
|
@@ -3662,6 +3683,7 @@ export async function apply(ctx) {
|
|
|
3662
3683
|
await writeCredentials(ctx, next)
|
|
3663
3684
|
await refreshSsoOnly(next)
|
|
3664
3685
|
await auditLog(ctx, 'oidc_unbind_success', { username: creds.username, ip: meta.ip, ua: meta.ua })
|
|
3686
|
+
clearSessionReauth(req)
|
|
3665
3687
|
sendJson(res, 200, { ok: true, bound: false })
|
|
3666
3688
|
} catch (e) {
|
|
3667
3689
|
sendJson(res, 500, { error: e && e.message ? e.message : String(e) })
|
|
@@ -3793,9 +3815,16 @@ export async function apply(ctx) {
|
|
|
3793
3815
|
redirectBackToApp(ctx, req, res, 'oidcerr=sub-mismatch', clearStateCookie)
|
|
3794
3816
|
return
|
|
3795
3817
|
}
|
|
3796
|
-
|
|
3818
|
+
// 把复验结果记到发起登录的那个会话上(2 分钟内有效)。
|
|
3819
|
+
const target = st.sessionToken ? sessions.get(st.sessionToken) : null
|
|
3820
|
+
if (!target || target.expiresAt <= Date.now()) {
|
|
3821
|
+
await auditLog(ctx, 'oidc_login_failure', { username: auditName, ip: meta.ip, ua: meta.ua, detail: '复验时会话已失效' })
|
|
3822
|
+
redirectBackToApp(ctx, req, res, 'oidcerr=session-expired', clearStateCookie)
|
|
3823
|
+
return
|
|
3824
|
+
}
|
|
3825
|
+
target.reauthAt = Date.now()
|
|
3797
3826
|
await auditLog(ctx, 'oidc_reauth_success', { username: auditName, ip: meta.ip, ua: meta.ua })
|
|
3798
|
-
redirectBackToApp(ctx, req, res, 'oidcreauth='
|
|
3827
|
+
redirectBackToApp(ctx, req, res, 'oidcreauth=1', clearStateCookie)
|
|
3799
3828
|
return
|
|
3800
3829
|
}
|
|
3801
3830
|
|
package/lib/client.js
CHANGED
|
@@ -26,8 +26,7 @@ window.__ModuleLoader__.load({
|
|
|
26
26
|
'.wua-float-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }',
|
|
27
27
|
'.wua-float-title { font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary, #222); }',
|
|
28
28
|
'.wua-inline-relay { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; padding-top: 12px; border-top: 1px dashed var(--dsw-alias-border-l1, #d9d9d9); }',
|
|
29
|
-
|
|
30
|
-
'.wua-save-fab:disabled { opacity: .55; cursor: default; }',
|
|
29
|
+
|
|
31
30
|
'.wua-modal-mask { position: fixed; inset: 0; z-index: 10000; display: flex; align-items: center; justify-content: center; padding: 24px; background: rgba(0, 0, 0, .45); }',
|
|
32
31
|
'.wua-modal { width: min(420px, 100%); display: flex; flex-direction: column; gap: 12px; padding: 20px 22px; border-radius: 12px; background: var(--dsw-alias-bg-layer-1, #fff); box-shadow: 0 24px 60px rgba(0, 0, 0, .35); }',
|
|
33
32
|
'.wua-intro { margin: 0; font-size: 13px; line-height: 1.6; color: var(--dsw-alias-label-secondary, #888); }',
|
|
@@ -250,7 +249,7 @@ window.__ModuleLoader__.load({
|
|
|
250
249
|
const [username, setUsername] = react.useState('');
|
|
251
250
|
const [password, setPassword] = react.useState('');
|
|
252
251
|
const [confirm, setConfirm] = react.useState('');
|
|
253
|
-
|
|
252
|
+
|
|
254
253
|
const [ttl, setTtl] = react.useState('12');
|
|
255
254
|
const [msg, setMsg] = react.useState(null);
|
|
256
255
|
const [busy, setBusy] = react.useState(false);
|
|
@@ -279,8 +278,7 @@ window.__ModuleLoader__.load({
|
|
|
279
278
|
const [saveOpen, setSaveOpen] = react.useState(false);
|
|
280
279
|
const [savePw, setSavePw] = react.useState('');
|
|
281
280
|
const [saveErr, setSaveErr] = react.useState('');
|
|
282
|
-
const [
|
|
283
|
-
const [reauthExp, setReauthExp] = react.useState(0);
|
|
281
|
+
const [verifyAction, setVerifyAction] = react.useState('save');
|
|
284
282
|
// 远程访问信任开关。
|
|
285
283
|
const [remoteTrustBusy, setRemoteTrustBusy] = react.useState(false);
|
|
286
284
|
const [remoteTrustMsg, setRemoteTrustMsg] = react.useState(null);
|
|
@@ -334,19 +332,6 @@ window.__ModuleLoader__.load({
|
|
|
334
332
|
// 读取 OIDC 回调带回来的回执。绑定/登录结束后浏览器会被 302 回应用,
|
|
335
333
|
// 结果通过 query 传递(oidcbound=1 / oidcerr=...):因为是整页跳转,
|
|
336
334
|
// 组件重新挂载时才知道刚才发生了什么。
|
|
337
|
-
// 从 sessionStorage 恢复 SSO 复验令牌(2 分钟内有效)。
|
|
338
|
-
react.useEffect(() => {
|
|
339
|
-
try {
|
|
340
|
-
const raw = sessionStorage.getItem('dsh_wua_reauth')
|
|
341
|
-
if (!raw) return
|
|
342
|
-
const v = JSON.parse(raw)
|
|
343
|
-
if (v && typeof v.token === 'string' && v.exp > Date.now()) {
|
|
344
|
-
setReauthToken(v.token)
|
|
345
|
-
setReauthExp(v.exp)
|
|
346
|
-
}
|
|
347
|
-
} catch (e) { /* ignore */ }
|
|
348
|
-
}, []);
|
|
349
|
-
|
|
350
335
|
react.useEffect(() => {
|
|
351
336
|
let qs = ''
|
|
352
337
|
try { qs = String((location && location.search) || '') } catch (e) { qs = '' }
|
|
@@ -357,10 +342,7 @@ window.__ModuleLoader__.load({
|
|
|
357
342
|
const reauth = p.get('oidcreauth')
|
|
358
343
|
if (bound === null && err === null && reauth === null) return
|
|
359
344
|
if (reauth !== null) {
|
|
360
|
-
|
|
361
|
-
setReauthToken(reauth)
|
|
362
|
-
setReauthExp(Date.now() + 120000)
|
|
363
|
-
setBindMsg({ kind: 'ok', text: 'SSO 验证成功(2 分钟内有效)。请回到设置页点击「保存设置」。' })
|
|
345
|
+
setBindMsg({ kind: 'ok', text: 'SSO 验证成功(2 分钟内有效)。请点击「保存设置」继续。' })
|
|
364
346
|
} else if (bound !== null) {
|
|
365
347
|
setBindMsg({ kind: 'ok', text: 'OIDC 身份绑定成功。此后只有该身份可通过 SSO 登录。' })
|
|
366
348
|
} else {
|
|
@@ -409,7 +391,7 @@ window.__ModuleLoader__.load({
|
|
|
409
391
|
setMsg({ kind: 'err', text: '两次输入的密码不一致' });
|
|
410
392
|
return;
|
|
411
393
|
}
|
|
412
|
-
if (enabled && !credentials.current && !
|
|
394
|
+
if (enabled && !credentials.current && !reauthValid) {
|
|
413
395
|
setSaveErr('修改认证信息需要验证:请输入当前密码,或使用 SSO 验证。');
|
|
414
396
|
return;
|
|
415
397
|
}
|
|
@@ -432,7 +414,6 @@ window.__ModuleLoader__.load({
|
|
|
432
414
|
username: name,
|
|
433
415
|
password,
|
|
434
416
|
current: credentials.current || '',
|
|
435
|
-
reauthToken: credentials.reauthToken || undefined,
|
|
436
417
|
ttl: Number(ttl),
|
|
437
418
|
oidc: oidcPayload,
|
|
438
419
|
}).then((r) => {
|
|
@@ -445,13 +426,9 @@ window.__ModuleLoader__.load({
|
|
|
445
426
|
}
|
|
446
427
|
setPassword('');
|
|
447
428
|
setConfirm('');
|
|
448
|
-
setCurrent('');
|
|
449
429
|
setSaveOpen(false);
|
|
450
430
|
setSavePw('');
|
|
451
431
|
setSaveErr('');
|
|
452
|
-
setReauthToken(null);
|
|
453
|
-
setReauthExp(0);
|
|
454
|
-
try { sessionStorage.removeItem('dsh_wua_reauth') } catch (e) { /* ignore */ }
|
|
455
432
|
api('/dsh-webui-oauth/status').then((s) => {
|
|
456
433
|
if (s && !s.error) setStatus(s);
|
|
457
434
|
});
|
|
@@ -484,23 +461,27 @@ window.__ModuleLoader__.load({
|
|
|
484
461
|
});
|
|
485
462
|
};
|
|
486
463
|
|
|
487
|
-
const
|
|
464
|
+
const doDisable = (auth) => {
|
|
488
465
|
if (busy) return;
|
|
489
|
-
|
|
490
|
-
|
|
466
|
+
const credentials = auth || {};
|
|
467
|
+
if (!credentials.current && !reauthValid) {
|
|
468
|
+
setSaveErr('禁用认证需要验证:请输入当前密码,或使用 SSO 验证。');
|
|
491
469
|
return;
|
|
492
470
|
}
|
|
493
471
|
setBusy(true);
|
|
494
472
|
setMsg(null);
|
|
495
|
-
api('/dsh-webui-oauth/disable', { current }).then((r) => {
|
|
473
|
+
api('/dsh-webui-oauth/disable', { current: credentials.current || '' }).then((r) => {
|
|
496
474
|
setBusy(false);
|
|
497
475
|
if (r && r.ok) {
|
|
498
476
|
setMsg({ kind: 'ok', text: '已禁用认证,所有会话已注销。刷新页面后不再要求登录。' });
|
|
499
|
-
|
|
477
|
+
setSaveOpen(false);
|
|
478
|
+
setSavePw('');
|
|
479
|
+
setSaveErr('');
|
|
500
480
|
api('/dsh-webui-oauth/status').then((s) => {
|
|
501
481
|
if (s && !s.error) setStatus(s);
|
|
502
482
|
}).catch(() => {});
|
|
503
483
|
} else {
|
|
484
|
+
setSaveErr('当前密码不正确');
|
|
504
485
|
setMsg({ kind: 'err', text: '当前密码不正确' });
|
|
505
486
|
}
|
|
506
487
|
}).catch(() => {
|
|
@@ -563,7 +544,22 @@ window.__ModuleLoader__.load({
|
|
|
563
544
|
|
|
564
545
|
const enabled = Boolean(status && status.enabled);
|
|
565
546
|
const remoteTrust = Boolean(status && status.remoteTrust);
|
|
566
|
-
const reauthValid = Boolean(
|
|
547
|
+
const reauthValid = Boolean(status && status.reauthActive);
|
|
548
|
+
|
|
549
|
+
// 顶部「保存设置」/「禁用认证」:需要验证时统一打开弹窗(密码或 SSO)。
|
|
550
|
+
const onSaveClick = () => {
|
|
551
|
+
if (!enabled || reauthValid) { onSave({}); return; }
|
|
552
|
+
setVerifyAction('save');
|
|
553
|
+
setSaveErr('');
|
|
554
|
+
setSaveOpen(true);
|
|
555
|
+
};
|
|
556
|
+
const onDisableClick = () => {
|
|
557
|
+
if (!enabled) return;
|
|
558
|
+
if (reauthValid) { doDisable({}); return; }
|
|
559
|
+
setVerifyAction('disable');
|
|
560
|
+
setSaveErr('');
|
|
561
|
+
setSaveOpen(true);
|
|
562
|
+
};
|
|
567
563
|
const intro = status === null
|
|
568
564
|
? '正在读取认证状态…'
|
|
569
565
|
: (enabled
|
|
@@ -647,8 +643,9 @@ window.__ModuleLoader__.load({
|
|
|
647
643
|
patchWarn ? react.createElement('p', { className: 'wua-warn' }, patchWarn) : null,
|
|
648
644
|
persistNote ? react.createElement('p', { className: 'wua-warn' }, persistNote) : null,
|
|
649
645
|
react.createElement('div', { className: 'wua-actions' },
|
|
646
|
+
react.createElement('button', { className: 'wua-btn', disabled: busy || status === null, onClick: onSaveClick }, busy ? '保存中…' : '保存设置'),
|
|
650
647
|
enabled ? react.createElement('button', { className: 'wua-btn ghost', disabled: busy || status === null, onClick: onLogout }, '退出登录') : null,
|
|
651
|
-
enabled ? react.createElement('button', { className: 'wua-btn ghost', disabled: busy || status === null, onClick:
|
|
648
|
+
enabled ? react.createElement('button', { className: 'wua-btn ghost', disabled: busy || status === null, onClick: onDisableClick }, '禁用认证') : null,
|
|
652
649
|
),
|
|
653
650
|
]),
|
|
654
651
|
card('账号与密码', [], [
|
|
@@ -678,10 +675,6 @@ window.__ModuleLoader__.load({
|
|
|
678
675
|
react.createElement('input', { className: 'wua-input', type: 'password', value: confirm, onChange: (e) => setConfirm(e.target.value) }),
|
|
679
676
|
),
|
|
680
677
|
),
|
|
681
|
-
enabled ? react.createElement('div', { className: 'wua-row' },
|
|
682
|
-
react.createElement('label', { className: 'wua-label' }, '当前密码(修改或禁用认证需验证)'),
|
|
683
|
-
react.createElement('input', { className: 'wua-input', type: 'password', value: current, onChange: (e) => setCurrent(e.target.value) }),
|
|
684
|
-
) : null,
|
|
685
678
|
msg ? react.createElement('div', { className: msg.kind === 'ok' ? 'wua-msg' : 'wua-err' }, msg.text) : null,
|
|
686
679
|
]),
|
|
687
680
|
card('OIDC 单点登录', [
|
|
@@ -807,23 +800,13 @@ window.__ModuleLoader__.load({
|
|
|
807
800
|
)),
|
|
808
801
|
)),
|
|
809
802
|
]),
|
|
810
|
-
react.createElement('button', {
|
|
811
|
-
className: 'wua-save-fab',
|
|
812
|
-
disabled: busy || status === null,
|
|
813
|
-
onClick: () => {
|
|
814
|
-
if (!enabled) { onSave({}); return; }
|
|
815
|
-
if (reauthValid) { onSave({ reauthToken }); return; }
|
|
816
|
-
setSaveErr('');
|
|
817
|
-
setSaveOpen(true);
|
|
818
|
-
},
|
|
819
|
-
}, busy ? '保存中…' : '保存设置'),
|
|
820
803
|
saveOpen ? react.createElement('div', { className: 'wua-modal-mask' },
|
|
821
804
|
react.createElement('div', { className: 'wua-modal' },
|
|
822
|
-
react.createElement('h4', { className: 'wua-card-title' }, '验证后保存'),
|
|
805
|
+
react.createElement('h4', { className: 'wua-card-title' }, verifyAction === 'disable' ? '验证后禁用' : '验证后保存'),
|
|
823
806
|
react.createElement('p', { className: 'wua-hint', style: { borderTop: 'none', paddingTop: 0 } },
|
|
824
807
|
reauthValid
|
|
825
|
-
? '已通过 SSO 验证(2
|
|
826
|
-
: '
|
|
808
|
+
? '已通过 SSO 验证(2 分钟内有效),可直接继续。'
|
|
809
|
+
: '该操作需要验证当前身份:输入当前密码,或使用 SSO 验证。'),
|
|
827
810
|
reauthValid ? null : react.createElement('div', { className: 'wua-row' },
|
|
828
811
|
react.createElement('label', { className: 'wua-label' }, '当前密码'),
|
|
829
812
|
react.createElement('input', { className: 'wua-input', type: 'password', value: savePw, autoComplete: 'current-password', onChange: (e) => setSavePw(e.target.value) }),
|
|
@@ -831,10 +814,10 @@ window.__ModuleLoader__.load({
|
|
|
831
814
|
saveErr ? react.createElement('p', { className: 'wua-err' }, saveErr) : null,
|
|
832
815
|
react.createElement('div', { className: 'wua-actions' },
|
|
833
816
|
react.createElement('button', { className: 'wua-btn', disabled: busy, onClick: () => {
|
|
834
|
-
if (reauthValid) {
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
} }, busy ? '
|
|
817
|
+
if (!reauthValid && !savePw) { setSaveErr('请输入当前密码'); return; }
|
|
818
|
+
const auth = reauthValid ? {} : { current: savePw };
|
|
819
|
+
if (verifyAction === 'disable') doDisable(auth); else onSave(auth);
|
|
820
|
+
} }, busy ? '处理中…' : (verifyAction === 'disable' ? '验证并禁用' : '验证并保存')),
|
|
838
821
|
react.createElement('button', { className: 'wua-btn ghost', disabled: busy, onClick: () => setSaveOpen(false) }, '取消'),
|
|
839
822
|
),
|
|
840
823
|
(status && status.oidc && status.oidc.enabled && status.oidc.bound)
|