dsh-webui-oauth 0.7.5 → 0.7.6
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 +25 -0
- package/README.en.md +4 -4
- package/README.md +1 -1
- package/lib/client.js +109 -100
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
本文件记录每个版本的**用户可见**变更。破坏性变更与升级动作单独标注。
|
|
4
4
|
|
|
5
|
+
## 0.7.6
|
|
6
|
+
|
|
7
|
+
把 DeepSeek 登录回调输入**合并进官方「等待登录」对话框**,不再单独占一个悬浮入口。
|
|
8
|
+
|
|
9
|
+
### 调整:回调粘贴框内联到等待登录窗口
|
|
10
|
+
|
|
11
|
+
- 官方账号登录的 `SignInDialog` 在等待浏览器时显示固定文案
|
|
12
|
+
(「没有自动打开新页面?」/「Page did not open automatically?」);
|
|
13
|
+
- 插件用 `MutationObserver` 找到这段文案,把「粘贴回调 URL」输入框注入到
|
|
14
|
+
**同一个对话框**里,文案变化/对话框关闭时自动移除;
|
|
15
|
+
- 移除 0.7.5 的独立「DeepSeek 登录回调」悬浮按钮;
|
|
16
|
+
- 这是尽力而为的 DOM 适配:官方 UI 改版时最多退化成"不显示输入框",
|
|
17
|
+
不影响登录本身;`POST /dsh-webui-oauth/oauth-relay` 端点保持可用,
|
|
18
|
+
也可以从控制台/其他入口调用。
|
|
19
|
+
|
|
20
|
+
### 保持不变
|
|
21
|
+
|
|
22
|
+
- 回调仍然**不公开**,中转端点要求会话;
|
|
23
|
+
- 输入 URL 只接受本机端口的 `/oauth/callback` + 单个 `code`/`state`;
|
|
24
|
+
- state/PKCE 由账号平台校验。
|
|
25
|
+
|
|
26
|
+
### 测试
|
|
27
|
+
|
|
28
|
+
- 客户端源码/回填测试通过;全量 22 个测试文件、599 项断言通过。
|
|
29
|
+
|
|
5
30
|
## 0.7.5
|
|
6
31
|
|
|
7
32
|
设置页交互修正:登录回调改成全局悬浮窗、远程信任可一键开关、保存改为右下角悬浮按钮 + 二次验证。
|
package/README.en.md
CHANGED
|
@@ -429,10 +429,10 @@ wired into `npm test`).
|
|
|
429
429
|
`http://127.0.0.1:<local port>`;
|
|
430
430
|
- after authorization the browser is redirected to that loopback callback — usually unreachable from
|
|
431
431
|
a remote machine, but the full URL stays in the address bar;
|
|
432
|
-
-
|
|
433
|
-
|
|
434
|
-
`/oauth/callback` handler (state and
|
|
435
|
-
token exchange;
|
|
432
|
+
- the official "Waiting for sign in" dialog grows a callback paste box (the plugin injects it into
|
|
433
|
+
that dialog via MutationObserver; there is no separate floating entry); paste the URL and the plugin
|
|
434
|
+
**replays it internally** to the account platform's registered `/oauth/callback` handler (state and
|
|
435
|
+
PKCE are still validated by the platform) to finish the token exchange;
|
|
436
436
|
- the relay endpoint requires the plugin session and accepts only the local port's
|
|
437
437
|
`/oauth/callback` with exactly one `code`/`state` (SSRF / open-redirect protection);
|
|
438
438
|
- on `http://127.0.0.1:3080` the official login already completes directly; no relay needed.
|
package/README.md
CHANGED
|
@@ -423,7 +423,7 @@ IdP 侧日志确认完整往返:`discovery → jwks → authorize(issued code)
|
|
|
423
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
424
|
- 劫持官方登录的 `callbackOrigin`:本地回环访问保留原 origin(cookie 同 host,回调直接命中);域名访问改写成 `http://127.0.0.1:<本机端口>`;
|
|
425
425
|
- 浏览器授权后会被跳到这个回环回调地址——远程机器上通常打不开,但地址栏里有完整 URL;
|
|
426
|
-
-
|
|
426
|
+
- 官方「等待登录」窗口里会直接出现一个回调粘贴框(插件用 MutationObserver 注入到该对话框内,不单独占一个悬浮入口);把地址栏 URL 粘贴进去,插件在服务端**内部重放**给账号平台注册的 `/oauth/callback` handler(state + PKCE 仍由平台校验),完成换 token;
|
|
427
427
|
- 中转端点要求插件会话,且只接受本机端口的 `/oauth/callback` + 单个 `code`/`state`(防 SSRF / 开放重定向);
|
|
428
428
|
- 本机 `http://127.0.0.1:3080` 访问时官方登录原本就能直接走完,无需中转。
|
|
429
429
|
- **DSH Desktop**:支持 `dsh-desktop-*` 标记透传(登录 / 退出后重登不再丢失标记),但**不提供**上游 0.3.6 的「宿主载体桥」。该桥只用 socket 是否回环来判定,而本部署是 nginx 同机反代——所有请求的 socket 都是 `127.0.0.1`,启用它等于"持有核心 Cookie 即可绕过插件会话 / SSO",与「仅允许 SSO」冲突。若必须使用 DSH Desktop 的 shell 直连模式,请改用上游原版插件,并自行评估该旁路风险。
|
package/lib/client.js
CHANGED
|
@@ -25,8 +25,7 @@ window.__ModuleLoader__.load({
|
|
|
25
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
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
|
-
'.wua-
|
|
29
|
-
'.wua-fab:hover { filter: brightness(1.05); }',
|
|
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); }',
|
|
30
29
|
'.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
30
|
'.wua-save-fab:disabled { opacity: .55; cursor: default; }',
|
|
32
31
|
'.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); }',
|
|
@@ -66,108 +65,118 @@ window.__ModuleLoader__.load({
|
|
|
66
65
|
if (styleEl.parentNode !== null) styleEl.parentNode.removeChild(styleEl);
|
|
67
66
|
}, 'dsh-webui-oauth: styles');
|
|
68
67
|
|
|
69
|
-
// ----------------
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
|
|
68
|
+
// ---------------- DeepSeek 登录回调:合并进官方「等待登录」对话框 ----------------
|
|
69
|
+
//
|
|
70
|
+
// 官方账号登录是 SignInDialog(另一个插件)渲染的,没有开放插槽;但它等待
|
|
71
|
+
// 浏览器时会显示一段固定文案("没有自动打开新页面?" / "Page did not open
|
|
72
|
+
// automatically?")。这里用 MutationObserver 找到那段文案,把回调输入框
|
|
73
|
+
// 插到同一个对话框里——不再单独挂一个悬浮按钮。
|
|
74
|
+
//
|
|
75
|
+
// 这是**尽力而为**的 DOM 适配:官方 UI 改版时最多退化成"不显示输入框",
|
|
76
|
+
// 不影响登录本身;/dsh-webui-oauth/oauth-relay 端点仍然可用。
|
|
77
|
+
(function mountInlineRelay() {
|
|
73
78
|
if (typeof document === 'undefined' || !document.body) return;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
msg.textContent = text;
|
|
134
|
-
msg.className = ok ? 'wua-msg' : 'wua-err';
|
|
135
|
-
msg.style.display = 'block';
|
|
79
|
+
const PROMPTS = ['没有自动打开新页面', 'Page did not open automatically'];
|
|
80
|
+
const MARK = 'data-dsh-wua-relay-inline';
|
|
81
|
+
let timer = null;
|
|
82
|
+
const injected = new Set();
|
|
83
|
+
|
|
84
|
+
const build = () => {
|
|
85
|
+
const box = document.createElement('div');
|
|
86
|
+
box.className = 'wua-inline-relay';
|
|
87
|
+
box.setAttribute(MARK, '1');
|
|
88
|
+
|
|
89
|
+
const label = document.createElement('div');
|
|
90
|
+
label.className = 'wua-label';
|
|
91
|
+
label.textContent = '远程访问?把浏览器地址栏里的完整回调 URL 粘贴到这里';
|
|
92
|
+
|
|
93
|
+
const row = document.createElement('div');
|
|
94
|
+
row.className = 'wua-actions';
|
|
95
|
+
row.style.alignItems = 'stretch';
|
|
96
|
+
const input = document.createElement('input');
|
|
97
|
+
input.className = 'wua-input';
|
|
98
|
+
input.type = 'text';
|
|
99
|
+
input.placeholder = 'http://127.0.0.1:3080/oauth/callback?code=…&state=…';
|
|
100
|
+
input.style.flex = '1';
|
|
101
|
+
const submit = document.createElement('button');
|
|
102
|
+
submit.type = 'button';
|
|
103
|
+
submit.className = 'wua-btn';
|
|
104
|
+
submit.textContent = '完成登录';
|
|
105
|
+
row.appendChild(input);
|
|
106
|
+
row.appendChild(submit);
|
|
107
|
+
|
|
108
|
+
const msg = document.createElement('p');
|
|
109
|
+
msg.style.display = 'none';
|
|
110
|
+
box.appendChild(label);
|
|
111
|
+
box.appendChild(row);
|
|
112
|
+
box.appendChild(msg);
|
|
113
|
+
|
|
114
|
+
const show = (text, ok) => {
|
|
115
|
+
msg.textContent = text;
|
|
116
|
+
msg.className = ok ? 'wua-msg' : 'wua-err';
|
|
117
|
+
msg.style.display = 'block';
|
|
118
|
+
};
|
|
119
|
+
submit.addEventListener('click', () => {
|
|
120
|
+
const url = input.value.trim();
|
|
121
|
+
if (!url) { show('请粘贴完整回调地址', false); return; }
|
|
122
|
+
submit.disabled = true;
|
|
123
|
+
fetch('/dsh-webui-oauth/oauth-relay', {
|
|
124
|
+
method: 'POST',
|
|
125
|
+
headers: { 'content-type': 'application/json' },
|
|
126
|
+
body: JSON.stringify({ url }),
|
|
127
|
+
}).then((r) => {
|
|
128
|
+
if (r.status === 401) { location.href = '/dsh-webui-oauth/login'; return null; }
|
|
129
|
+
return r.json();
|
|
130
|
+
}).then((res) => {
|
|
131
|
+
submit.disabled = false;
|
|
132
|
+
if (!res) return;
|
|
133
|
+
if (res.ok) { show('已收到回调,正在完成登录…', true); input.value = ''; }
|
|
134
|
+
else show(res.hint || res.reason || res.error || '中转失败', false);
|
|
135
|
+
}).catch(() => { submit.disabled = false; show('无法连接认证服务', false); });
|
|
136
|
+
});
|
|
137
|
+
return box;
|
|
136
138
|
};
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
139
|
+
|
|
140
|
+
const scan = () => {
|
|
141
|
+
for (const node of injected) {
|
|
142
|
+
const anchor = node.__dshWuaAnchor;
|
|
143
|
+
const stillWaiting = anchor && anchor.isConnected &&
|
|
144
|
+
PROMPTS.some((p) => (anchor.textContent || '').includes(p));
|
|
145
|
+
if (stillWaiting) continue;
|
|
146
|
+
try { node.remove(); } catch (e) { /* ignore */ }
|
|
147
|
+
injected.delete(node);
|
|
148
|
+
}
|
|
149
|
+
const paragraphs = document.querySelectorAll('p');
|
|
150
|
+
for (const el of paragraphs) {
|
|
151
|
+
const text = (el.textContent || '').trim();
|
|
152
|
+
if (!PROMPTS.some((p) => text.includes(p))) continue;
|
|
153
|
+
const host = el.parentElement;
|
|
154
|
+
if (!host || host.querySelector('[' + MARK + ']')) continue;
|
|
155
|
+
const box = build();
|
|
156
|
+
box.__dshWuaAnchor = el;
|
|
157
|
+
host.appendChild(box);
|
|
158
|
+
injected.add(box);
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
140
161
|
};
|
|
141
162
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
});
|
|
163
|
+
const schedule = () => {
|
|
164
|
+
if (timer !== null) return;
|
|
165
|
+
timer = setTimeout(() => {
|
|
166
|
+
timer = null;
|
|
167
|
+
try { scan(); } catch (e) { /* ignore */ }
|
|
168
|
+
}, 200);
|
|
169
|
+
};
|
|
169
170
|
|
|
170
|
-
|
|
171
|
+
const observer = new MutationObserver(schedule);
|
|
172
|
+
observer.observe(document.body, { childList: true, subtree: true, characterData: true });
|
|
173
|
+
schedule();
|
|
174
|
+
ctx.effect(() => () => {
|
|
175
|
+
observer.disconnect();
|
|
176
|
+
if (timer !== null) clearTimeout(timer);
|
|
177
|
+
for (const node of injected) { try { node.remove(); } catch (e) { /* ignore */ } }
|
|
178
|
+
injected.clear();
|
|
179
|
+
}, 'dsh-webui-oauth: inline relay');
|
|
171
180
|
})();
|
|
172
181
|
|
|
173
182
|
// ---------------- 会话失效:统一回登录页 ----------------
|
|
@@ -780,7 +789,7 @@ window.__ModuleLoader__.load({
|
|
|
780
789
|
react.createElement('div', { className: 'wua-sep' }),
|
|
781
790
|
react.createElement('h4', { className: 'wua-card-title' }, 'DeepSeek 官方账号登录(远程回调)'),
|
|
782
791
|
react.createElement('p', { className: 'wua-hint', style: { borderTop: 'none', paddingTop: 0 } },
|
|
783
|
-
'在「设置 → 账号」点击 DeepSeek 登录后,浏览器会跳到 127.0.0.1
|
|
792
|
+
'在「设置 → 账号」点击 DeepSeek 登录后,浏览器会跳到 127.0.0.1 的回调地址(远程机器上通常打不开)。官方「等待登录」窗口里会直接出现一个粘贴框:把地址栏里的完整 URL 粘进去即可。'),
|
|
784
793
|
]),
|
|
785
794
|
card('最近登录记录', [], [
|
|
786
795
|
react.createElement('p', { className: 'wua-hint', style: { borderTop: 'none', paddingTop: 0 } },
|