feat: expand companion platform, device controls, and memoir workflows
ci / verify (push) Canceled after 0s

Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
This commit is contained in:
Codex Local
2026-10-08 10:06:44 +08:00
parent 98fb2965d0
commit bf1233d5df
861 changed files with 94614 additions and 3209 deletions
+27 -42
View File
@@ -1,56 +1,41 @@
# Web登录会话与失效恢复说明
# Web 登录与页面恢复
## 1. 登录边界
## 路由规则
Web端使用OIDC授权码加PKCE完成登录,OIDC提供方只证明外部身份。浏览器取得短期访问令牌后调用`GET /api/v1/me`,由云端根据`issuer + subject`解析当前有效的平台账号、客户组织、家庭成员关系、运营角色和能力。业务工作台只在OIDC用户与云端账号映射同时有效时挂载,不依据浏览器保存的角色或客户组织自行放行。
- `/login` 为独立登录页,不挂载家庭或维护布局。家庭端默认手机号验证码登录,“账号登录”保留已有 OIDC 账号入口。
- 根地址的全部家庭栏目、`/maintenance`、`/onboarding/setup` 和 `/onboarding/devices` 均先校验身份。未登录时使用 `replaceState` 跳转 `/login?returnTo=...`。
- 校验中只显示加载状态;家庭业务接口与布局在认证完成后才挂载。`designPreview=1` 同样受保护。
- 成功登录后恢复目标页面的路径、查询参数和锚点。没有显式目标时使用当前标签页最近访问的业务页,再回退首页。
- 已登录用户访问登录页直接返回目标业务页。退出或接口返回 401 时卸载业务内容,并保留当时的页面地址供重新登录。
- 浏览器前进、后退或恢复缓存文档时执行相应的路由或身份校验。403 保持为权限错误,不触发全局退出。
OIDC客户端把当前用户状态保存在`sessionStorage`,关闭当前浏览器会话后不继续复用;启用自动静默续期,并在访问令牌到期前60秒发起续期。业务接口只通过`Authorization: Bearer`发送访问令牌,页面和业务状态不持久化令牌正文。
返回地址只接受当前源的已知业务路由,拒绝外部地址、协议相对地址、反斜杠、登录页和回调页,避免开放重定向及登录循环。返回地址仅决定页面位置,服务端继续校验资源权限。
## 2. 首次登录和回调
## 认证方式
1. 用户点击“登录研发环境”,浏览器跳转OIDC提供方;
2. OIDC提供方完成账号验证和PKCE交换后返回`/auth/callback`;
3. Web客户端只处理一次回调,并从OIDC事务状态恢复登录前的站内相对地址;无合法返回地址时回到`/`,同时拒绝协议相对地址、跨源地址和回调页自身,避免重复交换授权码和开放重定向;
4. 客户端调用`/api/v1/me`取得服务端权限事实;
5. 只有两层状态均成功时才展示家庭业务和当前账号允许的运营工作台。
`AuthProvider` 统一管理整站会话。家庭端可以使用 HttpOnly 账号 Cookie 或 OIDC;维护端仍需有效 OIDC 令牌和服务端维护角色。Cookie 不授予维护权限。
回调失败、网络错误或云端账号映射失败时停留在身份区,不挂载业务工作台。若OIDC用户仍有效,页面允许重新读取云端账号;用户也可以重新发起认证。
短信登录通过 `/api/v1/web/login/session`、`/api/v1/web/login/sms/send` 和 `/api/v1/web/login/sms/verify` 完成。账号 Cookie 恢复后仍需 `/api/v1/me` 取得有效账号和权限。发送失败、验证码错误和网络异常会显示在登录页。配置说明见 [家庭端短信登录](customer-sms-login.md)。
## 3. 业务接口返回401
OIDC 使用授权码与 PKCE,登录目标存入 OIDC 事务状态。`/auth/callback` 只交换一次授权码,验证返回地址后恢复业务路由。令牌过期、续期失败和业务请求 401 会清理本地认证状态。
所有带令牌的API请求统一处理HTTP 401。任何家庭或运营接口返回401时,API客户端触发页面级`platform:authentication-required`事件,而不是只在当前子页面显示局部错误。认证状态管理器对并发401执行合并处理:
## 设备绑定
1. 清除`sessionStorage`中的旧OIDC用户和访问令牌;
2. 立即清除云端账号状态,使全部家庭与运营工作台卸载;
3. React组件卸载时取消仍在执行的业务请求和SSE读取;
4. 身份区明确显示“登录状态已经失效,请重新登录”;
5. 用户点击“重新登录”后重新执行OIDC与`/api/v1/me`双重校验,成功后重新挂载业务区。
`/onboarding/entry#token=...&verifier=...` 先兑换一次性入口并删除地址中的凭据,再进入统一登录流程。返回目标固定为 `/onboarding/setup`,一次性凭据不写入返回参数或页面历史记录缓存。
401表示身份凭据已经失效;权限不足必须由服务端返回403,页面不得把403升级为全局退出。网络超时、503和普通业务冲突也只在对应页面提供恢复,不清除有效登录状态。
来自有效匿名设备设置会话的短信登录继续使用设备所属组织的原有注册规则,表单位于独立登录页。已有短信账号或 OIDC 账号登录后,通过 `/api/v1/web/setup/auth-link` 关联原设置会话;此接口同时核验同源请求、设置 Cookie、设置 CSRF 和账号身份。关联后旋转设置 Cookie,继续选择使用者和设备按键确认。
## 4. 令牌到期和静默续期失败
## 自动化验证
OIDC客户端发现已保存用户过期、收到访问令牌到期事件或静默续期失败时,同样删除本地旧用户并进入重新登录状态。不能继续保留顶部“已登录”账号卡片,也不能让已挂载工作台使用旧令牌反复请求。手工退出使用OIDC标准退出跳转,并在用户卸载事件后清空本地状态。
```powershell
npm run build --prefix web
$env:PLAYWRIGHT_REQUIRE_FROM = "$env:USERPROFILE/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright/package.json"
node test/e2e/customer-sms-login.mjs
node test/e2e/login-routing.mjs
node test/e2e/memory-workspace.mjs
powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "& './scripts/test-onboarding-integration.ps1' -Packages onboarding,httpapi -VerboseTests -OutputDirectory '.tmp-login-routing'"
```
## 5. 工作台导航与恢复
浏览器脚本运行真实 React 页面、路由和客户端,短信、账号与 OIDC 响应为测试替身,不发送真实短信。覆盖 11 个家庭栏目拦截、深链恢复、重载、退出后回退、401、缓存文档恢复、返回地址验证、OIDC 回调去重和设备入口继续绑定。数据库脚本使用临时真实数据库,包含短信账号关联设置会话及身份、同源、CSRF 的拒绝用例。
家庭业务使用根地址;运营工作台分别使用`?workspace=config`、`billing`、`alerts`和`diagnostics`。点击工作台入口通过`history.pushState`写入浏览器历史,前进/后退通过`popstate`恢复页面;刷新或直接打开合法地址时,在OIDC身份和云端账号加载完成后恢复目标工作台。
URL只表达界面位置,不授予运营权限。客户端必须等待云端账号解析完成后检查`platform_role`:没有运营权限的账号即使直接输入运营地址,也回到家庭业务并使用`replaceState`清除无权访问或未知的`workspace`值。会话短暂失效时保留合法工作台位置,重新登录并重新取得同等权限后回到原工作台;如果新账号权限不同,则安全回退家庭业务。
页面切换不会把表单正文、密钥引用、访问令牌或客户组织编号写入URL。快速自动检查应按接近人工操作的节奏切换,避免把瞬时超过用户级读取突发值的脚本行为误判为导航故障;真实检查仍要求所有请求不出现429。
## 6. 轻量验证
`test/e2e/visual-authentication-recovery.mjs`使用真实本地OIDC登录,在家庭数据刷新时只注入一次401,验证:
- 身份区显示会话失效和重新登录入口;
- 家庭工作台立即卸载;
- 本地OIDC用户条目清零;
- 未完成业务请求因组件卸载而取消;
- 重新登录可利用仍有效的OIDC服务端会话恢复账号与业务工作台;
- 除预期401和请求取消外,不出现失败请求、控制台错误或页面横向溢出。
该检查不修改业务数据,不替代生产OIDC会话时长、MFA、跨设备退出、管理员强制失效和真实反向代理Cookie/安全头验收。
`test/e2e/visual-workspace-navigation.mjs`另验证运行配置和费用额度选择写入URL、刷新恢复、浏览器后退/前进、未知值回退家庭业务,以及直接打开告警工作台地址。`test/e2e/visual-ops-sensitive-access-request.mjs`从未登录的联调诊断深链发起真实OIDC认证,验证回调后仍停留在目标工作台,再执行无提交的申请表单门禁检查。检查要求零429、零控制台错误、零非预期失败请求和零横向溢出。
截图及结果分别位于 `.design-evidence/customer-sms-login/`、`.design-evidence/login-routing/` 和 `.design-evidence/call-memory/`。真实短信送达及部署环境的 OIDC/HTTPS 配置需在对应环境验证。