refactor(后端): 权限判断收到一处,路由文件之间不再互相 import
- services/permissions.ts:「超管或创建者」原来在比赛、题单、比赛题目(4 处)、 services/contest.ts 各抄一遍,「题目全部权限」在题目和标签两个路由各写一份 - services/submission-filters.ts:提交列表的用户名 / 班级 / 题号筛选,原来 submission.ts、flowchart.ts 从 submission-statistics.ts 这个路由文件里拿 - services/class-activity.ts:课堂做题记录的口径和「正在上课的班」,原来后台 conf.ts 从 classroom.ts 路由里拿 - services/problem-tags.ts:标签名去重、按名查标签,原来 tag.ts 从 admin/problem.ts 拿 纯搬家。三种身份(超管 / 老师 / 学生)打了 26 个受影响的接口,改前改后状态码和响应体 逐字节一致,包括「不是自己的比赛 / 题单 / 题目报不存在」那几条。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5787efe023
commit
465dbfd42f
14 files changed
+388
-361
No files matched your search
@@ -9,22 +9,14 @@ import { and, count, countDistinct, desc, eq, ilike, inArray } from "drizzle-orm
|
||||
import { Hono } from "hono"
|
||||
|
||||
import { requireTeacher, type AppEnv } from "../../auth/middleware"
|
||||
import type { AuthUser } from "../../auth/session"
|
||||
import { db, schema } from "../../db"
|
||||
import { failure, parseBody, success } from "../../http"
|
||||
import { contestStatus } from "../../services/contest"
|
||||
import { queryInteger, sampleUser } from "../helpers"
|
||||
import { ownsOrSuper } from "../../services/permissions"
|
||||
|
||||
export const adminContestRoutes = new Hono<AppEnv>()
|
||||
|
||||
/**
|
||||
* 「只能管自己建的」。对齐旧 `ensure_created_by`:超管放行,其余人只能碰自己创建的。
|
||||
* 越权时报的是**不存在**而不是无权限 —— 不泄露「有这么个东西但你看不到」。
|
||||
*/
|
||||
function ownedBy(user: AuthUser, contest: { createdById: number }) {
|
||||
return user.adminType === "Super Admin" || contest.createdById === user.id
|
||||
}
|
||||
|
||||
async function serialize(row: {
|
||||
contest: typeof schema.contest.$inferSelect
|
||||
user: typeof schema.user.$inferSelect
|
||||
@@ -132,7 +124,7 @@ adminContestRoutes.get("/contests", requireTeacher, async (c) => {
|
||||
|
||||
adminContestRoutes.get("/contests/:id", requireTeacher, async (c) => {
|
||||
const [row] = await selectContest(queryInteger(c.req.param("id"), 0, { min: 1 }))
|
||||
if (!row || !ownedBy(c.get("user")!, row.contest)) {
|
||||
if (!row || !ownsOrSuper(c.get("user")!, row.contest)) {
|
||||
return failure(c, 404, "contest-not-found", "Contest does not exist")
|
||||
}
|
||||
return success(c, await serialize(row))
|
||||
@@ -170,7 +162,7 @@ adminContestRoutes.put("/contests/:id", requireTeacher, async (c) => {
|
||||
const parsed = await parseBody(c, updateContestRequestSchema)
|
||||
if (!parsed.success) return parsed.response
|
||||
const [existing] = await selectContest(id)
|
||||
if (!existing || !ownedBy(c.get("user")!, existing.contest)) {
|
||||
if (!existing || !ownsOrSuper(c.get("user")!, existing.contest)) {
|
||||
return failure(c, 404, "contest-not-found", "Contest does not exist")
|
||||
}
|
||||
const error = validatePayload(parsed.data)
|
||||
@@ -307,7 +299,7 @@ adminContestRoutes.put("/contests/:id/acm-helper", requireTeacher, async (c) =>
|
||||
.from(schema.contest)
|
||||
.where(eq(schema.contest.id, contestId))
|
||||
.limit(1)
|
||||
if (!contest || !ownedBy(c.get("user")!, contest)) {
|
||||
if (!contest || !ownsOrSuper(c.get("user")!, contest)) {
|
||||
return failure(c, 404, "contest-not-found", "Contest does not exist")
|
||||
}
|
||||
// rank 必须属于这场比赛。旧后端只按 rank_id 取,不校验归属 ——
|
||||
|
||||
Reference in new issue
Block a user