# 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` 固定为空字符串，不会回填注册邮箱。
