star-chart-search-enhancer/docs/superpowers/specs/2026-04-22-logto-protected-api-mock-design.md

217 lines
6.5 KiB
Markdown
Raw Permalink 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.

# Logto 受保护 API Mock 联调设计
## 背景
当前扩展已经具备基础的 Logto 登录能力:
- popup 可触发登录和登出
- background 可通过 `@logto/chrome-extension` 获取 access token
- 页面功能在未登录时会被 auth gate 拦住
但现阶段业务数据仍然来自页面站内接口,尚未真正验证“扩展拿到 Logto access token 后,请求受保护 API”这条链路。后续真实后端将由其他人提供因此当前阶段的目标不是接入正式接口而是先在本地完成一套可重复验证的模拟联调方案。
## 目标
- 新增一个专用于 Logto 受保护 API 的扩展客户端。
- 扩展请求受保护 API 前,必须先通过 background 获取 access token。
- 请求时自动附加 `Authorization: Bearer <token>` 请求头。
- 提供一个本地 mock 受保护 API用于验证扩展到后端的完整调用链路。
- 提供自动化测试,分别覆盖:
- token 注入逻辑
- mock API 授权成功/失败行为
## 非目标
- 不接入真实业务后端。
- 不做真实 JWT 签名校验、JWKS 拉取或资源权限判定。
- 不修改现有星图页面数据采集逻辑为正式后端模式。
- 不在本阶段设计复杂的 token 刷新监控或重试策略。
## 方案对比
### 方案 A只做本地 mock 后端
- 直接搭一个假接口,扩展请求它并观察返回。
- 优点:最接近最终使用方式。
- 缺点:如果联调失败,不容易快速判断问题出在 token 注入还是 mock 服务本身。
### 方案 B只做代码级测试
- 不起本地服务,只用测试替身验证请求头是否带 token。
- 优点:实现快,定位问题直接。
- 缺点:无法证明完整链路可运行。
### 方案 C先代码级测试再接本地 mock 后端
- 先用单元测试锁定 token 注入行为,再起 mock 服务完成真实联调。
- 优点:定位问题更快,同时保留完整链路验证。
- 缺点:改动稍多于单一方案。
推荐采用方案 C。
## 架构设计
### 1. 扩展受保护 API 客户端
新增一个独立客户端模块,职责如下:
- 向 background 发送 `auth:get-access-token` 消息
- 读取返回的 access token
- 使用该 token 请求指定后端地址
- 将接口成功、未授权、网络失败这三类结果转成明确错误
这个客户端不直接耦合星图 DOM也不依赖 popup。它只负责“拿 token 并带 token 发请求”,这样后续替换正式后端时只需要调整接口地址和返回映射。
### 2. Background 认证桥接
现有 background 已支持 `auth:get-access-token`。本次不改变登录主流程,只把它当作唯一 token 来源:
- content script 不直接接触 Logto SDK
- 所有受保护 API 请求都通过 background 提供 token
这样可以保持认证逻辑集中,符合 MV3 扩展的边界约束。
### 3. 本地 mock 受保护 API
新增一个轻量本地服务作为测试后端,建议职责保持极小:
- 暴露固定测试 endpoint例如 `/api/mock/protected`
- 检查请求头中是否存在 `Authorization`
- 如果请求头形如 `Bearer <非空字符串>`,返回固定假数据
- 如果没有该头,返回 `401`
本阶段不要求验证 token 是否来自真实 Logto只验证“扩展是否按约定附带了 Bearer token”。
## 数据流
完整调用链路如下:
1. 扩展中的业务入口调用受保护 API 客户端
2. 客户端向 background 发送 `auth:get-access-token`
3. background 返回当前 access token
4. 客户端带上 `Authorization: Bearer <token>` 请求本地 mock API
5. mock API 校验请求头并返回假数据
6. 客户端将结果回传给调用方
失败分支:
- 如果 background 没返回合法 token客户端直接报错不发请求
- 如果 mock API 返回 `401`,客户端将其识别为未授权错误
- 如果请求超时或网络失败,客户端抛出网络错误
## 接口设计
### 扩展内客户端接口
建议客户端提供单一入口,例如:
- `loadProtectedMockData()`
内部行为:
- 先取 token
- 再发 GET 请求
- 返回结构化响应对象或抛出明确错误
后续替换真实后端时,可以保留同样入口,内部再切换到真实 endpoint。
### Mock API 返回
成功时返回固定 JSON例如
```json
{
"ok": true,
"source": "mock-protected-api",
"message": "authorized",
"receivedAuthHeader": "Bearer ..."
}
```
失败时返回:
```json
{
"ok": false,
"error": "unauthorized"
}
```
## 错误处理
### 无 token
- 判定条件background 未返回 `auth:token`,或 token 为空字符串
- 处理方式:立即抛错,提示当前未登录或 token 不可用
### 未授权
- 判定条件:后端返回 `401``403`
- 处理方式:抛出授权失败错误,供上层展示“请重新登录”
### 网络失败
- 判定条件fetch 抛异常、连接失败、超时
- 处理方式:抛出网络错误,不吞掉异常原因
## 实现边界
建议新增或调整如下模块:
### `src/content/market` 下新增受保护 API 客户端
- 不替换现有页面接口客户端
- 独立处理 mock 受保护 API 访问
- 便于后续把“页面抓取模式”和“后端接口模式”并行保留
### `src/shared/auth-messages.ts`
- 复用现有 `auth:get-access-token`
- 若现有消息结构足够,则不新增消息类型
### `scripts/` 或独立目录中的 mock 服务
- 提供本地测试服务启动脚本
- 默认监听本地固定端口
- 返回固定 JSON 结果
## 测试策略
### 单元测试
- 客户端请求前会先获取 access token
- 拿到 token 后,请求头中包含 `Authorization: Bearer <token>`
- token 缺失时不会发起 fetch
- 接口返回 `401` 时抛出未授权错误
- 接口返回成功时正确解析 JSON
### 联调测试
- 启动本地 mock 服务后,带 token 请求能成功
- 不带 token 请求返回 `401`
- 扩展客户端能读到 mock 返回的假数据
## 手动验证
1. 构建并加载扩展
2. 在 popup 中完成 Logto 登录
3. 启动本地 mock API
4. 触发扩展中的受保护接口请求
5. 确认 mock API 收到 `Authorization: Bearer <token>`
6. 确认扩展端收到成功响应
## 迁移到真实后端的路径
当真实后端可用时,仅需要替换以下内容:
- mock API 基地址
- 具体 endpoint 路径
- 返回数据结构映射
- 若真实后端要求额外 scope则补充 `auth-config` 中的 scopes
核心认证链路保持不变:
- 仍由 background 提供 token
- 仍由独立客户端附带 `Bearer` 请求头
- 仍按未授权和网络错误分别处理