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

4.4 KiB
Raw Blame History

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 工具。