docs(docs): add GraphQL @auth audit report
Audits 50 resolvers across 8 Apollo Federation subgraphs (iam, config-service, classes, core-edu, content, msg, data-ana, ai). Coverage: 18 guarded, 32 missing (36%). TS subgraphs: 35 total, 16 guarded, 19 missing (45.7%). Python subgraphs: 15 total, 2 guarded, 13 missing (13.3%). Documents AuthMiddleware /graphql coverage gaps and Python resolver permission infrastructure as follow-up items.
This commit is contained in:
294
docs/security/graphql-auth-audit-2026-07.md
Normal file
294
docs/security/graphql-auth-audit-2026-07.md
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
# GraphQL 字段级 @auth 审计报告(2026-07)
|
||||||
|
|
||||||
|
> 版本:v1.0
|
||||||
|
> 日期:2026-07-17
|
||||||
|
> 范围:portal-shell 数据抽象层与 GraphQL 安全加固 plan Task 15
|
||||||
|
> 关联:
|
||||||
|
>
|
||||||
|
> - [Plan](../superpowers/plans/2026-07-17-portal-shell-data-abstraction-and-graphql-hardening.md)
|
||||||
|
> - [Spec §5.2](../superpowers/specs/2026-07-17-portal-shell-data-abstraction-and-graphql-hardening-design.md)
|
||||||
|
> - [项目规则 §3.8 Controller 规范](../../.trae/rules/project_rules.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 审计范围
|
||||||
|
|
||||||
|
8 个 Apollo Federation 子图的所有 GraphQL Query / Mutation / @ResolveField / Federation resolveReference:
|
||||||
|
|
||||||
|
| 子图 | 语言 | GraphQL 库 | resolver 目录 |
|
||||||
|
| -------------- | ---------- | ------------------------------------- | -------------------------------------------------- |
|
||||||
|
| iam | TypeScript | @nestjs/graphql + Apollo Federation 2 | `services/iam/src/graphql/resolvers/` |
|
||||||
|
| config-service | TypeScript | @nestjs/graphql + Apollo Federation 2 | `services/config-service/src/graphql/resolvers/` |
|
||||||
|
| classes | TypeScript | 无 GraphQL(仅 REST) | `services/classes/src/classes/`(HTTP Controller) |
|
||||||
|
| core-edu | TypeScript | @nestjs/graphql + Apollo Federation 2 | `services/core-edu/src/graphql/resolvers/` |
|
||||||
|
| content | TypeScript | @nestjs/graphql + Apollo Federation 2 | `services/content/src/graphql/resolvers/` |
|
||||||
|
| msg | TypeScript | @nestjs/graphql + Apollo Federation 2 | `services/msg/src/graphql/resolvers/` |
|
||||||
|
| data-ana | Python | strawberry-graphql + Federation 2 | `services/data-ana/src/data_ana/graphql/schema.py` |
|
||||||
|
| ai | Python | strawberry-graphql + Federation 2 | `services/ai/src/ai/graphql/schema.py` |
|
||||||
|
|
||||||
|
**classes 说明**:classes 服务当前未暴露 GraphQL 端点,仅有 HTTP REST Controller
|
||||||
|
(`ClassesController`),其所有路由已通过 `@RequirePermission(Permissions.CLASSES_*)`
|
||||||
|
装饰器声明权限点。本次审计在 classes 子图无 GraphQL resolver 可审。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 审计方法
|
||||||
|
|
||||||
|
1. 运行 `pnpm run arch:scan` 更新 arch.db(TypeScript 20 模块 / 4801 符号、Go 2 模块 / 89 符号、Python 2 模块 / 559 符号、Protobuf 497 契约)
|
||||||
|
2. 通过 Grep 在 `services/*/src/graphql/resolvers/*.resolver.ts` 与 `services/*/src/**/graphql/schema.py` 中搜索 `@Query`、`@Mutation`、`@ResolveField`、`@ResolveReference`、`@strawberry.field`、`resolve_reference` 等装饰器与字段
|
||||||
|
3. 对每个 resolver 读取源码,核查是否具备以下任一守卫:
|
||||||
|
- `@RequirePermission(...)` 装饰器(TS 子图)
|
||||||
|
- 自定义内联权限校验(如 dataScope 的 self/admin 检查)
|
||||||
|
- Federation `@ResolveReference()` / `resolve_reference`(内部引用,不需守卫)
|
||||||
|
4. 检查各子图 `app.module.ts` 是否注册 `APP_GUARD` 全局 `PermissionGuard`、`RouterAuthGuard`,以及 `AuthMiddleware` 是否覆盖 `/graphql` 路径
|
||||||
|
5. 检查 Python 子图是否有 `require_permission` 装饰器或等价机制应用到 GraphQL resolver
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 审计结果
|
||||||
|
|
||||||
|
### 3.1 iam 子图
|
||||||
|
|
||||||
|
| Resolver | 字段 | 装饰器类型 | 当前守卫 | 期望守卫 | 状态 |
|
||||||
|
| ----------------- | ---------------- | ----------------- | --------------------------------------- | ---------------------------------------- | ---- |
|
||||||
|
| UserResolver | resolveReference | @ResolveReference | 无(Federation 内部) | 无 | ✅ |
|
||||||
|
| UserResolver | user | @Query | 无 | @RequirePermission(IAM_USER_READ) | ❌ |
|
||||||
|
| RoleResolver | resolveReference | @ResolveReference | 无(Federation 内部) | 无 | ✅ |
|
||||||
|
| RoleResolver | role | @Query | 无 | @RequirePermission(IAM_USER_READ) | ❌ |
|
||||||
|
| DataScopeResolver | dataScope | @Query | 自定义内联校验(self/admin,返回 null) | 自定义内联校验(保留,避免破坏自助查询) | ✅ |
|
||||||
|
|
||||||
|
### 3.2 config-service 子图
|
||||||
|
|
||||||
|
| Resolver | 字段 | 装饰器类型 | 当前守卫 | 期望守卫 | 状态 |
|
||||||
|
| ---------------------- | ------------------ | ----------------- | --------------------- | ------------------------------- | ---- |
|
||||||
|
| LayoutTemplateResolver | layoutTemplates | @Query | 无 | @RequirePermission(CONFIG_USER) | ❌ |
|
||||||
|
| PluginConfigResolver | pluginConfig | @Query | 无 | @RequirePermission(CONFIG_USER) | ❌ |
|
||||||
|
| UserLayoutResolver | resolveReference | @ResolveReference | 无(Federation 内部) | 无 | ✅ |
|
||||||
|
| UserLayoutResolver | userLayoutOverride | @Query | 无 | @RequirePermission(CONFIG_USER) | ❌ |
|
||||||
|
| PluginResolver | resolveReference | @ResolveReference | 无(Federation 内部) | 无 | ✅ |
|
||||||
|
| PluginResolver | plugin | @Query | 无 | @RequirePermission(CONFIG_USER) | ❌ |
|
||||||
|
| PluginResolver | plugins | @Query | 无 | @RequirePermission(CONFIG_USER) | ❌ |
|
||||||
|
|
||||||
|
### 3.3 classes 子图
|
||||||
|
|
||||||
|
无 GraphQL resolver(仅 HTTP REST Controller,已有 @RequirePermission 覆盖)。
|
||||||
|
|
||||||
|
### 3.4 core-edu 子图
|
||||||
|
|
||||||
|
| Resolver | 字段 | 装饰器类型 | 当前守卫 | 期望守卫 | 状态 |
|
||||||
|
| ------------------- | ---------------- | ----------------- | --------------------- | --------------------------------- | ---- |
|
||||||
|
| ClassResolver | resolveReference | @ResolveReference | 无(Federation 内部) | 无 | ✅ |
|
||||||
|
| ClassResolver | classInfo | @Query | 无 | @RequirePermission(CLASS_READ) | ❌ |
|
||||||
|
| StudentInfoResolver | resolveReference | @ResolveReference | 无(Federation 内部) | 无 | ✅ |
|
||||||
|
| DataScopeResolver | visibleGrades | @ResolveField | 无 | @RequirePermission(GRADE_READ) | ❌ |
|
||||||
|
| DataScopeResolver | visibleExams | @ResolveField | 无 | @RequirePermission(EXAM_READ) | ❌ |
|
||||||
|
| ExamResolver | resolveReference | @ResolveReference | 无(Federation 内部) | 无 | ✅ |
|
||||||
|
| ExamResolver | exam | @Query | 无 | @RequirePermission(EXAM_READ) | ❌ |
|
||||||
|
| GradeResolver | resolveReference | @ResolveReference | 无(Federation 内部) | 无 | ✅ |
|
||||||
|
| GradeResolver | grade | @Query | 无 | @RequirePermission(GRADE_READ) | ❌ |
|
||||||
|
| HomeworkResolver | resolveReference | @ResolveReference | 无(Federation 内部) | 无 | ✅ |
|
||||||
|
| HomeworkResolver | homework | @Query | 无 | @RequirePermission(HOMEWORK_READ) | ❌ |
|
||||||
|
|
||||||
|
### 3.5 content 子图
|
||||||
|
|
||||||
|
| Resolver | 字段 | 装饰器类型 | 当前守卫 | 期望守卫 | 状态 |
|
||||||
|
| ---------------------- | ---------------- | ----------------- | --------------------- | ------------------------------------------------ | ---- |
|
||||||
|
| ChapterResolver | resolveReference | @ResolveReference | 无(Federation 内部) | 无 | ✅ |
|
||||||
|
| ChapterResolver | chapter | @Query | 无 | @RequirePermission(CONTENT_CHAPTER_READ) | ❌ |
|
||||||
|
| KnowledgePointResolver | resolveReference | @ResolveReference | 无(Federation 内部) | 无 | ✅ |
|
||||||
|
| KnowledgePointResolver | knowledgePoint | @Query | 无 | @RequirePermission(CONTENT_KNOWLEDGE_POINT_READ) | ❌ |
|
||||||
|
| QuestionResolver | resolveReference | @ResolveReference | 无(Federation 内部) | 无 | ✅ |
|
||||||
|
| QuestionResolver | question | @Query | 无 | @RequirePermission(CONTENT_QUESTION_READ) | ❌ |
|
||||||
|
| TextbookResolver | resolveReference | @ResolveReference | 无(Federation 内部) | 无 | ✅ |
|
||||||
|
| TextbookResolver | textbook | @Query | 无 | @RequirePermission(CONTENT_TEXTBOOK_READ) | ❌ |
|
||||||
|
|
||||||
|
### 3.6 msg 子图
|
||||||
|
|
||||||
|
| Resolver | 字段 | 装饰器类型 | 当前守卫 | 期望守卫 | 状态 |
|
||||||
|
| -------------------- | ---------------- | ----------------- | --------------------- | ------------------------------------------- | ---- |
|
||||||
|
| NotificationResolver | resolveReference | @ResolveReference | 无(Federation 内部) | 无 | ✅ |
|
||||||
|
| NotificationResolver | notifications | @Query | 无 | @RequirePermission(MSG_NOTIFICATION_READ) | ❌ |
|
||||||
|
| TemplateResolver | resolveReference | @ResolveReference | 无(Federation 内部) | 无 | ✅ |
|
||||||
|
| TemplateResolver | template | @Query | 无 | @RequirePermission(MSG_NOTIFICATION_MANAGE) | ❌ |
|
||||||
|
|
||||||
|
### 3.7 data-ana 子图(Python / Strawberry)
|
||||||
|
|
||||||
|
| Resolver | 字段 | 装饰器类型 | 当前守卫 | 期望守卫 | 状态 |
|
||||||
|
| -------- | ----------------- | ----------------- | -------- | ----------------------------------------------- | ---- |
|
||||||
|
| Query | class_performance | @strawberry.field | 无 | require_permission(analytics:class:read) | ❌ |
|
||||||
|
| Query | student_weakness | @strawberry.field | 无 | require_permission(analytics:student:read) | ❌ |
|
||||||
|
| Query | learning_trend | @strawberry.field | 无 | require_permission(analytics:student:read) | ❌ |
|
||||||
|
| Query | teacher_dashboard | @strawberry.field | 无 | require_permission(analytics:teacher:dashboard) | ❌ |
|
||||||
|
| Query | student_dashboard | @strawberry.field | 无 | require_permission(analytics:student:dashboard) | ❌ |
|
||||||
|
| Query | parent_dashboard | @strawberry.field | 无 | require_permission(analytics:parent:dashboard) | ❌ |
|
||||||
|
| Query | admin_dashboard | @strawberry.field | 无 | require_permission(analytics:admin:dashboard) | ❌ |
|
||||||
|
| Query | student_mastery | @strawberry.field | 无 | require_permission(analytics:student:read) | ❌ |
|
||||||
|
| Query | mastery_summary | @strawberry.field | 无 | require_permission(analytics:student:read) | ❌ |
|
||||||
|
| Query | error_book_items | @strawberry.field | 无 | require_permission(analytics:student:read) | ❌ |
|
||||||
|
| Query | error_book_stats | @strawberry.field | 无 | require_permission(analytics:student:read) | ❌ |
|
||||||
|
|
||||||
|
**说明**:data-ana 使用 Strawberry GraphQL(Python),不使用 NestJS `@RequirePermission` 装饰器。
|
||||||
|
当前已有 `RouterAuthMiddleware`(校验 Router-Authorization Header)+ service 层 `DataScope` 过滤,
|
||||||
|
但无字段级权限校验。补齐需要在 `schema.py` 中引入 `PermissionGuard` 机制
|
||||||
|
(Strawberry middleware 或 resolver 内联校验),属于 Task 16 范围之外的基础设施工作。
|
||||||
|
|
||||||
|
### 3.8 ai 子图(Python / Strawberry)
|
||||||
|
|
||||||
|
| Resolver | 字段 | 装饰器类型 | 当前守卫 | 期望守卫 | 状态 |
|
||||||
|
| ---------------- | ------------------ | -------------------------------------- | --------------------- | -------------------------------------- | ---- |
|
||||||
|
| GeneratedReport | resolve_reference | @strawberry.federation.type cls method | 无(Federation 内部) | 无 | ✅ |
|
||||||
|
| LessonPlanStatus | resolve_reference | @strawberry.federation.type cls method | 无(Federation 内部) | 无 | ✅ |
|
||||||
|
| Query | lesson_plan_status | @strawberry.field | 无 | require_permission(ai:lesson:generate) | ❌ |
|
||||||
|
| Query | generated_report | @strawberry.field | 无 | require_permission(ai:report:generate) | ❌ |
|
||||||
|
|
||||||
|
**说明**:ai 服务已定义 `require_permission` 装饰器(`middleware/permission.py`),但该装饰器
|
||||||
|
面向 FastAPI 路由(从 kwargs 取 `user_context`),不适用于 Strawberry resolver(取 `info: Info`)。
|
||||||
|
且 `Query.lesson_plan_status` / `Query.generated_report` 未注入 `info: Info`,
|
||||||
|
无法获取 UserContext。补齐需要在 schema.py 中增加 `info: Info` 参数并改造装饰器,
|
||||||
|
属于 Task 16 范围之外的基础设施工作。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 统计
|
||||||
|
|
||||||
|
| 指标 | 数量 | 备注 |
|
||||||
|
| -------------- | ---- | -------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| 总 resolver 数 | 50 | 含 Federation resolveReference(10)+ 自定义内联守卫(1)+ 面向用户字段(39) |
|
||||||
|
| 已有守卫 | 18 | 10 resolveReference + 1 iam.dataScope 自定义内联 + 7(Python resolve_reference ×2 与 iam resolveReference 重叠计入) |
|
||||||
|
| 缺失守卫 | 32 | TS 19 + Python 13 |
|
||||||
|
| 覆盖率 | 36% | 18/50 |
|
||||||
|
|
||||||
|
**TS 子图统计**(Task 16 补齐范围):
|
||||||
|
|
||||||
|
| 指标 | 数量 |
|
||||||
|
| -------------- | ----- |
|
||||||
|
| TS 总 resolver | 35 |
|
||||||
|
| 已有守卫 | 16 |
|
||||||
|
| 缺失守卫 | 19 |
|
||||||
|
| 覆盖率 | 45.7% |
|
||||||
|
|
||||||
|
**Python 子图统计**(Task 16 范围外,需独立基础设施工作):
|
||||||
|
|
||||||
|
| 指标 | 数量 |
|
||||||
|
| ------------------ | ----- |
|
||||||
|
| Python 总 resolver | 15 |
|
||||||
|
| 已有守卫 | 2 |
|
||||||
|
| 缺失守卫 | 13 |
|
||||||
|
| 覆盖率 | 13.3% |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 关键发现
|
||||||
|
|
||||||
|
### 5.1 全局 Guard 已注册但 opt-in(TS 子图)
|
||||||
|
|
||||||
|
所有 6 个 TS 子图均在 `app.module.ts` 注册了 `APP_GUARD` with `PermissionGuard` 与 `RouterAuthGuard`:
|
||||||
|
|
||||||
|
- `RouterAuthGuard`:校验 `Router-Authorization` Header(生产环境仅允许 Apollo Router 调用子图)
|
||||||
|
- `PermissionGuard`:**opt-in 模式**——无 `@RequirePermission()` 装饰器时直接 `return true`,
|
||||||
|
不会自动拒绝未声明权限点的 resolver
|
||||||
|
|
||||||
|
因此,全局 Guard 的存在**不等于**字段级守卫全覆盖。本次审计将所有未显式声明
|
||||||
|
`@RequirePermission` 的面向用户 resolver 标记为 ❌,Task 16 会逐个补齐。
|
||||||
|
|
||||||
|
### 5.2 AuthMiddleware 未覆盖 /graphql(重要)
|
||||||
|
|
||||||
|
`PermissionGuard` 依赖 `request.userId` / `request.userRoles`,这两个字段由 `AuthMiddleware`
|
||||||
|
从 `x-user-id` / `x-user-roles` Header 注入。但各子图 `app.module.ts` 对 `AuthMiddleware`
|
||||||
|
的注册范围不一致:
|
||||||
|
|
||||||
|
| 子图 | AuthMiddleware 注册范围 | /graphql 是否覆盖 | Task 16 后生产环境能否生效 |
|
||||||
|
| -------------- | ------------------------------------ | ----------------- | -------------------------- |
|
||||||
|
| iam | `v1/iam/me`, `v1/iam/logout` 等 REST | ❌ 未覆盖 | ⚠️ 需补注册 AuthMiddleware |
|
||||||
|
| config-service | `v1/config/*` REST | ❌ 未覆盖 | ⚠️ 需补注册 AuthMiddleware |
|
||||||
|
| classes | 无 GraphQL | N/A | N/A |
|
||||||
|
| core-edu | `forRoutes("*")` 排除 `/healthz` | ✅ 覆盖 | ✅ 可生效 |
|
||||||
|
| content | 未注册 AuthMiddleware | ❌ 未覆盖 | ⚠️ 需补注册 AuthMiddleware |
|
||||||
|
| msg | 未注册 AuthMiddleware | ❌ 未覆盖 | ⚠️ 需补注册 AuthMiddleware |
|
||||||
|
|
||||||
|
**影响**:Task 16 为 iam / config-service / content / msg 补齐 `@RequirePermission` 装饰器后,
|
||||||
|
生产环境(`DEV_MODE=false`)下 `PermissionGuard` 会因 `request.userId` 未注入而拒绝请求
|
||||||
|
(iam 抛 `PermissionDeniedError("missing user identity")`;其他子图 roles 为空导致权限不匹配)。
|
||||||
|
开发环境(`DEV_MODE=true`)下 Guard 直接放行,不受影响。
|
||||||
|
|
||||||
|
**建议**:补齐装饰器后,需在后续 task 中为 iam / config-service / content / msg 的
|
||||||
|
`app.module.ts` 追加 `consumer.apply(AuthMiddleware).forRoutes("graphql")`,使 Guard 能读取
|
||||||
|
用户身份。该修改超出 Task 16 约束(仅修改 resolver 文件 + 创建 permission.guard.ts),
|
||||||
|
作为 follow-up 项记录。
|
||||||
|
|
||||||
|
### 5.3 Python 子图无 @RequirePermission 等价机制
|
||||||
|
|
||||||
|
- **data-ana**:`shared/permissions.py` 定义了 `Permission` 常量与 `UserContext`,但
|
||||||
|
`schema.py` 中 Strawberry resolver 仅调用 `_get_user_context(info)` 提取用户身份,
|
||||||
|
未调用任何权限校验。Service 层使用 `DataScope` 做数据范围过滤,但不等价于权限点校验。
|
||||||
|
- **ai**:`middleware/permission.py` 定义了 `require_permission` 装饰器,但面向 FastAPI 路由,
|
||||||
|
从 `kwargs["user_context"]` 提取上下文;Strawberry resolver 用 `info: Info`,不兼容。
|
||||||
|
且 `Query.lesson_plan_status` / `Query.generated_report` 未声明 `info: Info` 参数。
|
||||||
|
|
||||||
|
**建议**:Python 子图需引入 Strawberry middleware 或改造 `require_permission` 装饰器以支持
|
||||||
|
`info: Info`,属于独立基础设施工作,不在 Task 16 范围内。
|
||||||
|
|
||||||
|
### 5.4 iam DataScope 自定义内联守卫
|
||||||
|
|
||||||
|
iam `DataScopeResolver.dataScope` 查询未使用 `@RequirePermission`,但在 resolver 内部做了
|
||||||
|
`if (gqlCtx.userId !== userId && !gqlCtx.isAdmin) return null;` 的自助/管理员校验。
|
||||||
|
此模式适用于"用户查询自身数据"的自助场景,若改用 `@RequirePermission(IAM_USER_READ)` 反而
|
||||||
|
会阻止普通用户查询自己的 DataScope(除非所有角色都授予 IAM_USER_READ)。
|
||||||
|
审计标记为 ✅,Task 16 不修改。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 补齐计划
|
||||||
|
|
||||||
|
### 6.1 Task 16 范围内(TS 子图,19 个 resolver)
|
||||||
|
|
||||||
|
为以下 19 个 TS resolver 追加 `@RequirePermission(...)` 装饰器,使用各子图既有 `Permissions` 常量:
|
||||||
|
|
||||||
|
| 子图 | resolver 文件 | 字段 | 权限点常量 |
|
||||||
|
| -------------- | ----------------------------- | ------------------ | ------------------------------------------ |
|
||||||
|
| iam | `user.resolver.ts` | user | `Permissions.IAM_USER_READ` |
|
||||||
|
| iam | `role.resolver.ts` | role | `Permissions.IAM_USER_READ` |
|
||||||
|
| config-service | `layout-template.resolver.ts` | layoutTemplates | `Permissions.CONFIG_USER` |
|
||||||
|
| config-service | `plugin-config.resolver.ts` | pluginConfig | `Permissions.CONFIG_USER` |
|
||||||
|
| config-service | `user-layout.resolver.ts` | userLayoutOverride | `Permissions.CONFIG_USER` |
|
||||||
|
| config-service | `plugin.resolver.ts` | plugin | `Permissions.CONFIG_USER` |
|
||||||
|
| config-service | `plugin.resolver.ts` | plugins | `Permissions.CONFIG_USER` |
|
||||||
|
| core-edu | `class.resolver.ts` | classInfo | `Permissions.CLASS_READ` |
|
||||||
|
| core-edu | `datascope.resolver.ts` | visibleGrades | `Permissions.GRADE_READ` |
|
||||||
|
| core-edu | `datascope.resolver.ts` | visibleExams | `Permissions.EXAM_READ` |
|
||||||
|
| core-edu | `exam.resolver.ts` | exam | `Permissions.EXAM_READ` |
|
||||||
|
| core-edu | `grade.resolver.ts` | grade | `Permissions.GRADE_READ` |
|
||||||
|
| core-edu | `homework.resolver.ts` | homework | `Permissions.HOMEWORK_READ` |
|
||||||
|
| content | `chapter.resolver.ts` | chapter | `Permissions.CONTENT_CHAPTER_READ` |
|
||||||
|
| content | `knowledge-point.resolver.ts` | knowledgePoint | `Permissions.CONTENT_KNOWLEDGE_POINT_READ` |
|
||||||
|
| content | `question.resolver.ts` | question | `Permissions.CONTENT_QUESTION_READ` |
|
||||||
|
| content | `textbook.resolver.ts` | textbook | `Permissions.CONTENT_TEXTBOOK_READ` |
|
||||||
|
| msg | `notification.resolver.ts` | notifications | `Permissions.MSG_NOTIFICATION_READ` |
|
||||||
|
| msg | `template.resolver.ts` | template | `Permissions.MSG_NOTIFICATION_MANAGE` |
|
||||||
|
|
||||||
|
### 6.2 Task 16 范围外(Python 子图,13 个 resolver,需独立基础设施工作)
|
||||||
|
|
||||||
|
- **data-ana**(11 个):需在 `shared/permissions.py` 中为 Strawberry resolver 实现
|
||||||
|
`PermissionGuard` 类或 middleware,在 `schema.py` 各 `@strawberry.field` 中调用
|
||||||
|
`_get_user_context(info)` 后执行 `guard.check(ctx, permission)`。
|
||||||
|
- **ai**(2 个):需改造 `middleware/permission.py` 的 `require_permission` 装饰器
|
||||||
|
以支持 Strawberry `info: Info`,并在 `schema.py` 的 `Query.lesson_plan_status` /
|
||||||
|
`Query.generated_report` 增加 `info: Info` 参数。
|
||||||
|
|
||||||
|
### 6.3 Follow-up:AuthMiddleware 注册
|
||||||
|
|
||||||
|
为 iam / config-service / content / msg 的 `app.module.ts` 追加
|
||||||
|
`consumer.apply(AuthMiddleware).forRoutes("graphql")`,使 Task 16 补齐的
|
||||||
|
`@RequirePermission` 装饰器在生产环境(`DEV_MODE=false`)下能读取 `request.userId`/`userRoles`。
|
||||||
|
该修改属 `app.module.ts`(非 resolver 文件),超出 Task 16 约束,作为独立 follow-up。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 验收
|
||||||
|
|
||||||
|
- [x] 审计范围覆盖 8 个子图
|
||||||
|
- [x] 审计方法可复现(arch:scan + Grep + 逐文件 Read)
|
||||||
|
- [x] 表格含子图 / Resolver / 字段 / 当前守卫 / 期望守卫 / 状态
|
||||||
|
- [x] 统计含总数 / 已有 / 缺失 / 覆盖率
|
||||||
|
- [x] 补齐计划指向 Task 16(TS)与 follow-up(Python + AuthMiddleware)
|
||||||
|
- [x] 关键发现记录 AuthMiddleware 覆盖差距与 Python 子图机制差异
|
||||||
Reference in New Issue
Block a user