# Dramivio Web A03 账号安全实现交接

本报告是 **Web 实现作者的交接与验证记录**。此前 RN 独立复审仅适用于原 RN 范围，不能作为本次 Web 的独立审核。本次 Web 交接版本已由根代理独立复核，见[Web账号安全复审](dramivio-web-account-review.md)。

已在「我的 → 账号安全」接入登录设备列表、单个其他会话撤销及确认、旧邮箱和新邮箱两段 OTP 更换邮箱。页面沿用浅色、无阴影、中英双语和原生 dialog/form/button/input；按钮至少 44px，输入可修改。当前登录只显示标记，撤销入口仅提供给其他登录。

主要文件：

| 文件 | 实现 |
| --- | --- |
| `internal/webui/consumer-account-api.js`（新增） | Better Auth 官方同源 HTTP 薄适配；权威会话前后核对、会话列表所有者验证、单个撤销及 GET 对账、两段 OTP 改邮箱及 GET 对账 |
| `internal/webui/consumer-account.js`（新增） | 账号安全 dialog；页面活动范围、确认操作、未知结果检查步骤、输入变更和迟到结果处理、关闭后清理 |
| `internal/webui/consumer-account-api.test.cjs`（新增） | 9 组协议与异常检查 |
| `internal/webui/consumer-auth-api.js` | 安全输出补真实 `sessionId`，不保留 token；get-session 明确禁用 cookie cache 和 refresh |
| `internal/webui/consumer-auth.js` | 暴露只读会话副本和身份代数；更换/清理既有会话即失效旧范围；A03 最终同步在 viewer 后再核对真实会话 |
| `internal/webui/consumer-shell.js`、`index.html` | My 入口与初始化；新 dialog 在 navigation.js 之前，使用现有弹窗历史与键盘机制 |
| `internal/webui/consumer-i18n.js`、`consumer.css` | 中英文文案、设备列表与邮箱表单样式、可见焦点与 44px 控制 |
| `internal/webui/consumer-auth-api.test.cjs` | 更新权威 GET URL 与真实 session ID 安全输出断言 |

关键行为与定位：

- `consumer-account-api.js:34` 的 GET 必须对应捕获的 user/session；`consumer-account.js:8` 再核对 origin、页面范围及既有 auth 身份代数。关闭、换用户、同用户换会话会中止并丢弃旧页数据；会话撤销 token 只留在 dialog 内存，不进入 DOM、属性或浏览器存储。根代理复核指出既有播放器/进度仅按 user ID 判断后，依其要求最小修正 `consumer-auth.js:14`：既有非空真实 session ID 被替换时清旧 pending，发既有 reload 事件并整页重载一次；首次登录不提前清 pending，不添加媒体状态或协议。
- `consumer-account-api.js:75` 以 GET 列表确认目标不存在。撤销 POST 的 200 不是完成证明；丢失响应后不自动重复 POST。GET 503 时保留“重新核对”。
- `consumer-account-api.js:51` 使用旧邮箱 `email-verification` OTP；新邮箱请求使用 `/email-otp/request-email-change`，最终提交使用 `/email-otp/change-email`。请求成功只写“请求已接受”，不声称邮件已送达。
- `consumer-account.js:132` 捕获提交时邮箱与编辑版本；在途编辑保留用户的新输入，清理旧 OTP，不用迟到接受覆盖新选择。最终 POST 不明进入检查步骤；后续仅 GET。
- `consumer-account.js:124` 先确认同用户、同真实会话、已验证新邮箱，再刷新既有 viewer；`consumer-auth.js:101` 在 viewer 之后再 GET，阻止同步期间同用户换会话造成错误完成。bridge 失败继续停留检查步骤。
- `consumer-auth.js:14` 首次登录的 pending 操作仍等既有 bridged 事件恢复；`consumer-auth.js:26` 无旧身份的终态响应不会形成刷新循环。权威 null/终态清理旧账号；网络 503 保留未知状态。

最终验证数量：

| 验证 | 通过数量与证据 |
| --- | --- |
| 新增 account Node | 9/9；`internal/webui/consumer-account-api.test.cjs` |
| 全 Web Node | 139/139；执行 `node --test internal/webui/*.test.cjs`，最终记录 `work/existing-project/web-account-all-node-final.log` |
| 官方框架 | 1 个隔离实际 Better Auth 1.7.7 场景，含 6 个检查点；`work/existing-project/web-account-real-ba.mjs` 与 `web-account-real-ba-checks.json` |
| A03 Chrome | 15/15；`work/existing-project/web-consumer-account-smoke.cjs` 与 `web-account-browser-checks.json` |
| 既有登录 Chrome 回归 | 16/16；`work/existing-project/web-consumer-auth-smoke.cjs` 与 `web-consumer-auth-checks.json` |

官方框架场景使用已安装 1.7.7 处理器、独立内存数据库和测试投递回调，实际执行 Cookie 登录、列表、其他会话删除、旧/新邮箱 OTP 以及邮箱提交；分别在实际删除和实际改邮箱完成后丢失响应，适配层都只 POST 一次并以 GET 确认。它没有接触服务端业务运行时或真实邮件。

15 组 Chrome 检查加载仓库真实 vanilla UI 与同源 HTTP 协议夹具，覆盖：明确撤销确认；当前会话无撤销入口；撤销 200 但目标仍在；丢失撤销响应与 GET 503；两种 OTP 目的；邮箱最终丢失响应；邮箱 200 但邮箱未变；bridge 503；在途编辑；429；同用户替换会话及迟到列表；权威 null/终态；viewer 期间替换同用户会话；英文桌面键盘 Enter/Escape 与 44px 控制；390px 手机布局、无横向溢出与秘密不持久化。同用户替换会话明确断言导航次数由 1 到 2、旧 dialog/pending 清空、再次读取同一会话后仍为 2；viewer 期间替换也只重载一次。所有组均无页面 JS 错误。

截图已实际查看：

- [手机中文账号安全](web-account-preview/mobile-zh.png)
- [桌面英文新邮箱验证](web-account-preview/desktop-en.png)

本次只修改 Web 文件，没有修改 `services/platform` 或 RN 文件；未启用删除账号、App 资格、新数据库结构、新持久状态层或离线锁。Chrome 采用随机本机端口，最终账号页运行端口见检查 JSON；没有占用 3008/18091/18092。没有真实服务器 API、SMTP、Cloudflare 或真机端到端验证，这些由根代理按既定范围处理。

![中文手机账号安全](web-account-preview/mobile-zh.png)

![英文桌面邮箱验证](web-account-preview/desktop-en.png)
