DeepSeek Harness 插件

dsh-auth-plugin

DSH Web 用户授权插件 — 用户名密码 + 通用 OAuth 2.0(内置 GitHub/Google/Discord 等模板)+ Solana/EVM 钱包登录。零依赖核心,纯配置接入(英文原文)

跳到安装方式

来源信息

GitHub 仓库
v1xingyue/dsh-auth-plugin
最近更新
2026年8月18日
分类
远程与移动
GitHub stars
1
载体类型
plugin
目录证据
上游声明已找到 dsh.bundle
证据路径
package.json#dsh.bundle
核对版本
0.1.0-rc.8
上游核对日期
2026-08-20

该证据由上游目录提供。本站没有安装、运行或安全审核这个插件。

安装

默认先复制一段 Prompt,让 Agent 读 GitHub 仓库和源码;需要自己装时再切到命令。

复制这段 Prompt,发给 DSH、Codex 或其他 Agent,让它先读 GitHub 仓库和源码。

请先不要安装或执行任何命令。阅读这个插件的 GitHub 仓库、README 和关键源码,然后用清楚、直接的方式回答以下问题,帮助我判断它是否适合我的需求:

1. 这个插件是什么,解决什么问题;
2. 适合哪些用户和典型使用场景;
3. 安装后如何使用,并给出一个最小使用示例;
4. 有哪些已知限制,以及隐私、安全、兼容性或维护风险;
5. 给出“推荐 / 有条件推荐 / 不推荐”的明确建议和理由。

请区分仓库明确说明、根据源码推断和未知信息。证据不足时请明确说明,不要猜测或照抄 README。

GitHub:https://github.com/v1xingyue/dsh-auth-plugin
插件名:dsh-auth-plugin
作者:v1xingyue

检查来源文件

安装前先看这个插件目录里的 README 和其他文件。

文件资源管理器3 个文件
README.md来源说明 · 只读预览

DSH Auth Plugin — DeepSeek Harness 登录认证插件

给 DeepSeek Harness Web GUI 添加登录保护的 Cordis 插件。 零第三方依赖、单文件、极简配置,同时支持:

  • 🔑 用户名 + 密码登录(scrypt 哈希存储)
  • 🦖 Solana 钱包登录(ed25519 签名验证 + 公钥白名单)
  • 🛡️ 统一会话管理(HMAC-SHA256 令牌 + HttpOnly Cookie + 登出吊销)

---

目录

1. [特性总览](#特性总览) 2. [架构与工作原理](#架构与工作原理) 3. [安装](#安装) 4. [快速开始](#快速开始) 5. [配置参考](#配置参考) 6. [API 参考](#api-参考) 7. [前端集成](#前端集成) 8. [安全设计](#安全设计) 9. [用户与密钥管理](#用户与密钥管理) 10. [测试](#测试) 11. [已知问题与故障排除](#已知问题与故障排除) 12. [升级与回滚](#升级与回滚) 13. [生产部署建议](#生产部署建议) 14. [许可证](#许可证)

---

特性总览

特性说明状态
用户名密码登录scrypt 哈希存储,支持明文(开发环境)与哈希
通用 OAuth 2.0 登录任意授权码 provider(GitHub/Google/Discord…),纯配置接入
Solana 钱包登录Phantom/Solflare,ed25519 challenge-response
EVM 钱包登录MetaMask 等,EIP-191 personal_sign + ecrecover✅ 新增
白名单强制钱包登录 allowlist/allowlist 必填,禁止任意钱包
会话令牌HMAC-SHA256 自包含 token(JWT 风格)
防重放一次性 nonce/state,消费即删
登出吊销服务端内存黑名单,防被窃 cookie 复用
统一门卫包装 webserver fallback,保护全部 SPA 页面与静态资源
核心零依赖node:crypto(scrypt/ed25519/HMAC)+ Node 全局 fetch + 手写 base58
可选增强EVM 需要纯 JS 库 @noble/curves+@noble/hashes(缺失时自动禁用)⚙️
极简安装复制 1 个文件 + 2 行配置,无需 pnpm/npm
npm 发布就绪exports/files/engines/prepack 校验,npm pack 验证通过
bundle 化dsh.bundle 声明,dsh plugin add 一行安装即生效

版本1.8.0package.json)· 测试:60 项(34 单元 + 26 端到端)

---

架构与工作原理

模块解析基础

DSH 的 Loader 以 profile 目录~/.dsh/profiles/web/)为 baseUrl, 插件行的 name 直接传给 Node import(),因此:

  • name: "./dsh-auth-plugin.js" → 相对 profile 目录加载文件
  • name: "@scope/pkg" → 从 profile 的 node_modules 解析
  • name: "file:///abs/path" → 绝对路径加载

$DSH_HOME/profiles/node_modules 是 dsh 全依赖闭包的扁平回退目录 (含全部 195+ 个 @deepseek-ai/* 包),所以插件里 import "@deepseek-ai/schemastery" 等依赖无需安装即可解析—— 这是"复制即用"机制的根基。

请求流(门卫架构)

浏览器请求
   │
   ▼
webserver.match(pathname)
   │
   ├─ 精确路由表命中(exact)        → 直接处理
   │    /login                          → 登录页
   │    /api/auth/login                 → 密码登录 API
   │    /api/auth/logout                → 登出 API
   │    /api/auth/solana/challenge      → Solana 钱包 challenge
   │    /api/auth/solana/verify         → Solana 钱包 verify
   │    /api/auth/evm/challenge         → EVM 钱包 challenge
   │    /api/auth/evm/verify            → EVM 钱包 verify
   │
   ├─ 最长前缀路由命中(prefix)
   │    /api/auth/oauth/<id>/start      → OAuth 授权跳转(本插件)
   │    /api/auth/oauth/<id>/callback   → OAuth 回调(本插件)
   │    /api/*(client-connection 拥有)→ 保持 DSH 自带 loopback/
   │                                      trustedHosts 篱笆(刻意不拦截)
   │
   └─ 未命中 → fallback 座位(本插件包装)
        ├─ 公开路径?                → 放行
        ├─ 有有效会话 Cookie?       → 放行(用户信息挂 req.authUser)
        ├─ 浏览器请求(Accept: html)→ 302 → /login
        └─ API 调用                  → 401 JSON { code: "auth_required" }
              │
              ▼
        frontend-static(原始 dist 服务,SPA 页面与静态资源)

关键设计:插件注册为唯一 fallback 座位持有者(替换 frontend-static 的占座,认证通过后转交原始 handler)。这比注册 prefix "/" 路由可靠—— webserver 的 prefix 匹配 pathname.startsWith("/" + "/") 对非根路径恒为 false,prefix "/" 实际拦不住任何路径(早期版本的 bug,已修复)。

会话令牌

  • 结构:<base64url(payload)>.<HMAC-SHA256(base64url(payload))>
  • Payload:{ u: 用户名, r: 角色, exp: 过期时间戳 }
  • 传输:HttpOnly + SameSite=Strict + Max-Age 的 Cookie
  • 吊销:登出时 token 进内存黑名单(重启清空)

Solana 登录流程

登录页点击「使用 Solana 钱包登录」
   │
   ├─ 1. POST /api/auth/solana/challenge { publicKey }
   │      校验 base58(32B) → 白名单检查(403) → 签发一次性 nonce
   │      返回 { nonce, message: "DSH Login <nonce>", expiresAt }
   │
   ├─ 2. 钱包 signMessage(message)(用户在钱包中确认)
   │
   └─ 3. POST /api/auth/solana/verify { publicKey, signature, nonce }
          白名单检查(403) → 消费 nonce(401 防重放) → ed25519 验签(401)
          → 通过则签发会话 Cookie

消息编码兼容两种:Phantom signMessage 直接 UTF-8 字节,以及 SIWS v0 官方格式(\xff + 小写 "solana offchain message" + preamble,见 Agave 规范)。

OAuth 授权码流程

登录页点击「使用 GitHub 登录」
   │
   ├─ 1. GET /api/auth/oauth/<id>/start
   │      签发一次性 state(绑定 provider,防 CSRF)
   │      302 → provider 授权页 ?client_id&redirect_uri&state[&scope]
   │
   ├─ 2. 用户在 provider 页面授权
   │
   ├─ 3. provider 302 → /api/auth/oauth/<id>/callback?code=&state=
   │      消费 state(一次性+过期+绑定)→ 失败 302 /login?error=oauth_bad_state
   │      服务端 code 换 token(client_secret 不下发浏览器)
   │      带 Bearer 取 userinfo → 映射 id/name/email
   │      签发会话 Cookie → 302 /

零依赖:token 交换与 userinfo 用 Node 全局 fetch(Node 18+)。

EVM 钱包流程(MetaMask 等)

登录页点击「⬡ 使用 EVM 钱包登录」
   │
   ├─ 1. POST /api/auth/evm/challenge { address }
   │      校验 0x 地址 → 白名单检查(403) → 签发一次性 nonce
   │      返回 { nonce, message: "DSH Login <nonce>", expiresAt }
   │
   ├─ 2. 钱包 personal_sign(message, address)(用户在钱包中确认)
   │      返回 65 字节 r||s||v 十六进制签名
   │
   └─ 3. POST /api/auth/evm/verify { address, signature, nonce }
          白名单检查(403) → 消费 nonce(401 防重放)
          → EIP-191 消息哈希 → ecrecover 恢复地址(secp256k1)
          → 恢复地址 === 声明地址 → 签发会话 Cookie

依赖可选的 @noble/curves + @noble/hashes(纯 JS、无 native); 缺失时 EVM 登录自动禁用并打印警告,其他登录方式不受影响。 安装(仅启用 EVM 时需要):

dsh plugin --profile web add @noble/curves @noble/hashes

---

安装

方式 A:直接复制(推荐,已验证)

# 1. 复制单文件到 web profile 目录
cp lib/index.js ~/.dsh/profiles/web/dsh-auth-plugin.js
# 2. ~/.dsh/profiles/web/cordis.patch.yml 追加
- insert:
    - id: auth
      name: "./dsh-auth-plugin.js"
      config:
        users:
          admin: admin123

重启 dsh web 生效。不需要package.json、跑 pnpm 或安装依赖。

方式 B:npm 包安装(发布到 registry 后)

# 从 npm registry 安装到 web profile
dsh plugin --profile web add dsh-auth-plugin

然后同样在 cordis.patch.yml insert 一行(name 用包名,依赖由 dsh plugin/pnpm 解析):

- insert:
    - id: auth
      name: "dsh-auth-plugin"
      config:
        users:
          admin: admin123

> dsh plugin 是 pnpm 转发器;本插件为普通插件(无 dsh.bundle), > 安装后需 insert 一行声明(bundle 形态见方式 C)。

发布到 npm(维护者)

插件已做好发布就绪(package.jsonexports/files/engines/ prepack 校验):

npm pack                       # 本地验证产物(prepack 会先跑语法检查)
npm login
npm publish --access public
# 本地测试 tarball 安装
npm pack
dsh plugin --profile web add file:/path/to/your-org-dsh-auth-plugin-1.6.0.tgz

方式 C:bundle 化(推荐给发布后的 npm 包)✅ 已实现

本插件已声明 "dsh": { "bundle": { "patch": "./cordis.patch.yml" } }安装即自动成为配置层,连 cordis.patch.yml 的 insert 都省了:

dsh plugin --profile web add dsh-auth-plugin
# → pnpm 安装 → 自动加入 bundles 层 → auth 插件行自动生效
#    (默认 admin/admin123,启动警告,请立即覆盖配置)

安装后在 profile 的 cordis.patch.yml 按 id 覆盖 config 即可:

- id: auth
  config:
    users:
      admin: { password: "scrypt$...", role: admin }
    oauth:
      providers:
        github: { builtin: github, clientId: "...", clientSecret: "..." }

> 已在隔离 DSH_HOME 完整验证:dsh plugin add <tarball> → bundles 列表 > 自动追加 → --dump-config 出现 # == dsh-auth-plugin 段。

安装后文件布局

~/.dsh/profiles/web/
├── cordis.patch.yml         # 认证配置(insert auth 行)
├── dsh-auth-plugin.js       # 插件本体(单文件)
├── package.json             # profile manifest(可选加 "type": "module")
└── pnpm-workspace.yaml

---

快速开始

# cordis.patch.yml —— 最简配置:用户名密码登录
- insert:
    - id: auth
      name: "./dsh-auth-plugin.js"
      config:
        users:
          admin: admin123
# 用户名密码 + Solana 钱包登录(allowlist 必填)
- insert:
    - id: auth
      name: "./dsh-auth-plugin.js"
      config:
        users:
          admin:
            password: "scrypt$<salt>$<hash>"
            role: "admin"
        solana:
          enabled: true
          allowlist:
            - "Dy6mBH4YeqJCRZohd39iSFaf4jyLaxPeBakbZwt1jToL"

重启 dsh web 后访问 http://127.0.0.1:3080 即重定向到登录页。

---

配置参考

顶层配置

| 字段 | 类型 | 默认值 | 说明 | |------|------|--------|------| | enabled | boolean | true | 总开关 | | secret | string | 每次启动随机 | 会话签名密钥;可用环境变量 DSH_AUTH_SECRET 固定 | | ttlHours | number | 24 | 会话有效期(小时) | | title | string | DSH 登录 | 登录页标题 | | cookie | string | dsh_session | 会话 Cookie 名 | | users | dict | 空→admin/admin123 | 用户表(见下) | | solana | false \| object | false | Solana 钱包登录配置(见下) | | evm | false \| object | false | EVM 钱包登录配置(见下) | | oauth | object | {providers:{}} | 通用 OAuth 配置(见下) | | publicPaths | string[] | 见下 | 免认证路径(精确或前缀匹配) |

默认 publicPaths/login/api/auth/login/api/auth/logout/favicon.ico

> ⚠️ secret 为空时每次启动随机——重启后所有会话失效需重新登录。 > 这是保守设计(无需持久化);生产环境建议固定:secret: !!js process.env.DSH_AUTH_SECRET

users 三种写法

# 1. 极简:用户名: 明文密码(开发环境)
users:
  admin: admin123

# 2. 带角色
users:
  admin: { password: admin123, role: admin }

# 3. 推荐:scrypt 哈希
users:
  admin: { password: "scrypt$<salt>$<hash>", role: admin }

明文密码启动时会打印警告(仅限开发环境)。

solana 配置

字段类型默认值说明
enabledbooleantrue(对象形式)开关;solana: false 或省略 = 禁用
challengeTtlMsnumber300000nonce 有效期(5 分钟)
allowliststring[]必填公钥白名单(base58),至少 1 个
rolestringuser钱包登录默认角色

> ⚠️ allowlist 必填:启用时缺字段或空数组都会在加载时报配置错误 > (schema 层拦截)——不允许"任何钱包可登录"

evm 配置(MetaMask 等 EVM 钱包)

evm:
  enabled: true
  allowlist:                # 必填:只允许这些 0x 地址(小写或混合大小写均可)
    - "0x4e984616e2dd9dffe7f2413efc7da35ef64c4117"
  role: user
字段类型默认值说明
enabledbooleantrue(对象形式)开关;evm: false 或省略 = 禁用
challengeTtlMsnumber300000nonce 有效期(5 分钟)
allowliststring[]必填允许的 0x 地址,至少 1 个
rolestringuser登录成功角色

> ⚠️ 同 Solana:allowlist 必填;且需要可选依赖 > @noble/curves + @noble/hashesdsh plugin --profile web add @noble/curves @noble/hashes), > 缺失时该功能自动禁用(其余登录不受影响)。

oauth 通用 OAuth 2.0 配置

每个 provider 一条,纯配置接入任意标准授权码 OAuth 服务。 支持内置模板:填 builtin + 凭据即可,端点/字段映射/scope 自动填充。

#### 最简方式:内置模板(推荐)

oauth:
  providers:
    github:                       # provider id
      builtin: "github"           # ← 内置模板:端点/字段/scope 自动填充
      clientId: "Ov23li..."
      clientSecret: "ghp_..."     # 仅服务端使用,绝不下发浏览器

内置模板一览(BUILTIN_OAUTH,显式字段可覆盖模板):

builtin授权端点token 端点userinfo 端点默认 scopeidField
githubgithub.com/login/oauth/authorize…/access_tokenapi.github.com/userread:userid
googleaccounts.google.com/o/oauth2/v2/authoauth2.googleapis.com/token…/oauth2/v3/userinfoopenid email profilesub
discorddiscord.com/oauth2/authorizediscord.com/api/oauth2/tokendiscord.com/api/users/@meidentify emailid
gitlabgitlab.com/oauth/authorizegitlab.com/oauth/tokengitlab.com/api/v4/userread_userid
microsoftlogin.microsoftonline.com/common/oauth2/v2.0/authorize…/tokengraph.microsoft.com/v1.0/meUser.Readid
bitbucketbitbucket.org/site/oauth2/authorize…/access_tokenapi.bitbucket.org/2.0/useraccountuuid

#### 完整方式:显式配置(任意标准 OAuth 服务)

oauth:
  providers:
    custom:
      label: "我的服务"
      clientId: "..."
      clientSecret: "..."
      authorizeUrl: "https://.../authorize"
      tokenUrl: "https://.../token"
      userInfoUrl: "https://.../userinfo"
      scope: "read"                # 可选
      idField: "id"                # 可选,默认 id
      nameField: "name"            # 可选
      emailField: "email"          # 可选
      role: "user"                 # 可选
      # redirectUri: "https://..." # 可选:显式回调地址
字段类型默认值说明
builtinstring""内置模板名(见上表);填了则端点/字段/scope 用模板
labelstringprovider id / 模板登录页按钮文案
clientId / clientSecretstring必填OAuth 应用凭据
authorizeUrl / tokenUrl / userInfoUrlstring模板值三个端点
responseTypestringcode授权响应类型(授权码模式)
scopestring模板值/空请求的 scope(空格分隔)
redirectUristring自动显式回调地址;留空 = http://<Host>/api/auth/oauth/<id>/callback
idField / nameField / emailFieldstringid / 模板userinfo 字段映射
emailDomainsstring[][]邮箱域名白名单;非空时邮箱域名必须命中,否则拒绝(oauth_email_not_allowed
rolestringuser登录成功角色
userInfoHeadersdict{}取 userinfo 附加请求头
tokenParamsdict{}token 请求附加参数
stateTtlMsnumber600000state 有效期(10 分钟)

> 回调地址(OAuth 应用后台填写):http(s)://<你的地址>/api/auth/oauth/<id>/callback > 例如:http://127.0.0.1:3080/api/auth/oauth/github/callback > 未知 builtin 名或合并后缺必需字段会在启动时报配置错误。

---

API 参考

GET /login

返回内置登录页(含密码表单 + 可选 Solana 钱包区块)。状态码:200

POST /api/auth/login

用户名密码登录。支持 JSON 与 application/x-www-form-urlencoded

curl -X POST http://127.0.0.1:3080/api/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"secret"}'
结果状态码说明
成功(JSON 调用)200{ ok, username, role } + Set-Cookie
成功(浏览器表单)302重定向 / + Set-Cookie
凭据错误(JSON)401{ error: "invalid credentials" }
凭据错误(表单)401登录页 + 错误提示
缺参400{ error: "username and password required" }
方法错误405非 GET/POST

POST /api/auth/logout

登出。服务端将当前 token 加入吊销黑名单并清除 Cookie。 成功:200 { ok: true }(JSON)或 302 → /login(浏览器)。

POST /api/auth/solana/challenge

签发一次性 nonce(绑定公钥)。

curl -X POST http://127.0.0.1:3080/api/auth/solana/challenge \
  -H 'Content-Type: application/json' \
  -d '{"publicKey":"<32字节base58>"}'
结果状态码说明
成功200{ nonce, message, expiresAt }
公钥非法400{ error: "invalid public key" }
不在白名单403{ error: "public key not allowed" }

POST /api/auth/solana/verify

提交钱包签名换取会话。

curl -X POST http://127.0.0.1:3080/api/auth/solana/verify \
  -H 'Content-Type: application/json' \
  -d '{"publicKey":"<base58>","signature":"<base58 64B>","nonce":"<challenge 返回>"}'
结果状态码说明
成功200{ ok, username: "solana:<pubkey>", role, publicKey } + Set-Cookie
不在白名单403拒绝
nonce 无效/过期/重放401{ error: "invalid or expired challenge" }
签名验证失败401{ error: "signature verification failed" }
缺参400{ error: "publicKey, signature and nonce required" }

POST /api/auth/evm/challenge

EVM 钱包登录第一步:签发一次性 nonce(绑定 0x 地址)。

curl -X POST http://127.0.0.1:3080/api/auth/evm/challenge \
  -H 'Content-Type: application/json' \
  -d '{"address":"0x4e984616e2dd9dffe7f2413efc7da35ef64c4117"}'
结果状态码说明
成功200{ nonce, message: "DSH Login <nonce>", expiresAt }
地址非法400{ error: "invalid address" }
不在白名单403{ error: "address not allowed" }

POST /api/auth/evm/verify

提交 personal_sign 签名换取会话(需要可选依赖 @noble)。

curl -X POST http://127.0.0.1:3080/api/auth/evm/verify \
  -H 'Content-Type: application/json' \
  -d '{"address":"0x...","signature":"0x<65字节r||s||v>","nonce":"<challenge 返回>"}'
结果状态码说明
成功200{ ok, username: "evm:<address>", role, address } + Set-Cookie
不在白名单403拒绝
nonce 无效/过期/重放401{ error: "invalid or expired challenge" }
ecrecover 不匹配401{ error: "signature verification failed" }
缺参/地址非法400{ error: "address, signature and nonce required" }

GET /api/auth/oauth/<id>/start

发起 OAuth 登录。签发一次性 state 后 302 到 provider 授权页 (authorizeUrl?client_id&redirect_uri&state[&scope])。 未知 provider → 404

GET /api/auth/oauth/<id>/callback

OAuth 回调(用户从 provider 授权页跳回)。浏览器直接访问即可,无需手动调用。

# 模拟(真实流程由浏览器跳转完成)
curl -i "http://127.0.0.1:3080/api/auth/oauth/github/callback?code=xxx&state=<start 返回的 state>"
结果状态码行为
成功302重定向 / + Set-Cookie(会话已建立)
state 无效/过期/重放302重定向 /login?error=oauth_bad_state(防 CSRF)
token 交换失败302/login?error=oauth_token_failed
userinfo 获取失败302/login?error=oauth_userinfo_failed
userinfo 缺 id 字段302/login?error=oauth_no_id
未知 provider302/login?error=oauth_unknown_provider

会话身份:oauth:<providerId>:<id>(如 oauth:github:user-42)。

---

前端集成

密码表单

内置登录页为自包含 HTML(无外部资源),POST 到 /api/auth/login, 成功即设置 Cookie 并跳转 /

Solana 钱包登录

登录页内联脚本(零外部依赖):

1. 检测钱包:window.solana.isPhantomwindow.phantom.solana 2. 点击按钮 → provider.connect() 取公钥 3. 请求 challenge → provider.signMessage(TextEncoder.encode(message), "utf8") 4. 签名 base58 编码后 POST verify → 成功 location.href = "/"

支持 Phantom、Solflare 等注入 window.solana 的钱包;未安装钱包时 按钮下方提示"未检测到钱包"。

OAuth 第三方登录

登录页按配置为每个 provider 渲染一个按钮(<a href="/api/auth/oauth/<id>/start">), 点击即进入标准 OAuth 授权码流程,无需前端脚本。

EVM 钱包登录

登录页「⬡ 使用 EVM 钱包登录」按钮,内联脚本检测 window.ethereumeth_requestAccounts 取地址 → challenge → personal_sign → 提交 verify。

---

安全设计

实现
密码存储node:crypto scrypt(随机盐),支持明文仅限开发
令牌完整性HMAC-SHA256 签名,timingSafeEqual 比较
CookieHttpOnly; SameSite=Strict; Path=/; Max-Age
登出吊销服务端内存黑名单(revoked Set)
nonce 防重放一次性消费,consume 即删;绑定公钥/地址;TTL 5 分钟 + 惰性 sweep
OAuth state 防 CSRF一次性 + 绑定 provider + 10 分钟 TTL;伪造/重用 state → oauth_bad_state
client_secret 保密仅服务端持有,token 交换在服务端完成,绝不下发浏览器
userinfo 服务端获取Bearer token 不出服务端
EVM 验签EIP-191 personal_sign 消息哈希 + secp256k1 ecrecover 恢复地址,与声明地址比对
登录审计每次登录成功 info / 失败 warn 打点([dsh-auth] login ok/failed: <method> <身份> [原因]
白名单强制钱包登录 allowlist/allowlist 必填,challenge 与 verify 双重 403
消息重建服务端 DSH Login <nonce> 重建,不信任客户端回传 message
定时器依赖inject: ["webServer", "timer"] 显式声明(Cordis 强制)
/api 边界保持 DSH 自带 loopback/trustedHosts 篱笆,插件不越权

---

用户与密钥管理

生成 scrypt 哈希

# 方式一:内置脚本
node scripts/manage-users.js hash 你的密码

# 方式二:一行命令
node --input-type=module -e "
import { scrypt, randomBytes } from 'node:crypto';
const salt = randomBytes(16).toString('hex');
scrypt(process.argv[1], salt, 32, (e,k)=>{ if(e) throw e; console.log('scrypt\$'+salt+'\$'+k.toString('hex')); });
" 你的密码

添加/修改/删除用户

直接编辑 cordis.patch.ymlusers 表后重启即可。支持多用户、多角色 (admin / user,角色目前仅作标识,可由其他插件消费 req.authUser)。

Solana 公钥获取

钱包连接后从 publicKey.toString() 得到 base58 地址,填入 allowlist。 验证合法性:node -e "import('./dsh-auth-plugin.js').then(m => console.log(m.base58Decode('...').length === 32))"

---

测试

# 单元测试(核心零依赖,直接可跑;EVM 组需要 @noble,缺失时自动跳过)
node --test test/auth.test.js
# → 34 项:scrypt、HMAC 令牌、base58、ed25519 验签(含 SIWS v0)、
#   nonce/state 管理、EVM ecrecover、OAuth 模板合并

# 端到端测试(需要 DSH 的 node_modules 环境解析 schematery)
node --test test/e2e.test.js
# → 26 项:密码登录、Cookie、登出吊销、Solana/EVM 钱包、白名单 403、
#   fake OAuth provider 授权码流程(含内置模板)、邮箱域名白名单、防重放

---

已知问题与故障排除

已修复:cannot get property "timer" without inject(v1.1.0 → v1.2.0)

启用 Solana 后启动失败:

Error: dsh: plugin tree failed to load:
failed to apply loader entry auth (./dsh-auth-plugin.js):
cannot get property "timer" without inject

根因ctx.setInterval() 由 Cordis 的 timer 服务提供,新版 Cordis 要求显式声明所有经 ctx 使用的服务;原 inject = ["webServer"] 遗漏了 timer

修复const inject = ["webServer", "timer"]

完整排查记录见 [docs/repair-timer-inject.md](docs/repair-timer-inject.md)。

常见问题

现象处理
启动报 failed to import loader entry auth确认 ./dsh-auth-plugin.js 相对 profile 目录路径正确、文件存在
报找不到 @deepseek-ai/schemastery确认 ~/.dsh/profiles/node_modules 存在(dsh 首次启动自动生成)
登录后又被弹回登录页secret 未固定 → 重启后旧会话失效,属正常;或系统时间异常
cannot get property "timer"确认 inject"timer"(v1.2.0 起已内置)
Solana 按钮不显示确认 solana.enabled: true 且 allowlist 非空
钱包登录报 403钱包公钥不在 allowlist
钱包签名后 401nonce 过期(5 分钟)