npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

kczx-user-management

v1.5.6

Published

dsh 插件 · 登录门 + 账号 / 权限 / 配额 / 审计台账(dsh-passwords 的功能已并入):HTTPS 登录网关与自签证书、用户增删改查(可一并创建工作区)、工作区白名单与归属跟踪、每小时 token 与每日时长配额、沙盒档位下限、上传/git 开关、三本审计台账(登录/访问/操作)、IP 封禁、TOTP 两步验证。

Readme

kczx-user-management

本包是本地扩展版(1.0.0),已把 dsh-passwords 的账号 / 权限 / 配额 / 台账能力并入,不再是上游 0.9.5 的功能集。 上游同名包(npm 上的 @weibaohui/user-management)仅提供登录门禁与两步验证;本版额外提供工作区白名单与归属跟踪、新建账号即建工作区、 token 与时长配额、沙盒档位下限、上传 / git / 运维面门控、历史沙盒降级与隐藏字符清洗。 为便于从 registry 安装,本版以独立包名 kczx-user-management 发布,与上游互不覆盖。

给 dsh web 装一扇门:HTTPS 登录网关 + 账号管理 + 权限与配额执行 + 三本审计台账,一个插件完成。

安装

# 从 registry(发布之后;-w = pnpm 的 --workspace-root)
dsh plugin --profile web add kczx-user-management -w

# 从 tarball(离线 / 内网部署)
dsh plugin --profile web add ./kczx-user-management-1.0.0.tgz -w

# 或直接从目录 / 仓库(相对路径以你执行命令的目录为锚)
dsh plugin --profile web add link:E:\path\to\dsh-user-management -w
dsh plugin --profile web add github:you/dsh-user-management -w

# 装完重启 dsh web 生效

dsh plugin --profile <name> <args...> 就是在 ~/.dsh/profiles/<name> 里转发 pnpm <args...>;退出码 0 时按已安装状态 对账 dsh.profile.bundles——凡是声明了 dsh.bundle.patch 的依赖会自动进 layer 列表(本包含),所以不必手工改 bundles。

用 pnpm 直接安装

包内自带 dsh.bundle.patch,运行期依赖(@deepseek-ai/schemasterybcryptjsselfsigned)都在公共 registry 上,因此也可以当普通 npm 包装:

pnpm add kczx-user-management                      # 已发布到 registry 时
pnpm add ./kczx-user-management-1.0.0.tgz          # 从 tarball
pnpm add /abs/path/to/dsh-user-management          # 从目录

装进 dsh profile(把下面几行加进 ~/.dsh/profiles/web/package.json,再在该目录 pnpm install,然后重启 dsh web):

{
  "dependencies": { "kczx-user-management": "file:./kczx-user-management-1.0.0.tgz" },
  "dsh": { "profile": { "bundles": ["kczx-user-management"] } }
}

本包不依赖安装期脚本:postinstall 已移除(pnpm ≥10 默认拦截依赖构建脚本,留着只会让安装以 ERR_PNPM_IGNORED_BUILDS 退出码 1 收场),ensure:deps 保留为手动脚本,仅供「内网无 registry 且以 link: 挂载」的场景,从宿主补齐 @deepseek-ai/schemastery

pnpm add ./kczx-user-management-1.0.0.tgz        # 正常路径:依赖从 registry 装齐
node scripts/ensure-deps.mjs                     # 仅当上述解析不到 schemastery 时才需要

首次使用(初始化)

自助注册是关闭的:账号只能由管理员在设置页创建,第一个管理员走"初始化"流程。

  1. 启动 dsh。库里还没有任何账号时,启动日志会打印一行初始化密钥:

    [user-management] first-run setup key 3f0c… (also in ~/.dsh/user-management/setup-key.txt)

    密钥同时写在 $DSH_HOME/user-management/setup-key.txt;也可以用 settings.yamluser-management.setupKey 自行指定(那样不会往磁盘写密钥文件)。

  2. 浏览器打开 https://<主机>:8088/login —— 此时显示初始化页:填初始化密钥 + 用户名 + 密码。

  3. 提交后该账号直接成为管理员并登录,初始化密钥当场销毁(删文件、清内存);此后 /login 只显示登录表单, /user-management/api/setup 拒绝一切后续调用。

登录页样式与 dsh-passwords 一致(光晕背景 + 网格 + 玻璃卡片 + 渐变按钮),跟随宿主主题深浅色,右上角可切换中文 / English。 品牌可配:标题 / 副标题 / 页脚 / LOGO / 主色 / 默认语言都在 user-management: 段里,见「配置」一节。

它做什么

一扇门

独立的 node:https 监听器(默认 0.0.0.0:8088),自签证书(SAN 覆盖本机全部 IP + sslip.io / nip.io 别名), 未登录的页面访问跳转 /login、API 请求 401;登录页自带深浅色。设置页提供证书下载(PEM/DER)、指纹核对与 各系统导入向导。

账号与权限(管理员)

| 能力 | 说明 | |---|---| | 用户增删改查 | 用户表每行一个「编辑」全屏页:基本资料(部门 / 角色)+ 账号操作(重置密码 / 重置两步验证 / 禁用启用 / 删除)+ 权限与配额,集中一屏;改动类走「保存」,破坏性操作仍是各自带确认的按钮 | | 所属部门 | 账号资料字段(trim 后 ≤ 64 字,控制字符剔除):新增用户时可填,编辑页可随时改或清空;变更写进操作日志 | | 新建账号即建工作区 | 新增用户时勾选「同时创建该用户的工作区」:路径预填 <harness 根>/<部门>/<用户名>(可改成任意绝对路径);提交后目录按需创建、登记进 dsh 工作区列表,并把该路径写进这个账号的工作区白名单 | | 工作区白名单 | 留空 = 不允许任何工作区(白名单默认拒绝);填写后只允许这些目录下的工作区;条目 * = 允许全部。Windows 路径折叠大小写,末尾斜杠自动忽略 | | 工作区归属跟踪 | 谁创建的工作区归谁:别人看不到、也不能删改;删除后释放,账号删除后清理 | | 每小时 token 配额 | 由浏览器上报 dsh 自身的用量投影增量,服务端按小时窗口累计并拦截 | | 每日时长配额 | 按真实活跃时间累计(不是请求数),限流不因轮询膨胀 | | 沙盒档位下限 | read-only / workspace-write / danger-full-access:AI 无法通过设置、slash 命令或审批把自己提权 | | 上传 / git 开关 | 上传与「把数据带走」的通道分别控制(git RPC、会话导出、SSH 下载等) | | 策略封禁 | 与「禁用」不同:执行层每次请求都读,封禁即刻踢出会话 |

三本台账

| 台账 | 内容 | |---|---| | 登录记录 | 登录 / 失败 / 登出 / 改密 / 初始化(首次建号)/ 建号 / 删号 / 角色变更 / 资料变更 / 权限变更(含紧凑差异串);按页浏览 | | 访问记录 | 页面级访问(保留真实客户端 IP);按页浏览 | | 操作日志 | 经过网关的每次 API 调用与 WebSocket 连接(方法 / 路径 / 状态 / 来源 IP);对话类请求连内容一起记session.prompt 的提示词、session.rename 的会话标题写进同一行(最多 500 字,点开展开;粘贴的图片只记数量、不存 base64)。列表多一列「对话内容」,并按页浏览(默认每页 50,可选 20/50/100/200) |

另有 IP 封禁(回环与当前 IP 拒绝封禁,防自锁)与 TOTP 两步验证(默认关闭,可选开启)。

分页:登录记录 / 访问记录 / 操作日志 / 用户表四张表都是服务端分页——查询参数带 limit + offset, 响应回 { ..., total },前端底部是共用的分页控件(上一页 / 第 X/Y 页 · 共 N 条 / 下一页 / 每页 20·50·100·200, 默认 50)。筛选同样在服务端执行,所以「共 N 条」永远是匹配总数而不是当前页条数。账号表只有在调用方 传了 limit 时才分页,不传仍然整份返回(后端脚本的老用法不受影响)。

配置

~/.dsh/settings.yamluser-management: 段。改完即时生效的字段无需重启;只有监听器字段 (listenHost / port / plaintext / sites)会触发监听器重建,其余一律下一个请求就用新值。

user-management:
  enabled: true
  listenHost: '0.0.0.0'   # 只本机可达就填 127.0.0.1
  port: 8088
  plaintext: false        # true 时只允许回环(明文服务观察层用,见 MERGED-DEPLOYMENT.md)
  sites: []               # 空 = 自动枚举本机 IP;配域名 + 证书则按 SNI 选择
  setupKey: ''            # 首次初始化密钥;留空 = 启动时随机生成并写到 setup-key.txt
  historyMaxBytes: 67108864   # 历史改写缓冲上限(字节,默认 64 MiB,最小 1 MiB)

  # ── 登录页品牌(全部即时生效,改完刷新 /login 即可) ──
  title: 'DSH 控制台'      # 部署名:标签页标题后缀 + 标题兜底
  brandName: ''           # 卡片主标题;只填它,门面就是你的产品名
  loginTitle: ''          # 逐行覆盖内置文案('' = 用内置的「登录」/ Sign in)
  loginSub: ''
  setupTitle: ''          # 同上,作用于首次「初始化平台」表单
  setupSub: ''
  footerText: ''          # '' = 内置的「dsh 插件 · user-management」
  showFooter: true
  logoUrl: ''             # http(s)、data: URI,或本机图片文件;'' = 内置标记
  logoSize: 48            # 标记方框边长(px,24–96);图片按 contain 铺满方框
  auditConversationText: true   # 操作日志记录 session.prompt 文字与会话标题(见下)
  brandColor: ''          # '#rrggbb';只重染品牌色 token,danger/ok/warn 不动
  defaultLang: 'zh'       # 不带 ?lang= 时的门面语言(zh | en)
  showLangSwitch: true    # 是否显示页内中英切换

  # ── 会话与请求策略(全部即时生效) ──
  sessionDays: 7          # 会话有效期(天,滑动续期:过半后续一次)
  maxBodyBytes: 65536     # 本插件 API 的 JSON 请求体上限(字节)
  workspaceRoot: ''       # 新建账号工作区的根路径;'' = 问宿主(dsh 的工作区根,默认即 harness 所在目录)
  otpFailLimit: 5         # 两步验证连续错误几次后锁定
  otpLockoutSeconds: 60   # 锁定多久

登录页的品牌只吃 brandName / loginTitle 这类字段,改名不会影响账号与台账brandColor 只重染 品牌色那几个 token(按钮、LOGO 底、聚焦环及其阴影),状态色保持不变——颜色承载语义,不该被顺手改掉。

LOGO 支持本机图片(1.1.2 起):logoUrl 填绝对路径(E:/brand/logo.png)或 $DSH_HOME/user-management/ 下的文件名(把图片放到 users.json 旁边,然后填 logo.png)。裸路径本身不是 URL,浏览器会拿它当相对路径去 请求网关,所以本机图片由插件用 /user-management/branding/logo 这条公开路由回吐给登录页(和 /login 一样 必须免登录,否则图片是 401);该路由只吐这一个配置文件,且限定图片扩展名、非空、≤ 512 KiB,其余一律 404, 路径下的其它子路径在本机就直接 404,不会被匿名转发到 dsh 上游。改图不用重启——路由每次现读文件。

图片铺满方框logoSize 控制方框边长(默认 48px,24–96),图片用 object-fit: contain 填进去——方图正好 铺满,长图/竖图留白而不裁切。这是品牌字段里唯一会改布局的一个,调大小在设置页里改完刷新即可,不用重启。 未接线的占位字段仍有 loginFailLimit / lockoutSeconds(登录失败不自动锁定,用 IP 封禁代替), 保留只是为了老的 settings.yaml 继续可解析。

操作日志的分页(1.3.0 起)

台账上限 5000 行、每行还带对话文字,一次全拉进浏览器既慢又没必要。现在「操作日志」是服务端分页GET /user-management/api/audit?limit=50&offset=100 返回 { entries, total, limit, offset }——除了这一页, 还给出匹配总数,前端才说得出「第 3/40 页 · 共 1987 条」。表格下方有上一页 / 下一页和每页条数(20/50/100/200)。

两个容易做错的点,都按服务端口径处理:

  • 筛选也走服务端,和分页窗口是同一组参数:username / method / path / statusClass。若在前端过滤已取回的 那一页,翻页翻的就是"某个结果的子集",而且"共 N 条"只能是这一页的数字——那是错的。筛选条件改动会回到第 1 页 (换条件后停在旧页码没有意义),输入框有 250ms 去抖,不会每敲一个字发一次请求。
  • 删掉最后一条时若当前页只剩那一行,会退回上一页,而不是停在"第 N 页(空的)"。

操作日志里的对话内容(1.2.0 起)

「用户管理 → 操作日志」每一行是经过网关的一次 API 调用。以前只看得到 POST /api/session.prompt——可这一行不论用户说了什么都长一个样, 日志于是回答不了「这个账号到底做了什么」。现在网关把人打的那句话从请求体里取出来,随行写进台账:

  • session.promptcontent[] 里所有 type: 'text' 片段按行拼接;type: 'image' 只记数量(截图是 base64,不是台账该存的东西)。
  • session.rename → 会话标题。
  • 其它 RPC 完全不碰请求体,仍按原来的方式流式转发。
  • 存储侧每行最多 500 字(MAX_AUDIT_TEXT,超长截断);前端那一列默认单行省略、点一下就地展开全文,图片数量显示为徽标。
  • 只观察、不拦截:抓取只是顺路把请求体缓一下再原样重放给上游,字节不变;超过 2 MiB 的请求体(例如带大图的提示词)原样透传、不记录文字。
  • 隐私开关:user-management.auditConversationText: false(设置页「会话与请求策略」里也有这一项)关掉即完全不抓。台账是 $DSH_HOME/user-management/audit.jsonl 明文文件,提示词可能含口令 / 密钥时请关闭。

新建用户时一并创建工作区(1.5.0 起)

「用户 → 新增用户」多了一个复选框「同时创建该用户的工作区」。勾上后出现路径框,预填默认路径 <harness 根>/<部门>/<用户名>(部门留空就少一层,只到用户名),可以直接改成任意绝对路径;下方一行小字写着默认值, 改过之后还有「恢复默认」。不勾选则一切照旧。

提交时服务端按这个顺序做事,能失败的都在写账号之前失败

  1. 校验路径:自定义路径必须以盘符或 / 开头(相对路径会报错,不会被悄悄拼到进程 cwd 上);根路径本身 (harness 目录)不能当工作区;路径长度 ≤ 1024。部门是管理员随手写的自由文本,所以它会被折成一段合法的目录名 (\ / : * ? " < > | 与控制字符 → -,纯点号(..)直接丢掉),绝不允许它多出一层目录或跳出根路径。
  2. 归属冲突:这个路径已经属于别的账号 → 409,账号和目录都不会建。既有的目录(别人放的文件)会被领养而不是重新创建。
  3. 建目录mkdir -p,已存在则复用);失败则 400,不建账号
  4. 建账号,并把该路径写进它的工作区白名单allowedFolders)。白名单是默认拒绝的:不放开的话,账号 根本看不到自己那块地。第 3 步建出来的空目录在账号写入失败时会被回收(只删空目录)。
  5. 登记进 dsh 工作区列表:通过宿主的 workspaceRegistry 服务(运行期 ctx.inject,不写死依赖)。 这一步失败不回滚——目录和账号都是真的,接口把 registered: false 和原因一起返回,界面提示 「用户已创建、目录已建好,但未登记进工作区列表」。dsh 的工作区列表用 realpath 后的规范路径,所以登记返回的 规范路径也会一并加进白名单(软链接根目录 / Windows 大小写差异下,账号才看得到自己的那块地)。
  6. 认领归属:把该路径记到这个账号名下——别人看不到、也不能删改(见「工作区归属跟踪」)。

接口:POST /user-management/api/users 增加 createWorkspace: true 与可选的 workspacePath(留空 = 服务端按默认规则生成), 响应多一个 workspace 报告({ ok, path, source, directory, registered, owner, error },没勾选时为 null); GET /user-management/api/workspace-root(管理员)回 { root, sep },新增用户弹窗用它预填路径。 根路径优先取 user-management.workspaceRoot,未配置则问宿主的 sandboxPolicy.workspaceRoot(dsh 默认就是进程 cwd, 即 harness 所在目录),最后兜底 process.cwd()

被拒绝时提示什么(1.5.1 起)

被网关拦下的请求(没有建工作区的权限、目录不在白名单、配额用完、上传 / git 被关、沙盒提权被拒……)过去一律回 HTTP 403。 而 dsh 的浏览器客户端把任何非 2xx 都当成传输故障、根本不读响应体,于是用户看到的是:

workspace create failed: internal: transport failure for /api/workspace.create: HTTP 403

这读起来像程序坏了,而不像「你的账号没有这个权限」。现在这类拒绝改走 RPC 协议自带的错误位:HTTP 200 + result.ok: false, 原因写在 error.message 里,各处 dsh 界面都会原样显示:

workspace create failed: internal: 本账号没有创建 / 管理工作区的权限(请联系管理员在「用户管理 → 编辑用户 → 权限与配额」中开启「允许创建工作区」)

只有不是 RPC 调用的请求(没有 rpcId 可回显的裸请求、GET)仍回原来的 403 JSON —— 给它们编一个 rpcId 只会把「被拒绝」 变成客户端的协议错误,比原样回报更糟。

顺带修好一个盲区:被拒绝的请求以前完全不进操作日志(它没到上游,写日志的钩子永远不会触发)。现在会记一行 403 + 已拒绝:<原因>,管理员在「用户管理 → 操作日志」里直接看得到谁、想做什么、为什么被挡下来。

控制台页头的位置(1.5.3 起)

「用户管理」整页原来的页头是一条横贯屏幕的标题栏:宽屏上标题孤零零贴在左上角、按钮贴在右上角,和下面的 tab 条与内容列毫无关系。现在标题与 tab 收进居中内容列,而「退出登录」单独钉在窗口的右上角

[⏻ 退出登录]                                  ← 贴窗口右上角(与「编辑用户」页那颗同一位置)
     用户管理                                  ← 内容列内,与 tab 条左对齐
     [用户][登录记录][访问记录][操作日志][IP 封禁][证书][2FA]  [返回设置]
     …表格 / 台账…

「用户管理」这一行是「这一页是什么」,与内容左对齐;「返回设置」与 tab 条同一条线、右边缘对齐。退出登录之所以 不跟着内容列走,是因为上一层「编辑用户」页把自己的退出登录放在窗口右上角——同一个按钮不能因为进出一层就横向挪位。 普通用户没有 tab 条,但「返回设置」仍然落在同一条线上(右对齐),两个视图因此读起来还是同一页。观察者模式 (dsh-passwords 在前)没有退出登录,只有「返回设置」。

「退出登录」现在先问一句确定退出登录?退出后需要重新输入用户名和密码。):控制台页头和账号菜单里各有一个入口, 且都离用户正在做的事只差一次误点——确认放在共用的 doLogout() 里,谁都绕不过去。

「编辑用户」整页自带一个退出登录,钉在页面最右上角(关闭 / 保存之后隔一条竖线,不跟表单按钮混在一起):

┌ 编辑用户 · bob ─────────────────────────────────────────────┐
│                             [关闭] [保存] │ [⏻ 退出登录]    │
└─────────────────────────────────────────────────────────────┘

同一时刻屏幕上只有一个退出登录:打开这一页时,控制台那个窗口右上角的退出登录收起来(位置本来就是同一个,只是避免两颗同时存在)。不透明浮层确实盖住了它, 但「盖住」不等于「不在文档里」——那时它离一次 Tab+Enter 只有一步,而这颗按钮结束的是整个会话。收放由一个模块级的 小订阅(setFullPageOverlay)广播,编辑页挂载时置位、卸载时清除。

欢迎页右上角的账号信息(1.5.4 起)

会话头部只在有会话时才存在,于是落地页(欢迎页 / 新建会话页)成了唯一一个不说「你是谁」的页面——连账号菜单里 仅有的两件事(改密码、退出登录)也没地方点。现在账号芯片(头像 + 用户名,点开即账号菜单)还会落在shell 的全局 overlay 座位(ui-layout 的 shell.overlay,一个可叠加的 list 槽)上,固定在窗口右上角;一旦有会话成为当前会话, 这个芯片立刻消失,把位置让回会话头部那一个,不会出现两个。

实现上用的是该座位给每个全局占用者注入的 useSessionsstate.current 就是框架自己对「现在有没有打开会话」的回答, 不需要去偷看 DOM。hero 上的三个座位(品牌标记 / 工作区选择器 / 代理预设)都是单占位且已被占用,所以走 overlay 座位。

在设置页里改(1.1.0 起)

上面这些字段也可以在 设置 → 通用设置 里改,就在「语言」「外观」那几行下面:「登录页与策略配置」一行, 默认收起着(通用列里都是紧凑的单行偏好,十六个字段全展开会把这一页压垮),行内摘要写着登录页当前显示 什么、有几项被改过,点「展开」出现完整表单。本插件在浏览器侧把这个设置命名空间绑成 settingsScope.bind({ namespace: 'user-management' }),并注册进 settings.general.item 槽——宿主注册 命名空间、浏览器注册控件,两边经由同一份 settings 文档同步。

表单行为:改过没存的字段标「未保存」;写入过 settings.yaml 的字段标「已自定义」并带「恢复默认」 (unset,清掉覆盖回到内置值,也让 settings.yaml 保持干净);文本框留空等于清除覆盖;数字字段按 schema 的下限校验(比如请求体上限 ≥ 1024);保存按字段顺序逐个写入,任何一个值非法就整体不落盘并提示是哪一项。 远程浏览器访问时设置是只读的(settings RPC 只走回环),这一行会直接说明并让你改服务器上的 settings.yaml; 装了旧版插件时它也会明说「这些新字段保存了也不会生效」。

刻意没放进设置页的字段listenHost / port / plaintext / sites / enabled 会重启监听器——那正是承载 这个设置页的东西,从 UI 里翻可能把自己关在门外;trustedSecret / setupKey 是密钥,不该经浏览器表单往返。 这些继续只在 settings.yaml 里配。

数据文件

都在 $DSH_HOME/user-management/(默认 ~/.dsh/user-management/,0600,原子写):

| 文件 | 内容 | |---|---| | users.json | 账号(scrypt 哈希;迁移进来的账号保留 bcrypt,首次登录成功后自动升级为 scrypt;含 department 所属部门) | | sessions.json | 会话令牌(重启不掉线,7 天滑动过期) | | activity.jsonl / audit.jsonl | 登录与访问台账 / 操作日志(滚动保留最近若干条) | | bans.json | IP 封禁 | | usage.json | 按账号的当日活跃秒数与小时窗口 token(隔日自动清理) | | workspaces.json | 工作区归属(路径 → 账号) |

从 dsh-passwords 迁移

账号、权限与配额可由 scripts/import-from-dsh-passwords.mjs 一次性导入(读取源库时复用 dsh-passwords 自己的模块, 因为它的用户名是 AES-GCM 密文 + HMAC 索引):

node scripts/import-from-dsh-passwords.mjs --from E:\path\to\dsh-passwords --dry-run
node scripts/import-from-dsh-passwords.mjs --from E:\path\to\dsh-passwords
# 同名账号两边密码不同时,以源端为准(迁移期间通常正是你要的)
node scripts/import-from-dsh-passwords.mjs --from E:\path\to\dsh-passwords --overwrite-credentials

迁移是幂等的:重复运行只会更新权限,不会重复建号;默认不覆盖已有账号的密码

测试

npm test          # 203 项:策略纯函数、store、API 层、客户端契约、以及真实网关的端到端用例
npm run check     # 全量语法检查

端到端用例会起真实网关(明文模式 + 假上游)验证:工作区过滤含 gzip、目录门与请求体重放、沙盒提权拦截与审批改写、 上传 / git / 运维面门控、历史降级与隐藏字符清洗、配额拦截、归属认领与释放。

卸载 / 回滚

dsh plugin --profile web remove kczx-user-management

数据文件不会被删除,重新装回即恢复。若在合并部署中回退到「观察层」形态,见 MERGED-DEPLOYMENT.md

已知边界

  • 执行只对本地存在的账号生效:签名身份 / 未知用户名按「不限制」处理,避免迁移期误锁人。
  • 门只管走门的人:若另有插件把 dsh 宿主绑到 0.0.0.0(例如 dsh-lan-access),直连宿主端口即可绕过登录, 且绕过流量不会出现在任何一本台账里。要真正的强制约束,宿主端口必须保持回环。
  • 工作区白名单的语义:空名单 = 全部禁止(1.0.4 起;此前是"允许全部")。升级时会对老记录做一次性迁移——把"空名单"写成显式 的 *(允许全部),保持账号原有的可达范围;迁移只跑一次(留下 workspace-scope-migration.json 标记),之后你手工清空白名单 才等于全部禁止。新建账号默认空 = 无工作区权限。
  • token 配额依赖客户端上报:浏览器侧被禁用或页面未加载时不计费(时长配额不受影响,它在服务端计时)。
  • 会话历史要缓冲后才能改写:历史响应是一个 JSON 文档(长会话实测 9–16 MB),网关必须先缓冲才能做沙盒降级与隐藏字符 清洗,因此有 historyMaxBytes 上限(默认 64 MiB)。超过上限的会话会 fail-closed 返回 502,UI 显示「历史加载失败」—— 调大该值即可;1.0.0 之前用代理默认的 8 MiB,长会话必然踩中。
  • 上游 @weibaohui/user-management(0.9.5)与本包 kczx-user-management 是两个不同的包,互不覆盖,pnpm update 不会把两者串在一起。

License

MIT(沿用上游许可)。