# 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 ` - `POST /api/auth/logout`:可带 `Authorization: Bearer ` ### 3.3 错误响应格式 后端主要返回: ```json { "detail": "错误信息" } ``` Auth Portal 前端收到非 `2xx` 时,会转成如下错误文案: ```text 接口错误 : ``` ### 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 /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 `。 请求头: ```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=&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` 固定为空字符串,不会回填注册邮箱。