dsh-webui-oauth 0.7.3 → 0.7.5
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 +83 -0
- package/README.en.md +21 -15
- package/README.md +9 -6
- package/index.js +370 -15
- package/lib/client.js +394 -149
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -9,7 +9,28 @@ window.__ModuleLoader__.load({
|
|
|
9
9
|
const inject = ['slots'];
|
|
10
10
|
|
|
11
11
|
const CSS = [
|
|
12
|
-
'.wua-section { display: flex; flex-direction: column; gap:
|
|
12
|
+
'.wua-section { display: flex; flex-direction: column; gap: 16px; max-width: 760px; padding: 4px 2px; }',
|
|
13
|
+
'.wua-card { display: flex; flex-direction: column; gap: 12px; padding: 16px 18px; border: 1px solid var(--dsw-alias-border-l1, #e5e5e5); border-radius: 10px; background: var(--dsw-alias-bg-layer-2, #fff); }',
|
|
14
|
+
'.wua-card-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }',
|
|
15
|
+
'.wua-card-title { margin: 0; font-size: 14px; font-weight: 600; color: var(--dsw-alias-label-primary, #222); }',
|
|
16
|
+
'.wua-badges { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }',
|
|
17
|
+
'.wua-badge { font-size: 11px; line-height: 1.6; padding: 1px 8px; border-radius: 999px; border: 1px solid transparent; white-space: nowrap; }',
|
|
18
|
+
'.wua-badge.ok { color: var(--dsw-alias-state-success-primary, #1a7f37); background: color-mix(in srgb, var(--dsw-alias-state-success-primary, #1a7f37) 10%, transparent); border-color: color-mix(in srgb, var(--dsw-alias-state-success-primary, #1a7f37) 32%, transparent); }',
|
|
19
|
+
'.wua-badge.warn { color: var(--dsw-alias-state-warn-primary, #9a6700); background: color-mix(in srgb, var(--dsw-alias-state-warn-primary, #9a6700) 10%, transparent); border-color: color-mix(in srgb, var(--dsw-alias-state-warn-primary, #9a6700) 32%, transparent); }',
|
|
20
|
+
'.wua-badge.muted { color: var(--dsw-alias-label-tertiary, #777); background: color-mix(in srgb, var(--dsw-alias-label-tertiary, #777) 8%, transparent); border-color: color-mix(in srgb, var(--dsw-alias-label-tertiary, #777) 22%, transparent); }',
|
|
21
|
+
'.wua-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 12px; }',
|
|
22
|
+
'.wua-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }',
|
|
23
|
+
'.wua-sep { height: 1px; background: var(--dsw-alias-border-l1, #e5e5e5); margin: 2px 0; }',
|
|
24
|
+
'.wua-kv { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }',
|
|
25
|
+
'.wua-float { position: fixed; right: 24px; bottom: 24px; z-index: 9999; width: min(440px, calc(100vw - 48px)); display: flex; flex-direction: column; gap: 10px; padding: 16px 18px; border: 1px solid var(--dsw-alias-border-l2, #999); border-radius: 12px; background: var(--dsw-alias-bg-layer-1, #fff); box-shadow: 0 18px 48px rgba(0, 0, 0, .22); }',
|
|
26
|
+
'.wua-float-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }',
|
|
27
|
+
'.wua-float-title { font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary, #222); }',
|
|
28
|
+
'.wua-fab { position: fixed; right: 24px; bottom: 96px; z-index: 9998; padding: 9px 16px; font-size: 13px; cursor: pointer; color: #fff; background: var(--dsw-alias-brand-primary, #4a7cf7); border: none; border-radius: 999px; box-shadow: 0 10px 26px rgba(74, 124, 247, .35); }',
|
|
29
|
+
'.wua-fab:hover { filter: brightness(1.05); }',
|
|
30
|
+
'.wua-save-fab { position: fixed; right: 24px; bottom: 24px; z-index: 9997; padding: 10px 20px; font-size: 13px; font-weight: 600; cursor: pointer; color: #fff; background: var(--dsw-alias-brand-primary, #4a7cf7); border: none; border-radius: 999px; box-shadow: 0 12px 30px rgba(74, 124, 247, .38); }',
|
|
31
|
+
'.wua-save-fab:disabled { opacity: .55; cursor: default; }',
|
|
32
|
+
'.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); }',
|
|
33
|
+
'.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); }',
|
|
13
34
|
'.wua-intro { margin: 0; font-size: 13px; line-height: 1.6; color: var(--dsw-alias-label-secondary, #888); }',
|
|
14
35
|
'.wua-row { display: flex; flex-direction: column; gap: 5px; }',
|
|
15
36
|
'.wua-label { font-size: 12px; color: var(--dsw-alias-label-secondary, #888); }',
|
|
@@ -45,6 +66,110 @@ window.__ModuleLoader__.load({
|
|
|
45
66
|
if (styleEl.parentNode !== null) styleEl.parentNode.removeChild(styleEl);
|
|
46
67
|
}, 'dsh-webui-oauth: styles');
|
|
47
68
|
|
|
69
|
+
// ---------------- 全局悬浮:DeepSeek 登录回调中转 ----------------
|
|
70
|
+
// 不放在设置页里:官方登录是在「设置 → 账号」发起的,回调地址可能在任何
|
|
71
|
+
// 页面/标签页拿到。这里挂一个全局悬浮按钮,任何页面都能打开粘贴窗口。
|
|
72
|
+
(function mountRelayWidget() {
|
|
73
|
+
if (typeof document === 'undefined' || !document.body) return;
|
|
74
|
+
if (document.getElementById('dsh-wua-relay')) return;
|
|
75
|
+
|
|
76
|
+
const wrap = document.createElement('div');
|
|
77
|
+
wrap.id = 'dsh-wua-relay';
|
|
78
|
+
|
|
79
|
+
const fab = document.createElement('button');
|
|
80
|
+
fab.type = 'button';
|
|
81
|
+
fab.className = 'wua-fab';
|
|
82
|
+
fab.textContent = 'DeepSeek 登录回调';
|
|
83
|
+
fab.title = '官方 DeepSeek 登录的回调中转';
|
|
84
|
+
|
|
85
|
+
const panel = document.createElement('div');
|
|
86
|
+
panel.className = 'wua-float';
|
|
87
|
+
panel.style.display = 'none';
|
|
88
|
+
|
|
89
|
+
const head = document.createElement('div');
|
|
90
|
+
head.className = 'wua-float-head';
|
|
91
|
+
const title = document.createElement('span');
|
|
92
|
+
title.className = 'wua-float-title';
|
|
93
|
+
title.textContent = 'DeepSeek 登录回调中转';
|
|
94
|
+
const close = document.createElement('button');
|
|
95
|
+
close.type = 'button';
|
|
96
|
+
close.className = 'wua-btn ghost';
|
|
97
|
+
close.textContent = '关闭';
|
|
98
|
+
head.appendChild(title);
|
|
99
|
+
head.appendChild(close);
|
|
100
|
+
|
|
101
|
+
const hint = document.createElement('p');
|
|
102
|
+
hint.className = 'wua-hint';
|
|
103
|
+
hint.style.borderTop = 'none';
|
|
104
|
+
hint.style.paddingTop = '0';
|
|
105
|
+
hint.textContent = '把浏览器地址栏里的完整回调 URL 粘贴到这里。只接受 http://127.0.0.1 或 localhost 的本机端口地址。';
|
|
106
|
+
|
|
107
|
+
const input = document.createElement('input');
|
|
108
|
+
input.className = 'wua-input';
|
|
109
|
+
input.type = 'text';
|
|
110
|
+
input.placeholder = 'http://127.0.0.1:3080/oauth/callback?code=…&state=…';
|
|
111
|
+
|
|
112
|
+
const actions = document.createElement('div');
|
|
113
|
+
actions.className = 'wua-actions';
|
|
114
|
+
const submit = document.createElement('button');
|
|
115
|
+
submit.type = 'button';
|
|
116
|
+
submit.className = 'wua-btn';
|
|
117
|
+
submit.textContent = '完成登录';
|
|
118
|
+
actions.appendChild(submit);
|
|
119
|
+
|
|
120
|
+
const msg = document.createElement('p');
|
|
121
|
+
msg.style.display = 'none';
|
|
122
|
+
|
|
123
|
+
panel.appendChild(head);
|
|
124
|
+
panel.appendChild(hint);
|
|
125
|
+
panel.appendChild(input);
|
|
126
|
+
panel.appendChild(actions);
|
|
127
|
+
panel.appendChild(msg);
|
|
128
|
+
wrap.appendChild(fab);
|
|
129
|
+
wrap.appendChild(panel);
|
|
130
|
+
document.body.appendChild(wrap);
|
|
131
|
+
|
|
132
|
+
const show = (text, ok) => {
|
|
133
|
+
msg.textContent = text;
|
|
134
|
+
msg.className = ok ? 'wua-msg' : 'wua-err';
|
|
135
|
+
msg.style.display = 'block';
|
|
136
|
+
};
|
|
137
|
+
const setOpen = (open) => {
|
|
138
|
+
panel.style.display = open ? 'flex' : 'none';
|
|
139
|
+
fab.style.display = open ? 'none' : 'inline-flex';
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
fab.addEventListener('click', () => setOpen(true));
|
|
143
|
+
close.addEventListener('click', () => setOpen(false));
|
|
144
|
+
submit.addEventListener('click', () => {
|
|
145
|
+
const url = input.value.trim();
|
|
146
|
+
if (!url) { show('请粘贴完整回调地址', false); return; }
|
|
147
|
+
submit.disabled = true;
|
|
148
|
+
fetch('/dsh-webui-oauth/oauth-relay', {
|
|
149
|
+
method: 'POST',
|
|
150
|
+
headers: { 'content-type': 'application/json' },
|
|
151
|
+
body: JSON.stringify({ url }),
|
|
152
|
+
}).then((r) => {
|
|
153
|
+
if (r.status === 401) { location.href = '/dsh-webui-oauth/login'; return null; }
|
|
154
|
+
return r.json();
|
|
155
|
+
}).then((res) => {
|
|
156
|
+
submit.disabled = false;
|
|
157
|
+
if (!res) return;
|
|
158
|
+
if (res.ok) {
|
|
159
|
+
show('已收到回调,正在完成登录。请回到「设置 → 账号」查看状态。', true);
|
|
160
|
+
input.value = '';
|
|
161
|
+
} else {
|
|
162
|
+
show(res.hint || res.reason || res.error || '中转失败', false);
|
|
163
|
+
}
|
|
164
|
+
}).catch(() => {
|
|
165
|
+
submit.disabled = false;
|
|
166
|
+
show('无法连接认证服务', false);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
ctx.effect(() => () => wrap.remove(), 'dsh-webui-oauth: relay widget');
|
|
171
|
+
})();
|
|
172
|
+
|
|
48
173
|
// ---------------- 会话失效:统一回登录页 ----------------
|
|
49
174
|
const LOGIN_PATH = '/dsh-webui-oauth/login';
|
|
50
175
|
|
|
@@ -141,6 +266,15 @@ window.__ModuleLoader__.load({
|
|
|
141
266
|
const [oidcBoundHint, setOidcBoundHint] = react.useState('');
|
|
142
267
|
const [unbindPw, setUnbindPw] = react.useState('');
|
|
143
268
|
const [bindMsg, setBindMsg] = react.useState(null);
|
|
269
|
+
// 敏感操作复验(保存配置 / 禁用 / 解绑):密码或 SSO 一次性令牌。
|
|
270
|
+
const [saveOpen, setSaveOpen] = react.useState(false);
|
|
271
|
+
const [savePw, setSavePw] = react.useState('');
|
|
272
|
+
const [saveErr, setSaveErr] = react.useState('');
|
|
273
|
+
const [reauthToken, setReauthToken] = react.useState(null);
|
|
274
|
+
const [reauthExp, setReauthExp] = react.useState(0);
|
|
275
|
+
// 远程访问信任开关。
|
|
276
|
+
const [remoteTrustBusy, setRemoteTrustBusy] = react.useState(false);
|
|
277
|
+
const [remoteTrustMsg, setRemoteTrustMsg] = react.useState(null);
|
|
144
278
|
// 服务端算好的完整回调地址(含 scheme/host/port)。原生 OIDC 要求
|
|
145
279
|
// redirect_uri 与 IdP 登记值精确匹配,少一个字符都会被拒,所以要显示全。
|
|
146
280
|
const [oidcRedirectUri, setOidcRedirectUri] = react.useState('');
|
|
@@ -191,6 +325,19 @@ window.__ModuleLoader__.load({
|
|
|
191
325
|
// 读取 OIDC 回调带回来的回执。绑定/登录结束后浏览器会被 302 回应用,
|
|
192
326
|
// 结果通过 query 传递(oidcbound=1 / oidcerr=...):因为是整页跳转,
|
|
193
327
|
// 组件重新挂载时才知道刚才发生了什么。
|
|
328
|
+
// 从 sessionStorage 恢复 SSO 复验令牌(2 分钟内有效)。
|
|
329
|
+
react.useEffect(() => {
|
|
330
|
+
try {
|
|
331
|
+
const raw = sessionStorage.getItem('dsh_wua_reauth')
|
|
332
|
+
if (!raw) return
|
|
333
|
+
const v = JSON.parse(raw)
|
|
334
|
+
if (v && typeof v.token === 'string' && v.exp > Date.now()) {
|
|
335
|
+
setReauthToken(v.token)
|
|
336
|
+
setReauthExp(v.exp)
|
|
337
|
+
}
|
|
338
|
+
} catch (e) { /* ignore */ }
|
|
339
|
+
}, []);
|
|
340
|
+
|
|
194
341
|
react.useEffect(() => {
|
|
195
342
|
let qs = ''
|
|
196
343
|
try { qs = String((location && location.search) || '') } catch (e) { qs = '' }
|
|
@@ -198,8 +345,14 @@ window.__ModuleLoader__.load({
|
|
|
198
345
|
const p = new URLSearchParams(qs)
|
|
199
346
|
const bound = p.get('oidcbound')
|
|
200
347
|
const err = p.get('oidcerr')
|
|
201
|
-
|
|
202
|
-
if (bound
|
|
348
|
+
const reauth = p.get('oidcreauth')
|
|
349
|
+
if (bound === null && err === null && reauth === null) return
|
|
350
|
+
if (reauth !== null) {
|
|
351
|
+
try { sessionStorage.setItem('dsh_wua_reauth', JSON.stringify({ token: reauth, exp: Date.now() + 120000 })) } catch (e) { /* ignore */ }
|
|
352
|
+
setReauthToken(reauth)
|
|
353
|
+
setReauthExp(Date.now() + 120000)
|
|
354
|
+
setBindMsg({ kind: 'ok', text: 'SSO 验证成功(2 分钟内有效)。请回到设置页点击「保存设置」。' })
|
|
355
|
+
} else if (bound !== null) {
|
|
203
356
|
setBindMsg({ kind: 'ok', text: 'OIDC 身份绑定成功。此后只有该身份可通过 SSO 登录。' })
|
|
204
357
|
} else {
|
|
205
358
|
const LABELS = {
|
|
@@ -227,8 +380,9 @@ window.__ModuleLoader__.load({
|
|
|
227
380
|
return { ok: true, text: '' };
|
|
228
381
|
}
|
|
229
382
|
|
|
230
|
-
const onSave = () => {
|
|
383
|
+
const onSave = (auth) => {
|
|
231
384
|
if (busy) return;
|
|
385
|
+
const credentials = auth || {};
|
|
232
386
|
const name = username.trim();
|
|
233
387
|
// 与服务端 usernameError 一致的用户名约束
|
|
234
388
|
if (!/^[A-Za-z0-9_-]{3,32}$/.test(name)) {
|
|
@@ -246,8 +400,8 @@ window.__ModuleLoader__.load({
|
|
|
246
400
|
setMsg({ kind: 'err', text: '两次输入的密码不一致' });
|
|
247
401
|
return;
|
|
248
402
|
}
|
|
249
|
-
if (enabled && !current) {
|
|
250
|
-
|
|
403
|
+
if (enabled && !credentials.current && !credentials.reauthToken) {
|
|
404
|
+
setSaveErr('修改认证信息需要验证:请输入当前密码,或使用 SSO 验证。');
|
|
251
405
|
return;
|
|
252
406
|
}
|
|
253
407
|
setBusy(true);
|
|
@@ -265,7 +419,14 @@ window.__ModuleLoader__.load({
|
|
|
265
419
|
// OIDC 关闭时不能「仅允许 SSO」,否则等于关掉所有登录入口
|
|
266
420
|
ssoOnly: oidcEnabled ? oidcSsoOnly : false,
|
|
267
421
|
};
|
|
268
|
-
api('/dsh-webui-oauth/configure', {
|
|
422
|
+
api('/dsh-webui-oauth/configure', {
|
|
423
|
+
username: name,
|
|
424
|
+
password,
|
|
425
|
+
current: credentials.current || '',
|
|
426
|
+
reauthToken: credentials.reauthToken || undefined,
|
|
427
|
+
ttl: Number(ttl),
|
|
428
|
+
oidc: oidcPayload,
|
|
429
|
+
}).then((r) => {
|
|
269
430
|
setBusy(false);
|
|
270
431
|
if (r && r.ok) {
|
|
271
432
|
if (enabled) {
|
|
@@ -276,24 +437,37 @@ window.__ModuleLoader__.load({
|
|
|
276
437
|
setPassword('');
|
|
277
438
|
setConfirm('');
|
|
278
439
|
setCurrent('');
|
|
440
|
+
setSaveOpen(false);
|
|
441
|
+
setSavePw('');
|
|
442
|
+
setSaveErr('');
|
|
443
|
+
setReauthToken(null);
|
|
444
|
+
setReauthExp(0);
|
|
445
|
+
try { sessionStorage.removeItem('dsh_wua_reauth') } catch (e) { /* ignore */ }
|
|
279
446
|
api('/dsh-webui-oauth/status').then((s) => {
|
|
280
447
|
if (s && !s.error) setStatus(s);
|
|
281
448
|
});
|
|
282
449
|
} else if (r && r.error === 'current-invalid') {
|
|
450
|
+
setSaveErr('当前密码不正确');
|
|
283
451
|
setMsg({ kind: 'err', text: '当前密码不正确' });
|
|
284
452
|
} else if (r && r.error === 'weak-password') {
|
|
285
|
-
|
|
453
|
+
const text = '密码强度不足:至少 8 位,需包含大小写字母、数字和特殊符号';
|
|
454
|
+
setMsg({ kind: 'err', text }); setSaveErr(text);
|
|
286
455
|
} else if (r && r.error === 'username-invalid') {
|
|
287
|
-
|
|
456
|
+
const text = '用户名需为 3-32 位字母、数字、下划线或连字符';
|
|
457
|
+
setMsg({ kind: 'err', text }); setSaveErr(text);
|
|
288
458
|
} else if (r && r.error === 'legacy-hash') {
|
|
289
|
-
|
|
459
|
+
const text = '当前凭据为旧版哈希(0.2.0 起仅支持 scrypt),必须填写新密码才能保存';
|
|
460
|
+
setMsg({ kind: 'err', text }); setSaveErr(text);
|
|
290
461
|
} else if (r && (r.error === 'oidc-sso-only-unbound' || r.error === 'oidc-sso-only-invalid')) {
|
|
291
|
-
|
|
462
|
+
const text = (r && r.reason) || '无法启用「仅允许 SSO」';
|
|
463
|
+
setMsg({ kind: 'err', text }); setSaveErr(text);
|
|
292
464
|
} else if (r && typeof r.error === 'string') {
|
|
293
465
|
// 服务端 500/未知错误:展示真实信息便于排查
|
|
294
|
-
|
|
466
|
+
const text = '保存失败:' + r.error;
|
|
467
|
+
setMsg({ kind: 'err', text }); setSaveErr(text);
|
|
295
468
|
} else {
|
|
296
|
-
|
|
469
|
+
const text = '保存失败,请检查输入';
|
|
470
|
+
setMsg({ kind: 'err', text }); setSaveErr(text);
|
|
297
471
|
}
|
|
298
472
|
}).catch(() => {
|
|
299
473
|
setBusy(false);
|
|
@@ -379,6 +553,8 @@ window.__ModuleLoader__.load({
|
|
|
379
553
|
};
|
|
380
554
|
|
|
381
555
|
const enabled = Boolean(status && status.enabled);
|
|
556
|
+
const remoteTrust = Boolean(status && status.remoteTrust);
|
|
557
|
+
const reauthValid = Boolean(reauthToken && Date.now() < reauthExp);
|
|
382
558
|
const intro = status === null
|
|
383
559
|
? '正在读取认证状态…'
|
|
384
560
|
: (enabled
|
|
@@ -418,135 +594,152 @@ window.__ModuleLoader__.load({
|
|
|
418
594
|
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes());
|
|
419
595
|
}
|
|
420
596
|
|
|
597
|
+
const onToggleRemoteTrust = () => {
|
|
598
|
+
if (remoteTrustBusy) return;
|
|
599
|
+
const next = !remoteTrust;
|
|
600
|
+
setRemoteTrustBusy(true);
|
|
601
|
+
setRemoteTrustMsg(null);
|
|
602
|
+
api('/dsh-webui-oauth/remote-trust', { enabled: next }).then((r) => {
|
|
603
|
+
setRemoteTrustBusy(false);
|
|
604
|
+
if (r && r.ok) {
|
|
605
|
+
setStatus((s) => (s ? { ...s, remoteTrust: !!r.enabled } : s));
|
|
606
|
+
setRemoteTrustMsg({
|
|
607
|
+
kind: 'ok',
|
|
608
|
+
text: r.enabled
|
|
609
|
+
? '已开启远程访问信任。刷新页面后生效(设置 → 模型应可正常加载)。'
|
|
610
|
+
: '已关闭远程访问信任。刷新页面后生效。',
|
|
611
|
+
});
|
|
612
|
+
} else {
|
|
613
|
+
setRemoteTrustMsg({ kind: 'err', text: (r && (r.reason || r.error)) || '操作失败' });
|
|
614
|
+
}
|
|
615
|
+
}).catch(() => {
|
|
616
|
+
setRemoteTrustBusy(false);
|
|
617
|
+
setRemoteTrustMsg({ kind: 'err', text: '无法连接认证服务' });
|
|
618
|
+
});
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
// ---- 渲染辅助:卡片 + 徽章,避免重复嵌套 ----
|
|
622
|
+
const badge = (text, kind) => react.createElement('span', { className: 'wua-badge ' + (kind || 'muted') }, text);
|
|
623
|
+
const card = (title, badges, children) => react.createElement('section', { className: 'wua-card' },
|
|
624
|
+
react.createElement('div', { className: 'wua-card-head' },
|
|
625
|
+
react.createElement('h3', { className: 'wua-card-title' }, title),
|
|
626
|
+
react.createElement('div', { className: 'wua-badges' }, ...(badges || []).filter(Boolean))),
|
|
627
|
+
...(children || []));
|
|
628
|
+
|
|
421
629
|
return react.createElement('div', { className: 'wua-section' },
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
react.createElement('div', { className: 'wua-row' },
|
|
436
|
-
react.createElement('label', { className: 'wua-label' }, enabled ? '新密码(留空表示不修改)' : '密码(至少 8 位,含大小写、数字、特殊符号)'),
|
|
437
|
-
react.createElement('input', {
|
|
438
|
-
className: 'wua-input',
|
|
439
|
-
type: 'password',
|
|
440
|
-
value: password,
|
|
441
|
-
onChange: (e) => setPassword(e.target.value),
|
|
442
|
-
}),
|
|
443
|
-
),
|
|
444
|
-
react.createElement('div', { className: 'wua-row' },
|
|
445
|
-
react.createElement('label', { className: 'wua-label' }, '确认密码'),
|
|
446
|
-
react.createElement('input', {
|
|
447
|
-
className: 'wua-input',
|
|
448
|
-
type: 'password',
|
|
449
|
-
value: confirm,
|
|
450
|
-
onChange: (e) => setConfirm(e.target.value),
|
|
451
|
-
}),
|
|
452
|
-
),
|
|
453
|
-
enabled ? react.createElement('div', { className: 'wua-row' },
|
|
454
|
-
react.createElement('label', { className: 'wua-label' }, '当前密码(修改或禁用认证需验证)'),
|
|
455
|
-
react.createElement('input', {
|
|
456
|
-
className: 'wua-input',
|
|
457
|
-
type: 'password',
|
|
458
|
-
value: current,
|
|
459
|
-
onChange: (e) => setCurrent(e.target.value),
|
|
460
|
-
}),
|
|
461
|
-
) : null,
|
|
462
|
-
react.createElement('div', { className: 'wua-row' },
|
|
463
|
-
react.createElement('label', { className: 'wua-label' }, '会话有效期(登录后免登录时长)'),
|
|
464
|
-
react.createElement('select', {
|
|
465
|
-
className: 'wua-select',
|
|
466
|
-
value: ttl,
|
|
467
|
-
disabled: busy || status === null,
|
|
468
|
-
onChange: (e) => setTtl(e.target.value),
|
|
469
|
-
},
|
|
470
|
-
react.createElement('option', { key: '0', value: '0' }, '浏览器会话:关闭浏览器后失效'),
|
|
471
|
-
react.createElement('option', { key: '1', value: '1' }, '1 小时'),
|
|
472
|
-
react.createElement('option', { key: '12', value: '12' }, '12 小时(默认)'),
|
|
473
|
-
react.createElement('option', { key: '24', value: '24' }, '1 天'),
|
|
474
|
-
react.createElement('option', { key: '72', value: '72' }, '3 天'),
|
|
475
|
-
),
|
|
476
|
-
),
|
|
477
|
-
msg ? react.createElement('div', { className: msg.kind === 'ok' ? 'wua-msg' : 'wua-err' }, msg.text) : null,
|
|
478
|
-
react.createElement('div', { className: 'wua-row' },
|
|
479
|
-
react.createElement('label', { className: 'wua-label' }, 'OIDC 单点登录'),
|
|
480
|
-
react.createElement('label', { className: 'wua-label' },
|
|
481
|
-
react.createElement('input', {
|
|
482
|
-
type: 'checkbox',
|
|
483
|
-
checked: oidcEnabled,
|
|
484
|
-
onChange: (e) => setOidcEnabled(e.target.checked),
|
|
485
|
-
}),
|
|
486
|
-
' 启用(登录页显示「SSO 单点登录」按钮)'),
|
|
487
|
-
),
|
|
488
|
-
oidcEnabled ? react.createElement('div', { className: 'wua-section' },
|
|
489
|
-
react.createElement('div', { className: 'wua-row' },
|
|
490
|
-
react.createElement('label', { className: 'wua-label' },
|
|
491
|
-
react.createElement('input', {
|
|
492
|
-
type: 'checkbox',
|
|
493
|
-
checked: oidcSsoOnly,
|
|
494
|
-
onChange: (e) => setOidcSsoOnly(e.target.checked),
|
|
495
|
-
}),
|
|
496
|
-
' 仅允许 SSO(未登录时自动跳转到 SSO,并禁用密码登录)'),
|
|
497
|
-
react.createElement('p', { className: 'wua-hint', style: { borderTop: 'none', paddingTop: 0 } },
|
|
498
|
-
oidcBound
|
|
499
|
-
? '启用后密码登录接口会被拒绝,未登录访问一律跳转 SSO。请确认 IdP 可用,否则将无法登录;如被锁在门外,可手工把 dsh-webui-oauth.json 里的 ssoOnly 改回 false。'
|
|
500
|
-
: '尚未绑定 OIDC 身份,无法启用:请先在下方完成「绑定 OIDC 身份」。'),
|
|
501
|
-
),
|
|
502
|
-
react.createElement('div', { className: 'wua-row' },
|
|
503
|
-
react.createElement('label', { className: 'wua-label' }, 'Issuer(IdP 地址,须为 https)'),
|
|
504
|
-
react.createElement('input', { className: 'wua-input', type: 'text', value: oidcIssuer, spellCheck: false, onChange: (e) => setOidcIssuer(e.target.value) }),
|
|
505
|
-
),
|
|
506
|
-
react.createElement('div', { className: 'wua-row' },
|
|
507
|
-
react.createElement('label', { className: 'wua-label' }, 'Client ID'),
|
|
508
|
-
react.createElement('input', { className: 'wua-input', type: 'text', value: oidcClientId, spellCheck: false, onChange: (e) => setOidcClientId(e.target.value) }),
|
|
509
|
-
),
|
|
510
|
-
react.createElement('div', { className: 'wua-row' },
|
|
511
|
-
react.createElement('label', { className: 'wua-label' }, 'Client Secret(留空保留原值;机密客户端必填)'),
|
|
512
|
-
react.createElement('input', { className: 'wua-input', type: 'password', value: oidcClientSecret, autoComplete: 'new-password', onChange: (e) => setOidcClientSecret(e.target.value) }),
|
|
630
|
+
card('概览', [
|
|
631
|
+
enabled ? badge('认证已启用', 'ok') : badge('认证未启用', 'muted'),
|
|
632
|
+
oidcEnabled ? badge('OIDC 已启用', 'ok') : badge('OIDC 未启用', 'muted'),
|
|
633
|
+
oidcEnabled ? (oidcBound ? badge('身份已绑定', 'ok') : badge('身份未绑定', 'warn')) : null,
|
|
634
|
+
(status && status.oidc && status.oidc.ssoOnlyActive) ? badge('仅允许 SSO', 'ok') : null,
|
|
635
|
+
remoteTrust ? badge('远程信任已开启', 'ok') : null,
|
|
636
|
+
], [
|
|
637
|
+
react.createElement('p', { className: 'wua-intro' }, intro),
|
|
638
|
+
patchWarn ? react.createElement('p', { className: 'wua-warn' }, patchWarn) : null,
|
|
639
|
+
persistNote ? react.createElement('p', { className: 'wua-warn' }, persistNote) : null,
|
|
640
|
+
react.createElement('div', { className: 'wua-actions' },
|
|
641
|
+
enabled ? react.createElement('button', { className: 'wua-btn ghost', disabled: busy || status === null, onClick: onLogout }, '退出登录') : null,
|
|
642
|
+
enabled ? react.createElement('button', { className: 'wua-btn ghost', disabled: busy || status === null, onClick: onDisable }, '禁用认证') : null,
|
|
513
643
|
),
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
644
|
+
]),
|
|
645
|
+
card('账号与密码', [], [
|
|
646
|
+
react.createElement('div', { className: 'wua-grid' },
|
|
647
|
+
react.createElement('div', { className: 'wua-row' },
|
|
648
|
+
react.createElement('label', { className: 'wua-label' }, '用户名(3-32 位字母、数字、下划线或连字符)'),
|
|
649
|
+
react.createElement('input', { className: 'wua-input', type: 'text', value: username, spellCheck: false, onChange: (e) => setUsername(e.target.value) }),
|
|
650
|
+
),
|
|
651
|
+
react.createElement('div', { className: 'wua-row' },
|
|
652
|
+
react.createElement('label', { className: 'wua-label' }, '会话有效期(登录后免登录时长)'),
|
|
653
|
+
react.createElement('select', { className: 'wua-select', value: ttl, disabled: busy || status === null, onChange: (e) => setTtl(e.target.value) },
|
|
654
|
+
react.createElement('option', { key: '0', value: '0' }, '浏览器会话:关闭浏览器后失效'),
|
|
655
|
+
react.createElement('option', { key: '1', value: '1' }, '1 小时'),
|
|
656
|
+
react.createElement('option', { key: '12', value: '12' }, '12 小时(默认)'),
|
|
657
|
+
react.createElement('option', { key: '24', value: '24' }, '1 天'),
|
|
658
|
+
react.createElement('option', { key: '72', value: '72' }, '3 天'),
|
|
659
|
+
),
|
|
660
|
+
),
|
|
517
661
|
),
|
|
518
|
-
react.createElement('div', { className: 'wua-
|
|
519
|
-
react.createElement('
|
|
520
|
-
|
|
662
|
+
react.createElement('div', { className: 'wua-grid' },
|
|
663
|
+
react.createElement('div', { className: 'wua-row' },
|
|
664
|
+
react.createElement('label', { className: 'wua-label' }, enabled ? '新密码(留空表示不修改)' : '密码(至少 8 位,含大小写、数字、特殊符号)'),
|
|
665
|
+
react.createElement('input', { className: 'wua-input', type: 'password', value: password, onChange: (e) => setPassword(e.target.value) }),
|
|
666
|
+
),
|
|
667
|
+
react.createElement('div', { className: 'wua-row' },
|
|
668
|
+
react.createElement('label', { className: 'wua-label' }, '确认密码'),
|
|
669
|
+
react.createElement('input', { className: 'wua-input', type: 'password', value: confirm, onChange: (e) => setConfirm(e.target.value) }),
|
|
670
|
+
),
|
|
521
671
|
),
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
react.createElement('
|
|
533
|
-
react.createElement('
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
672
|
+
enabled ? react.createElement('div', { className: 'wua-row' },
|
|
673
|
+
react.createElement('label', { className: 'wua-label' }, '当前密码(修改或禁用认证需验证)'),
|
|
674
|
+
react.createElement('input', { className: 'wua-input', type: 'password', value: current, onChange: (e) => setCurrent(e.target.value) }),
|
|
675
|
+
) : null,
|
|
676
|
+
msg ? react.createElement('div', { className: msg.kind === 'ok' ? 'wua-msg' : 'wua-err' }, msg.text) : null,
|
|
677
|
+
]),
|
|
678
|
+
card('OIDC 单点登录', [
|
|
679
|
+
oidcEnabled ? badge('已启用', 'ok') : badge('未启用', 'muted'),
|
|
680
|
+
(oidcEnabled && oidcBound) ? badge('身份已绑定', 'ok') : null,
|
|
681
|
+
], [
|
|
682
|
+
react.createElement('label', { className: 'wua-label' },
|
|
683
|
+
react.createElement('input', { type: 'checkbox', checked: oidcEnabled, onChange: (e) => setOidcEnabled(e.target.checked) }),
|
|
684
|
+
' 启用(登录页显示「SSO 单点登录」按钮)'),
|
|
685
|
+
oidcEnabled ? react.createElement('div', { className: 'wua-section', style: { gap: '12px' } },
|
|
686
|
+
react.createElement('div', { className: 'wua-row' },
|
|
687
|
+
react.createElement('label', { className: 'wua-label' },
|
|
688
|
+
react.createElement('input', { type: 'checkbox', checked: oidcSsoOnly, onChange: (e) => setOidcSsoOnly(e.target.checked) }),
|
|
689
|
+
' 仅允许 SSO(未登录时自动跳转到 SSO,并禁用密码登录)'),
|
|
690
|
+
react.createElement('p', { className: 'wua-hint', style: { borderTop: 'none', paddingTop: 0 } },
|
|
691
|
+
oidcBound
|
|
692
|
+
? '启用后密码登录接口会被拒绝,未登录访问一律跳转 SSO。请确认 IdP 可用,否则将无法登录;如被锁在门外,可手工把 dsh-webui-oauth.json 里的 ssoOnly 改回 false。'
|
|
693
|
+
: '尚未绑定 OIDC 身份,无法启用:请先在下方完成「绑定 OIDC 身份」。'),
|
|
694
|
+
),
|
|
695
|
+
react.createElement('div', { className: 'wua-grid' },
|
|
696
|
+
react.createElement('div', { className: 'wua-row' },
|
|
697
|
+
react.createElement('label', { className: 'wua-label' }, 'Issuer(IdP 地址,须为 https)'),
|
|
698
|
+
react.createElement('input', { className: 'wua-input', type: 'text', value: oidcIssuer, spellCheck: false, onChange: (e) => setOidcIssuer(e.target.value) }),
|
|
699
|
+
),
|
|
700
|
+
react.createElement('div', { className: 'wua-row' },
|
|
701
|
+
react.createElement('label', { className: 'wua-label' }, 'Client ID'),
|
|
702
|
+
react.createElement('input', { className: 'wua-input', type: 'text', value: oidcClientId, spellCheck: false, onChange: (e) => setOidcClientId(e.target.value) }),
|
|
703
|
+
),
|
|
704
|
+
),
|
|
705
|
+
react.createElement('div', { className: 'wua-grid' },
|
|
706
|
+
react.createElement('div', { className: 'wua-row' },
|
|
707
|
+
react.createElement('label', { className: 'wua-label' }, 'Client Secret(留空保留原值;机密客户端必填)'),
|
|
708
|
+
react.createElement('input', { className: 'wua-input', type: 'password', value: oidcClientSecret, autoComplete: 'new-password', onChange: (e) => setOidcClientSecret(e.target.value) }),
|
|
709
|
+
),
|
|
710
|
+
react.createElement('div', { className: 'wua-row' },
|
|
711
|
+
react.createElement('label', { className: 'wua-label' }, 'Scope(空格分隔)'),
|
|
712
|
+
react.createElement('input', { className: 'wua-input', type: 'text', value: oidcScope, spellCheck: false, onChange: (e) => setOidcScope(e.target.value) }),
|
|
713
|
+
),
|
|
714
|
+
),
|
|
715
|
+
react.createElement('div', { className: 'wua-row' },
|
|
716
|
+
react.createElement('label', { className: 'wua-label' }, 'Redirect Base(可选;反代重写 Host 时必填,如 https://dsh.example.com)'),
|
|
717
|
+
react.createElement('input', { className: 'wua-input', type: 'text', value: oidcRedirectBase, spellCheck: false, onChange: (e) => setOidcRedirectBase(e.target.value) }),
|
|
718
|
+
),
|
|
719
|
+
react.createElement('div', { className: 'wua-row' },
|
|
720
|
+
react.createElement('label', { className: 'wua-label' }, '回调地址(填入 IdP 的 Redirect URI 允许列表,须精确匹配)'),
|
|
721
|
+
oidcRedirectUri
|
|
722
|
+
? react.createElement('p', { className: 'wua-hint', style: { borderTop: 'none', paddingTop: 0, wordBreak: 'break-all' } },
|
|
723
|
+
react.createElement('code', null, oidcRedirectUri))
|
|
724
|
+
: react.createElement('p', { className: 'wua-warn' },
|
|
725
|
+
'无法确定回调地址:请先填写上方 Redirect Base(或在本插件的 remote-web-ui 设置里配置 publicBaseUrl)。'),
|
|
726
|
+
),
|
|
727
|
+
react.createElement('div', { className: 'wua-row' },
|
|
728
|
+
react.createElement('label', { className: 'wua-label' },
|
|
729
|
+
react.createElement('input', { type: 'checkbox', checked: oidcTrustBrowser, onChange: (e) => setOidcTrustBrowser(e.target.checked) }),
|
|
730
|
+
' 信任浏览器 origin(默认开启;关闭时仅用上方 Redirect Base)'),
|
|
731
|
+
),
|
|
732
|
+
react.createElement('div', { className: 'wua-row' },
|
|
733
|
+
react.createElement('label', { className: 'wua-label' }, 'Prompt(可选,建议留空)'),
|
|
734
|
+
react.createElement('input', { className: 'wua-input', type: 'text', value: oidcPrompt, spellCheck: false, placeholder: '留空则不发送该参数', onChange: (e) => setOidcPrompt(e.target.value) }),
|
|
735
|
+
react.createElement('p', { className: 'wua-hint', style: { borderTop: 'none', paddingTop: 0 } },
|
|
736
|
+
'多数 IdP 无需该参数。部分 IdP 会对不认识的值直接拒绝授权(提示 unsupported prompt value requested),此时请保持留空。'),
|
|
737
|
+
),
|
|
738
|
+
) : null,
|
|
739
|
+
]),
|
|
740
|
+
oidcEnabled ? card('OIDC 身份绑定', [
|
|
741
|
+
oidcBound ? badge('已绑定', 'ok') : badge('未绑定', 'warn'),
|
|
742
|
+
], [
|
|
550
743
|
oidcBound
|
|
551
744
|
? react.createElement('p', { className: 'wua-intro' },
|
|
552
745
|
'当前已绑定身份:', react.createElement('code', null, oidcBoundHint || '(已绑定)'),
|
|
@@ -559,7 +752,7 @@ window.__ModuleLoader__.load({
|
|
|
559
752
|
react.createElement('input', { className: 'wua-input', type: 'password', value: unbindPw, autoComplete: 'current-password', onChange: (e) => setUnbindPw(e.target.value) }),
|
|
560
753
|
)
|
|
561
754
|
: null,
|
|
562
|
-
react.createElement('div', { className: 'wua-
|
|
755
|
+
react.createElement('div', { className: 'wua-actions' },
|
|
563
756
|
oidcBound
|
|
564
757
|
? react.createElement('button', { className: 'wua-btn ghost', disabled: busy || status === null, onClick: onUnbind }, '解除绑定')
|
|
565
758
|
: react.createElement('button', { className: 'wua-btn', disabled: busy || status === null, onClick: onBind }, '绑定 OIDC 身份'),
|
|
@@ -568,15 +761,30 @@ window.__ModuleLoader__.load({
|
|
|
568
761
|
: null,
|
|
569
762
|
),
|
|
570
763
|
bindMsg ? react.createElement('p', { className: bindMsg.kind === 'ok' ? 'wua-msg' : 'wua-err' }, bindMsg.text) : null,
|
|
571
|
-
) : null,
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
react.createElement('
|
|
764
|
+
]) : null,
|
|
765
|
+
card('远程访问', [
|
|
766
|
+
remoteTrust ? badge('远程信任已开启', 'ok') : badge('远程信任未开启', 'muted'),
|
|
767
|
+
], [
|
|
768
|
+
react.createElement('p', { className: 'wua-intro' },
|
|
769
|
+
remoteTrust
|
|
770
|
+
? '已通过 index.html 注入 __DSH_TRANSPORT__.ownsHost = true,反代域名下「设置 → 模型」可正常读取。刷新页面后生效。'
|
|
771
|
+
: '若经域名反代访问时「设置 → 模型」报 settings are unavailable in this browser,可开启远程访问信任。'),
|
|
772
|
+
react.createElement('div', { className: 'wua-actions' },
|
|
773
|
+
react.createElement('button', {
|
|
774
|
+
className: 'wua-btn',
|
|
775
|
+
disabled: remoteTrustBusy || status === null,
|
|
776
|
+
onClick: onToggleRemoteTrust,
|
|
777
|
+
}, remoteTrustBusy ? '处理中…' : (remoteTrust ? '关闭远程访问信任' : '开启远程访问信任')),
|
|
778
|
+
),
|
|
779
|
+
remoteTrustMsg ? react.createElement('p', { className: remoteTrustMsg.kind === 'ok' ? 'wua-msg' : 'wua-err' }, remoteTrustMsg.text) : null,
|
|
780
|
+
react.createElement('div', { className: 'wua-sep' }),
|
|
781
|
+
react.createElement('h4', { className: 'wua-card-title' }, 'DeepSeek 官方账号登录(远程回调)'),
|
|
782
|
+
react.createElement('p', { className: 'wua-hint', style: { borderTop: 'none', paddingTop: 0 } },
|
|
783
|
+
'在「设置 → 账号」点击 DeepSeek 登录后,浏览器会跳到 127.0.0.1 的回调地址(远程机器上通常打不开)。点右下角的「DeepSeek 登录回调」悬浮按钮,把地址栏里的完整 URL 粘贴进去即可。'),
|
|
784
|
+
]),
|
|
785
|
+
card('最近登录记录', [], [
|
|
786
|
+
react.createElement('p', { className: 'wua-hint', style: { borderTop: 'none', paddingTop: 0 } },
|
|
787
|
+
'完整审计日志可用 node index.js audit 查看。'),
|
|
580
788
|
audit === null
|
|
581
789
|
? react.createElement('p', { className: 'wua-intro' }, '正在读取…')
|
|
582
790
|
: (audit.length === 0
|
|
@@ -589,7 +797,44 @@ window.__ModuleLoader__.load({
|
|
|
589
797
|
entry.ip ? react.createElement('span', { className: 'wua-audit-ip' }, entry.ip) : null,
|
|
590
798
|
)),
|
|
591
799
|
)),
|
|
592
|
-
),
|
|
800
|
+
]),
|
|
801
|
+
react.createElement('button', {
|
|
802
|
+
className: 'wua-save-fab',
|
|
803
|
+
disabled: busy || status === null,
|
|
804
|
+
onClick: () => {
|
|
805
|
+
if (!enabled) { onSave({}); return; }
|
|
806
|
+
if (reauthValid) { onSave({ reauthToken }); return; }
|
|
807
|
+
setSaveErr('');
|
|
808
|
+
setSaveOpen(true);
|
|
809
|
+
},
|
|
810
|
+
}, busy ? '保存中…' : '保存设置'),
|
|
811
|
+
saveOpen ? react.createElement('div', { className: 'wua-modal-mask' },
|
|
812
|
+
react.createElement('div', { className: 'wua-modal' },
|
|
813
|
+
react.createElement('h4', { className: 'wua-card-title' }, '验证后保存'),
|
|
814
|
+
react.createElement('p', { className: 'wua-hint', style: { borderTop: 'none', paddingTop: 0 } },
|
|
815
|
+
reauthValid
|
|
816
|
+
? '已通过 SSO 验证(2 分钟内有效),可直接保存。'
|
|
817
|
+
: '修改认证配置需要验证当前身份:输入当前密码,或使用 SSO 验证。'),
|
|
818
|
+
reauthValid ? null : react.createElement('div', { className: 'wua-row' },
|
|
819
|
+
react.createElement('label', { className: 'wua-label' }, '当前密码'),
|
|
820
|
+
react.createElement('input', { className: 'wua-input', type: 'password', value: savePw, autoComplete: 'current-password', onChange: (e) => setSavePw(e.target.value) }),
|
|
821
|
+
),
|
|
822
|
+
saveErr ? react.createElement('p', { className: 'wua-err' }, saveErr) : null,
|
|
823
|
+
react.createElement('div', { className: 'wua-actions' },
|
|
824
|
+
react.createElement('button', { className: 'wua-btn', disabled: busy, onClick: () => {
|
|
825
|
+
if (reauthValid) { onSave({ reauthToken }); return; }
|
|
826
|
+
if (!savePw) { setSaveErr('请输入当前密码'); return; }
|
|
827
|
+
onSave({ current: savePw });
|
|
828
|
+
} }, busy ? '保存中…' : '验证并保存'),
|
|
829
|
+
react.createElement('button', { className: 'wua-btn ghost', disabled: busy, onClick: () => setSaveOpen(false) }, '取消'),
|
|
830
|
+
),
|
|
831
|
+
(status && status.oidc && status.oidc.enabled && status.oidc.bound)
|
|
832
|
+
? react.createElement('button', { className: 'wua-btn ghost', disabled: busy, onClick: () => {
|
|
833
|
+
location.href = '/dsh-webui-oauth/oidc/reauth?base=' + encodeURIComponent(location.origin || '');
|
|
834
|
+
} }, '使用 SSO 验证')
|
|
835
|
+
: null,
|
|
836
|
+
),
|
|
837
|
+
) : null,
|
|
593
838
|
react.createElement('p', { className: 'wua-hint' },
|
|
594
839
|
'密码规则:至少 8 位,必须包含大写字母、小写字母、数字和特殊符号(如 !@#$%^&*)。认证在 HTTP/传输层强制执行(WebUI 资源、/api 接口、WebSocket 全部要求有效会话),会话保存在服务端并由 HttpOnly Cookie 携带,修改密码会吊销其他所有会话。凭据以 scrypt 哈希保存在插件目录的 dsh-webui-auth.json(0.2.0 起仅接受 scrypt,旧版 SHA-256 凭据需删除该文件重新创建);忘记密码时删除该文件并重启 DSH 即可重置。可选用 OIDC 单点登录:配置 issuer/clientId/clientSecret 后登录页出现 SSO 按钮,认证通过后以 IdP 的 sub 作为本地会话用户名,并沿用相同的免登录时长机制。'),
|
|
595
840
|
);
|