Imported from xbzbing/dsh-auth-gateway (
AGENTS.md). Install upstream withnpx skills add xbzbing/dsh-auth-gateway. Copyright stays with the author.
AGENTS.md
dsh-auth-gateway 是 dsh web 前面的密码 + TOTP 认证网关(Cordis 插件):网关独占对外端口,bundle patch 把内部 webserver 钉在 127.0.0.1:<N+1>,每个 HTTP 请求与 WebSocket 升级先过认证门再转发。ESM("type": "module")、Node >= 20。改 lib/ 前先读 docs/zh/DEVELOPMENT.md;安全语义见 docs/zh/SECURITY.md。
仓库布局
index.js 插件入口:gateway 生命周期、tapIndex 注入(randomUUID polyfill +
basePath 全局量)、初始密码铸造、安全事件与登录审计接线
lib/
gateway.js 认证门禁:路由、认证状态机(会话/onboarding/OTP 三态)、三层防爆破、basePath 剥离
gateway-otp.js OTP 路由 handler(自 gateway.js 拆分,经 priv 桥接访问网关私有方法)
gateway-panel-api.js 设置面板 API(/login-api/*,含 cookieSecure 运行时覆盖)
forward.js HTTP/WS 转发管道:Host/Origin 回环改写、upgrade 双向管道、lanAddresses
upstream-auth.js 上游 BrowserAuth cookie 铸造(credentials record 读密钥 + 缓存)
rate-limit.js 三层防爆破状态机(全局限流 / 按地址锁定 / OTP 窗口)
auth.js 内存会话表(256-bit token)+ Cookie 编解码
audit-log.js 审计日志文件 sink(JSONL,$DSH_HOME/auth-gateway/log/audit.log,按天轮转、保留 90 天)
locale.js 页面语言解析(settings preference > Accept-Language > zh)
errors.js 页面错误文案总字典(中英;errorsFor 按页选取 + 场景覆盖)
store.js 密码存储(异步 scrypt,$DSH_HOME/auth-gateway/password.json)
otp-store.js OTP 记录(mtime+size 缓存;secret AES-256-GCM 密封落盘)
otp-crypto.js 主密钥解析(env > key 文件)与 seal/unseal;解密失败分类为 OTPCryptoError
totp.js TOTP RFC 6238/4226 + 备份码;qr-svg.js 零依赖 QR SVG
*-page.js 自包含 HTML 页面(login / onboarding / otp),共享脚手架在 page-shell.js
policy.js 密码强度策略(服务端权威,客户端仅提前反馈)
config.js Standard Schema v1 配置校验
paths.js $DSH_HOME 路径解析
version.js 自身版本/仓库读取(package.json)+ SemVer 子集比较
update-check.js 新版本检查:唯一的对外请求(npm registry latest),默认不自动发起、仅手动按钮或 updateCheck=true 触发;TTL 缓存、绝不抛错
lan-trust-script.js 认证后 LAN trust bootstrap(透传代理仅拦截 connection 注册,见下方安全例外)
client/ 设置面板(slot settings.section);src/index.jsx 源码,index.js+.map 为入库构建产物
locale/ 插件管理页展示用的标题/描述字典(zh.json / en.json);icon.svg 为插件图标(package.json 的 icon 字段),dsh 0.1.7 起不执行插件代码即可读取
scripts/ deploy.sh 同步流水线;verify.sh/e2e.mjs 实机验证;
reset.mjs/uninstall.mjs 凭据命令(bin);screenshots.mjs README 截图
tests/ node:test 单测(文件清单见 package.json 的 test script)
docs/ zh/ 与 en/ 双语文档目录
命令
npm test # 全量测试;测试文件在 package.json 显式列出——
# 新增 tests/*.mjs 必须手动加入该列表,否则不会被执行
npm run check # node --check lib/*.js + npm test(无 linter/typecheck)
npm run build:client # esbuild 构建 client bundle(改动 client/src/ 后必须运行)
npm run build:check # 重建并断言产物与源码一致
npm run deploy # 语法检查 → 测试 → 同步到 $DSH_PROFILE_DIR(默认 ~/.dsh/profiles/web)→ 安装后验证
单文件语法检查用 node --check <file>;实机端到端见下方「实机验证」。
约定
- 一切变更必须符合 dsh/Cordis 规范(最高优先级):只用官方扩展点——
ctx.effect、ctx.inject、ctx.slots、ctx.emit、webServer.tapIndex(仅限自包含全局量注入)、dsh.bundlepatch、client 插件 inject 声明。禁止触碰宿主运行时内部:不包装/替换window.__ModuleLoader__的任何方法,不包装第三方模块的 factory/apply/provide,不做全局 DOM 探测与样式注入。跨插件冲突或对 dsh 升级敏感的实现,一律视为规范违规并重构。 - LAN trust 是本项目唯一记录在案的安全例外:dsh 把配置平面(settings/credentials RPC)钉死在 loopback,官方注释写明"直到真实认证层存在"(
until a real authentication layer exists)但从未实现——本项目自行承担该认证层角色,因此允许对window.__ModuleLoader__做最小介入:在loader.load上套透传代理,仅拦截@deepseek-ai/dsh-client-connection的注册(其余插件原样通过,否则视为违规);其apply包装不得赋值ctx.provide(mixin-bound accessor,会污染共享 ReflectService 并让所有 provide 落入 connection 的 fiber scope——这是 0.4.2 破坏 better-sidebar 的机制),只能在共享ctx.reflect上临时替换未绑定的provide本体捕获 handle、转发originalProvide.call(this, ...)保调用者归属,apply 返回后同步翻转connection.isLoopback。实现见lib/lan-trust-script.js;任何放宽(如包装其他插件、触碰 ctx.provide、修改 loader 语义)都属破坏性变更。 - 零运行时依赖:host 代码只用 Node 内置模块;client 构建产物只允许 external 引用 dsh 运行时模块。新依赖需要证明现有手段不可行。
- 依赖只从公共 npm registry 解析:package-lock.json 的
resolved必须指向https://registry.npmjs.org/,禁止内网镜像;提交前检查 lock 文件无内网 registry 残留。 - 回环钉扎是安全根基:webserver 必须保持
127.0.0.1(cordis.patch.yml),对外暴露由网关listenHost承担;任何放宽都是破坏性变更。 - 新增 lib 文件必须同步两处清单:package.json 的
testscript(测试可见性)与 scripts/deploy.sh 的JS_FILES(部署同步按显式列表复制,不在列表即不到达已安装副本)。展示资源(locale/*.json、icon.svg)同样按 deploy.sh 的RESOURCE_FILES显式列表同步,并需在 package.json 的files中声明才随 npm 包分发。 - Cordis patch 的
config:是整对象替换:profile patch 覆盖字段时必须重申 bundle patch 的全部字段(含!!js动态端口表达式),漏写即回退默认值。 - 客户端面板经注入的 basePath 全局量构造 API 路径(
window.__dshAuthGatewayBasePath__,由 index.js tapIndex 写入):面板内禁止根绝对路径 fetch/跳转,否则子路径部署失效。 settings.section的order必须严格大于官方全部 section 的最大值:slot 列表按order稳定排序,与官方取值相等时位置由插件加载顺序决定;第三方面板必须落在所有官方菜单之后。- 登录失败只返回统一错误码
invalid-credentials(防凭据枚举);受保护流程(OTP 绑定/禁用)才允许细分错误码。页面文案一律走 lib/errors.js 字典,不硬编码。 - 安全状态变更必须留审计:登录/登出/改密/OTP 启停经
onAuthEvent输出(只含 kind/ip/reason,绝不带凭据),并与暴力破解告警(onSecurityEvent)一同落盘audit.log(lib/audit-log.js);错误密码计入与登录共享的按地址锁定。 - 凭据落盘模式:原子写(temp + rename)、文件 0600 / 目录 0700;scrypt 只用异步 API;OTP secret 先 AES-256-GCM 密封再写盘,主密钥缺失时显式报错、绝不静默重生成。
- CLI 脚本输出中英双语(reset/uninstall 及首次部署控制台提示),保持既有格式风格。
文档与发布
- 文档双语:
docs/zh/+docs/en/一一对应(第 3 行语言切换,跨语言链接../zh/、../en/),README.md(中文)与 README.en.md 成对修改;改动后校验链接可解析。docs 不打进 npm 包(files不含 docs/)。 - 版本号同时改
package.json与package-lock.json(根 +packages[""]两处);打 tagvX.Y.Z,gh release用中文发布说明。 - 提交信息:conventional 前缀(
feat:/fix:/docs:/chore:)+ 中文描述。
实机验证
scripts/verify.sh(curl 门禁)与 scripts/e2e.mjs(Playwright)需要运行中的实例(BASE=... PASSWORD=... ./scripts/verify.sh),且都会真实修改密码。本插件只能在本地 dsh 环境开发;测试用隔离实例——DSH_HOME 指到临时目录,profile 也建在同一 DSH_HOME 下,用完删除:
rm -rf /tmp/dsh-gw-home
DSH_HOME=/tmp/dsh-gw-home dsh plugin --profile shots add file:$PWD
DSH_HOME=/tmp/dsh-gw-home dsh --profile shots --port 8002 --no-open # 初始密码打印在控制台