Files
lionwebsite-backend/docs/login-command-plan.md
T

138 lines
6.9 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.
# LionWebsite 一键登录(/login):实现说明
状态:**已全部完成并上线**。主站侧提交 `00e2715`(文档 `dfb647b`、`628f3ad`),
桌面端 `9b5bc94`;机器人侧由 `debian-qq-chatgpt` 于 host-vm103-debian-qq 完成
(见共享盘 `tasks/2026-09-21-lionwebsite-login-bot-command/`)。
关联待办:`td-20260921-a1b2c3`(已归档为
`todos/archive/done-td-20260921-a1b2c3-lionwebsite-login-tg.md`)。
用户已明确选择**接法 B:共享密钥自签票据**,理由是机器人到主站的网络调用不可靠。
因此主站**不提供**任何票据签发端点,也不需要机器人侧发起 HTTP 请求——机器人本地
用共享密钥算出票据即可。
## 1. 已经上线的东西(主站侧)
### 1.1 票据格式
```
v1.<签发时间的 Unix 秒级时间戳>.<HMAC-SHA256 十六进制小写>
```
签名原文为 `v1.<时间戳>`(固定前缀 `v1.` 加时间戳本身),密钥为共享密钥的 UTF-8 字节。
等价于 `hex(HMAC_SHA256(key, "v1." + timestamp))`。
### 1.2 校验规则
- 必须恰好三段,第一段必须是 `v1`;
- 签名比较用常量时间(`MessageDigest.isEqual`);
- 时间戳只接受 `[now - 300s, now]`,**未来时间戳一律拒绝**(防伪造者用远期时间换长期有效);
- 窗口内**允许重放**(用户明确接受),过期即失效;
- 默认 300 秒有效期,可用 `personal.login.ticket-ttl-seconds` 调整,下限 30 秒。
### 1.3 端点
| 端点 | 作用 |
| --- | --- |
| `GET /login?t=<票据>` | 校验票据 → 作废旧会话(防会话固定)→ 建新会话 → 302 跳 `/index`;失败 302 跳 `/denied` |
| `GET /login/logout` | 销毁会话 → 302 跳 `/denied` |
| `GET /denied` | 静态提示页:「请在机器人里发送 `/login`」 |
nginx 无需改动:`location /` 会把 `/login` 改写成后端 `/personal/login`。
### 1.4 会话与鉴权
- 登录成功下发 `JSESSIONID`,参数为 `Path=/`、`HttpOnly`、`SameSite=Lax`,14 天滑动过期。
**`Path=/` 是必须的**:nginx 会把 `/user` 改写成后端 `/personal/user`,若沿用容器按
请求路径推导的 `/personal`,浏览器判定 `/user` 不匹配就不会带会话,面板会一直 401。
- `PersonalInterceptor` 放行「有效会话 **或** `AuthCode=alone`」,两者都拒绝时返回 **401**。
`alone` 是留给下载器前端与存储节点 `/message2me` 推送的,本次**没有**退役它。
### 1.5 密钥
- 主站从环境变量 `PERSONAL_LOGIN_SECRET` 读取(`personal.login.secret`),
由 systemd drop-in `/etc/systemd/system/lionwebsite.service.d/login-secret.conf`
加载 `/etc/lionwebsite/login-secret.env`(`600` 权限,不在仓库里)。
- 密钥为空时**一律拒绝**票据,不会退化成放行。
- 轮换方式:改两侧配置并重启。轮换后旧票据在 300 秒内自然失效。
## 2. 机器人侧待实施(host-vm103-debian-qq)
### 2.1 生成票据
密钥必须与主站一致。**已由主人批准写入 PersonalHub 保险库**,引用为
`secret://services/service-lionwebsite/panel-login-secret`;机器人侧按既有秘密读取流程
申请取值,不要写进仓库或聊天记录。Python 侧计算方式:
```python
import hashlib, hmac, time
def lionwebsite_login_link(secret: str, base: str = "https://personal.lionwebsite.xyz") -> str:
stamp = str(int(time.time()))
signed = f"v1.{stamp}"
digest = hmac.new(secret.encode(), signed.encode(), hashlib.sha256).hexdigest()
return f"{base}/login?t=v1.{stamp}.{digest}"
```
Shell 等价写法(`openssl`)供人工验证用:
```sh
SIG=$(printf 'v1.%s' "$STAMP" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')
```
### 2.2 命令接入(已实施:复用既有 `/login`)
**复用既有 `/login`,不新建命令名;一条回复里同时给出两个登录链接。**
实现方式(vm103 实际做法,比原计划更稳):**机器人进程不持有密钥**。
签发走保险库固定动作 `CollabSecretService.use_secret`,值只交给进程内签名函数,
不返回、不落盘、不进日志;审计只记引用与动作名 `lionwebsite-login-link`。
QQ 侧 `plugins/login.py` 只经内部令牌 POST `/internal/panel-login`,
由 PersonalService 统一产出文案,因此 QQ 与 Telegram 不会漂移。
- Telegram:`telegram_panel_login()` 的返回文案改为同时给出 PersonalHub 面板链接与
LionWebsite 面板链接,`TELEGRAM_COMMANDS` 里 `login` 的描述同步更新。
- QQ(LionQQBot):既有 `/login`(别名 `/登录`)返回同样两条链接,共用同一套生成逻辑。
- PersonalHub 未完整配置(缺 password / session_secret / TOTP)时,现有逻辑会回一句
配置提示;此时**仍要发出 LionWebsite 链接**,不要让一条链接的失败带掉另一条。
- 帮助菜单(Telegram `/help` 与 QQ 菜单)中 `/login` 的描述同步更新。
- 文案不要写成「两条都只能用一次」:PersonalHub 链接是一次性的,
LionWebsite 票据是 300 秒窗口内可重放。
示例文案:
```
已生成登录链接:
· Personal Hub:[打开面板](< PersonalHub 链接 >)
· LionWebsite:[打开面板](< LionWebsite 链接 >)
两条链接 5 分钟内有效,请勿转发。
```
### 2.3 与 PersonalHub 现有 /login 的关系
PersonalHub 的 `/login` 原本只签发**它自己面板**的票据
(`OneTimePanelLoginTickets` + `/panel/telegram-login`)。改造时保留这条,
再在同一回复里附加 LionWebsite 链接;两者的票据机制与有效期互不影响。
## 3. 交付与复验
- 机器人侧提交状态:**两个机器人仓库的改动尚未提交 Git**(`v103` 侧待主人确认后
按项目约定提交,注意 PersonalHub 工作区另有前一轮未提交改动,勿混提)。
- `us9929main-chatgpt` 的独立复验(2026-09-21):
- 票据边界实测:`now` 与 `now-290s` 放行,`now-301s`、`now-400s`、`now+60s` 均落 `/denied`;
篡改签名与畸形串同样落 `/denied`。
- 密钥明文扫描:抓取两个任务目录全部正文与线程消息共 17 KiB,未出现密钥明文,
只有 4 处 `secret://` 引用。
- 内部接口鉴权:`POST /internal/panel-login` 无令牌与错令牌均返回 403。
- PersonalHub `/health` 返回 200;共享盘 waiting 指针已消费,队列已清空。
## 4. 可选收尾(未做,需要时另开任务)
- 移动端 `PrivateMainForMobile` 仍写死 `authCode: "alone"`。后端两种方式都接受,
所以它能用;要一并切到会话,照搬 `PrivateMain/src/store/index.js` 的改法即可。
- `sourcecode/storageNode` 有两处硬编码 `alone`(`CustomUtil.java` 的 `/message2me`
与 `MultiThreadedHTTPServer.java` 的本机鉴权),属另一仓库,需单独发布后才能退役字面量。
- 概览页的「本机订阅」链接 `https://personal.lionwebsite.xyz/sub/self` 目前是 404,
后端没有对应映射,属历史遗留,与本次登录改造无关。