# M3 — Token 鉴权与移动端(远期试水) > 阅读前提:先读 [`README.md`](./README.md)。M3 依赖 M2(已有 `/api` JSON 契约与 session 鉴权)。 > > **定位**:远期、低投入、探索性。React Native 部分主要是"没做过、试试水"。范围**可能收缩**——其中**token 鉴权 + ingestion 端点收口**是有持久价值的安全改进,应优先;RN app 是加分项。Orchestrator 可只取前半。 ## 1. 目标 1. 引入 **bearer token 鉴权**,让非浏览器客户端(移动端、设备脚本)能安全访问。 2. 把 M2 暂时维持裸奔的 **ingestion 端点**(`POST /location/record`、`POST /poo/record`)收口到 token 鉴权下。 3. 做一个 **React Native** 移动端,用类 OAuth 流程拿 token 后消费现有 `/api`。 ## 2. 现状(M2 完成后) - `/api/*` 走 session cookie + `X-CSRF-Token`。 - `app/services/auth.py` 有 server-side session(`auth_sessions` 表,token_hash 存储)。 - `POST /location/record`、`POST /poo/record` 仍**无鉴权**(设备/脚本裸调用)。 ## 3. 目标架构 ### 3.1 Token 模型 - 新表 `auth_tokens`:`id`、`user_id`、`token_hash`(仅存哈希)、`label`(设备名)、`created_at`、`expires_at`(可空=长期)、`revoked_at`。 - bearer 校验:`Authorization: Bearer ` → 哈希比对 `auth_tokens` → 命中且未撤销未过期则认定身份。 ### 3.2 类 OAuth 签发流程(无第三方的 Authorization Code 简化版) 1. 移动端在**内置浏览器**打开 `/authorize?...`。 2. 用户账号密码登录(走现有 session),页面展示"授权此设备"。 3. 批准后服务端生成**一次性 authorization code**,重定向到 app 深链 `homeautomation://callback?code=...`。 4. app 用 code 调 `POST /api/auth/token` 换取 bearer token 并存本地。 > 简化兜底:批准页直接展示一次性 token 由 app 捕获。优先实现重定向 + code 交换的正规版。 ### 3.3 统一鉴权依赖 - `/api` 的数据/CRUD 端点接受**session cookie 或 bearer**两者之一(同一套端点同时服务 Web 与移动端)。 - ingestion 端点(location/poo record)改为**要求 bearer**。 ### 3.4 React Native - **Expo + TypeScript**,复用 M2 的 OpenAPI 类型化 client(共享契约)。 - 内置浏览器走 §3.2 流程拿 token;之后所有请求带 `Authorization: Bearer`。 ## 4. 迁移注意(重要) - ingestion 端点一旦要求 bearer,**现有调用方(HA/设备脚本)必须先配置 token**,否则记录会中断。 - 上线顺序:先签发 token 能力(T01–T02)→ 给现有设备配 token → 再对 ingestion 端点强制 bearer(T03),避免断流。可设一个过渡开关或灰度。 ## 5. 任务依赖图 ``` M3-T01 token 模型 + 迁移 └─► M3-T02 签发流程(authorize + code 交换) └─► M3-T03 统一鉴权依赖 + ingestion 端点收口(含过渡开关) ├─► M3-T04 Web 端 token 管理 UI(列出/撤销设备) └─► M3-T05 React Native app(试水) └─► M3-T06 文档收尾 ``` ## 6. 原子任务(任务卡) ### M3-T01 — token 数据模型 + Alembic 迁移 - **Status**: `todo` · **Depends**: none(M2 完成后) - **Files**: `create app/models/token.py`、`alembic_app/versions/_auth_tokens.py`;`modify app/models/__init__.py`;`create tests/test_token_model.py` - **Acceptance**: - [ ] 迁移在全新库 upgrade 后建出 `auth_tokens` 表;downgrade 可回滚。 - [ ] token 仅以哈希存储(与 `auth_sessions` 同等强度),明文不入库。 - [ ] 校验闸门全绿。 - **Reviewer**: 哈希算法/长度与现有 session token 一致;`expires_at` 可空语义明确。 ### M3-T02 — 签发流程:authorize 页 + code 交换端点 - **Status**: `todo` · **Depends**: M3-T01 - **Files**: `create app/api/routes/api/token.py`、`app/schemas/token.py`;前端 `/authorize` 页(M2 SPA 内);`create tests/test_api_token.py` - **Acceptance**: - [ ] 登录用户在 `/authorize` 批准后得到一次性 code;`POST /api/auth/token` 用 code 换取 bearer,code 一次性且短时效。 - [ ] 未登录访问 `/authorize` 跳登录;无效/过期 code 换取失败。 - [ ] 返回的 bearer 仅此一次明文出现,库中只存哈希。 - [ ] 校验闸门全绿。 - **Reviewer**: code 一次性、绑定用户、短 TTL;深链 redirect 白名单校验,防开放重定向。 ### M3-T03 — 统一鉴权依赖 + ingestion 端点收口 - **Status**: `todo` · **Depends**: M3-T02 - **Files**: `modify app/dependencies.py`(新增"cookie 或 bearer"统一身份依赖);`modify app/api/routes/location.py`、`poo.py`(要求 bearer,带过渡开关);`modify tests` - **Acceptance**: - [ ] `/api` 数据/CRUD 端点用合法 bearer 可访问(等价于 session)。 - [ ] ingestion 端点:带合法 bearer 通过,缺/错 token 在强制模式下 401;过渡开关可临时放行(默认关)。 - [ ] 撤销的 token 立即失效。 - [ ] 校验闸门全绿。 - **Reviewer**: 过渡开关默认安全(强制);bearer 与 session 两路鉴权不产生绕过;ingestion 行为变更有测试覆盖。 ### M3-T04 — Web 端 token 管理 UI - **Status**: `todo` · **Depends**: M3-T03 - **Acceptance**: 在 SPA 内可列出已签发设备 token(label/创建时间/最近使用)、可撤销;撤销后该 token 立即失效;前端闸门全绿。 ### M3-T05 — React Native app(试水) - **Status**: `todo` · **Depends**: M3-T03 · `[experimental]` - **Files**: `create mobile/`(Expo + TS,复用 OpenAPI 类型化 client) - **Acceptance**: - [ ] 内置浏览器走签发流程拿到 token 并安全存储(Keychain/Keystore)。 - [ ] 至少跑通:登录拿 token → 拉取一类数据展示 → 记一条 ingestion。 - [ ] `npm run lint`/`typecheck`/`build`(或 Expo 等价) 全绿。 - **Reviewer**: token 存安全存储而非明文;client 基于共享 OpenAPI 类型。 ### M3-T06 — 文档收尾 - **Status**: `todo` · **Depends**: M3-T05 - **Acceptance**: README/architecture 增 token 鉴权与移动端说明;roadmap 勾选 M3;`openapi/` 同步。 ## 7. 里程碑完成定义(DoD) - 存在 bearer token 鉴权与签发流程;token 仅哈希存储、可撤销。 - ingestion 端点已收口到 bearer(过渡完成后强制)。 - `/api` 同时支持 session 与 bearer。 - (加分)React Native app 能拿 token 并消费 `/api`。 - 后端 + 前端 + 移动端各自校验闸门全绿,`openapi/` 入库。 > 提醒:本里程碑探索性强,T05 可作为独立试水随时叫停,不影响 T01–T04 带来的安全收口价值。