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

@weibaohui/user-management

v0.7.0

Published

dsh 插件 · 用户管理:给 dsh web 加登录门禁——未登录访问弹登录/注册页,首个注册者自动成为管理员;支持 TOTP 两步验证(验证器 App 扫码/手输密钥绑定);管理员可管理所有用户(删除、重置密码、角色调整、重置两步验证)并查看登录记录与访问记录,普通用户只能查看和修改自己的信息。

Readme

@weibaohui/user-management

DSH plugin npm version

用户管理 + 登录门禁:自带一个 HTTPS 网关(默认 https://<本机IP>:19843)挡在 dsh web 前面——未登录访问任何页面自动跳到登录/注册页,API 与 WebSocket 一律 401;内置管理员/普通用户两级角色,管理员管所有用户(新增、删除、禁用、重置密码、角色调整、IP 封禁、重置两步验证)并查看登录记录、访问记录、操作日志,普通用户只能查看和修改自己的信息。支持 TOTP 两步验证(Google Authenticator / 1Password 等验证器 App 扫码绑定,密码之后再加一道动态码)。

demo

两步验证演示(登录动态码 + 扫码绑定,亮色 / 暗色):

| 亮色主题 | 暗色主题 | |---|---| | totp-light | totp-dark |

核心功能

  • HTTPS 登录网关:独立 node:https 监听器(默认端口 19843)反代 loopback 的 dsh web——所有请求先过会话校验,未登录的页面访问 302 跳 /login,API 请求、WebSocket 升级一律 401。登录页为插件自带的独立页面(登录 / 注册双 Tab),不依赖宿主前端,深浅色自适应
  • 首个注册者即管理员:系统内没有任何账号时,第一个注册的账号自动成为管理员,此后注册的都是普通用户
  • 用户管理(仅管理员):用户列表(角色 / 状态 / 两步验证 / 创建时间 / 最后登录)、新增用户(可选管理员或普通用户角色)、删除用户、重置密码(生成随机临时密码,仅展示一次)、提升 / 降级角色、禁用 / 启用账号(禁用立即踢出会话并拒绝登录)、重置两步验证(用户丢手机的恢复通道);最后一个管理员不可删、不可降、不可禁
  • TOTP 两步验证(RFC 6238,默认关闭,自行开启):设置页扫码(二维码)或手输密钥绑定验证器 App,输入一个实时动态码完成绑定;开启后登录 = 用户名 + 密码 + 6 位动态码(登录页动态码框常驻选填:未开启留空即登,开启的账号必须校验,填错行内提示);登录表单带「记住用户名」勾选项;动态码 30 秒一换、一次一用(±1 窗口容时钟漂移,用过的码立即作废);连续错 5 次锁定 60 秒防爆破(内存态,重启清零);关闭需输登录密码确认,管理员可对任意用户重置。零第三方依赖,兼容 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 导入命令——导入信任链后浏览器不再弹证书警告
  • 会话持久:7 天滑动过期,dsh 重启不掉线;HttpOnly Cookie,密码 scrypt 加盐哈希,用户管理本身零第三方依赖

安装

dsh plugin --profile web add @weibaohui/user-management -w

装完重启 dsh web 即生效。

使用

  1. 装完重启后,通过网关访问:https://<本机IP>:19843(浏览器首次会提示自签证书警告,按下方「HTTPS 证书」引导导入信任链后消失)——首个注册的账号就是管理员
  2. 管理入口:Web UI → 设置页 → 用户管理,七个页签:用户 / 登录记录 / 访问记录 / 操作日志 / IP 封禁 / HTTPS 证书 / 两步验证
  3. 管理员:用户表(新增用户 / 重置密码 / 角色调整 / 禁用启用 / 重置两步验证 / 删除)+ 四本审计/封禁台账 + 证书下载与信任引导 + 自己的两步验证
  4. 普通用户:个人信息卡 + 修改密码 + 两步验证 + 自己的登录记录
  5. 开启两步验证:设置页「两步验证」页签(或个人信息卡)→ 开启 → 验证器 App 扫二维码(或手动输入密钥)→ 输入 App 显示的 6 位动态码 → 绑定完成;之后登录输用户名密码,在「两步验证码」框填入 App 现查的 6 位动态码(未开启的账号留空即可,不会报错);勾选「记住用户名」后下次登录自动带出用户名,密码交给浏览器密码管理器记忆
  6. 侧栏左上角点头像/用户名:修改密码、退出登录
  7. 数据与审计文件都在 ~/.dsh/user-management/users.json / sessions.json / activity.jsonl / audit.jsonl / bans.json,0600 权限,原子写;审计账本滚动保留最近 5000 条)
  8. 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, 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.yamluser-management: 段(也支持设置页热生效)。

字段

| 字段 | 默认 | 说明 | |---|---|---| | enabled | 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 / loginFailLimit / lockoutSeconds / maxBodyBytes | 7 / 5 / 60 / 16384 | 预留字段(当前未接线:会话由 store 管、body 上限在 API 内);TOTP 防爆破锁定内置为连续错 5 次锁 60 秒(内存态,重启清零) |

场景 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 后删旧证书文件重启才会重签(否则复用旧证书保指纹稳定)。合并新增的公网 IP 同理:要让旧自签证书 SAN 补上该 IP,删 localhost.crt / localhost.key 重启即可重签。
  • 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 + 降级路由级网关已移除)
  • 开放注册:注册始终开放(新账号均为普通用户),请勿将 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 别名仅作兜底
  • 审计口径:操作日志不记录请求体内容与静态资源;被门禁拒绝的请求(401/302)不记录,防止扫描刷屏;临时密码、明文密码永不落盘、不进日志
  • 两步验证的边界:TOTP 密钥以 base32 明文存于 users.json(0600 + 原子写)——动态码验证需要原文,无法像密码那样单向哈希,这是自托管实现的通行做法(Gitea 同款);验证码只在密码正确后才被要求,接口不会向未持正确密码者泄露某账号是否开启了两步验证;动态码一次一用 + 连错 5 次锁 60 秒;丢失验证器:由管理员在用户表「重置两步验证」恢复,最后的管理员丢手机且无第二个管理员时,需手动编辑 users.json 删除该用户的 totpSecret 字段并重启;开启两步验证不踢已有会话(已登录设备不受影响,管的是"下次进门")

联系我 :飞书群

link