Files
SimpleFarm/.workbuddy/memory/MEMORY.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

58 lines
4.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.
# SimpleFarm 项目长期约定
## 是什么与运行
终端农场游戏 + 多人服务器(HTTP + SSE 给 AI/脚本;TUI/网页纯客户端)。
uv 管理:所有命令带 `uv run `;依赖只改 pyproject(uv add/remove),uv.lock 要提交;
requirements.txt 是导出产物只读。门禁(改动后必跑):
`uv run ruff format . && uv run ruff check . && uv run pyright && uv run python -m unittest discover -s tests`
(现 528 项;TestClient/真机档 39 项因无 httpx 自动 skip;`ruff check .` 会扫 tests,
测试文件要自己保持 lint 干净)。性能复核 `uv run python tools/bench.py --repeat 5`。
## 玩法与存档
- 一天 40 分钟(DEFAULT_TICK_SECONDS=2400);**没有手动快进一天**的入口,别再加。
- 天气 5 种决定水分蒸发,`state.forecast` 明天提前可见;收获进 `inventory` 谷仓、
`sell` 才换钱;建筑每天在 advance_day 里**先于生长**执行。
- 存档 SQLite `saves/farm.db` 一牧场一行:标量列 + 整份 payload JSON(加字段改
`_raw_state()`,不改表结构);SAVE_VERSION=2 / MIN=1 向后兼容;老 JSON 启动时自动
迁移(原文件 *.json.migrated 留底)。`StorageError = sqlite3.Error`,别写 except OSError。
WAL 是持久属性,建库设一次即可。测试钩子:`advance_day(weather="sunny")` 钉死天气。
## 架构约定
- 分层:engine 不认识网络;`server/actions.py` 是动作唯一登记表;RemoteFarm 与
FarmEngine 同名同语义(加动作两边都补)。StateView.apply():带 plots=整体替换、
不带=合并字段。farm/keys.py 是按键名唯一来源(全小写),tui.py 不许手写字符串字面量。
- session 的 `dirty`(推送节流,250ms 清)与 `unsaved`(自动存档依据,只有 save() 清)
必须分开——合并会让 autosave 瞎掉。网页禁用 window.prompt/confirm(嵌入预览里被
静默屏蔽),交互一律页面内弹层。
- **用户名登录(2026-09-23)**:仅用户名、一账号一牧场(用户名即 farm_id)。
`auth.py` 签发 HMAC HttpOnly Cookie `sf_auth`(30 天),密钥 `<save_dir>/secret.key`
(重启不掉线)。app.py 三个认 ?farm= 的地方(session_of / SSE / POST /api/farms)
都是**有 Cookie 强制本人牧场、无 Cookie 保持 ?farm= 旧语义**(脚本/AI 兼容)。
动登录逻辑别破坏这条边界;/api/me 刻意不 guard。
- SSE `/api/stream` 是唯一实时通道(单向);写操作一律 POST /api/actions;停机必须
hub.close_all()。uvicorn+zttp HTTP/2:uvicorn 0.53 不内置 h2,必须 `http="zttp"`;
信号接管=子类覆盖 Server.handle_exit(不调 super());as_asgi() 是 pyright 类型桥。
- app.py 结构:create_app 只装配,路由全在模块级 `_register_*`;轻量路径只走
brief_source()(BRIEF_KEYS 白名单要同步);>60 行的函数应视为信号。
- 守护进程:路径只有文件名时用 ensure_parent_dir()(别手写 makedirs(dirname));
pid 由服务进程自己写;端口自检用 port_busy()(判断「能连上」而非「能 bind」)。
## 本机环境(coreutils 残缺禁管道 / LF / 句柄等详见用户级记忆 ~/.workbuddy/MEMORY.md)
- `RemoteFarm.snapshot()` 是本地缓存不发请求;realtime=False 的脚本必须自己 refresh(),
否则轮询读到旧数据像卡死。
- 本机 HTTP 一律走 farm/net.py 的 open_url()(环境有 HTTP_PROXY 没 no_proxy)。
- 幂等补丁脚本注意:`new == ""` 时 `if new in text` 恒真会误报 ALREADY 漏改;
删除类编辑直接用 Edit 工具。
- 批量改同一文件用幂等补丁脚本(read_bytes/write_bytes + APPLIED/ALREADY/MISS);
同一文件一条消息里多次 Edit 会被静默覆盖。
## 远端与推送
- https://gitea.4i.hk/hhhge/SimpleFarm(public,main)。推送:
`GCM_INTERACTIVE=always git push origin main`(缺它 GCM 拒交互,报
"Failed to authenticate");还不行加 GCM_TRACE=1。沙箱会丢
.git/refs/remotes 写入使 status 显示 [gone]——假象,验证推送看 `git ls-remote`。
## 文档
README.md 是入口文档(接口表、SSE 协议、设计取舍);当天记录在
.workbuddy/memory/YYYY-MM-DD.md。