Files
pangolin/docs/superpowers/plans/2026-06-29-device-session-management.md
T
wangjia 58526b09e6
ci-pangolin / Lint — shellcheck (push) Successful in 7s
ci-pangolin / OpenAPI Sync Check (push) Successful in 19s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Successful in 7s
ci-pangolin / Flutter — analyze + test (push) Successful in 23s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (push) Successful in 5s
ci-pangolin / Codegen Drift — token 生成物未漂移 (push) Successful in 5s
ci-pangolin / Go — build + test (push) Successful in 9s
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Successful in 14s
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Failing after 4m12s
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Successful in 15s
feat(client): P6 DevicesScreen 重做 + DevicePlatform 枚举
回应「状态码用枚举更好」:新增 DevicePlatform 枚举(线格式 fromWire 解析,
UI 只碰枚举,穷尽 switch),Device.platformKind getter;platform 图标按枚举 switch。
DevicesScreen 按 §7 视觉稿重做:行内 名称+「本机」标 / 平台·客户端版本·最后登录 /
在线绿点光晕·离线灰点 + 行尾 ⋯ 菜单(强制退出 / 清除登录信息·危险红)+ 危险二次
确认弹窗;本机行不显示 ⋯(不自我踢)。l10n 新增在线/离线/最后登录/从未登录/强制
退出/清除登录信息/取消 + 确认文案(中英);「当前设备」改「本机」。
新图标 moreVertical/trash/alertTriangle。localDeviceIdProvider 供本机判定。
测试:devices_screen widget 3 例(本机标+在线/离线+版本渲染 / ⋯菜单→强制退出→
确认→POST logout / 清除→DELETE);全量 flutter test 58 绿。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 06:10:37 +08:00

70 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 设备 & 会话管理 + 每设备流量归因 —— 实现计划(P1–P6)
> 执行真相源(checkbox)。设计见 `docs/device-session-management-design.html`;阅读版 `docs/device-session-management-plan.html`。
> SQL 走中性方言(`dialect.Upsert`、无 `NOW()`/`UTC_TIMESTAMP()`);UI 走 token 单源;不碰 CI-gated golden 的 Linux 基线。每期 build+test 过即 commit。
## 计划文档(先产出)
- [x] `docs/superpowers/plans/2026-06-29-device-session-management.md`(本文件)
- [x] `docs/device-session-management-plan.html`(阅读版)
- [x] 登记 `docs/index.html`「实现计划」
- [x] 总方案 `device-session-management-design.html` 加「→ 实现计划」链接
## P1 · 设备注册打通(登录即注册设备)
- [x] 客户端加依赖 `uuid` + `device_info_plus``client/pubspec.yaml`
- [x]`client/lib/services/device_identity.dart``deviceId()`secure storage 读;无则 `Uuid().v4()` 写;读失败 ≠ 没有,不重生成)、`deviceName()`/`platform()``clientVersion()`
- [x] 弃用 `connection_provider.dart``_kDeviceId='mac-001'` → 用 `device_identity`
- [x] `auth_api.dart` login/register 请求体加 `device:{id,name,platform,client_version}`
- [x] `auth/handler.go``loginRequest`/`registerRequest``Device deviceMeta`
- [x] `auth/service.go` Login/Register:成功签发后调 `DeviceRegistrar.RegisterIfAbsent`consumer-side 接口;`devices.Service` 实现;`RegisterIfAbsent` 增返回 `deviceID`);`MaxDevices` 由 plan 解析
- [x] `normalizePlatform``linux`
- [x] `main.go` 构造 `devices.Service` 注入 `authSvc`
- [x] 测试:`device_identity` 单测;auth 集成(登录后 devices 落行)
- [x] 验收:登录后 `GET /v1/me/devices` 非空
## P2 · sessions 表 + 在线/最后登录
- [x] migration `000016_sessions_and_device_meta`mysql+sqlite,含 down):建 sessions 表;devices 加 `client_version`+`totp_trusted_until`
-**拆出**devices 唯一键 `UNIQUE(uuid)``UNIQUE(user_id,uuid)` + platform CHECK 加 `linux` 需 SQLite 表重建(最高风险 DDL),隔离到单独后续迁移(多账户同机=已知降级,linux 注册 best-effort 失败不阻断登录)
- [x]`server/internal/sessions/``Store`Create/Rotate/Revoke/RevokeByDevice/LastLoginByDevice
- [x] `TokenManager.IssueWithJTI`/`RefreshWithJTI`/`ParseRefreshJTI` 外露 refresh JTI
- [x] `auth.Service` 注入 `SessionStore`Login/Register `Create`Refresh `Rotate`+last_activeLogout `Revoke`
- [x] `nodes/handler_grpc.go: ReportUsage` deviceID>0 时 touch `devices.last_seen`
- [x] `devices.Service.ListDevices`+API struct+store join:增 `client_version`/`online`/`last_login`(注入 `LastLoginSource`
- [x] 客户端 `Device` model 加 `clientVersion`/`online`/`lastLogin``account_api` 解析(fromJson 自动)
- [x] 测试:sessions store(3) + ListDevices 计算 + device model(2) + auth 设备/会话;migration sqlite 实库 v16
- [ ] 验收:列表显示在线/版本/最后登录(待 P6 UI + 端到端);停 agent ~3min 转离线
## P3 · 两个操作(强制退出 + 清除增强)
- [x] 新端点 `POST /v1/me/devices/{uuid}/logout`handler + `Service.ForceLogout`):`sessions.RevokeByDevice` + 逐 jti `TokenManager.Revoke`
- [x] `DeleteDevice` 增强:加 `sessions.RevokeByDevice``CredentialRevoker` 改 per-device
- [x] `CredentialRevoker` 接口改 `RevokeDevice(ctx, dpUUID)``nodes.Hub` 实现;`main.go` 注入 hub 替 NoopRevoker
- [x] 客户端 `account_api.forceLogout` + `devicesProvider.forceLogout`
- [x] 测试:ForceLogout 删 jti+标 revoked_atDeleteDevice per-device revoke
- [ ] 验收:强制退出→refresh 失败;清除→设备消失+凭证作废
## P4 · 每设备流量验证
- [ ] `ConnectNode` 兜底 `RegisterIfAbsent`device 缺失时)
- [ ] 端到端确认 `usage_device_daily` 填充
- [ ] 统计页设备下拉接真实设备 + `deviceUsageProvider`#10
- [ ] 测试:集成(注册→连接→上报→有行);统计页设备下拉 widget
- [ ] 验收:统计页按设备看分流量
## P5 · (未来)2FA 信任设备 —— 本轮不编码
- 占位:清除联动 `totp_trusted_until`P2 已预留列)。
## P6 · UI 重做(DevicesScreen,五端)
- [x] `DevicesScreen` 重做:名称/平台·版本/在线绿点/最后登录 + 本机标 + 行尾 ⋯ 菜单 + 危险确认弹窗;走 token
- [x] 宽/窄屏适配;空/加载态
- [x] l10n`app_text.dart`+`strings_{zh,en}.dart` 新增文案
- [ ] goldenDevicesScreen 新 goldenMac 生成,非 CI-gated
- [x] 测试:widget(在线/离线/菜单/确认/调用)
- [ ] 验收:与 §7 视觉稿一致,五端生效
## Verification
- 后端 `go build ./... && go test ./...`(含 sqlite 实库)+ `./server/run_sqlite_test.sh`
- 客户端 `flutter analyze` + `flutter test`
- 端到端:登录→设备列表;停 agent→离线;强制退出→refresh 失败;清除→消失+凭证作废;走流量→`usage_device_daily` 有行
- **线上 000016 迁移在 pangolin1 跑 `pangolin-migrate up` —— 生产写,执行前单独确认**
## 不在本轮
- P5 只占位;存量空 devices 无需回填(新登录自然铺开)