kczx-user-management 1.0.0 → 1.0.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/README.md +6 -2
- package/package.json +1 -1
- package/src/gateway-core.js +33 -4
- package/src/index.js +8 -0
package/README.md
CHANGED
|
@@ -98,6 +98,7 @@ user-management:
|
|
|
98
98
|
sites: [] # 空 = 自动枚举本机 IP;配域名 + 证书则按 SNI 选择
|
|
99
99
|
title: 'DSH 控制台'
|
|
100
100
|
autoActivate: false # false = 自助注册需管理员启用
|
|
101
|
+
historyMaxBytes: 67108864 # 历史改写缓冲上限(字节,默认 64 MiB,最小 1 MiB)
|
|
101
102
|
```
|
|
102
103
|
|
|
103
104
|
## 数据文件
|
|
@@ -130,7 +131,7 @@ node scripts/import-from-dsh-passwords.mjs --from E:\path\to\dsh-passwords --ove
|
|
|
130
131
|
## 测试
|
|
131
132
|
|
|
132
133
|
```bash
|
|
133
|
-
npm test #
|
|
134
|
+
npm test # 85 项:策略纯函数、store、API 层、以及真实网关的端到端用例
|
|
134
135
|
npm run check # 全量语法检查
|
|
135
136
|
```
|
|
136
137
|
|
|
@@ -151,7 +152,10 @@ dsh plugin --profile web remove kczx-user-management
|
|
|
151
152
|
- **门只管走门的人**:若另有插件把 dsh 宿主绑到 `0.0.0.0`(例如 dsh-lan-access),直连宿主端口即可绕过登录,
|
|
152
153
|
且**绕过流量不会出现在任何一本台账里**。要真正的强制约束,宿主端口必须保持回环。
|
|
153
154
|
- **token 配额依赖客户端上报**:浏览器侧被禁用或页面未加载时不计费(时长配额不受影响,它在服务端计时)。
|
|
154
|
-
-
|
|
155
|
+
- **会话历史要缓冲后才能改写**:历史响应是**一个 JSON 文档**(长会话实测 9–16 MB),网关必须先缓冲才能做沙盒降级与隐藏字符
|
|
156
|
+
清洗,因此有 `historyMaxBytes` 上限(默认 64 MiB)。超过上限的会话会 **fail-closed 返回 502**,UI 显示「历史加载失败」——
|
|
157
|
+
调大该值即可;1.0.0 之前用代理默认的 8 MiB,长会话必然踩中。
|
|
158
|
+
- 上游 `@weibaohui/user-management`(0.9.5)与本包 `kczx-user-management` 是两个不同的包,互不覆盖,`pnpm update` 不会把两者串在一起。
|
|
155
159
|
|
|
156
160
|
## License
|
|
157
161
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kczx-user-management",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "dsh 插件 · 登录门 + 账号 / 权限 / 配额 / 审计台账(dsh-passwords 的功能已并入):HTTPS 登录网关与自签证书、用户增删改查、工作区白名单与归属跟踪、每小时 token 与每日时长配额、沙盒档位下限、上传/git 开关、三本审计台账(登录/访问/操作)、IP 封禁、TOTP 两步验证。",
|
|
6
6
|
"license": "MIT",
|
package/src/gateway-core.js
CHANGED
|
@@ -100,6 +100,19 @@ function createGateway(options) {
|
|
|
100
100
|
const SESSION_HISTORY_RE = /^\/api\/session[.\/]history$/
|
|
101
101
|
/** Hard cap on an inspected request body; over it the request is refused. */
|
|
102
102
|
const MAX_GATE_BODY = 64 * 1024
|
|
103
|
+
/**
|
|
104
|
+
* Buffer cap for the rewritten session-history response, in bytes.
|
|
105
|
+
*
|
|
106
|
+
* The history RPC answers with the whole replay window in ONE JSON document,
|
|
107
|
+
* so a long session is megabytes — and the clamp/sanitize pass can only run on
|
|
108
|
+
* a buffered body. The cap stops a runaway response from pinning unbounded
|
|
109
|
+
* memory, but it has to clear real sessions: at the proxy's 8 MiB default a
|
|
110
|
+
* multi-hour session failed closed with a 502 and its history became
|
|
111
|
+
* unreadable in the UI. Override with `user-management.historyMaxBytes`.
|
|
112
|
+
*/
|
|
113
|
+
const DEFAULT_HISTORY_MAX_BYTES = 64 * 1024 * 1024
|
|
114
|
+
/** Floor accepted from config: under this the rewrite can only fail closed. */
|
|
115
|
+
const MIN_HISTORY_MAX_BYTES = 1024 * 1024
|
|
103
116
|
/** workspaceId → path, harvested from workspace-list responses (see below). */
|
|
104
117
|
const workspacePathById = new Map()
|
|
105
118
|
|
|
@@ -107,6 +120,19 @@ function createGateway(options) {
|
|
|
107
120
|
try { return new URL(req.url || '/', 'http://dsh.local').pathname } catch { return '' }
|
|
108
121
|
}
|
|
109
122
|
|
|
123
|
+
/**
|
|
124
|
+
* The history buffer cap as configured right now.
|
|
125
|
+
*
|
|
126
|
+
* Read per request rather than captured at construction, so a settings change
|
|
127
|
+
* rides the plugin's in-place hot reload instead of needing a listener restart.
|
|
128
|
+
*/
|
|
129
|
+
function historyBufferCap() {
|
|
130
|
+
const configured = Number(options.historyMaxBytes)
|
|
131
|
+
return Number.isFinite(configured) && configured >= MIN_HISTORY_MAX_BYTES
|
|
132
|
+
? Math.floor(configured)
|
|
133
|
+
: DEFAULT_HISTORY_MAX_BYTES
|
|
134
|
+
}
|
|
135
|
+
|
|
110
136
|
/**
|
|
111
137
|
* Ask the host half for an account's permissions. Kept behind deps so this
|
|
112
138
|
* module stays cordis-free and testable: no store, no policy, just a lookup.
|
|
@@ -144,10 +170,10 @@ function createGateway(options) {
|
|
|
144
170
|
/**
|
|
145
171
|
* Decide whether a response must be rewritten before the client sees it.
|
|
146
172
|
*
|
|
147
|
-
*
|
|
148
|
-
* must not receive entries outside it
|
|
149
|
-
*
|
|
150
|
-
*
|
|
173
|
+
* Two responses qualify: the workspace list (an account with a folder
|
|
174
|
+
* allow-list must not receive entries outside it — the unrestricted case
|
|
175
|
+
* returns null and streams through untouched, so it pays nothing) and the
|
|
176
|
+
* session history, which every account gets clamped and sanitized.
|
|
151
177
|
*/
|
|
152
178
|
function rewritePlanFor(req) {
|
|
153
179
|
if ((req.method || '').toUpperCase() !== 'POST') return null
|
|
@@ -186,6 +212,9 @@ function createGateway(options) {
|
|
|
186
212
|
if (SESSION_HISTORY_RE.test(path)) {
|
|
187
213
|
const allowedMode = perms && typeof perms.sandboxMode === 'string' ? perms.sandboxMode : null
|
|
188
214
|
return {
|
|
215
|
+
// The whole replay window arrives as one JSON document; the cap has to
|
|
216
|
+
// be sized for a real session, not for a small test fixture.
|
|
217
|
+
maxBytes: historyBufferCap(),
|
|
189
218
|
transform: (parsed) => {
|
|
190
219
|
// Clamp first (it rewrites values in place), then sanitize: a hidden
|
|
191
220
|
// character smuggled into a preset name must not survive the rewrite.
|
package/src/index.js
CHANGED
|
@@ -690,6 +690,13 @@ const Config = z.object({
|
|
|
690
690
|
// account (first admin excepted) into the disabled state until an admin
|
|
691
691
|
// enables it from the users table; true activates registrations immediately.
|
|
692
692
|
autoActivate: z.boolean().default(false),
|
|
693
|
+
// Buffer cap for the rewritten session-history response, in bytes. The history
|
|
694
|
+
// RPC answers with the whole replay window in one JSON document — megabytes
|
|
695
|
+
// for a long session — and the sandbox clamp / hidden-character strip can only
|
|
696
|
+
// run on a buffered body. Too small a cap makes history fail closed (HTTP 502)
|
|
697
|
+
// and the UI shows no history at all; the default is sized for multi-hour
|
|
698
|
+
// sessions. Lower it to bound memory, raise it for monsters.
|
|
699
|
+
historyMaxBytes: z.natural().min(1024 * 1024).default(64 * 1024 * 1024),
|
|
693
700
|
listenHost: z.string().default('0.0.0.0'),
|
|
694
701
|
port: z.natural().min(1).max(65535).default(19843),
|
|
695
702
|
sites: z
|
|
@@ -1154,6 +1161,7 @@ const plugin = {
|
|
|
1154
1161
|
sites,
|
|
1155
1162
|
certsDir,
|
|
1156
1163
|
title: cfg.title,
|
|
1164
|
+
historyMaxBytes: cfg.historyMaxBytes,
|
|
1157
1165
|
decider,
|
|
1158
1166
|
handleApi,
|
|
1159
1167
|
renderLoginPage,
|