跨端: fix(客户端) 换身份必须清全部账号数据 + 鸿蒙管理台门禁改三态

两份审查报告(`docs/reviews/electron-gui-review.md` /
`harmony-client-review.md`)里两条**数据隔离**缺陷。

## ① 换身份不清数据 ⇒ 在新账号的界面下显示旧账号的邮件

`setActive` 之后 `api/config` 单例里的 API_BASE 与 bearer 就翻到了新账号,
于是此后每个请求都带**新账号**的凭证。而各 store 里还留着**旧**账号的:
  · mailStore.sent / currentMail
  · sessionStore.sessions / currentSession / currentSessionMails
  · contactStore.contacts / archivedContacts

⇒ 肉眼完全看不出来(不报错、不空屏),而**从旧视图发出的写操作**
(归档 / 转发 / 批准权限)改的是**新账号**。

新增 `src/lib/resetAccountData.ts` —— **一处实现,三个入口都调它**:

    ① 主动切账号(AccountSwitcher.pick)
    ② 登出(authStore.logout)
    ③ 任意接口 401(api/client.ts 的 unauthorized 回调 → markAnonymous)

只在 ① 里清是最容易漏的那种做法:② 和 ③ 各自还会重新泄露一次,
而它们都不在切换账号的代码路径上,grep 也找不到。
**身份变化有三条路径,清空也该有三条。**

★ 只碰**数据** store;`uiStore` 的 reset 仍由 App.tsx 负责
  (它还要复位窄屏分栏、写信态那些纯界面状态)。

判据:`test/stores/resetAccountData.test.ts`(4 格)。

## ② 鸿蒙管理台门禁**失败开放**(fail-open)

`AdminUsersPage.ets` 原来的条件是 `roleKnown && !this.isAdmin`,
于是 `roleKnown === false`(loadRole() 失败、**身份还没读到**)
落进 else 分支 ⇒ **把完整管理台整个渲染出来**。
一次网络抖动 = 管理入口对所有人可见。

讽刺的是该文件自己的头注释写的就是正确规则
(「不能把读不到当成是管理员」)—— 代码做的正是这条注释禁止的事。

⇒ 改三态:`!roleKnown` 显示「正在确认身份…」、`!isAdmin` 显示墙、
  否则管理台。

服务端 `middleware/user.go` 的 `AdminOnly` 仍在,所以**不是越权**;
但非管理员会看到完整用户列表、建号表单、改密入口 ——
属于客户端信息泄露 + 无意义的失败请求风暴。
This commit is contained in:
2026-09-28 08:27:01 +08:00
parent 044a664cc3
commit 18b148e476
28 changed files with 690 additions and 64 deletions

View File

@ -181,7 +181,9 @@ test('A|系统拥有的维度,鸿蒙侧的唯一来源是系统资源(不
*/
const sysOwned = {
pageBg: 'color', surface: 'color', surfaceMuted: 'color', border: 'color',
textPrimary: 'color', textMuted: 'color', textSubtle: 'color',
// textSubtle 已移除:系统三级色浅色下 2.85:1,且本机二级色同值
// (见 SELF_OWNED_COLORS 里 textSubtleLight/Dark 那段),小字走 textSubtleFor()。
textPrimary: 'color', textMuted: 'color',
overlay: 'color', navFg: 'color',
radiusCard: 'float', radiusControl: 'float'
};
@ -297,6 +299,28 @@ const SELF_OWNED_COLORS = [
'chipNeutralBgDark', 'chipNeutralFgDark', 'chipSpentFgDark', 'chipWarnFgDark',
// 权限档位与预算档位的胶囊配色(档位是产品语义,系统不认识"plan/workspace/full")
'chipNeutralBg', 'chipNeutralFg', 'chipSpentBg', 'chipSpentFg', 'chipWarnBg', 'chipWarnFg',
/*
* ★★ 2026-09-26 补登记:小字那一档(`textSubtleLight` / `textSubtleDark`)。
*
* 为什么必须是**自己写**的色,而不是 `$r('sys.color.ohos_id_color_text_*')`:
*
* 2026-09-19 这条设备判据已经抓到过「浅色三级色 = rgb(153,153,153) 压白底
* = 2.85:1」,当时的修法是让 `textSubtleFor()` 两个主题都返回 `textMuted`
* (`ohos_id_color_text_secondary`),理由是"二级色语义对得上"。
*
* ★★ 但 2026-09-26 复扫证明**那个前提不成立**:本机 HarmonyOS 6.1.1 上
* `ohos_id_color_text_secondary` 实测**也是 rgb(153,153,153)**,与三级色同值
* ⇒ 换过去之后**一点没变**,判据继续红。
*
* 系统二级/三级色只保证「比一级淡」这层**层次关系**,不保证 WCAG 下限;
* 同一个坑在本文件已经犯过三次(`surface` 当前景色、accent 深色没调亮、
* 这一次)。而 WebUI 那边 gray-400/gray-500 是**两套主题各自适配过**的值
* (浅 4.83:1 / 深 5.51:1),不是同一个色翻个主题。
*
* ⇒ 这一档的"跨端身份"就是 WebUI 的 gray-400,所以按 gray-400 逐字对齐,
* 并给出两个主题各自实测 ≥3:1 的值。
*/
'textSubtleLight', 'textSubtleDark',
/*
* ★★ 2026-09-19 补登记(对应用户「底栏数字为什么显示在图标下面?」与
* 用户「你写的app和webui大面积不符」那两轮改动)。