# iOS Auth API Contract

> 冻结日期：2026-05-27 | 版本：1.0 | 未经评审不得修改请求/响应字段

## 1. 基础约定

| 项目 | 值 |
|------|-----|
| Base URL（生产） | `https://api.longde.cloud` |
| Content-Type | `application/json` |
| 认证方式 | `Authorization: Bearer <accessToken>` |
| 成功响应格式 | `{ success: true, data: <T>, timestamp: "ISO8601" }` |
| 错误响应格式 | `{ success: false, statusCode: <int>, message: "<中文>", errorCode: "<语义码>" }` |

## 2. Token 生命周期

| Token | 有效期 | 存储位置 |
|-------|--------|----------|
| accessToken (JWT) | 1 小时 | Keychain |
| refreshToken (opaque) | 7 天 | Keychain |

- refreshToken 是一次性的：每次 `/auth/refresh` 成功后旧的立即吊销，返回新的
- accessToken 过期 → iOS 用 refreshToken 换新，不要重新走 Apple 登录
- refreshToken 过期 → 回到登录页

## 3. 接口清单

### 3.1 Apple 登录

```
POST /auth/apple
```

**请求体：**

| 字段 | 类型 | 必需 | 说明 |
|------|------|------|------|
| identityToken | string | 是 | Apple 返回的 JWT identityToken |
| authorizationCode | string | 否 | Apple 返回的授权码（建议传） |
| nonce | string | 否 | iOS 生成的原始 nonce（未哈希） |
| fullName.givenName | string | 否 | 用户的名 |
| fullName.familyName | string | 否 | 用户的姓 |
| email | string | 否 | Apple 返回的邮箱 |

**成功响应 `data`：**

| 字段 | 类型 | 说明 |
|------|------|------|
| accessToken | string | JWT，含 type: "user" |
| refreshToken | string | 96 位十六进制字符串 |
| user.id | string | 用户 ID |
| user.email | string\|null | 邮箱 |
| user.nickname | string\|null | 昵称 |
| user.avatarUrl | string\|null | 头像 URL |
| user.role | string | 角色 |
| user.status | string | 状态 |
| user.onboardingCompleted | boolean | 是否完成引导 |

**错误：**

| errorCode | HTTP | 说明 |
|-----------|------|------|
| AUTH_INVALID_APPLE_TOKEN | 401 | identityToken 无效、过期或验证失败 |

### 3.2 刷新 Token

```
POST /auth/refresh
```

**请求体：**

| 字段 | 类型 | 必需 |
|------|------|------|
| refreshToken | string | 是 |

**成功响应 `data`：** 同登录响应（新 accessToken + 新 refreshToken + user）

**错误：**

| errorCode | HTTP | 说明 |
|-----------|------|------|
| AUTH_REFRESH_TOKEN_EXPIRED | 401 | 超过 7 天未使用 |
| AUTH_REFRESH_TOKEN_REVOKED | 401 | 已被登出/安全事件撤销 |
| AUTH_USER_DISABLED | 401 | 账号被管理员禁用 |
| AUTH_USER_DELETED | 401 | 账号已注销 |

### 3.3 获取当前用户

```
GET /users/me
```

需要 Bearer token。

**成功响应 `data`：** 用户对象（同登录响应中的 user）

**错误：**

| errorCode | HTTP | 说明 |
|-----------|------|------|
| AUTH_UNAUTHORIZED | 401 | 未登录或 token 过期 |
| AUTH_USER_DISABLED | 401 | 账号被禁用 |
| AUTH_USER_DELETED | 401 | 账号已注销 |
| AUTH_WRONG_TOKEN_TYPE | 401 | 使用了 admin token |

### 3.4 登出

```
POST /auth/logout
```

需要 Bearer token。

**请求体：**

| 字段 | 类型 | 必需 |
|------|------|------|
| refreshToken | string | 是 |

**成功响应：** `{ success: true, message: "已退出登录" }`

**错误：**

| errorCode | HTTP | 说明 |
|-----------|------|------|
| AUTH_UNAUTHORIZED | 401 | token 已过期（不影响客户端清本地状态） |

## 4. 完整错误码表

| errorCode | 含义 | iOS 处理策略 |
|-----------|------|-------------|
| AUTH_INVALID_APPLE_TOKEN | Apple token 验证失败 | 提示用户重试 Apple 登录 |
| AUTH_USER_DISABLED | 账号被禁用 | 清空本地 session，显示禁用提示 |
| AUTH_USER_DELETED | 账号已注销 | 清空本地 session，回到欢迎页 |
| AUTH_REFRESH_TOKEN_EXPIRED | Refresh token 过期 | 清空本地 session，回到登录页 |
| AUTH_REFRESH_TOKEN_REVOKED | Refresh token 被撤销 | 清空本地 session，回到登录页 |
| AUTH_UNAUTHORIZED | 未认证 | 尝试 refresh，失败则回登录页 |
| AUTH_WRONG_TOKEN_TYPE | Token 类型错误 | 清空本地 session，重新登录 |
| AUTH_DEV_LOGIN_FORBIDDEN | 生产环境禁用 dev 登录 | 不触发（仅 iOS 不关心） |
| VALIDATION_ERROR | 请求参数校验失败 | 检查发送的字段 |
| NOT_FOUND | 资源未找到 | 提示用户 |
| FORBIDDEN | 权限不足 | 提示用户 |
| RATE_LIMITED | 请求过快 | 稍后重试 |
| INTERNAL_ERROR | 服务器错误 | 提示用户稍后重试 |

## 5. iOS 实现要点

1. **Token 存储** — 用 Keychain（不是 UserDefaults）
2. **401 自动刷新** — APIClient 拦截 401，用 refreshToken 换新 accessToken，失败则清 session
3. **并发刷新** — 多个请求同时 401 时只发一次 refresh
4. **AppSession 状态** — 维护状态机：`unauthenticated → authenticating → authenticated → refreshing/expired/disabled/deleted`
5. **Apple 登录 nonce** — 用 `SecRandomCopyBytes` 生成，SHA256 后传给 Apple，原始值传给后端
6. **authorizationCode** — 提取并传给后端
