refactor: 另外两处守卫也收到注册行上

走查题单进度时撞到 `/problem-sets/:id/user-progress` 对学生 403 —— 那是**正确**的
(它是给教师看全班进度的),但守卫又写在 handler 体内。评审的 M3 只列了 3 处,
按同一模式扫全仓,还有 2 处:

  content.ts     POST /messages                       isSuperAdmin
  problemset.ts  GET  /problem-sets/:id/user-progress  isTeacherOrAbove

改用 requireSuperAdmin / requireTeacher,理由同 M3:守卫要能从注册行上看出来,
不然下一个人加同类端点容易漏掉那个 if。

实测档位没变:学生两条都 403,教师看全班进度 200。
路由遮蔽检查仍然干净。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-08 06:28:44 -06:00
parent 2edb8cfb0d
commit 95625d6711
2 changed files with 6 additions and 9 deletions

View File

@@ -15,11 +15,11 @@ import {
import { and, asc, count, desc, eq, inArray } from "drizzle-orm"
import { Hono } from "hono"
import { requireAuth, type AppEnv } from "../auth/middleware"
import { requireAuth, requireSuperAdmin, type AppEnv } from "../auth/middleware"
import { db, schema } from "../db"
import { failure, success } from "../http"
import { JudgeStatus } from "../judge/status"
import { isSuperAdmin, objectValue, queryInteger, sampleUser } from "./helpers"
import { objectValue, queryInteger, sampleUser } from "./helpers"
export const contentRoutes = new Hono<AppEnv>()
@@ -108,9 +108,8 @@ contentRoutes.get("/messages", requireAuth, async (c) => {
}))
})
contentRoutes.post("/messages", requireAuth, async (c) => {
contentRoutes.post("/messages", requireSuperAdmin, async (c) => {
const user = c.get("user")!
if (!isSuperAdmin(user)) return failure(c, 403, "permission-denied", "Permission denied")
const parsed = createMessageRequestSchema.safeParse(await c.req.json().catch(() => null))
if (!parsed.success) return failure(c, 400, "invalid-request", "Invalid message payload")
if (parsed.data.recipientId === user.id) return failure(c, 400, "invalid-recipient", "Can not send a message to yourself")

View File

@@ -27,13 +27,13 @@ import {
} from "drizzle-orm"
import { Hono } from "hono"
import { optionalAuth, requireAuth, type AppEnv } from "../auth/middleware"
import { optionalAuth, requireAuth, requireTeacher, type AppEnv } from "../auth/middleware"
import { db, schema } from "../db"
import { publishAchievementNotification } from "../events"
import { failure, success } from "../http"
import { JudgeStatus } from "../judge/status"
import { updateAchievementsForProblemSet } from "../services/achievements"
import { isTeacherOrAbove, objectValue, queryInteger, sampleUser } from "./helpers"
import { objectValue, queryInteger, sampleUser } from "./helpers"
export const problemsetRoutes = new Hono<AppEnv>()
@@ -362,9 +362,7 @@ problemsetRoutes.get("/problem-sets/:id/badges", async (c) => {
return success(c, badges.map((badge) => badgeData(badge)))
})
problemsetRoutes.get("/problem-sets/:id/user-progress", requireAuth, async (c) => {
const user = c.get("user")!
if (!isTeacherOrAbove(user)) return failure(c, 403, "permission-denied", "Permission denied")
problemsetRoutes.get("/problem-sets/:id/user-progress", requireTeacher, async (c) => {
const id = queryInteger(c.req.param("id"), 0, { min: 1 })
const [problemSet] = await db.select({ id: schema.problemset.id }).from(schema.problemset).where(and(
eq(schema.problemset.id, id), eq(schema.problemset.visible, true), ne(schema.problemset.status, "draft"),