dsh-multi-chat 0.5.0 → 0.5.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-multi-chat",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "DSH 多对话:在官方 DeepSeek Harness Web 界面里并排运行和监控多个对话实例(多窗口墙),并内置带口令认证的手机访问网关。npx dsh-multi-chat install|start|stop",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -119,6 +119,27 @@ cache-control: no-store\r
119
119
  socket.end();
120
120
  }
121
121
  /**
122
+ * Send a 302 that mints a session cookie and drops the `token` query param.
123
+ * A `?token=` URL authenticates exactly one request, so on first arrival the
124
+ * gateway exchanges it for an HMAC-signed cookie and redirects to the same
125
+ * path without `token=` (keeps the secret out of the address bar / history).
126
+ */
127
+ function respondSessionRedirect(socket, parsed, cookie) {
128
+ let location = parsed.target;
129
+ try {
130
+ const url = new URL(parsed.target, "http://gateway.local");
131
+ url.searchParams.delete("token");
132
+ location = url.pathname + url.search;
133
+ } catch {}
134
+ socket.write(`HTTP/1.1 302 Found\r
135
+ location: ${location}\r\nset-cookie: ${cookie}\r\ncontent-length: 0\r
136
+ connection: close\r
137
+ cache-control: no-store\r
138
+ \r
139
+ `);
140
+ socket.end();
141
+ }
142
+ /**
122
143
  * Rebuild the request head for the loopback target. Host and Origin point at
123
144
  * `127.0.0.1:<target-port>` so the DSH `/api` browser-trust fence sees a local
124
145
  * request. Non-upgrade requests get `connection: close` (fresh connection per
@@ -158,17 +179,16 @@ function startGateway(options) {
158
179
  return false;
159
180
  }
160
181
  };
161
- const isAuthed = (parsed) => {
182
+ const authKind = (parsed) => {
162
183
  const cookie = parsed.map.cookie ?? "";
163
184
  const session = /(?:^|;\s*)dsh_gw_session=([^;]+)/.exec(cookie);
164
- if (session !== null && verifySession(session[1] ?? "")) return true;
185
+ if (session !== null && verifySession(session[1] ?? "")) return "cookie";
165
186
  const bearer = parsed.map.authorization ?? "";
166
- if (bearer.startsWith("Bearer ") && bearer.slice(7) === token) return true;
187
+ if (bearer.startsWith("Bearer ") && bearer.slice(7) === token) return "bearer";
167
188
  try {
168
- return new URL(parsed.target, "http://gateway.local").searchParams.get("token") === token;
169
- } catch {
170
- return false;
171
- }
189
+ if (new URL(parsed.target, "http://gateway.local").searchParams.get("token") === token) return "query";
190
+ } catch {}
191
+ return "none";
172
192
  };
173
193
  const attempts = /* @__PURE__ */ new Map();
174
194
  const isBlocked = (ip) => {
@@ -262,10 +282,15 @@ connection: close\r
262
282
  handleLogin(socket, parsed, buffer.subarray(parsed.headBytes));
263
283
  return;
264
284
  }
265
- if (!isAuthed(parsed)) {
285
+ const kind = authKind(parsed);
286
+ if (kind === "none") {
266
287
  respondLogin(socket, name, path === "/" ? "/" : path);
267
288
  return;
268
289
  }
290
+ if (kind === "query") {
291
+ respondSessionRedirect(socket, parsed, `dsh_gw_session=${signSession(Date.now() + maxAgeHours * 36e5)}; Path=/; HttpOnly; SameSite=Strict; Max-Age=${maxAgeHours * 3600}`);
292
+ return;
293
+ }
269
294
  const target = connect(targetPort, "127.0.0.1");
270
295
  const rest = buffer.subarray(parsed.headBytes);
271
296
  let connected = false;