← Back to home@zoushangkun-dev

dsh-startup-auth-cn

给 DeepSeek Harness 手搓的开机门:六幕冷启动动画 + PIN / Windows Hello / 验证器动态码,纯本地、零依赖

Stars
0
Language
JavaScript
Created
Oct 23, 2026
Updated
Oct 26, 2026

Introduction

dsh-boot-handmade

仓库名:dsh-startup-auth-cn · 包名:dsh-boot-handmade(两者故意不同,包名是装进 profile 的标识)

给 DeepSeek Harness 手搓的开机门:六幕冷启动动画,加上一把真正拦得住人的锁。 纯前端 + 本地宿主半,零依赖、零联网、零第三方服务。

视觉语言参考了 B 站视频《deepseek 那些豪气冲天的启动动画》(@熟橘枯摘 · BV1T7nM6xEqo)——完整的出处链和三位更早的作者见图末「参考动画与致谢」。 全部用 DOM + CSS + SVG 画出来,没有视频、没有位图、没有第三方素材,任意分辨率都清晰;音效是 WebAudio 现场合成的。

更新记录

0.4.1

  • 启动更快:点图标到门可操作 780ms → 400ms;解锁到进工作区约 1.95s,另有 4 秒 强制上限——装饰动画永远不能把人挡在门外。
  • 门内改 PIN:更改登录密码 就在门上。刚验证过(PIN / 指纹 / 动态码,5 分钟内) 只填新 PIN;否则先给当前 PIN。写盘是原子的,不存在改到一半的中间态。
  • 进门后的入口:右下角常驻 ⚿,随时改 PIN。层级顶到 2147483645,并且挂载后 用 elementFromPoint 自检是否被别的插件盖住,被盖就重排/让位。
  • 面板关不掉:叉号、Esc、点外面,三条独立的路。
  • 指纹自愈:采样中途关窗会让 CredentialUIBroker 卡住,下一次采不到样。 现在失败后自动杀掉 broker 重试一次。
  • 账号:门上新增「DeepSeek 账号」,验证后自动打开应用自带的账号菜单。
  • 去掉没做出来的东西:邮件验证码、微信 / QQ 入口从门上移除(邮件救援整套已在 0.4.12 删除,见下)。

0.4.0

  • 按钮点击音效(WebAudio 现场合成)。

它是什么

DeepSeek Harness 启动时先播一段六幕的开机动画(黑屏描边 → 过曝白闪 → 技术档案 → 权限门 → 核验 → 进入), 然后停在一道验证门前:输 PIN、按指纹、或者填验证器动态码,三者任意一个通过才进得去工作区。

它不是"开机自启个小窗"那种装饰,是档在应用前面的最后一步。

三种开门方式

方式说明
访问 PIN4–8 位数字,scrypt 加盐哈希存本地,连续错 5 次锁 30 秒
Windows Hello调用系统凭据验证(指纹 / 人脸 / Hello PIN),并把系统窗口强制置顶,不会被应用挡住
验证器动态码标准 TOTP(RFC 6238),手机扫码或手输密钥即可,完全离线;配 8 个一次性备用码防手机丢失

安装

# 1. 克隆到任意目录
git clone https://github.com/zoushangkun-dev/dsh-startup-auth-cn.git

# 2. 装进 desktop profile(link 方式,改代码即时生效)
& "D:\dsh\resources\runtime\cli\bin\dsh.cmd" plugin --profile desktop add "link:<克隆到的路径>"

# 3. 重开客户端

Windows 用户也可以直接双击仓库里的 安装.cmd(会先检查客户端是否已退出)。

装在哪个路径都可以,但不要放在会被清理的临时目录——profile 里存的是软链。 客户端运行中直接装通常会成功(实测 pnpm 3.7 秒);如果报 EPERM ... package.json.lock,退出客户端再装。

首次使用

  1. 重新打开客户端,动画播完会出现验证门;
  2. 先设一个 4–8 位 PIN(这一步会同时把你放进去);
  3. 之后再启动,可以直接输 PIN,也可以点「用验证器 6 位码进入」先配好验证器。

强烈建议配置验证器:点「设置验证器(6 位动态码)」→ 屏幕给出 32 位密钥 + 8 个备用码 → 手机装 Microsoft Authenticator / Google Authenticator,选「其他账户 → 输入代码」,把密钥粘进去。

那 8 个备用码只显示一次,请抄在纸上或存进密码管理器。手机丢了或刷机了,它是唯一退路。

配置与数据

全部在本机 ~/.dsh/ 下,插件不联网、不上传任何东西:

文件内容
boot-auth.jsonPIN 的 scrypt 哈希 + salt(不存明文)
boot-totp.jsonTOTP 密钥 + 备用码的 scrypt 哈希

逃生口与回滚

场景怎么做
忘了 PIN / 被锁 30 秒点「用验证器 6 位码进入」,或填备用码
手机和电脑都不在手上双击 急救绕过.cmd,创建一个标记文件,宿主看到就直接放行
想彻底撤掉双击 卸载.cmd,或 dsh plugin --profile desktop remove dsh-boot-handmade
想退回旧版本双击 回滚.cmd 选择目标提交

急救标记是一个本地文件:%USERPROFILE%\.dsh\boot-auth-bypass。删掉它(或双击 恢复锁定.cmd)即恢复验证。 门不会因为"等久了"自己打开——没有倒计时放行,也没有超时自动进入。

安全边界(请先读这段)

它是仪式感 + 家用门闩,不是保险箱。

  • 这个锁保护的是动画和验证这一层,不是整个 Web 服务:懂技术的人可以打开开发者工具删掉遮罩节点,或者直接调宿主接口。
  • 真正的强制门禁必须做在应用外壳前面(Electron 主进程 / 系统级),profile 插件够不着。
  • PIN 是 scrypt 加盐哈希,会话令牌只在内存,错 5 次锁 30 秒;TOTP 密钥和备用码哈希都在本机。
  • 但如果有人已经能读写你的用户目录,他可以直接删掉 boot-auth.json 绕过一切——这是本地锁的固有边界。

换句话说:别把唯一一份重要数据的访问权押在它身上。

常见问题

Q:点「Windows Hello」后系统窗口没出现,或者出现在应用后面? A:插件会把 Credential Dialog Xaml Host 那个窗口强制置顶并抢前台。仍然没出现的话,先到锁屏试试指纹是否正常;不正常说明是本机指纹驱动在用户会话里起不来,需要修驱动,和插件无关。

Q:验证器填了码一直说不对? A:确认填的是 6 位、且是当前显示的码(30 秒一换,插件容忍前后 60 秒)。手机和电脑时间差太多也会对不上。

Q:动画太长 / 太闪? A:门在约 0.8 秒后就可操作,装饰文字在门后面并行播放,不阻塞输入。频闪和撕裂效果已移除。

Q:能改成英文界面吗? A:目前界面文案是中文,代码里没有硬编码依赖,欢迎 PR。

技术细节

  • 动画:纯 DOM + CSS + SVG,没有图片、没有字体文件、没有构建步骤
  • 宿主半:一个 cordis 插件,注册 /boot-auth/* 路由
  • PIN / 备用码:crypto.scryptSync + 随机盐
  • TOTP:HMAC-SHA1,与 RFC 6238 官方向量对齐(仓库内 tools/test_totp.mjs 可复跑)
  • 测试:tools/test_boot_auth.mjs、test_totp.mjs、test_pin_change.mjs、test_settings_ui.mjs、test_settings_section.mjs

已知限制

  • 应用内指纹依赖本机 Windows Hello 驱动;部分老驱动只能在锁屏用,应用内验证窗会拿不到采样。
  • 启动画面从点击到出现仍受 Electron 本体启动时间影响,插件只负责"出现之后"的那段。
  • 「直接进入(跳过验证)」是双入口模式的临时按钮,稳定几版后会移除。

参考动画与致谢

本项目的视觉语言与分幕节奏参考了下面这条视频,并沿着它的出处一路往上追到了三位更早的作者。 我们只做形式参考与工程重写,没有搬用任何原作者的素材或代码。

  • 《deepseek 那些豪气冲天的启动动画》— @熟橘枯摘 · BV1T7nM6xEqo

它自己标注的三条源头:

灵感来源作者原仓库
最早的启动动画(按空格/回车推进的第一幕)沈宏宏6shenhonghong9/dsh-startup-screen
龙娘启动动画(线描手法)@mytjxsmytjxs/codex-startup-animation
550C 启动动画@yannicksong0106yannicksong0106/dsh-550c-boot

如果这几样对你有用,请去原仓库点个 star——功劳是他们的。

License

MIT

桌面启动器(V4.1 起内置)

DSH 主进程要几秒才建出窗口,这段时间里连点图标只会拉起几个立刻退出的进程, 看起来像「点了没反应」。插件自带一个启动器脚本解决这个问题:

安装启动器.cmd     在桌面创建「DeepSeek Harness (Launcher)」快捷方式
卸载启动器.cmd     移除该快捷方式
launcher/          启动器本体(dsh-launcher.vbs + dsh-launcher.ps1)

点一下,约 100 ms 内弹出小窗显示「正在启动…」,主窗口出现后自动关闭; 如果 DSH 已经在运行,启动器直接把已有窗口切到前面,不会重复启动。

排错:launcher/launcher.log 会记录每次调用的每一步。

在应用内修改访问 PIN(V4.1 起)

门上有「修改访问 PIN」入口,不用退出应用、不用把 PIN 重置成未知状态:

  • 刚验证过(PIN / 指纹 / 动态码任意一种,5 分钟内):直接填新 PIN 两遍即可
  • 没验证过:先填当前 PIN,再填新 PIN 两遍
  • 改完不会退出登录,下次开门用新 PIN

为什么要有它:以前改 PIN 只能到门外重设,一旦中途失败,账号就进了死胡同。 现在改动是原子的——要么旧 PIN 还能用,要么新 PIN 已生效,没有中间态。

接口:POST /boot-auth/pin/change,参数 { next, confirm, current? }。 宿主只在「5 分钟内刚验证过」或「当前 PIN 正确」时接受。

DeepSeek 账号:由应用负责,门只负责带路(V4.1 起)

账号的登录与切换属于 DSH 应用本身,插件拿不到(核心的 service-accounts 没有对插件开放)。所以门上只做一件事:

门 →「DeepSeek 账号」→ 验证后进入 → 自动打开应用自带的账号菜单

为什么值得做:不是所有人都找得到那个入口。门在验证完成后会在 3 秒内查找 账号/头像元素并点开;找不到就什么都不做——此时人已经在应用里,界面和平时一样。

进门之后也能改 PIN(V4.1 起)

门的入口只在门出现时存在,进了应用就找不到了。所以进入后右下角会常驻一个 ⚿ 按钮,点开就是「修改访问 PIN」面板,参数与门内的那个完全一致。

  • 需要当前 PIN(刚验证过 5 分钟内可留空)
  • 改完不退出登录,下次开门用新 PIN
  • 面板自带样式,不依赖门那套样式表(门关掉时会一起移除)

入口位置与撞车处理(V4.1 起)

入口挂在左下角「已登录 DeepSeek」那一行,而不是飘在角落——那里才是人会看的地方。

找不到时按顺序退:头像/账号元素 → 侧栏底部 → 标题栏 → 右下角浮标。

防撞车:插入后量一次矩形,和自己兄弟节点重叠就撤掉、退到下一个位置; 之后每 2 秒复查(最多 10 次),因为别的插件可能是稍后才注入的。 被挤掉时不会盖住别人的控件。

启动器卡在进度条怎么办

症状:小窗弹出来了,进度条跑满,但窗口一直不出现。

原因是任务管理器里还留着一个没有窗口的 DSH 进程,它占着单实例锁; 新进程启动后被它挡回来、立刻退出,所以什么都看不到。

启动器现在会识别这种情形:约 5 秒后把小窗文案改成「已有实例在后台,等待窗口」, 20 秒后提示「请在任务管理器结束 DSH 后重试」。真遇到了,按提示做一次即可。

桌面启动器(0.4.11 起恢复发布)

双击 安装启动器.cmd 会在桌面加一个 DeepSeek Harness (Launcher), 点它会出现一个小窗:深色卡片、DEEPSEEK HARNESS、正在启动…、橙色进度条, 右侧是百分比。窗口一出现小窗就自己关掉。

进度条最多只到 92%,永远不会假装跑完——满格和"还在等"必须能一眼分清。

进度条上面还有一行字,说明现在在做什么,而不只是走了多少:

时间步骤
0 – 1.2s正在启动进程
1.2 – 6s等待窗口出现
6s 以后界面加载中

「进度条跑满但窗口不来」的根因(已修)

上一次异常退出会在后台留下没有窗口的 DSH 进程。它占着单实例锁, 新进程启动后立刻被挡回来并退出,于是屏幕上什么都不会出现。

现在启动器在拉起程序前先判断:

  • 有窗口 → 聚焦已有窗口,不重复启动
  • 没有窗口但有进程 → 认定为残留,全部结束,再干净启动(日志写 stale instances cleared)

卸载:双击 卸载启动器.cmd。原有的 DeepSeek Harness 快捷方式从头到尾没被改过。

安装启动器.cmd 只是给本地用;装了插件的人不需要跑任何脚本:

右下角 / 左下角的入口 → 「启动方式(可选)」→ 启用桌面启动器
  • 默认不装,旧的快捷方式一直可用
  • 启用后桌面多一个 DeepSeek Harness (Launcher),点下去立刻有反馈
  • 不满意点同一处「卸载启动器」,旧入口从未被动过

之所以做成选装:新入口解决的是「连点好几次」的体感问题,但它依赖 DOM 与窗口行为, 不是所有机器都需要,也不该在没验证过的情况下替用户做主。

入口改叫「更改登录细节」(V4.1 起)

这块面板现在管两件事,所以入口名不能只写改 PIN:

鼠标移到「更改登录细节」上
 ├─ 更改登录        → PIN 表单
 └─ 更改启动方式    → 桌面启动器开关

名字取“细节”是因为找的人不一定是在找密码——也可能是想换启动方式。 单独写「更改登录密码」会让另一半功能显得不存在。

指纹采样失败后的自愈(V4.1 起)

Windows 把 Hello 提示框放在 CredentialUIBroker.exe 里,而且会复用这个进程。 如果在采样中途把窗口关掉(点取消、按 Esc、直接关),broker 会保留一次失效的采样会话: 下一次窗口照样弹出来、也显示「请扫描指纹」,但采不到任何东西,只有重启才行。

现在两种处理:

  • 自动:这次 Hello 返回 Canceled / RetriesExhausted / no-result 时,宿主先 taskkill /F /IM CredentialUIBroker.exe 再自动重试一次
  • 手动:POST /boot-auth/hello/reset 随时清掉卡住的 broker

代价是重试会多弹一次窗;但比“指纹彻底用不了、只能重启”好。

进门后的入口

右下角常驻 ⚿ 浮标,点开是「更改登录 PIN」。它完全在我们自己的 DOM 里, 不碰应用界面的框架节点(之前挂进标题栏把界面卡死过,那条路废了)。

进应用后的那个 ⚿ 浮标曾经有两种实现:挂进应用标题栏(后来确认会把界面卡死, 因为那部分 DOM 归 React 管),以及右下角浮标。两者在当前版本上都不稳定,故整段移除。

现在能改 PIN 的唯一位置是门本身(「更改登录密码」)。它不碰应用界面,是安全的。 桌面启动器改用 安装启动器.cmd 安装,不再依赖应用内入口。

层级与点得到(V4.1 起)

别的插件也在往右下角放东西,层级低于它们就会变成"看得见、点不到"。三层保障:

  1. 门:z-index: 2147483640 + isolation: isolate——锁不该被任何插件盖住
  2. 常驻入口:2147483645,面板 2147483646
  3. 运行时自检:挂上 400ms 后用 elementFromPoint 量一次自己是不是最上层; 被挡就重新 append 到 documentElement 末尾(同层级里后者赢),还被挡就上移一格

第 3 条会往控制台写一行 entry was covered by <对方>,方便以后定位是谁在抢位置。

面板关不掉(V4.1 起)

现象:点叉号没反应,反而听到余额挂件「叮」一声——因为那块角落同时被别的挂件占着, 它的点击层压在我们的叉号上。

修法不是抢赢了事,而是让关闭有三条独立的路:

  1. 叉号(34×34 热区、面板内 z-index: 5)
  2. Esc(键盘,完全不经过那块区域)
  3. 点面板外面任何地方

面板整体也从 right:20px 让到 right:84px,不再和挂件重叠。

开机耗时记录

每次启动,门会把几个关键节点上报给宿主,追加到:

~/.dsh/boot-timing.log

格式是 时间戳 相对毫秒 阶段,阶段包括 run-start、gate-ready、unlocked、finish。 启动器自己的时间线在 launcher/launcher.log。

两处加起来就能回答「哪一段慢」,不需要开控制台。

新用户怎么知道有启动器(0.4.11 起)

装了插件的人不会自己找得到新入口,所以补了四件事:

  1. 入口自愈:ensure 会检查桌面快捷方式。说好装了却不见了(误删、清理工具), 就静默补一个回来。
  2. 一次性公告:进入应用后弹一次「现在有了新的启动方式」,里面有一段和启动器 一致的步骤 + 百分比示意,让人知道等待时长什么样。
  3. 避让其他弹窗:弹之前扫一遍其他插件的 modal / 更新日志 / toast。有就每 1.5 秒 再看一次,最多等 60 秒;它们说完了才轮到自己。这条通知不急,不该插队。
  4. 按版本记忆:勾选「下一次更新前不再展示」会写进 ~/.dsh/boot-announce.json, 记录的是版本号而不是布尔值——发新版本会自动重新提示一次。

接口:POST /boot-auth/announce,action 为 status / dismiss / ensure。

没设过 PIN 的人怎么办(0.4.11 起)

插件第一次跑的时候 boot-auth.json 还不存在,门会进入设置模式:

为了让这把锁生效,请先为它设置一个 PIN(4-32 位数字)

  ○ ○ ○ ○          ← 先给 4 格,够 4 位就是一个完整 PIN
  [1][2][3] …      ← 可视化键盘

输完第一遍 → 提示「请再输一次这个 PIN,确认没有按错」
两遍一致   → 写入并放行
两遍不一致 → 清空重来,不会把错误的那一次写进去

为什么必须输两遍:以前只输一次就生效,一个手滑就把自己永久锁在外面—— 而那时唯一的出路是 急救绕过.cmd,等于锁白做了。

另外,点是自适应的:4 位时显示 4 格,第 5 位开始才多一格,到 8 位封顶。 不会让人觉得"还有 4 格没输完"。

所有密码输入都能看明文(0.4.11 起)

门上的点旁边多了个「显示」按钮;设置里改 PIN 的三个框上方有「显示输入内容」勾选。

  • 打开后点变成真正的数字,1 2 3 4 一目了然(数字槽 22px 宽,不会溢出压到按钮)
  • 能核对「我输入的是不是我想的那个」
  • 记不住的人可以拍照留存——全是一样的点,拍照没有任何意义

默认关闭。这是本地 UI,不上传、不记录,除了你自己没人看得到。

「直接进入」保留到 0.5.0(决定,不是遗漏)

门上有一个 直接进入(跳过验证)。它不是忘了删,是故意留的退路:

现在还不知道会不会出现结构性的 bug 让人根本进不来。真有那一天, 「看起来像锁」没有意义,「进得去」才重要。

计划在 0.5.0 移除,前提是这之前没有出现过"门本身进不去"的问题。

在那之前,能救急的三条路,按代价从低到高:

办法做什么恢复
直接进入门上点一下,跳过验证无副作用,随时可用
急救绕过.cmd在 ~/.dsh/ 放一个 boot-auth-bypass 标记文件删掉标记即恢复
回滚.cmd停用插件本体重新启用即可

前两条都不碰你已经设置的 PIN,只是临时放行;第三条是把插件关掉。

PIN 长度:4–32 位(0.4.11 起)

最早限定 4–8 位,那是我拍的数字,不是任何标准。现在放宽到 4–32 位纯数字:

  • 键盘只有 0–9,所以不会出现"按不出来的字符"
  • 超过 8 位时点会自动换行,不会挤出屏幕
  • 门的输入框、设置里的改 PIN、宿主校验三处一致

同时删掉了门下方那行内部备注「双入口模式 · 稳定后会把「直接进入」移除」—— 那是给开发者看的,不该出现在用户的界面上。

门只管进门(0.4.11 起)

门上原本还塞了一个「更改登录密码」表单,和设置里的那份重复。现在门上只剩通过验证需要的:

数字键盘 + 显示
WINDOWS HELLO · 指纹 / 人脸 / PIN
用验证器 6 位码进入(配好了才显示)
DeepSeek 账号
直接进入(跳过验证)

改 PIN、改外观、开关启动器小窗,全在左侧栏的「⚙ 设置」里。

门的输入:一个普通密码框 + 眼睛按钮(0.4.11 起)

自绘的点、屏幕键盘、可变槽位——全部撤掉。回到最稳的形态:

输入登录密码 · 已输入 9 位
[ ● ● ● ● ● ● ● ● ● ]  [👁]  [解锁]
  • 输入框就是浏览器的密码框,输入、退格、粘贴、回车全部原生行为
  • 右边一个眼睛按钮:闭眼时是点,点一下变睁眼,点立刻变成数字; 再点一下回到闭眼,数字变回点
  • 图标和颜色跟随深浅主题(深色下是浅灰/米白)
  • 位数写在提示行(已输入 9 位),因为 9 位以上靠肉眼数点容易数错

切换只改 input.type,不碰输入值,所以不存在"点一下就清空"。

长度逻辑集中在一个函数里

没有任何写死的位数。门的提示、校验、上限全部走同一个函数:

var PIN_MIN = 4, PIN_MAX = 32

function pinInfo(value) {
  var len = String(value || '').length
  return {
    length: len,
    tooShort: len > 0 && len < PIN_MIN,
    okRange: len >= PIN_MIN && len <= PIN_MAX,
    remaining: Math.max(0, PIN_MIN - len),
    room: Math.max(0, PIN_MAX - len),
  }
}
  • 点不用我们算:输入框是原生密码框,打几个字就几个点
  • paintPin() 用它更新提示:还差 2 位 / 已输入 9 位
  • 提交前用它校验,报错文案里的数字也来自这两个常量

验证覆盖 0 / 2 / 4 / 9 / 32 / 33 位六种情况,全部符合预期。 要改范围(比如放宽到 64),只改 PIN_MAX 一处。

设置条目进应用自己的设置页(0.4.11 起)

用官方 API 注册,不往 React DOM 里塞东西:

ctx.inject(['settings'], (sctx) => {
  const scope = sctx.settings.register('boot-auth', Schema, {})
  scope.watch(apply)   // 用户改了 → 写进 ~/.dsh/boot-settings.json
})

条目会出现在 设置 → 插件 → 插件配置 → boot-auth,和 Vision Router、Cappuccin 并列。

当前可配置项:

项类型作用
外观auto / light / dark门的深浅色
启动小窗布尔点启动器时是否显示进度小窗

| 当前 PIN / 新 PIN / 再输一次 | 文本(write-only 语义) | 三个都填好且两次一致才执行 |

改 PIN 也在这张卡片里:三个字段,填完保存即执行(需要当前 PIN, 或 5 分钟内刚验证过身份)。表单本身不显示结果,执行结果记在 ~/.dsh/boot-settings.json 的 lastPinResult 里(ok: current-pin at … 或失败原因)。 同样的输入不会重复执行。

为什么整段都有 try/catch

插件是 link 安装的,@deepseek-ai/schemastery 不在自己目录里,裸 import 会 ERR_MODULE_NOT_FOUND。所以:先常规 import,失败再从各 profile 的 node_modules 里找; 任何一步失败都只打一行日志,门本身完全不受影响。已用假宿主验证过三种情况: 正常注册、watch 生效、没有 settings 服务的宿主照常启动。

为什么设置没能进「内置插件」(查清了)

这个宿主的 settings 服务没有 register,只有 describe 和 getSnapshot:

skipped: register failed: sctx.settings.register is not a function

Vision Router 之所以能在设置页里出现,是因为它自带了一整套兼容层 (lib/dsh-settings-017-compat.js、dsh-contract-compat.js),把 configEditor、 web 路由、revision registry 全部实现了,而且命名空间写死 vision-router,不可复用。

所以结论:在当前版本上,做不到"和别的插件并列",除非逆向那几个未公开接口。

现在的做法

应用内不再放任何我们自己的 UI(那块属于 DSH,插进去是局外元素)。 设置回到它该在的地方——门本身:

需要访问许可

输入登录密码 · 已输入 9 位
● ● ● ● ● ● ● ● ●      👁   解锁 →
────────────────────────────────
更改登录 PIN          ← 点开在纸内展开,不是浮层
直接进入(跳过验证)

点「更改登录 PIN」会在同一页纸上展开三个字段(当前 / 新 / 再输一次), 就地保存、就地显示结果。外观(深/浅)已并入这张卡片,启动小窗在设置文件里。

设置页也要先验证(0.4.11 定稿)

打开 设置.cmd 的页面后,先出现的是:

先验证一次身份
改 PIN 需要第二因素:验证器动态码、备份码,或 Windows Hello。
[ 验证器 6 位码 / 备份码 ]
[验证]  [用 Windows Hello]

通过后才显示 PIN 表单和小窗开关。验证有效 5 分钟,改完即作废。

接口层同样强制:/boot-auth/pin/change 没有 step-up 令牌直接 401,连当前 PIN 都不看。

为什么没有邮件救援(0.4.12 删掉)

曾经做过一条"忘记 PIN 就发邮件验证码"的路,用的是本地 SMTP(126 邮箱授权码)。 后来整套删掉了,原因不是难做,是可靠性不够:

  1. 邮件能不能发出去,取决于邮箱服务商当天的心情、端口是否被封、授权码是否过期。 一条"救援通道",只有在最坏的时候才被使用;它却建立在最不可控的一环上。
  2. 配置过程本身要求用户去网页版生成授权码——对普通用户是一道门槛, 对想进门的人也未必拦得住。
  3. 安全模型上,它把门的安全性降到了邮箱的安全性:能进邮箱的人就能重置这把锁。

所以决定留到能自建发信服务(有自己的域名、SPF/DKIM、退信处理)时再做。 在那之前,可靠的三条路是:PIN、Windows Hello、验证器 6 位码 + 8 个一次性备份码。