fix: add missing @RequirePermission decorators

Adds @RequirePermission to 19 TS GraphQL resolvers across 5
subgraphs (iam, config-service, core-edu, content, msg) per
audit report §6.1. Maps: iam user/role -> IAM_USER_READ;
config-service 5 queries -> CONFIG_USER; core-edu classInfo ->
CLASS_READ, exam -> EXAM_READ, grade -> GRADE_READ, homework
-> HOMEWORK_READ, datascope visibleGrades/visibleExams ->
GRADE_READ/EXAM_READ; content chapter/knowledgePoint/question/
textbook -> CONTENT_*_READ; msg notifications ->
MSG_NOTIFICATION_READ, template -> MSG_NOTIFICATION_MANAGE.
Federation resolveReference left unguarded. Python subgraphs
(data-ana, ai) deferred to follow-up infrastructure work.
This commit is contained in:
SpecialX
2026-07-17 13:26:58 +08:00
parent 315b954998
commit 1b5781bf42
17 changed files with 117 additions and 30 deletions

View File

@@ -21,6 +21,10 @@ import {
type ClassEntity,
type StudentEntity,
} from "../dataloader.service.js";
import {
Permissions,
RequirePermission,
} from "../../middleware/permission.guard.js";
/**
* ClassInfo ObjectTypeFederation @key
@@ -38,10 +42,10 @@ export class ClassInfo {
@Field()
gradeId!: string;
@Field({ nullable: true })
@Field(() => String, { nullable: true })
headTeacherId: string | null = null;
@Field({ nullable: true })
@Field(() => String, { nullable: true })
description: string | null = null;
@Field()
@@ -78,6 +82,7 @@ export class ClassResolver {
}
@Query(() => ClassInfo, { nullable: true })
@RequirePermission(Permissions.CLASS_READ)
async classInfo(
@Args("id", { type: () => ID }) id: string,
): Promise<ClassEntity | null> {

View File

@@ -26,6 +26,10 @@ import {
import type { RedisClientType } from "redis";
import { ExamsRepository } from "../../exams/exams.repository.js";
import { GradesRepository } from "../../grades/grades.repository.js";
import {
Permissions,
RequirePermission,
} from "../../middleware/permission.guard.js";
/**
* ScopedGrade 引用类型(仅用于 DataScope 返回)
@@ -40,10 +44,10 @@ class ScopedGrade {
@Field()
studentId!: string;
@Field({ nullable: true })
@Field(() => String, { nullable: true })
examId: string | null = null;
@Field({ nullable: true })
@Field(() => String, { nullable: true })
homeworkId: string | null = null;
@Field()
@@ -52,7 +56,7 @@ class ScopedGrade {
@Field()
totalScore!: string;
@Field({ nullable: true })
@Field(() => String, { nullable: true })
feedback: string | null = null;
@Field()
@@ -61,7 +65,7 @@ class ScopedGrade {
@Field()
schoolId!: string;
@Field({ nullable: true })
@Field(() => String, { nullable: true })
idempotencyKey: string | null = null;
@Field()
@@ -89,7 +93,7 @@ class ScopedExam {
@Field()
title!: string;
@Field({ nullable: true })
@Field(() => String, { nullable: true })
description: string | null = null;
@Field()
@@ -107,7 +111,7 @@ class ScopedExam {
@Field()
statusChangedAt!: string;
@Field({ nullable: true })
@Field(() => String, { nullable: true })
statusChangedBy: string | null = null;
@Field()
@@ -116,7 +120,7 @@ class ScopedExam {
@Field()
createdBy!: string;
@Field({ nullable: true })
@Field(() => String, { nullable: true })
archivedAt: string | null = null;
@Field()
@@ -227,6 +231,7 @@ export class DataScopeResolver {
* Router 通过 @requires 将 studentScopeToken 传入 parent。
*/
@ResolveField(() => [ScopedGrade])
@RequirePermission(Permissions.GRADE_READ)
async visibleGrades(
@Parent() parent: { userId: string; studentScopeToken: string },
): Promise<ScopedGrade[]> {
@@ -250,6 +255,7 @@ export class DataScopeResolver {
* visibleExams通过 classScopeToken 解析可见班级 ID查询考试
*/
@ResolveField(() => [ScopedExam])
@RequirePermission(Permissions.EXAM_READ)
async visibleExams(
@Parent() parent: { userId: string; classScopeToken: string },
): Promise<ScopedExam[]> {

View File

@@ -17,6 +17,10 @@ import {
Directive,
} from "@nestjs/graphql";
import { DataLoaderService, type ExamEntity } from "../dataloader.service.js";
import {
Permissions,
RequirePermission,
} from "../../middleware/permission.guard.js";
/**
* Exam ObjectTypeFederation @key
@@ -37,7 +41,7 @@ export class Exam {
@Field()
title!: string;
@Field({ nullable: true })
@Field(() => String, { nullable: true })
description: string | null = null;
@Field()
@@ -55,7 +59,7 @@ export class Exam {
@Field()
statusChangedAt!: string;
@Field({ nullable: true })
@Field(() => String, { nullable: true })
statusChangedBy: string | null = null;
@Field()
@@ -64,7 +68,7 @@ export class Exam {
@Field()
createdBy!: string;
@Field({ nullable: true })
@Field(() => String, { nullable: true })
archivedAt: string | null = null;
@Field()
@@ -94,6 +98,7 @@ export class ExamResolver {
* 通过 Apollo Router 访问,直连被 RouterAuthGuard 拒绝ADR-036
*/
@Query(() => Exam, { nullable: true })
@RequirePermission(Permissions.EXAM_READ)
async exam(
@Args("id", { type: () => ID }) id: string,
): Promise<ExamEntity | null> {

View File

@@ -16,6 +16,10 @@ import {
Directive,
} from "@nestjs/graphql";
import { DataLoaderService, type GradeEntity } from "../dataloader.service.js";
import {
Permissions,
RequirePermission,
} from "../../middleware/permission.guard.js";
/**
* Grade ObjectTypeFederation @key
@@ -30,10 +34,10 @@ export class Grade {
@Field()
studentId!: string;
@Field({ nullable: true })
@Field(() => String, { nullable: true })
examId: string | null = null;
@Field({ nullable: true })
@Field(() => String, { nullable: true })
homeworkId: string | null = null;
@Field()
@@ -42,7 +46,7 @@ export class Grade {
@Field()
totalScore!: string;
@Field({ nullable: true })
@Field(() => String, { nullable: true })
feedback: string | null = null;
@Field()
@@ -51,7 +55,7 @@ export class Grade {
@Field()
schoolId!: string;
@Field({ nullable: true })
@Field(() => String, { nullable: true })
idempotencyKey: string | null = null;
@Field()
@@ -71,6 +75,7 @@ export class GradeResolver {
}
@Query(() => Grade, { nullable: true })
@RequirePermission(Permissions.GRADE_READ)
async grade(
@Args("id", { type: () => ID }) id: string,
): Promise<GradeEntity | null> {

View File

@@ -19,6 +19,10 @@ import {
DataLoaderService,
type HomeworkEntity,
} from "../dataloader.service.js";
import {
Permissions,
RequirePermission,
} from "../../middleware/permission.guard.js";
/**
* Homework ObjectTypeFederation @key
@@ -39,7 +43,7 @@ export class Homework {
@Field()
title!: string;
@Field({ nullable: true })
@Field(() => String, { nullable: true })
description: string | null = null;
@Field()
@@ -74,6 +78,7 @@ export class HomeworkResolver {
}
@Query(() => Homework, { nullable: true })
@RequirePermission(Permissions.HOMEWORK_READ)
async homework(
@Args("id", { type: () => ID }) id: string,
): Promise<HomeworkEntity | null> {