All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 41s
- apple-auth.service.ts: verifyIdentityToken 增加 NODE_ENV 检查, 生产环境缺 APPLE_BUNDLE_ID 时运行时返回 401,不再走 mock - 新增 CAPIErrorCode 语义错误码体系 (src/common/errors/) - 新增 CapiException 携带 errorCode 的 HttpException 子类 - GlobalExceptionFilter 响应自动包含 errorCode 字段 - AuthService/JwtAuthGuard/AppleAuthService 全部改用 CapiException - 新增 LoginResponseDto/RefreshResponseDto/LogoutResponseDto/UserDto - Auth controller Swagger 添加 type 参数 - 新增 docs/ios-auth-api-contract.md Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
155 lines
5.0 KiB
Markdown
155 lines
5.0 KiB
Markdown
# 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** — 提取并传给后端
|