Files
SimpleFarm/.workbuddy/memory/2026-09-23.md
T
snowy-yang 698a5e497a feat: 用户名登录——一账号一个牧场,登录后改 URL 也进不了别人的档
多用户之前是「知道牧场名就能玩」:?farm=boss 直接进别人家里。
现在网页端用用户名登录(用户选择:不设密码、一账号一个牧场,
用户名即身份——互信小圈子模型,防的是随手改 URL,不是蓄意攻击)。

* 新增 farm/server/auth.py:HMAC-SHA256 签名的 HttpOnly Cookie
  (sf_auth,30 天,恒时比较防篡改)。密钥存 <save_dir>/secret.key,
  重启不掉线,也不用建表存 token。
* app.py 三个认 ?farm= 的地方(session_of / SSE / POST /api/farms)
  统一规则:有 Cookie 强制本人的牧场(?farm=/X-Farm-Id 被忽略),
  无 Cookie 保持旧语义——bot/TUI/CLI 脚本零改动照常工作。
* 新路由:POST /api/login(全局 token 照常把它挡在门外)、
  POST /api/logout、GET /api/me(刻意不 guard:首屏只问"登录没")。
* 网页:移除牧场名输入框,改为登录遮罩 + 用户徽章 + 退出按钮;
  首屏先 whoami() 再决定连接还是弹登录。

测试 +13:Cookie 伪造/过期/跨密钥/垃圾串/非法用户名/密钥跨重启复用/
session_of 优先级(starlette Request 用 scope dict 直接构造,不需要
httpx)9 项纯逻辑档 + 4 项 TestClient 档。528 项 OK(skip 39)。

真机验收 16/16:登录/HttpOnly/?farm= 被忽略/动作落自己牧场/篡改
退回未登录/退出/非法名 400;跨重启 Cookie 仍有效且进度保留。
2026-09-23 21:20:25 +08:00

60 lines
4.4 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.
## 2026-09-23 修网页版「点播种没反应」+ 顺手挖出自动存档失效的真 bug
- 用户报网页版点播种没反应。服务端 plant 动作完全正常 → 定位到前端。
- **根因 1(前端)**:播种按钮走 `window.prompt` 选作物,重置走 `window.confirm`
——这俩在 sandbox iframe(内置预览面板)里会被**静默屏蔽**:不弹窗、直接返回
null,于是"点了没反应"。普通浏览器里是好的,所以一直没暴露。
修复:改成页面内弹层(`#pickmask` + `openCropPick`,未锁定作物生成按钮 +
取消按钮,点遮罩关闭);重置改两步确认(第一次点变"再点一次确认",3 秒还原)。
- **根因 2(后端,真 bug)**:顺着验证存档时发现 `default` 牧场**根本没写进库**
——内存里每次重启都是新档。排查出 `session.dirty` 一个标志位伺二主:
Publisher 推送循环每 ~250ms 就把 dirty 清掉(无人在线时更是直接清),
而 `autosave()` 每 60s 才看一眼 dirty——窗口太小,**自动存档几乎永远不触发**
(约 0.4% 撞率)。进度只在手动存档/优雅停机时落盘,进程被强杀就全丢。
修复:新增 `session.unsaved` 专管「有变化没落盘」,`_on_change`/`load`/
`touch`/`advance_day` 置位、只有成功的 `save()` 清位;`autosave()` 改看
unsaved;dirty 继续只管推送节流。hub.py 的推送循环一字未动。
- 两只旧测试(`test_autosave_skips_when_clean` / `..._writes_when_dirty` /
`autosave_all...`)直接操纵 dirty 驱动 autosave,固化了被修掉的耦合——
全部改成用 unsaved 表达意图。新增回归测试:
「推送循环清了 dirty 也要照常落盘」「没有新变化不重复写库」。
- **真机验证**:0 客户端在线、纯 HTTP 种一块地,70 秒内看到库行更新
(gold 120→112,种子钱都对得上)。
- 门禁:ruff/pyright 全绿,515 项测试 OK(skip 35)。
### 教训
- 嵌入预览面板里 `window.prompt`/`confirm`/`alert` 都不可信,交互一律用页面内
弹层实现。
- 一个布尔标志被两个子系统共用时,其中一个高频清零,另一个低频读取——
低频的那个等于瞎了。存盘依据必须由"成功的写入"来清。
## 2026-09-23 用户名登录(多用户)
- 用户要求「添加多用户的 需要使用用户名登录」;AskUserQuestion 确认:
**仅用户名**(不设密码)+ **一账号一个牧场**(用户名即牧场名)。
- 新增 `farm/server/auth.py`:`LoginManager` 签发/校验 HMAC-SHA256 签名的
HttpOnly Cookie(`sf_auth`,30 天,SameSite=Lax)。Cookie 值 =
`base64("用户名|过期时间") + "." + hmac`,验签 `hmac.compare_digest`。
密钥 32 字节存 `<save_dir>/secret.key`(0600)——**重启不掉线**,无需建表。
- `app.py`:新增 `POST /api/login`(guard 照常检查全局 token)、
`POST /api/logout`、`GET /api/me`(刻意不 guard,首屏探测用);
`_Services` 加 `auth` 字段与 `user_of()`;**三个认 ?farm= 的地方全部改为
「有 Cookie 强制用自己的牧场」**:`session_of`(?farm=/X-Farm-Id 被忽略)、
SSE `/api/stream`、`POST /api/farms`。无 Cookie 的脚本/AI/CLI 保持旧语义
(互信小圈子,`?farm=` 不受影响),bot/TUI 零改动。
- `webui.py`:移除「牧场名」输入框和「进入牧场」按钮,改为登录遮罩
(`#loginmask` 复用 `.mask/.pick` 样式)+ 用户徽章 `#who` + 退出按钮;
JS 先 `whoami()` 再决定 `connect()` 还是弹登录;令牌输入框保留
(change 时重连)。farmName() = 登录用户。
- 测试 +13:`LoginCookieTestCase` 9 项纯逻辑(伪造/过期/跨密钥/垃圾串/非法名/
密钥复用/session_of 优先级——starlette Request 可直接用 scope dict 构造,
不需要 httpx)+ AuthTestCase 4 项 TestClient 档(照例 skip)。
门禁 528 项 OK(skip 39),ruff/pyright 全绿。
- 真机验收 16/16:登录/Cookie/HttpOnly/?farm= 被忽略/动作落自己牧场/
篡改退回未登录/退出/非法用户名 400/首页元素。跨重启:secret.key 复用,
Cookie 仍认 carol、显式 save 后作物也在。
- 教训:幂等补丁脚本里 `new == ""` 时 `if new in text` 恒真,会误报 ALREADY
跳过删除类替换(这次是删全局 Enter 监听那行,靠核对输出抓回来)。
删除类编辑别走补丁脚本,直接用 Edit 工具。