Login gate for the dsh web UI: unauthenticated visitors get a login/register page and the first registrant becomes admin; includes user and role management plus login and access audit logs.
Install
# from npm (prebuilt)
dsh plugin --profile web add @weibaohui/user-management
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:weibaohui/user-management
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
This plugin publishes its README in Chinese only.
用户管理 + 登录门禁:自带一个 HTTPS 网关(默认 https://<本机IP>:19843)挡在 dsh web 前面——未登录访问任何页面自动跳到登录/注册页,API 与 WebSocket 一律 401;内置管理员/普通用户两级角色,管理员管所有用户(新增、删除、禁用、重置密码、角色调整、IP 封禁、重置两步验证)并查看登录记录、访问记录、操作日志,普通用户只能查看和修改自己的信息。支持 TOTP 两步验证(Google Authenticator / 1Password 等验证器 App 扫码绑定,密码之后再加一道动态码)与注册审批制(自助注册默认待管理员启用)。
两步验证演示(登录动态码 + 扫码绑定,亮色 / 暗色):
| 亮色主题 | 暗色主题 |
|---|---|
核心功能
- HTTPS 登录网关:独立
node:https监听器(默认端口 19843)反代 loopback 的 dsh web——所有请求先过会话校验,未登录的页面访问 302 跳/login,API 请求、WebSocket 升级一律 401。登录页为插件自带的独立页面(登录 / 注册双 Tab),不依赖宿主前端,深浅色自适应 - 首个注册者即管理员:系统内没有任何账号时,第一个注册的账号自动成为管理员,此后注册的都是普通用户
- 注册审批制(默认):
autoActivate关闭时,自助注册的账号(首个管理员除外)落为禁用状态——注册页提示「账号需管理员审核启用后方可登录」,管理员在用户表点「启用」即完成审批;开关打开则注册即激活直接登录。审批动作零新界面,复用用户表的禁用/启用按钮与状态徽章 - 用户管理(仅管理员):用户列表(角色 / 状态 / 两步验证 / 创建时间 / 最后登录)、新增用户(可选管理员或普通用户角色)、删除用户、重置密码(生成随机临时密码,仅展示一次)、提升 / 降级角色、禁用 / 启用账号(禁用立即踢出会话并拒绝登录)、重置两步验证(用户丢手机的恢复通道);最后一个管理员不可删、不可降、不可禁
- TOTP 两步验证(RFC 6238,默认关闭,自行开启):设置页扫码(二维码)或手输密钥绑定验证器 App,输入一个实时动态码完成绑定;开启后登录 = 用户名 + 密码 + 6 位动态码(登录页动态码框常驻选填:未开启留空即登,开启的账号必须校验,填错行内提示);登录表单带「记住用户名」勾选项;动态码 30 秒一换、一次一用(±1 窗口容时钟漂移,用过的码立即作废);连续错 5 次锁定 60 秒防爆破(
loginFailLimit/lockoutSeconds可调;内存态,重启清零);关闭需输登录密码确认,管理员可对任意用户重置。零第三方依赖,兼容 Google Authenticator / 1Password / 微软验证器 / Aegis 等 - 侧栏用户身份:dsh 侧栏左上角显示当前用户头像 + 用户名(适配侧栏收缩),点击弹出用户菜单——修改密码(改密后其他会话全部登出)、退出登录
- 三本审计账:登录记录(登录 / 登录失败 / 登出 / 改密 / 注册 / 重置密码 / 角色变更 / 删除 / 禁用 / 封禁等)、访问记录(页面级访问)、操作日志(经过网关的每一次 API 调用与 WebSocket 连接,含方法 / 路径 / 响应状态 / 来源 IP),均支持按用户、类型、路径、状态过滤
- IP 封禁:封禁列表 + 日志 IP 列一键封禁——命中地址在会话校验之前就被 403 拒绝(登录页也看不到);回环地址与当前请求所用 IP 服务端拒绝封禁,防止把自己锁在门外
- HTTPS 证书引导:自动签发 100 年自签证书(SAN 覆盖本机全部 IP + 每个 IP 的
sslip.io/nip.io通配 DNS 别名 + localhost);设置页「HTTPS 证书」页一键下载 PEM/DER、核对指纹、复制 macOS / Windows / Linux 导入命令——导入信任链后浏览器不再弹证书警告 - 网关证书管理界面(v0.10+):设置页「网关」页签集中管理 HTTPS 证书与监听状态——实时展示运行阶段 / 监听地址 / 上游 / 每个 SNI 站点的证书来源(自动生成 / 文件路径 / 本页添加)、覆盖主机名、有效期与指纹;粘贴 PEM 证书对即可上线真域名证书(先「检测」校验配对与 SAN,再「保存并应用」,私钥只存服务器
certs/custom/、0600 权限、界面不回显);一键重新生成自签证书(改了 hosts 后重签,替代手动删文件重启)与重启网关 - 会话持久:7 天滑动过期(
sessionDays可调),dsh 重启不掉线;HttpOnly Cookie,密码 scrypt 加盐哈希;认证与数据层零第三方依赖——TOTP 为 node:crypto 手写实现(过 RFC 6238 官方测试向量)、二维码为内置 MIT 编码器,仅证书签发用selfsigned
安装
dsh plugin --profile web add @weibaohui/user-management -w
装完重启 dsh web 即生效。
使用
- 装完重启后,通过网关访问:
https://<本机IP>:19843(浏览器首次会提示自签证书警告,按下方「HTTPS 证书」引导导入信任链后消失)——首个注册的账号就是管理员 - 管理入口:Web UI → 设置页 → 用户管理,八个页签:用户 / 登录记录 / 访问记录 / 操作日志 / IP 封禁 / HTTPS 证书 / 网关 / 两步验证
- 管理员:用户表(新增用户 / 重置密码 / 角色调整 / 禁用启用 / 重置两步验证 / 删除)+ 四本审计/封禁台账 + 证书下载与信任引导 + 网关状态与证书管理 + 自己的两步验证
- 普通用户:个人信息卡 + 修改密码 + 两步验证 + 自己的登录记录
- 开启两步验证:设置页「两步验证」页签(或个人信息卡)→ 开启 → 验证器 App 扫二维码(或手动输入密钥)→ 输入 App 显示的 6 位动态码 → 绑定完成;之后登录输用户名密码,在「两步验证码」框填入 App 现查的 6 位动态码(未开启的账号留空即可,不会报错);勾选「记住用户名」后下次登录自动带出用户名,密码交给浏览器密码管理器记忆
- 侧栏左上角点头像/用户名:修改密码、退出登录
- 数据与审计文件都在
~/.dsh/user-management/(users.json/sessions.json/activity.jsonl/audit.jsonl/bans.json,0600 权限,原子写;审计账本滚动保留最近 5000 条) - IP 封禁按连接源地址(
remoteAddress)判定:本插件自带的网关不做代理改写,看到的即是客户端真实地址;若你在网关前面另加反代层,看到的将是反代的 IP
给其他插件:解析请求的用户身份
user-management 向宿主提供 cordis 服务 user-management,兄弟插件据此知道自己收到的请求是"谁"(归属定时任务/分享/编辑记录等场景)。
消费方式(host 半)
// ⚠️ 不要把 'user-management' 写进静态 inject 数组 —— user-management 未安装时
// 你的插件会卡死激活。用运行时 inject,装了才有、没装就跳过:
module.exports = {
name: 'my-plugin',
inject: ['webServer'],
apply(ctx) {
ctx.inject(['user-management'], (scope) => {
const um = scope['user-management']
ctx.effect(() => ctx.webServer.register({
kind: 'prefix', path: '/my-plugin/api',
handler: async (req, res) => {
const user = await um.resolveRequest(req) // 解析请求 cookie
if (!user) return sendJson(res, 401, { error: '未登录' })
if (user.role !== 'admin') return sendJson(res, 403, { error: '需要管理员' })
// user.username / user.id 可用于操作归属
},
}), 'my-plugin: api')
})
},
}
API
| 方法 | 说明 |
|---|---|
resolveRequest(req) |
从请求的 um_session cookie 解析当前用户(推荐入口) |
resolveToken(token) |
已自行取出 token 时的底层变体 |
返回 UmUser:{ id, username, role: 'admin'|'user', disabled, totpEnabled, createdAt, lastLoginAt };以下情况一律返回 null:未登录、会话过期/伪造、用户被禁用或删除(禁用/删除即刻失效)、user-management 自身存储故障(不向消费方抛错)。服务一 provide 即可安全调用(内部等待存储就绪)。
浏览器端(client 半)不需要这个服务
页面里直接 fetch('/user-management/api/session') 即可,网关本地应答 { user: {...} | null }(HttpOnly cookie 自动携带)。
边界
这只回答"这个请求是谁发的",不构成数据隔离——dsh 宿主的会话与工作区仍是全实例共享。另外身份可信的前提是 dsh web 只监听 loopback(本插件网关架构的默认要求):一旦有人绕过网关直连宿主端口,cookie 校验仍由 store 把关,但请保持宿主不对外暴露。
Remote Gateway 配置
v0.4 起,本插件自带 HTTPS 远程访问网关:dsh web 留在 loopback(127.0.0.1:3080),网关(独立 node:https 监听器)反代到它——网关是唯一对外入口,认证不可绕过。配置走 ~/.dsh/settings.yaml 的 user-management: 段(也支持设置页热生效)。
字段
| 字段 | 默认 | 说明 |
|---|---|---|
enabled |
true |
关掉则不启动网关监听器 |
autoActivate |
false |
注册审批开关。false(默认,审批制):自助注册的账号(首个管理员除外)直接落为禁用状态,注册页提示等待管理员审核,管理员在用户表点「启用」即完成审批;true:注册即激活、直接登录(历史行为) |
listenHost |
0.0.0.0 |
网关监听地址;127.0.0.1 仅本机可达 |
port |
19843 |
网关 HTTPS 端口 |
sites[] |
[](=自动) |
站点白名单 + 证书;空 = 自动枚举本机所有 IP(含 sslip.io / nip.io 别名)。配了 sites 与自动列表合并而非替换:见下方「场景 3」 |
sites[].hosts |
— | Host 白名单(域名/IP,支持 *.example.com 通配)。不带 cert/key 的 site 的 hosts 合并进自签 site(去重) |
sites[].cert / sites[].key |
'' |
PEM 文件路径(fullchain + privkey);配了则保留为独立 SNI site(按域名选证书),不配则按 hosts 自签 |
title |
DSH 控制台 |
登录页标题 |
sessionDays |
7 |
会话有效期(天),滑动过期——Cookie Max-Age 与服务端会话 TTL 同时生效,保存即对新签发的会话生效 |
loginFailLimit / lockoutSeconds |
5 / 60 |
两步验证防爆破:连续错 N 次动态码后锁定 M 秒(内存态,重启清零;改设置即重置计数) |
maxBodyBytes |
16384 |
API JSON 体积上限(字节);界面粘贴的证书对也走此上限,fullchain 较大时适当调高 |
场景 4:设置页「网关」页签管理证书(v0.10+,免配置文件)
不想碰 YAML 时,管理员在 设置页 → 用户管理 → 网关 页签完成证书管理:
- 状态卡:运行阶段(运行中/重启中/已停止/异常/已禁用)、监听地址、上游、启动时间、最近错误;「重启网关」按钮强制重载监听器(改完
sites文件路径等设置后可用) - 证书表:列出当前监听器的每个 SNI 站点——来源徽章(自动生成 / 文件路径 / 本页添加,悬停有说明;域名都匹配不上或直接用 IP 访问时,出的就是「自动生成」那张自签证书,主机名重叠按 自动生成 → 文件路径 → 本页添加 顺序取用)、覆盖主机名、有效期、SHA-256 指纹(可复制);自签行带「重新生成自签证书」(删旧证书→重签,SAN 跟随当前 hosts),上传行带「删除」
- 添加证书:粘贴 fullchain PEM + 私钥 PEM + 主机名列表 → 「检测」先服务端校验(证书可解析、私钥配对、SAN 覆盖名称、是否过期)→ 「保存并应用」持久化到
~/.dsh/user-management/certs/custom/(0600,私钥不回显)并自动重启网关 - 与
sites[]文件路径配置共存:自动站点 > 配置文件站点 > 上传站点(同主机名 SNI 先到先得;上传条目之间禁止重复主机名);界面能力与「场景 2」等价,选哪种全凭习惯;主机名重叠时按 自动生成 → 文件路径 → 本页添加 顺序取用
场景 1:零配置(推荐 · 私网)
不配 sites → 网关自动枚举本机所有非 loopback IP(IPv4 + IPv6,含 Tailscale)填进 hosts,并签发 100 年自签证书(SAN 覆盖这些 IP + localhost);listenHost 默认 0.0.0.0。打开 https://<本机任意 IP>:19843 → 信任自签证书 → 注册/登录(首个访问者即管理员)。
场景 2:域名 + 证书
user-management:
listenHost: '0.0.0.0'
port: 19843
sites:
- hosts: ['dsh.example.com']
cert: '/etc/letsencrypt/live/dsh.example.com/fullchain.pem'
key: '/etc/letsencrypt/live/dsh.example.com/privkey.pem'
cert/key是 PEM 文件路径(fullchain + privkey,如certbot certonly -d <域名>申请);配了就加载你的证书,不配则按 hosts 自签。- 多域名走多项
sites(每项自己的hosts+cert+key),网关按 SNI 选证书。
场景 3:加一个不在本机网卡上的公网 IP(NAT)
服务器有个 NAT 进来的公网 IP(不在本机网卡上,不会被自动枚举到)。配 sites(不配 cert/key)即可把它合并进自签 site——本机 IP / LAN / Tailscale 访问不丢,公网 IP 也进自签证书 SAN:
user-management:
listenHost: '0.0.0.0'
port: 19843
sites:
- hosts: ['111.228.30.150'] # NAT 公网 IP,不带 cert/key -> 合并进自签 site
- 合并而非替换(v0.5.4+):配的 hosts 加到自动枚举列表里(localhost + 本机所有 IP + sslip/nip 别名 + 你配的 IP),自签证书 SAN 覆盖全部;带 cert/key 的 site 仍保留为独立 SNI site。
- 早期版本配
sites会替换自动列表,本机 IP 全丢 → 本地/LAN/Tailscale 访问被 421。v0.5.4 起改为加法合并。 - 公网 IP 走自签证书仍有「不受信任 CA」警告(导入信任链后消失);要真正零警告,给
<ip>.sslip.io等用 Let's Encrypt 签真证书(见「场景 2」写法)。
安全边界
- 零配置下首个访问者即管理员——仅适用于可信私网(Tailscale 等);公网暴露前请:配
sites白名单 + 用域名证书 + 先在 loopback(https://127.0.0.1:19843)注册首个 admin 再对外。 - 自签证书 100 年有效,持久化在
~/.dsh/user-management/certs/;改了 hosts/SAN 后要重签:设置页「网关」页签点「重新生成自签证书」,或手动删localhost.crt/localhost.key重启(否则复用旧证书保指纹稳定)。合并新增的公网 IP 同理——要让旧自签证书 SAN 补上该 IP 就重签一次。 - dsh web 始终留在 loopback,网关是唯一对外入口——直连 3080 绕不过认证。
安全边界(务必阅读)
- 门禁是"进门"级别:进门之后,所有登录用户看到的是同一个 dsh 实例的会话与数据——本插件做的是"谁能访问",不是多用户数据隔离
- 拦截机制:v0.4 起门禁是独立 HTTPS 网关监听器(
node:https,见上文「Remote Gateway 配置」)——所有请求先过网关的会话校验(未登录 document 跳/login,API/WS 返 401),通过后才反代到 loopback dsh web;不再依赖宿主webServer.server监听器重排(旧版 0.3 的 attachGate + 降级路由级网关已移除) - 开放注册与审批:注册始终开放(新账号均为普通用户)。默认为审批制(
autoActivate: false):注册成功但账号处于禁用状态,无法登录,需管理员在「用户」表点「启用」放行(登录页会提示等待审核);首个注册的管理员不受此开关影响。开关打开时注册即激活直接登录。请勿将 dsh web 暴露给不受信任的网络 - 证书下载与信任引导:网关免登录提供
GET /user-management/api/cert(PEM,?format=der得 Windows 用的 .cer)与GET /user-management/api/cert-info(SHA-256 指纹 / 有效期 / 覆盖名称)。设置页「HTTPS 证书」卡片一键下载 + 复制各系统导入命令。自签证书不会因 SAN 完整而不弹警告——消除警告靠导入信任链(导入后 IP / sslip.io / nip.io 三种访问方式全部干净);公网 IP 可用 Let's Encrypt 给<ip>.sslip.io签真证书实现真正零警告;Tailscale 用户优先用tailscale cert+机器名.尾网名.ts.net(真 CA 证书、零警告),尾网 IP 的 SAN 别名仅作兜底 - 网关管理 API 仅限管理员:
GET /user-management/api/gateway/status(状态 + 每站点证书详情)与POST /user-management/api/gateway/certs(dryRun: true只检测不保存;正式提交要求证书可解析、私钥配对、主机名合法且不与已上传条目冲突)、DELETE .../gateway/certs/:id、POST .../gateway/certs/regenerate(删自签重签)、POST .../gateway/restart全部匿名 401 / 普通用户 403,动作均记入活动台账;接口任何响应都不回显私钥,私钥只落certs/custom/(0600) - 审计口径:操作日志不记录请求体内容与静态资源;被门禁拒绝的请求(401/302)不记录,防止扫描刷屏;临时密码、明文密码永不落盘、不进日志
- 两步验证的边界:TOTP 密钥以 base32 明文存于
users.json(0600 + 原子写)——动态码验证需要原文,无法像密码那样单向哈希,这是自托管实现的通行做法(Gitea 同款);验证码只在密码正确后才被要求,接口不会向未持正确密码者泄露某账号是否开启了两步验证;动态码一次一用 + 连错 5 次锁 60 秒;丢失验证器:由管理员在用户表「重置两步验证」恢复,最后的管理员丢手机且无第二个管理员时,需手动编辑users.json删除该用户的totpSecret字段并重启;开启两步验证不踢已有会话(已登录设备不受影响,管的是"下次进门")
常见问题
忘记管理员密码/用户名、IP 封错解封、证书警告、网关没起来、审计文件清理等常见问题的处理办法见 docs/FAQ.md。
联系我 :飞书群
版本兼容性
本插件与 DeepSeek Harness(@deepseek-ai/dsh)的版本对应关系:
| 插件版本 | 适配 dsh 版本 | 备注 |
|---|---|---|
| 0.9.7 | 0.1.7-rc.2 | 当前版本,已在 @deepseek-ai/dsh@0.1.7-rc.2 下验证运行 |
发版约定:每次发布新版本时,请在上表追加一行,记录该插件版本实际验证所用的
@deepseek-ai/dsh版本。package.json的engines.dsh声明最低支持版本;本表记录实际验证版本,二者配合使用。
Links
More in this category
toby-bridges/api-relay-audit★ 884
Runs local security audits of AI API relays and LLM proxies from DeepSeek Harness, producing Markdown reports for prompt injection, model substitution signals, tool-call rewriting, error leakage, stream integrity, and profile-gated Web3 risks.
SeaOf0/dsh-redteam-model★ 716
Authorized-security DSH collection: nine work modes (redteam coordinator, pentest, code audit, binary analysis, attack-defense, AV evasion, incident response, cloud security, CTF solving) and fifteen runtime plugins, managed from a settings page with one-click deploy, install, update and uninstall.
howmp/dsh-pentest★ 609
Authorized pentest mode for DeepSeek Harness — exploration chain, assets and findings with a Web view.
PerryLink/dsh-auto-review★ 233
Second-model auto-review on the approval answerer chain: a read-only reviewer subagent returns structured allow/deny verdicts with reasons, fail-closed by default.
ADWMC/helm-d#helmd★ 225
Single-bundle reverse-engineering and pentest security plugin: first-turn tool narrowing, domain routing, and 33 tools covering APK, web, native binary, protocol, malware and LLM samples — with unpacking, license-bypass and anti-analysis case playbooks, an H-CoT evaluation engine (semantic routing, /hcot command), a web workbench with a ledger-driven tool shelf, an on-disk case workflow (auto-persisted evidence chain, E-numbered validated findings, post-compaction resume), GitHub-based external tool discovery, and 361 on-demand reference docs.
NanmiCoder/dsh-auto-mode★ 164
Adds an Auto permission preset between Workspace Write and Full access: routine work stays in the official workspace-write sandbox while the current session model reviews escalation and destructive calls, granting one exact wider access once, asking when the intent is ambiguous, and denying critical paths.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.