@wenbin_wb/dsh-bridge 2.10.4 → 2.10.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 CHANGED
@@ -4,6 +4,16 @@
4
4
 
5
5
  ---
6
6
 
7
+ ## [v2.10.5] - 2026-09-06
8
+
9
+ ### 🐞 修复
10
+
11
+ - **修复「仅密码登录」模式自我锁死**:`password_only` 模式在尚未设置任何密码时,服务端会拒绝一切登录(防空白登录),而管理员因进不去面板也无法设置密码 → 永久死锁。现未设密码时放行(与「扫码+密码」默认模式一致——没有可校验的哈希即无秘密可保护),让管理员能进入面板完成密码初始化;设置密码后门禁才真正生效(错误密码会被拒绝)。
12
+ - **修复无密码时找不到设置密码入口**:此前"外部访问门禁"卡片(含访客访问密码输入框)仅在安全防护已开启时渲染;未开启防护时面板没有密码输入框,用户先开防护再想设密码就会被卡。现只要系统**尚未设置任何密码**,密码设置区就始终显示。
13
+ - **切换「仅密码」模式 / 开启防护的引导**:UI 在切换到 `password_only` 或开启防护时若尚未设密码,会就地弹窗引导先设置访问密码;服务端 `authUpdateConfig` 同步增加守卫——无密码时禁止开启 `password_only` 防护(除非同请求携带新密码),双保险杜绝再次自我锁死。
14
+
15
+ ---
16
+
7
17
  ## [v2.10.4] - 2026-09-05
8
18
 
9
19
  ### 🐞 修复
package/client/client.js CHANGED
@@ -1820,6 +1820,11 @@ var AccessAuthCard = React.memo(function AccessAuthCard2({ auth, rpcCall, onUpda
1820
1820
  const next = !enabled;
1821
1821
  const noPasswordYet = !auth?.hasPassword && !auth?.hasAdminPassword;
1822
1822
  if (next && noPasswordYet && mode !== "token_only") {
1823
+ if (mode === "password_only") {
1824
+ window.alert("\u300C\u4EC5\u5BC6\u7801 / PIN \u7801\u767B\u5F55\u300D\u6A21\u5F0F\u5FC5\u987B\u5148\u8BBE\u7F6E\u8BBF\u95EE\u5BC6\u7801\u624D\u80FD\u5F00\u542F\u5B89\u5168\u9632\u62A4\u3002\n\n\u8BF7\u5148\u5728\u4E0B\u65B9\u300C\u8BBE\u7F6E\u5916\u90E8\u8BBF\u5BA2\u8BBF\u95EE\u5BC6\u7801 / PIN \u7801\u300D\u8F93\u5165\u5BC6\u7801\u5E76\u70B9\u51FB\u300C\u4FDD\u5B58\u8BBF\u95EE\u5BC6\u7801\u300D\uFF0C\u7136\u540E\u518D\u5F00\u542F\u3002");
1825
+ setTopMsg({ ok: false, text: "\u8BF7\u5148\u8BBE\u7F6E\u8BBF\u5BA2\u8BBF\u95EE\u5BC6\u7801\uFF0C\u518D\u5F00\u542F\u5B89\u5168\u9632\u62A4" });
1826
+ return;
1827
+ }
1823
1828
  const go = window.confirm(
1824
1829
  '\u26A0\uFE0F \u60A8\u5C1A\u672A\u8BBE\u7F6E\u4EFB\u4F55\u8BBF\u95EE\u5BC6\u7801\u6216\u7BA1\u7406\u5BC6\u7801\u3002\n\n\u5F00\u542F\u5B89\u5168\u9632\u62A4\u540E\uFF0C\u4EFB\u4F55\u77E5\u9053\u5C40\u57DF\u7F51 IP / \u96A7\u9053\u5730\u5740\u7684\u8BBF\u5BA2\u4ECD\u53EF\u76F4\u63A5\u8FDB\u5165\uFF08\u5F53\u524D\u76F8\u5F53\u4E8E"\u514D\u5BC6\u5F00\u653E"\u72B6\u6001\uFF09\u3002\n\n\u662F\u5426\u4ECD\u8981\u5F00\u542F\uFF1F\u5EFA\u8BAE\u5148\u5173\u95ED\uFF0C\u5728\u4E0B\u65B9\u300C\u8BBE\u7F6E\u5916\u90E8\u8BBF\u5BA2\u8BBF\u95EE\u5BC6\u7801\u300D\u5904\u8BBE\u7F6E\u5BC6\u7801\u540E\u518D\u5F00\u542F\u3002'
1825
1830
  );
@@ -1851,6 +1856,11 @@ var AccessAuthCard = React.memo(function AccessAuthCard2({ auth, rpcCall, onUpda
1851
1856
  }
1852
1857
  };
1853
1858
  const handleChangeMode = async (m) => {
1859
+ if (m === "password_only" && !auth?.hasPassword && !auth?.hasAdminPassword) {
1860
+ window.alert("\u300C\u4EC5\u5BC6\u7801 / PIN \u7801\u767B\u5F55\u300D\u9700\u8981\u5148\u8BBE\u7F6E\u8BBF\u95EE\u5BC6\u7801\u3002\n\n\u8BF7\u5728\u4E0B\u65B9\u300C\u8BBE\u7F6E\u5916\u90E8\u8BBF\u5BA2\u8BBF\u95EE\u5BC6\u7801 / PIN \u7801\u300D\u5904\u8F93\u5165\u5BC6\u7801\u5E76\u70B9\u51FB\u300C\u4FDD\u5B58\u8BBF\u95EE\u5BC6\u7801\u300D\uFF0C\u7136\u540E\u518D\u5207\u6362\u5230\u6B64\u6A21\u5F0F\u6216\u5F00\u542F\u5B89\u5168\u9632\u62A4\u3002");
1861
+ setTopMsg({ ok: false, text: "\u8BF7\u5148\u5728\u4E0B\u65B9\u8BBE\u7F6E\u8BBF\u5BA2\u8BBF\u95EE\u5BC6\u7801\uFF0C\u518D\u5207\u6362\u4E3A\u300C\u4EC5\u5BC6\u7801\u767B\u5F55\u300D\u6A21\u5F0F" });
1862
+ return;
1863
+ }
1854
1864
  const prev = mode;
1855
1865
  setMode(m);
1856
1866
  try {
@@ -2064,7 +2074,10 @@ var AccessAuthCard = React.memo(function AccessAuthCard2({ auth, rpcCall, onUpda
2064
2074
  // =========================================================================
2065
2075
  // ---- 第一道防线:外部访问门禁(控制谁能进入 Web 界面使用 AI) ----
2066
2076
  // =========================================================================
2067
- enabled && React.createElement(
2077
+ // 第一道防线卡片:已开启防护,或【尚未设置任何密码】时始终显示——
2078
+ // 未设密码时必须给出密码输入框,否则用户开启防护(尤其 password_only)后
2079
+ // 会因无密码被锁在登录墙外且找不到设密码入口(自我锁死,v2.10.5 修复)。
2080
+ (enabled || !auth?.hasPassword) && React.createElement(
2068
2081
  "div",
2069
2082
  { style: s.card },
2070
2083
  React.createElement(
package/client/index.js CHANGED
@@ -758,9 +758,15 @@ const AccessAuthCard = React.memo(function AccessAuthCard({ auth, rpcCall, onUpd
758
758
  const handleToggleEnabled = async () => {
759
759
  const prev = enabled;
760
760
  const next = !enabled;
761
- // 开启安全防护但尚未设置任何密码:明确提示当前是"裸奔"状态,避免用户误以为已受密码保护
761
+ // 开启安全防护但尚未设置任何密码
762
762
  const noPasswordYet = !auth?.hasPassword && !auth?.hasAdminPassword;
763
763
  if (next && noPasswordYet && mode !== 'token_only') {
764
+ if (mode === 'password_only') {
765
+ // password_only 模式必须已有密码才能开启(服务端同样强制),就地引导设密码
766
+ window.alert('「仅密码 / PIN 码登录」模式必须先设置访问密码才能开启安全防护。\n\n请先在下方「设置外部访客访问密码 / PIN 码」输入密码并点击「保存访问密码」,然后再开启。');
767
+ setTopMsg({ ok: false, text: '请先设置访客访问密码,再开启安全防护' });
768
+ return;
769
+ }
764
770
  const go = window.confirm(
765
771
  '⚠️ 您尚未设置任何访问密码或管理密码。\n\n开启安全防护后,任何知道局域网 IP / 隧道地址的访客仍可直接进入(当前相当于"免密开放"状态)。\n\n是否仍要开启?建议先关闭,在下方「设置外部访客访问密码」处设置密码后再开启。'
766
772
  );
@@ -798,6 +804,13 @@ const AccessAuthCard = React.memo(function AccessAuthCard({ auth, rpcCall, onUpd
798
804
  };
799
805
 
800
806
  const handleChangeMode = async (m) => {
807
+ // 切换到「仅密码登录」但尚未设置任何密码:提前引导先设密码,避免切过去后
808
+ // 无密码可登录(服务端同样有守卫拒绝,双保险防自我锁死)
809
+ if (m === 'password_only' && !auth?.hasPassword && !auth?.hasAdminPassword) {
810
+ window.alert('「仅密码 / PIN 码登录」需要先设置访问密码。\n\n请在下方「设置外部访客访问密码 / PIN 码」处输入密码并点击「保存访问密码」,然后再切换到此模式或开启安全防护。');
811
+ setTopMsg({ ok: false, text: '请先在下方设置访客访问密码,再切换为「仅密码登录」模式' });
812
+ return;
813
+ }
801
814
  const prev = mode;
802
815
  setMode(m);
803
816
  try {
@@ -974,7 +987,10 @@ const AccessAuthCard = React.memo(function AccessAuthCard({ auth, rpcCall, onUpd
974
987
  // =========================================================================
975
988
  // ---- 第一道防线:外部访问门禁(控制谁能进入 Web 界面使用 AI) ----
976
989
  // =========================================================================
977
- enabled && React.createElement('div', { style: s.card },
990
+ // 第一道防线卡片:已开启防护,或【尚未设置任何密码】时始终显示——
991
+ // 未设密码时必须给出密码输入框,否则用户开启防护(尤其 password_only)后
992
+ // 会因无密码被锁在登录墙外且找不到设密码入口(自我锁死,v2.10.5 修复)。
993
+ (enabled || !auth?.hasPassword) && React.createElement('div', { style: s.card },
978
994
  React.createElement('div', { style: { marginBottom: 14 } },
979
995
  React.createElement('div', { style: { ...s.label, fontSize: 14, display: 'flex', alignItems: 'center', gap: 6 } },
980
996
  '🛡️ 第一道防线:外部访问门禁(控制谁能使用 AI)',
@@ -365,10 +365,11 @@ export class AuthManager {
365
365
  }
366
366
 
367
367
  if (!this.hasPassword) {
368
- // 若处于仅密码模式但尚未设置密码,不允许直接空白登录
369
- if (this.mode === 'password_only') {
370
- return { success: false, error: '管理员尚未设置访问密码,请先在控制台中设置密码' }
371
- }
368
+ // 管理员尚未设置任何访问密码:无哈希可校验,放行(与 token_and_password 一致)。
369
+ // 历史实现曾在此拒绝 password_only 模式的空白登录,但那会形成死锁:
370
+ // 无密码 登录墙永远 401 进不去面板 → 看不到设密码表单 → 永远无法设密码。
371
+ // 未设密码 = 没有受保护秘密,应先让管理员进入完成密码初始化;
372
+ // 设置密码后,墙才会真正生效(此时任意/错误密码均被拒绝)。
372
373
  return { success: true }
373
374
  }
374
375
 
package/lib/bridge-rpc.js CHANGED
@@ -119,6 +119,25 @@ export function installBridgeRpc(ctx, { service, authManager, platformManager, l
119
119
  const adminErr = checkAdminAuth(authManager, payload);
120
120
  if (adminErr) return adminErr;
121
121
  }
122
+
123
+ // 防自我锁死守卫(v2.10.5):
124
+ // password_only(仅密码)模式下若从未设置任何密码,开启防护或维持该模式会把
125
+ // 管理员锁在登录墙外(无哈希可校验、密码登录被拒 → 进不去面板设密码 → 死锁)。
126
+ // 因此:无任何密码时禁止单独开启 enabled(除非本次同请求携带 password),
127
+ // 也禁止单独切换到 password_only(除非已设密码或本次带 password)。
128
+ const willHaveNoPassword = !authManager.hasPassword && !authManager.hasAdminPassword
129
+ && (password === undefined || !password);
130
+ const nextMode = mode ?? authManager.mode;
131
+ const nextEnabled = enabled ?? authManager.enabled;
132
+ if (willHaveNoPassword && nextMode === 'password_only') {
133
+ if (nextEnabled) {
134
+ return fail('bad-request', '仅密码模式需要先设置访问密码:请先在下方「设置外部访客访问密码」处设置密码,再开启安全防护');
135
+ }
136
+ if (mode !== undefined) {
137
+ return fail('bad-request', '仅密码模式需要先设置访问密码:请先在下方「设置外部访客访问密码」处设置密码后再切换');
138
+ }
139
+ }
140
+
122
141
  if (enabled != null) await authManager.setEnabled(enabled);
123
142
  if (mode != null) await authManager.setMode(mode);
124
143
  if (scope != null) await authManager.setScope(scope);
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@wenbin_wb/dsh-bridge",
3
- "version": "2.10.4",
3
+ "version": "2.10.5",
4
4
  "description": "手机扫码即可在移动端/公网继续用 DeepSeek Harness,人不在电脑前也能接着干。一键局域网二维码、Cloudflare 公网隧道、自建隧道与微信 / QQ / 飞书 / Telegram Bot(多工作区/会话持久化/媒体/卡片审批/流式输出),无需自己搭公网服务器。",
5
- "releaseNotes": "【v2.10.4】\n• 修复:全新安装(未设置任何密码)时远程访问被误锁,需输入『管理密码』(任意输入皆可通过)才可进入——现无密码时远程免锁直进,并明确提示先设置访问密码\n• 修复:Telegram 代理在 Node ≥ 24 下不生效(createConnection 构造参数被忽略,改为构造后赋值)\n• 修复:移动端点击 workspace 分组名误关抽屉、自建隧道 gzip 会话剥离失效、目录弹窗深色模式适配\n• 新功能:自建隧道 SSE 流式传输 + WebSocket Ping/Pong 保活;远程/移动端禁用下拉刷新防误触",
5
+ "releaseNotes": "【v2.10.5】\n• 修复:「仅密码登录」模式在尚未设置密码时会把管理员锁在登录墙外(死锁:无密码 无法登录 进不去面板设密码)——现未设密码时放行让管理员完成初始化,设置后门禁才真正生效\n• 修复:未开启安全防护时设置面板没有访问密码输入框——现未设密码时始终显示密码设置区,避免开启后找不到设密码入口\n• 修复:UI 层切换到「仅密码」/ 开启安全防护时若尚未设密码,就地引导先设置访问密码(服务端同步守卫,双保险)",
6
6
  "type": "module",
7
7
  "main": "lib/index.js",
8
8
  "exports": {