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/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,89 @@
|
|
|
2
2
|
|
|
3
3
|
本文件记录每个版本的**用户可见**变更。破坏性变更与升级动作单独标注。
|
|
4
4
|
|
|
5
|
+
## 0.7.5
|
|
6
|
+
|
|
7
|
+
设置页交互修正:登录回调改成全局悬浮窗、远程信任可一键开关、保存改为右下角悬浮按钮 + 二次验证。
|
|
8
|
+
|
|
9
|
+
### 修复:登录回调中转
|
|
10
|
+
|
|
11
|
+
- **输入框缺失**:旧实现把中转窗渲染在设置页组件内部,受容器布局影响显示不全;
|
|
12
|
+
现在改为在客户端插件 `apply()` 里挂**全局**悬浮组件(原生 DOM,不依赖 slot),
|
|
13
|
+
输入框、按钮、提示都完整可见。
|
|
14
|
+
- **不再放在设置页**:右下角常驻「DeepSeek 登录回调」悬浮按钮,任何页面都能打开;
|
|
15
|
+
设置页只保留说明文字。
|
|
16
|
+
|
|
17
|
+
### 新增:远程访问信任开关(不再只靠改 YAML)
|
|
18
|
+
|
|
19
|
+
- 设置 → 身份认证 → 远程访问,新增「开启 / 关闭远程访问信任」按钮;
|
|
20
|
+
- 新增 `POST /dsh-webui-oauth/remote-trust`(要求会话):写入插件配置
|
|
21
|
+
`dsh-webui-oauth.json` 的 `remoteTrust`,并**动态**安装/移除 index tap;
|
|
22
|
+
- 配置合并写入,不会覆盖 OIDC 段;
|
|
23
|
+
- `webServer.tapIndex` 不可用时返回 `tap-index-unavailable`,不假装成功;
|
|
24
|
+
- 仍兼容 settings 的 `remote-web-ui.trustRemoteHost`(手工配置优先级的兜底)。
|
|
25
|
+
|
|
26
|
+
### 新增:设置保存改为悬浮按钮 + 二次验证
|
|
27
|
+
|
|
28
|
+
- 账号卡片里的「保存账号密码」按钮已移除;
|
|
29
|
+
- 插件设置子页右下角常驻「保存设置」悬浮按钮;
|
|
30
|
+
- 点击后弹窗验证身份,再真正保存:
|
|
31
|
+
- **当前密码**;或
|
|
32
|
+
- **SSO 验证**:`GET /dsh-webui-oauth/oidc/reauth` 走一次 IdP 授权,
|
|
33
|
+
回调签发**一次性、2 分钟**的复验令牌(不新建会话),客户端存在
|
|
34
|
+
sessionStorage 并在保存时提交;
|
|
35
|
+
- `configure` / `disable` / `oidc/unbind` 均接受 `reauthToken` 替代密码;
|
|
36
|
+
- 令牌一次性、不可重放;密码路径保持不变。
|
|
37
|
+
|
|
38
|
+
### 测试
|
|
39
|
+
|
|
40
|
+
- `test/oidc-bind.test.mjs` 新增 9 项:SSO 复验全链路(发起 → 回调令牌 →
|
|
41
|
+
无密码保存 → 令牌不可重放)、远程信任端点(未登录 401 / 类型校验 / tapIndex 缺失降级);
|
|
42
|
+
- 全量 22 个测试文件、599 项断言通过。
|
|
43
|
+
|
|
44
|
+
## 0.7.4
|
|
45
|
+
|
|
46
|
+
修正 0.7.3 对官方 DeepSeek 登录的处理:`/oauth/callback` **不再公开**,改为
|
|
47
|
+
「回环回调 + 用户粘贴 + 服务端内部重放」;设置页改为卡片式布局并加入回调中转悬浮窗。
|
|
48
|
+
|
|
49
|
+
### 安全修正:/oauth/callback 不再放行匿名访问
|
|
50
|
+
|
|
51
|
+
0.7.3 把它加进公开白名单是错的:任何能访问该端点的人都可以触发一次换 token
|
|
52
|
+
尝试(虽然 state/PKCE 会挡,但没有必要暴露)。现在:
|
|
53
|
+
|
|
54
|
+
- `/oauth/callback` **不在** `PUBLIC_EXACT_PATHS`,仍受会话闸门保护;
|
|
55
|
+
- 它由账号平台在登录开始时动态注册,晚注册同样会被闸门包装(回归断言);
|
|
56
|
+
- 闸门包装时把原始 handler 挂到 `__dshWuaOriginal`,仅供服务端内部重放使用。
|
|
57
|
+
|
|
58
|
+
### 新增:官方 DeepSeek 登录的远程回调中转
|
|
59
|
+
|
|
60
|
+
- **劫持 callbackOrigin**:包装 `deepseekAccount.startSignIn`,把域名 origin 改写成
|
|
61
|
+
`http://127.0.0.1:<本机端口>`(上游 `loginOrigin()` 只接受回环);
|
|
62
|
+
本地回环访问保留原 origin(cookie 同 host,回调直接命中)。
|
|
63
|
+
- **悬浮中转窗**:设置 → 身份认证 → 远程访问 →「打开回调中转窗口」,
|
|
64
|
+
把浏览器地址栏里的完整回调 URL 粘贴进去。
|
|
65
|
+
- **服务端内部重放**:`POST /dsh-webui-oauth/oauth-relay`(要求会话)校验 URL
|
|
66
|
+
只为本机端口的 `/oauth/callback` + 单个 `code`/`state`,然后直接调用账号平台
|
|
67
|
+
注册的原始 handler;state/PKCE 仍由平台校验。防 SSRF / 开放重定向。
|
|
68
|
+
- 版本兼容:`deepseekAccount` 服务不存在或方法变化时静默跳过;
|
|
69
|
+
`startSignIn` 重复安装不会二次包装。
|
|
70
|
+
|
|
71
|
+
### 设置页重构(美化 + 优化)
|
|
72
|
+
|
|
73
|
+
- 由一整列平铺表单改为 6 张卡片:概览 / 账号与密码 / OIDC 单点登录 /
|
|
74
|
+
OIDC 身份绑定 / 远程访问 / 最近登录记录;
|
|
75
|
+
- 顶部徽章直接显示:认证、OIDC、绑定、仅允许 SSO、远程信任;
|
|
76
|
+
- 字段按两列自适应网格排列,主操作收敛到卡片底部;
|
|
77
|
+
- 新增「远程访问」卡片(远程信任状态 + 官方登录回调中转悬浮窗);
|
|
78
|
+
- 保持所有既有字段、回填规则(除密钥外全部预填)与错误提示不变。
|
|
79
|
+
|
|
80
|
+
### 测试
|
|
81
|
+
|
|
82
|
+
- 新增 `test/oauth-relay.test.mjs`(29 项):回环 origin 判定、回调 URL 校验
|
|
83
|
+
(端口/host/路径/重复参数/hash)、startSignIn 桥接与防重复包装;
|
|
84
|
+
- `test/gate-default-deny.test.mjs` 改为断言 `/oauth/callback` **匿名 401**、
|
|
85
|
+
带会话 200、原始 handler 可被中转调用;
|
|
86
|
+
- 全量 22 个测试文件、587 项断言通过。
|
|
87
|
+
|
|
5
88
|
## 0.7.3
|
|
6
89
|
|
|
7
90
|
整合 `dsh-remote-trust`,并确认与官方 DeepSeek 账号登录(0.2.1+)的兼容边界。
|
package/README.en.md
CHANGED
|
@@ -90,7 +90,7 @@ Authentication is enforced in four layers, all implemented by **wrapping the web
|
|
|
90
90
|
|
|
91
91
|
> **Since 0.6.7 the gate is a deny-by-default allowlist.** Previously only `/api` and `/plugins` were wrapped, but the webServer matches "exact first, then longest prefix" — a third-party route such as `/dsh-any-background/font` or `/dsh-550c-boot/update` is more specific than this plugin's `""` prefix and therefore won the match, bypassing the gate entirely (measured: anonymous requests read user fonts, wallpapers and image fingerprints with 200, while write endpoints returned 401 because they go through the RPC channel — "write-protected, read-naked"). A denylist can never keep up with the next plugin you install; a default-deny allowlist cannot miss.
|
|
92
92
|
>
|
|
93
|
-
> **The complete anonymous allowlist**: `/dsh-webui-oauth/login`, `/setup`, `/oidc/login`, `/oidc/callback
|
|
93
|
+
> **The complete anonymous allowlist**: `/dsh-webui-oauth/login`, `/setup`, `/oidc/login`, `/oidc/callback`. `/oauth/callback` (the official DeepSeek account callback) is **not** on it — it is protected by the session gate; on remote deployments the plugin's "callback relay" completes it instead (see "DeepSeek official account login"). Operators can extend the list via `remote-web-ui.publicPaths`.
|
|
94
94
|
>
|
|
95
95
|
> **When the session is invalid**: a browser navigation (`Accept: text/html`) gets a 302 back to the login page; `fetch`/XHR calls get a 401, which the client plugin intercepts and turns into a redirect to the login page. Visiting the login page with a valid session redirects to the app root (carrying the core launch token, so the core cookie exchange still happens).
|
|
96
96
|
|
|
@@ -112,13 +112,16 @@ Authentication is enforced in four layers, all implemented by **wrapping the web
|
|
|
112
112
|
- **Remote-access trust (0.7.3, optional built-in equivalent of `dsh-remote-trust`)**: when the page is
|
|
113
113
|
reached through a domain reverse proxy, the client treats it as non-loopback and Settings → Models
|
|
114
114
|
reports `settings are unavailable in this browser`. Enable:
|
|
115
|
+
**Since 0.7.5 you can simply click "开启远程访问信任" on the settings page**; editing YAML is no longer
|
|
116
|
+
required (and is still supported):
|
|
115
117
|
```yaml
|
|
116
118
|
remote-web-ui:
|
|
117
119
|
trustRemoteHost: true
|
|
118
120
|
```
|
|
119
121
|
It injects `__DSH_TRANSPORT__.ownsHost = true` into index.html through the official
|
|
120
122
|
`webServer.tapIndex`; the injection is idempotent, so installing `dsh-remote-trust` as well does not
|
|
121
|
-
duplicate it.
|
|
123
|
+
duplicate it. The settings toggle writes `remoteTrust` into `dsh-webui-oauth.json` and applies
|
|
124
|
+
dynamically (refresh the page for the injected index to take effect). If `tapIndex` is unavailable it only logs a warning and everything else keeps working
|
|
122
125
|
(version compatibility).
|
|
123
126
|
**Security boundary**: the declaration marks the page as a "trusted local" surface and also opens
|
|
124
127
|
other client-side features gated on `isLoopback`; enable it only when the page is already protected
|
|
@@ -417,19 +420,22 @@ wired into `npm test`).
|
|
|
417
420
|
|
|
418
421
|
## Known limits
|
|
419
422
|
|
|
420
|
-
- **DeepSeek official account login (0.2.1
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
`
|
|
424
|
-
`
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
423
|
+
- **DeepSeek official account login (0.2.1+, remote callback relay)**: `/oauth/callback` is **not**
|
|
424
|
+
a public path — it stays behind the session gate. Upstream `dsh-deepseek-account-platform`'s
|
|
425
|
+
`loginOrigin()` only accepts `http://localhost:<port>`, `http://127.0.0.1:<port>` and
|
|
426
|
+
`http://[::1]:<port>` (explicit non-zero port) and rejects reverse-proxied domains. The plugin:
|
|
427
|
+
- bridges the official login's `callbackOrigin`: loopback access keeps its own origin (cookie host
|
|
428
|
+
matches, the callback lands directly); a domain origin is rewritten to
|
|
429
|
+
`http://127.0.0.1:<local port>`;
|
|
430
|
+
- after authorization the browser is redirected to that loopback callback — usually unreachable from
|
|
431
|
+
a remote machine, but the full URL stays in the address bar;
|
|
432
|
+
- click the always-on "DeepSeek 登录回调" floating button at the bottom-right of any page and paste
|
|
433
|
+
the URL; the plugin **replays it internally** to the account platform's registered
|
|
434
|
+
`/oauth/callback` handler (state and PKCE are still validated by the platform) to finish the
|
|
435
|
+
token exchange;
|
|
436
|
+
- the relay endpoint requires the plugin session and accepts only the local port's
|
|
437
|
+
`/oauth/callback` with exactly one `code`/`state` (SSRF / open-redirect protection);
|
|
438
|
+
- on `http://127.0.0.1:3080` the official login already completes directly; no relay needed.
|
|
433
439
|
- **DSH Desktop**: `dsh-desktop-*` marker pass-through is supported (markers survive login and
|
|
434
440
|
re-login), but upstream 0.3.6's **host carrier bridge is intentionally not included**. That bridge
|
|
435
441
|
decides "loopback" purely from the socket address, and this deployment sits behind an nginx proxy on
|
package/README.md
CHANGED
|
@@ -90,7 +90,7 @@ DSH WebUI 身份认证插件(持久化插件)。在「设置 → 身份认
|
|
|
90
90
|
|
|
91
91
|
> **0.6.7 起为「默认拒绝」白名单模型。** 此前只包 `/api` 与 `/plugins`,而 webServer 按「exact 优先、其次最长前缀」匹配——第三方插件注册的 `/dsh-any-background/font`、`/dsh-550c-boot/update` 等更具体的路由会抢在本插件的 `""` 前缀之前命中,完全绕过鉴权(实测未登录即 200 读出用户字体、壁纸与镜像指纹,而写端点因走 RPC 通道反而 401)。黑名单追不上新装的插件,白名单不会漏。
|
|
92
92
|
>
|
|
93
|
-
> **能匿名访问的完整清单**:`/dsh-webui-oauth/login`、`/setup`、`/oidc/login`、`/oidc/callback
|
|
93
|
+
> **能匿名访问的完整清单**:`/dsh-webui-oauth/login`、`/setup`、`/oidc/login`、`/oidc/callback`。`/oauth/callback`(官方 DeepSeek 账号登录的回调)**不在白名单**——它受会话闸门保护;远程部署由插件的「回调中转」端点代为完成(见「DeepSeek 官方账号登录」)。部署方可用 `remote-web-ui.publicPaths` 追加(见下)。
|
|
94
94
|
>
|
|
95
95
|
> **会话失效时的行为**:浏览器直接导航(`Accept: text/html`)→ 302 回登录页;`fetch`/XHR 接口 → 401,由客户端插件拦截后跳登录页。已有有效会话再访问登录页 → 直接 302 回首页(带核心 launch token,顺带完成核心 Cookie 交换)。
|
|
96
96
|
|
|
@@ -121,11 +121,12 @@ DSH WebUI 身份认证插件(持久化插件)。在「设置 → 身份认
|
|
|
121
121
|
|
|
122
122
|
|
|
123
123
|
- **远程访问信任(0.7.3,原 `dsh-remote-trust` 的可选内置)**:经域名反代访问时,客户端会把页面判为"非 loopback",设置 → 模型会报 `settings are unavailable in this browser`。开启下面这一项即可(等价于安装 `dsh-remote-trust`,但不需要额外插件):
|
|
124
|
+
**0.7.5 起也可以直接在设置页点「开启远程访问信任」**,无需改 YAML;手工配置仍然兼容:
|
|
124
125
|
```yaml
|
|
125
126
|
remote-web-ui:
|
|
126
127
|
trustRemoteHost: true
|
|
127
128
|
```
|
|
128
|
-
它通过官方 `webServer.tapIndex` 往 index.html 注入 `__DSH_TRANSPORT__.ownsHost = true`;注入幂等,与 `dsh-remote-trust`
|
|
129
|
+
它通过官方 `webServer.tapIndex` 往 index.html 注入 `__DSH_TRANSPORT__.ownsHost = true`;注入幂等,与 `dsh-remote-trust` 同时安装也不会重复。设置页开关写入插件配置 `dsh-webui-oauth.json` 的 `remoteTrust`,动态生效(刷新页面后 index 生效)。`tapIndex` 不存在时只记一条 warn,插件其余功能照常(版本兼容)。
|
|
129
130
|
**安全边界**:该声明会把页面当作"可信本机",同时放开其他以 `isLoopback` 为条件的本机专属客户端表面;只应在页面已由本插件保护时启用,且服务端仍需 `--trusted-host <对外域名>`(它不改变服务端信任护栏)。
|
|
130
131
|
- **追加公开路径(0.6.7,默认拒绝的配套开关)**:机器调用、无法携带浏览器会话的端点(GitHub webhook、第三方 OAuth 回跳等)必须显式声明,否则会被闸门 401。同一 settings 段:
|
|
131
132
|
```yaml
|
|
@@ -419,10 +420,12 @@ IdP 侧日志确认完整往返:`discovery → jwks → authorize(issued code)
|
|
|
419
420
|
|
|
420
421
|
## 已知边界
|
|
421
422
|
|
|
422
|
-
- **DeepSeek 官方账号登录(0.2.1
|
|
423
|
-
-
|
|
424
|
-
-
|
|
425
|
-
-
|
|
423
|
+
- **DeepSeek 官方账号登录(0.2.1+,远程回调中转)**:`/oauth/callback` **不是公开路径**,仍受会话闸门保护。上游 `@deepseek-ai/dsh-deepseek-account-platform` 的 `loginOrigin()` 只接受 `http://localhost:<port>`、`http://127.0.0.1:<port>`、`http://[::1]:<port>`(显式非零端口),不支持反代域名。插件的处理方式:
|
|
424
|
+
- 劫持官方登录的 `callbackOrigin`:本地回环访问保留原 origin(cookie 同 host,回调直接命中);域名访问改写成 `http://127.0.0.1:<本机端口>`;
|
|
425
|
+
- 浏览器授权后会被跳到这个回环回调地址——远程机器上通常打不开,但地址栏里有完整 URL;
|
|
426
|
+
- 点击页面右下角常驻的「DeepSeek 登录回调」悬浮按钮,把地址栏 URL 粘贴进去;插件在服务端**内部重放**给账号平台注册的 `/oauth/callback` handler(state + PKCE 仍由平台校验),完成换 token;
|
|
427
|
+
- 中转端点要求插件会话,且只接受本机端口的 `/oauth/callback` + 单个 `code`/`state`(防 SSRF / 开放重定向);
|
|
428
|
+
- 本机 `http://127.0.0.1:3080` 访问时官方登录原本就能直接走完,无需中转。
|
|
426
429
|
- **DSH Desktop**:支持 `dsh-desktop-*` 标记透传(登录 / 退出后重登不再丢失标记),但**不提供**上游 0.3.6 的「宿主载体桥」。该桥只用 socket 是否回环来判定,而本部署是 nginx 同机反代——所有请求的 socket 都是 `127.0.0.1`,启用它等于"持有核心 Cookie 即可绕过插件会话 / SSO",与「仅允许 SSO」冲突。若必须使用 DSH Desktop 的 shell 直连模式,请改用上游原版插件,并自行评估该旁路风险。
|
|
427
430
|
- **审计日志字段类型**:0.7.2 起 `null` 与 boolean 原样写入(不再变成字符串 `"null"` / `"true"`);旧日志解析代码若假设"所有字段都是字符串",需相应调整。
|
|
428
431
|
- **dsh 0.2.2-rc.1 的 fallback 形状**:该版本起 `webServer.fallback` 可能是 `{ resource }`(`dsh-host-frontend-static` 就是这样注册的),而不再是函数。插件已兼容函数 / `{ resource }` / `{ handler }` 三种形状;资源型 fallback 同样会被闸门包装。若升级后 `/` 返回 `500` 且 body 为空(nginx 日志 `500 0`),请确认插件版本 ≥ 0.7.1。
|