# 知习 api-server 安全基线

> v0.1 安全设计文档。本后端存储用户资料、知识库、上传文件、主动回忆回答、AI 分析结果和学习记录，第一版必须建立基础安全边界。

---

## 1. 全局安全中间件

| 措施 | 实现 | 文件 |
|------|------|------|
| helmet | `app.use(helmet())` 设置安全 HTTP 头 | `src/main.ts` |
| CORS | 仅允许配置域名。生产环境仅允许 `longde.cloud` | `src/main.ts` |
| body size limit | JSON 请求体最大 10MB | `src/main.ts` |
| 异常过滤 | 生产环境不返回 stack trace | `src/common/filters/global-exception.filter.ts` |

---

## 2. 认证与 Token

### JWT

- `accessToken`: JWT，1 小时过期
- `refreshToken`: 128 位随机 hex，入库只存 SHA-256 hash
- logout 时 `revokedAt = now()` 撤销所有 refresh token
- `/users/me` 及其所有子路由强制 `@UseGuards(JwtAuthGuard)`

```
POST /auth/apple   → 返回 accessToken + refreshToken
POST /auth/refresh → 消耗旧 refreshToken，发放新 token pair（rotation）
POST /auth/logout  → 撤销该用户所有 refresh token
```

### 存储安全

```
refresh_tokens.tokenHash = SHA-256(实际 token)
数据库中永远不存明文 refreshToken
```

---

## 3. 权限与越权防护

### 角色体系

| 角色 | 权限范围 |
|------|---------|
| `USER` | 访问自己的资源 |
| `ADMIN` | 用户管理、数据查看、反馈管理 |
| `SUPER_ADMIN` | 全部权限（含 ADMIN） |

角色层级：`SUPER_ADMIN` ⊃ `ADMIN` ⊃ `USER`

实现文件：

| 文件 | 作用 |
|------|------|
| `common/types/role.enum.ts` | Role 枚举 + `ROLE_HIERARCHY` + `hasRole()` |
| `common/decorators/roles.decorator.ts` | `@Roles(Role.ADMIN)` 装饰器 |
| `common/guards/roles.guard.ts` | 全局 `RolesGuard`，校验用户 role |

用法：

```ts
@Get('admin/users')
@Roles(Role.ADMIN)  // 仅 ADMIN 及以上可访问
async listUsers() {}
```

`RolesGuard` 已注册为全局 `APP_GUARD`（在 JwtAuthGuard 之后执行），默认不需要特殊角色即可访问的路由不加 `@Roles()` 即可。

### 资源归属校验

所有用户资源操作必须校验 `userId` 归属：

```ts
// src/common/utils/security.util.ts
export async function findByIdAndUserId(delegate, id, userId, resourceName)
export function ensureOwnership(record, userId, resourceName)
```

### 需校验的资源

| 资源 | 校验字段 |
|------|---------|
| KnowledgeBase | `userId` |
| KnowledgeItem | `userId` |
| LearningSession | `userId` |
| ActiveRecallAnswer | `userId` |
| AiAnalysisJob | `userId` |
| AiAnalysisResult | `userId` |
| FocusItem | `userId` |
| ReviewCard | `userId` |
| ReviewLog | `userId` |
| DocumentImport | `userId` |

---

## 4. 参数校验

- 全局 `StrictValidationPipe`:
  - `whitelist: true` — 自动剥离未声明字段
  - `forbidNonWhitelisted: true` — 未知字段返回 400
  - 字符串字段最大长度 5000 字符
- 分页 DTO: page≥1, limit 1-100

---

## 5. 限流（Redis）

| 场景 | Key | 限制 |
|------|-----|------|
| 登录 | `rate:ip:{ip}:login:{date}` | 20次/IP/天 |
| 反馈 | `rate:ip:{ip}:feedback:hourly` | 5次/IP/时 |
| AI 分析 | `rate:user:{userId}:ai:daily:{date}` | 50次/用户/天 |
| 文件上传 | `rate:user:{userId}:upload:hourly` | 10次/用户/时 |

实现: `src/common/utils/rate-limit.service.ts`

---

## 6. 文件上传安全

| 措施 | 说明 |
|------|------|
| 类型白名单 | PDF, Word, Excel, 纯文本, Markdown, CSV, PNG, JPEG, WebP |
| 大小限制 | 最大 20MB |
| 随机文件名 | `sanitizeFilename()` 生成随机 key，不信任用户原始文件名 |
| 默认私有 | 所有文件默认私有访问 |
| 路径隔离 | `users/{userId}/...` |

---

## 7. Redis 安全使用

- 不存核心业务结果（用户资料/知识点/AI分析结果等必须在 MySQL）
- 队列任务只存 `jobId`/`userId` 等引用 ID
- 所有临时 key 必须设置 TTL
- 防重复提交锁必须有 TTL，解锁校验 token
- 不在 Redis 中存 token 明文

---

## 8. COS 安全使用

- Bucket 默认私有读写
- 后端不向前端暴露 SecretId/SecretKey
- 下载私有文件通过签名 URL
- 上传路径按 `users/{userId}/{randomKey}` 组织
- 预留临时上传 URL（STS）机制

---

## 9. Swagger 安全

- 开发环境默认开启
- 生产环境默认关闭
- 生产环境如需开启，必须配置 Basic Auth（`SWAGGER_USER`/`SWAGGER_PASSWORD`）
- 生产环境手动设置 `ENABLE_SWAGGER=true`

---

## 10. 数据库安全

- 不使用 root 连接业务
- 业务账号 `zhixi_user` 仅需 SELECT/INSERT/UPDATE/DELETE
- 迁移账号和业务账号分离（`prisma db push` 与运行时连接帐号可不同）
- 数据库自动备份建议: `mysqldump zhixi | gzip > backup-$(date +%Y%m%d).sql.gz`

### 日志中禁止打印

```
DATABASE_URL（含密码）
JWT_SECRET
AI_API_KEY
COS SecretKey
用户完整 refreshToken
用户上传文件的完整内容
Authorization header
```

---

## 11. 安全检查清单

- [x] helmet 已启用
- [x] CORS 仅允许白名单域名
- [x] JWT + refresh token rotation + hash 存储
- [x] logout 撤销 refresh token
- [x] 所有用户数据接口需要认证
- [x] 资源所有权校验工具已就绪
- [x] StrictValidationPipe 全局启用（whitelist + forbidNonWhitelisted）
- [x] Redis 限流已实现
- [x] 文件类型/大小白名单
- [x] 全局异常过滤器生产环境不暴露 stack trace
- [x] Swagger 生产环境默认关闭
- [x] 敏感信息不在日志中打印原则已确立
