第一次提交
This commit is contained in:
602
auth-portal/src/docs/api.md
Normal file
602
auth-portal/src/docs/api.md
Normal file
@ -0,0 +1,602 @@
|
||||
# Nanobot Auth Portal 接口文档
|
||||
|
||||
## 1. 文档范围
|
||||
|
||||
本文档覆盖 `nanobot-auth-portal` 当前实际依赖和对接的接口:
|
||||
|
||||
- Auth Portal 前端页面路由:`/`、`/login`、`/register`
|
||||
- 后端认证接口:`/api/auth/*`
|
||||
- 登录完成后的浏览器交接接口:目标业务前端 `/handoff`
|
||||
|
||||
说明:
|
||||
|
||||
- Auth Portal 自身是一个独立前端,不在本仓库内实现后端 API。
|
||||
- 文档中的后端接口来自当前联调使用的 `nanobot-backend` 实现。
|
||||
- 当前项目只直接调用 `POST /api/auth/login` 和 `POST /api/auth/register`,但为了便于联调,本文档一并补全了同一条登录链路上的 `handoff`、`me`、`logout` 接口。
|
||||
|
||||
## 2. 服务地址与环境变量
|
||||
|
||||
### 2.1 Auth Portal 页面地址
|
||||
|
||||
默认运行在当前主机 `3081` 端口。
|
||||
|
||||
### 2.2 后端 API Base URL
|
||||
|
||||
Auth Portal 按以下优先级计算后端地址:
|
||||
|
||||
1. `NEXT_PUBLIC_BACKEND_API_URL`
|
||||
2. 浏览器当前域名 + `NEXT_PUBLIC_BACKEND_API_PORT`,默认 `10000`
|
||||
3. SSR 回退地址 `http://127.0.0.1:10000`
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
NEXT_PUBLIC_BACKEND_API_URL=https://nanobot-api.bwgdi.com
|
||||
NEXT_PUBLIC_BACKEND_API_PORT=10000
|
||||
NEXT_PUBLIC_FRONTEND_PORT=3080
|
||||
```
|
||||
|
||||
### 2.3 目标业务前端 Base URL
|
||||
|
||||
登录或注册成功后,Auth Portal 会从响应里解析目标前端地址,优先级如下:
|
||||
|
||||
1. `backend_connection.frontend_base_url`
|
||||
2. `backend_connection.api_base_url`
|
||||
3. `backend_connection.public_base_url`
|
||||
4. `local_backend.public_base_url`
|
||||
|
||||
如果命中的是后 2 到 4 项,Auth Portal 会将端口替换为 `NEXT_PUBLIC_FRONTEND_PORT`,默认 `3080`。
|
||||
|
||||
## 3. 公共约定
|
||||
|
||||
### 3.1 请求格式
|
||||
|
||||
- 请求体格式:`application/json`
|
||||
- 成功响应:JSON
|
||||
- Auth Portal 前端请求超时:`8000ms`
|
||||
|
||||
### 3.2 鉴权方式
|
||||
|
||||
- `POST /api/auth/login`:无需鉴权
|
||||
- `POST /api/auth/register`:无需鉴权
|
||||
- `POST /api/auth/handoff/consume`:无需鉴权
|
||||
- `GET /api/auth/me`:需要 `Authorization: Bearer <access_token>`
|
||||
- `POST /api/auth/logout`:可带 `Authorization: Bearer <access_token>`
|
||||
|
||||
### 3.3 错误响应格式
|
||||
|
||||
后端主要返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "错误信息"
|
||||
}
|
||||
```
|
||||
|
||||
Auth Portal 前端收到非 `2xx` 时,会转成如下错误文案:
|
||||
|
||||
```text
|
||||
接口错误 <status>: <detail>
|
||||
```
|
||||
|
||||
### 3.4 Token 与 handoff 约定
|
||||
|
||||
- `access_token` 是后端进程内维护的 Web 会话 token。
|
||||
- `refresh_token` 当前实现始终返回空字符串 `""`。
|
||||
- 当前认证链路没有独立的 `/api/auth/refresh`。
|
||||
- `handoff_code` 是短时有效的浏览器交接码。
|
||||
- `handoff_code` 默认 TTL 为 `90` 秒。
|
||||
- `handoff_code` 被消费后默认允许 `15` 秒内的短暂重放窗口,用于前端页面刷新或重试。
|
||||
|
||||
## 4. 数据模型
|
||||
|
||||
### 4.1 BackendConnectionInfo
|
||||
|
||||
```json
|
||||
{
|
||||
"backend_id": "backend-001",
|
||||
"client_id": "client-001",
|
||||
"name": "Boardware Genius",
|
||||
"public_base_url": "https://nanobot-api.example.com",
|
||||
"api_base_url": "https://nanobot-api.example.com",
|
||||
"ws_base_url": "wss://nanobot-api.example.com",
|
||||
"frontend_base_url": "https://nanobot.example.com",
|
||||
"registered": true
|
||||
}
|
||||
```
|
||||
|
||||
字段说明:
|
||||
|
||||
- `backend_id`:backend 唯一标识
|
||||
- `client_id`:backend 在 AuthZ 中的客户端 ID
|
||||
- `name`:backend 名称
|
||||
- `public_base_url`:公开后端地址
|
||||
- `api_base_url`:业务 API 地址
|
||||
- `ws_base_url`:WebSocket 地址
|
||||
- `frontend_base_url`:目标业务前端入口地址
|
||||
- `registered`:当前 backend 是否已完成本地身份注册
|
||||
|
||||
### 4.2 AuthzLocalBackendStatus
|
||||
|
||||
```json
|
||||
{
|
||||
"backend_id": "backend-001",
|
||||
"client_id": "client-001",
|
||||
"name": "Boardware Genius",
|
||||
"public_base_url": "https://nanobot-api.example.com",
|
||||
"authz": {
|
||||
"enabled": true,
|
||||
"base_url": "https://authz.example.com"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
字段说明:
|
||||
|
||||
- `backend_id`:本地 backend identity 中记录的 backend ID
|
||||
- `client_id`:本地 backend identity 中记录的 client ID
|
||||
- `name`:本地 backend 名称
|
||||
- `public_base_url`:本地 backend 对外地址
|
||||
- `authz.enabled`:是否启用 AuthZ
|
||||
- `authz.base_url`:当前 backend 使用的 AuthZ 服务地址
|
||||
|
||||
### 4.3 RegisterAuthzStatus
|
||||
|
||||
仅 `POST /api/auth/register` 响应会返回。
|
||||
|
||||
```json
|
||||
{
|
||||
"enabled": true,
|
||||
"base_url": "https://authz.example.com",
|
||||
"user_registered": true,
|
||||
"backend_registered": true
|
||||
}
|
||||
```
|
||||
|
||||
字段说明:
|
||||
|
||||
- `enabled`:本次注册是否启用了 AuthZ
|
||||
- `base_url`:本次注册使用的 AuthZ 服务地址
|
||||
- `user_registered`:用户是否已在 AuthZ 完成注册或确认存在
|
||||
- `backend_registered`:backend 身份是否已在 AuthZ 完成注册
|
||||
|
||||
### 4.4 TokenResponse
|
||||
|
||||
登录、注册、handoff 消费都会返回 token 响应,但字段不完全相同。
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": "opaque-token",
|
||||
"refresh_token": "",
|
||||
"token_type": "bearer",
|
||||
"user_id": "bwgdi",
|
||||
"username": "bwgdi",
|
||||
"email": "steven@example.com",
|
||||
"role": "owner",
|
||||
"handoff_code": "short-lived-code",
|
||||
"handoff_expires_at": 1760000000,
|
||||
"existing_user": false,
|
||||
"authz": {
|
||||
"enabled": true,
|
||||
"base_url": "https://authz.example.com",
|
||||
"user_registered": true,
|
||||
"backend_registered": true
|
||||
},
|
||||
"backend_connection": {
|
||||
"backend_id": "backend-001",
|
||||
"client_id": "client-001",
|
||||
"name": "Boardware Genius",
|
||||
"public_base_url": "https://nanobot-api.example.com",
|
||||
"api_base_url": "https://nanobot-api.example.com",
|
||||
"ws_base_url": "wss://nanobot-api.example.com",
|
||||
"frontend_base_url": "https://nanobot.example.com",
|
||||
"registered": true
|
||||
},
|
||||
"local_backend": {
|
||||
"backend_id": "backend-001",
|
||||
"client_id": "client-001",
|
||||
"name": "Boardware Genius",
|
||||
"public_base_url": "https://nanobot-api.example.com",
|
||||
"authz": {
|
||||
"enabled": true,
|
||||
"base_url": "https://authz.example.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
字段说明:
|
||||
|
||||
- `access_token`:后端签发的登录 token
|
||||
- `refresh_token`:当前固定为空字符串
|
||||
- `token_type`:当前固定为 `bearer`
|
||||
- `user_id`:当前等于用户名
|
||||
- `username`:用户名
|
||||
- `email`:仅注册响应会返回,登录响应通常没有
|
||||
- `role`:当前固定为 `owner`
|
||||
- `handoff_code`:仅登录/注册响应返回,用于跳转到目标前端
|
||||
- `handoff_expires_at`:UNIX 时间戳,单位秒
|
||||
- `existing_user`:仅注册响应返回,表示这次注册是否命中了已有用户
|
||||
- `authz`:仅注册响应返回,表示本次 AuthZ 注册结果
|
||||
- `backend_connection`:当前登录用户对应的目标 backend 路由信息
|
||||
- `local_backend`:本地 backend identity 视图
|
||||
|
||||
### 4.5 AuthUser
|
||||
|
||||
`GET /api/auth/me` 返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "bwgdi",
|
||||
"username": "bwgdi",
|
||||
"email": "",
|
||||
"role": "owner",
|
||||
"quota_tier": "single-user"
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 前端页面路由
|
||||
|
||||
### 5.1 GET /
|
||||
|
||||
用途:Portal 首页。
|
||||
|
||||
行为:立即重定向到 `/login`。
|
||||
|
||||
### 5.2 GET /login
|
||||
|
||||
用途:显示登录页面。
|
||||
|
||||
Query 参数:
|
||||
|
||||
- `next`:登录成功后希望进入的目标业务前端路径,默认 `/`
|
||||
|
||||
示例:
|
||||
|
||||
```text
|
||||
/login?next=/mcp
|
||||
```
|
||||
|
||||
成功后浏览器会跳转到:
|
||||
|
||||
```text
|
||||
<frontend_base_url>/handoff?code=<handoff_code>&next=/mcp
|
||||
```
|
||||
|
||||
### 5.3 GET /register
|
||||
|
||||
用途:显示注册页面。
|
||||
|
||||
Query 参数:
|
||||
|
||||
- `next`:注册成功后希望进入的目标业务前端路径,默认 `/mcp`
|
||||
|
||||
示例:
|
||||
|
||||
```text
|
||||
/register?next=/mcp
|
||||
```
|
||||
|
||||
## 6. 后端认证接口
|
||||
|
||||
### 6.1 POST /api/auth/login
|
||||
|
||||
用途:用户名密码登录,并返回目标 backend 路由与 handoff 信息。
|
||||
|
||||
鉴权:否。
|
||||
|
||||
请求体:
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "bwgdi",
|
||||
"password": "123456"
|
||||
}
|
||||
```
|
||||
|
||||
请求字段:
|
||||
|
||||
- `username`:必填,登录用户名
|
||||
- `password`:必填,登录密码
|
||||
|
||||
成功响应:`200 OK`
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": "opaque-token",
|
||||
"refresh_token": "",
|
||||
"token_type": "bearer",
|
||||
"user_id": "bwgdi",
|
||||
"username": "bwgdi",
|
||||
"role": "owner",
|
||||
"handoff_code": "short-lived-code",
|
||||
"handoff_expires_at": 1760000000,
|
||||
"backend_connection": {
|
||||
"backend_id": "backend-001",
|
||||
"client_id": "client-001",
|
||||
"name": "Boardware Genius",
|
||||
"public_base_url": "https://nanobot-api.example.com",
|
||||
"api_base_url": "https://nanobot-api.example.com",
|
||||
"ws_base_url": "wss://nanobot-api.example.com",
|
||||
"frontend_base_url": "https://nanobot.example.com",
|
||||
"registered": true
|
||||
},
|
||||
"local_backend": {
|
||||
"backend_id": "backend-001",
|
||||
"client_id": "client-001",
|
||||
"name": "Boardware Genius",
|
||||
"public_base_url": "https://nanobot-api.example.com",
|
||||
"authz": {
|
||||
"enabled": true,
|
||||
"base_url": "https://authz.example.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
错误码:
|
||||
|
||||
- `400 Bad Request`:`username` 为空
|
||||
- `401 Unauthorized`:用户名或密码错误
|
||||
- `500 Internal Server Error`:本地用户文件不存在或格式非法
|
||||
|
||||
实现备注:
|
||||
|
||||
- 登录成功后,Portal 必须使用 `handoff_code` 跳到目标业务前端 `/handoff`。
|
||||
- 如果后端没有返回 `frontend_base_url` 或 `handoff_code`,Portal 会直接提示错误,不会继续跳转。
|
||||
|
||||
### 6.2 POST /api/auth/register
|
||||
|
||||
用途:创建本地登录账号,并在需要时完成 AuthZ 用户/后端注册,然后返回 handoff 信息。
|
||||
|
||||
鉴权:否。
|
||||
|
||||
当前 Portal 页面实际发送字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "bwgdi",
|
||||
"email": "steven@example.com",
|
||||
"password": "123456"
|
||||
}
|
||||
```
|
||||
|
||||
后端支持的完整请求体:
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "bwgdi",
|
||||
"email": "steven@example.com",
|
||||
"password": "123456",
|
||||
"authz_base_url": "https://authz.example.com",
|
||||
"backend_name": "Boardware Genius",
|
||||
"backend_id": "backend-001",
|
||||
"base_url": "https://nanobot-api.example.com",
|
||||
"frontend_base_url": "https://nanobot.example.com"
|
||||
}
|
||||
```
|
||||
|
||||
请求字段:
|
||||
|
||||
- `username`:必填,用户名
|
||||
- `email`:选填,邮箱
|
||||
- `password`:必填,密码
|
||||
- `authz_base_url`:选填,覆盖后端当前配置的 AuthZ 地址
|
||||
- `backend_name`:选填,注册 backend 时使用的名称
|
||||
- `backend_id`:选填,期望写入或复用的 backend ID
|
||||
- `base_url`:选填,注册 backend 时使用的公开 API 地址
|
||||
- `frontend_base_url`:选填,注册 backend 时使用的公开前端地址
|
||||
|
||||
成功响应:当前实现为 `200 OK`
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": "opaque-token",
|
||||
"refresh_token": "",
|
||||
"token_type": "bearer",
|
||||
"user_id": "bwgdi",
|
||||
"username": "bwgdi",
|
||||
"email": "steven@example.com",
|
||||
"role": "owner",
|
||||
"handoff_code": "short-lived-code",
|
||||
"handoff_expires_at": 1760000000,
|
||||
"existing_user": false,
|
||||
"authz": {
|
||||
"enabled": true,
|
||||
"base_url": "https://authz.example.com",
|
||||
"user_registered": true,
|
||||
"backend_registered": true
|
||||
},
|
||||
"backend_connection": {
|
||||
"backend_id": "backend-001",
|
||||
"client_id": "client-001",
|
||||
"name": "Boardware Genius",
|
||||
"public_base_url": "https://nanobot-api.example.com",
|
||||
"api_base_url": "https://nanobot-api.example.com",
|
||||
"ws_base_url": "wss://nanobot-api.example.com",
|
||||
"frontend_base_url": "https://nanobot.example.com",
|
||||
"registered": true
|
||||
},
|
||||
"local_backend": {
|
||||
"backend_id": "backend-001",
|
||||
"client_id": "client-001",
|
||||
"name": "Boardware Genius",
|
||||
"public_base_url": "https://nanobot-api.example.com",
|
||||
"authz": {
|
||||
"enabled": true,
|
||||
"base_url": "https://authz.example.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
错误码:
|
||||
|
||||
- `400 Bad Request`:`username` 或 `password` 缺失
|
||||
- `409 Conflict`:用户名已存在,且提交的密码与已有密码不一致
|
||||
- `4xx/5xx`:AuthZ 上游服务直接返回的错误
|
||||
- `502 Bad Gateway`:调用 AuthZ 服务失败
|
||||
|
||||
实现备注:
|
||||
|
||||
- 如果用户名已存在但密码相同,后端会把本次请求视为“继续完成配置”,不会直接报错。
|
||||
- 如果本地 `web_auth_users.json` 不存在,注册会自动创建。
|
||||
- 注册成功后同样必须依赖 `handoff_code` 进入目标业务前端。
|
||||
|
||||
### 6.3 POST /api/auth/handoff/consume
|
||||
|
||||
用途:由目标业务前端消费 `handoff_code`,换取可直接使用的 `access_token`。
|
||||
|
||||
鉴权:否。
|
||||
|
||||
请求体:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": "short-lived-code"
|
||||
}
|
||||
```
|
||||
|
||||
成功响应:`200 OK`
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": "opaque-token",
|
||||
"refresh_token": "",
|
||||
"token_type": "bearer",
|
||||
"user_id": "bwgdi",
|
||||
"username": "bwgdi",
|
||||
"role": "owner"
|
||||
}
|
||||
```
|
||||
|
||||
错误码:
|
||||
|
||||
- `400 Bad Request`:`code` 为空
|
||||
- `401 Unauthorized`:`code` 无效、已失效,或 payload 非法
|
||||
- `410 Gone`:`code` 已过期,或已超过重放窗口
|
||||
|
||||
实现备注:
|
||||
|
||||
- 当前目标前端在 `/handoff` 页面里调用该接口。
|
||||
- 目标前端拿到 token 后会继续调用 `GET /api/auth/me` 校验登录态。
|
||||
|
||||
### 6.4 GET /api/auth/me
|
||||
|
||||
用途:获取当前登录用户信息。
|
||||
|
||||
鉴权:是,必须提供 `Authorization: Bearer <access_token>`。
|
||||
|
||||
请求头:
|
||||
|
||||
```http
|
||||
Authorization: Bearer opaque-token
|
||||
```
|
||||
|
||||
成功响应:`200 OK`
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "bwgdi",
|
||||
"username": "bwgdi",
|
||||
"email": "",
|
||||
"role": "owner",
|
||||
"quota_tier": "single-user"
|
||||
}
|
||||
```
|
||||
|
||||
错误码:
|
||||
|
||||
- `401 Unauthorized`:缺少 `Authorization` 头
|
||||
- `401 Unauthorized`:`Authorization` 头格式错误
|
||||
- `401 Unauthorized`:token 为空
|
||||
- `401 Unauthorized`:token 无效或已失效
|
||||
|
||||
### 6.5 POST /api/auth/logout
|
||||
|
||||
用途:注销当前 token。
|
||||
|
||||
鉴权:可选。带 token 时会尝试从后端内存中删除该 token。
|
||||
|
||||
请求头:
|
||||
|
||||
```http
|
||||
Authorization: Bearer opaque-token
|
||||
```
|
||||
|
||||
成功响应:`200 OK`
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true
|
||||
}
|
||||
```
|
||||
|
||||
实现备注:
|
||||
|
||||
- 即使没有传 token,接口也会返回 `{"ok": true}`。
|
||||
- Portal 或业务前端通常还会同步清理浏览器本地 token。
|
||||
|
||||
## 7. 登录与跳转链路
|
||||
|
||||
### 7.1 登录链路
|
||||
|
||||
1. 用户访问目标业务前端的受保护页面,例如 `/mcp`
|
||||
2. 业务前端发现未登录,跳转到 Auth Portal:`/login?next=/mcp`
|
||||
3. Auth Portal 提交 `POST /api/auth/login`
|
||||
4. 后端返回 `handoff_code` 与 `backend_connection.frontend_base_url`
|
||||
5. Auth Portal 跳转到目标业务前端:
|
||||
|
||||
```text
|
||||
https://target-frontend.example.com/handoff?code=<handoff_code>&next=/mcp
|
||||
```
|
||||
|
||||
6. 目标业务前端在 `/handoff` 页面调用 `POST /api/auth/handoff/consume`
|
||||
7. 目标业务前端保存 token 后调用 `GET /api/auth/me`
|
||||
8. 用户最终进入 `/mcp`
|
||||
|
||||
### 7.2 注册链路
|
||||
|
||||
1. 用户访问 Auth Portal:`/register?next=/mcp`
|
||||
2. Auth Portal 提交 `POST /api/auth/register`
|
||||
3. 后端按需创建本地用户,并与 AuthZ 同步用户/后端身份
|
||||
4. 后端返回 `handoff_code` 与目标前端地址
|
||||
5. Portal 跳转到目标业务前端 `/handoff`
|
||||
6. 后续步骤与登录链路一致
|
||||
|
||||
## 8. 联调示例
|
||||
|
||||
### 8.1 登录
|
||||
|
||||
```bash
|
||||
curl -X POST 'https://nanobot-api.example.com/api/auth/login' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"username": "bwgdi",
|
||||
"password": "123456"
|
||||
}'
|
||||
```
|
||||
|
||||
### 8.2 消费 handoff_code
|
||||
|
||||
```bash
|
||||
curl -X POST 'https://nanobot-api.example.com/api/auth/handoff/consume' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"code": "short-lived-code"
|
||||
}'
|
||||
```
|
||||
|
||||
### 8.3 获取当前用户
|
||||
|
||||
```bash
|
||||
curl 'https://nanobot-api.example.com/api/auth/me' \
|
||||
-H 'Authorization: Bearer opaque-token'
|
||||
```
|
||||
|
||||
## 9. 当前实现限制
|
||||
|
||||
- `refresh_token` 目前没有实际用途,返回值固定为空字符串。
|
||||
- token 与 handoff code 都保存在后端进程内存中,后端重启后会失效。
|
||||
- 本仓库前端页面目前没有暴露 `register` 的高级可选字段,只使用基础注册参数。
|
||||
- `GET /api/auth/me` 当前返回的 `email` 固定为空字符串,不会回填注册邮箱。
|
||||
Reference in New Issue
Block a user