fix(阶段3): 匿名不可读用户档案,真名改为默认不下发

F1:GET /profiles/:username 只挂了 optionalAuth、handler 内无登录判断,
匿名可读 email、adminType、className、lastLogin。用户名又能经 /rankings/users
公开枚举,等于可以无 cookie 批量收集全校学生的邮箱与最后登录时间。
handler 开头补上未登录即返回空,对齐旧后端 account/views/oj.py 的
UserProfileAPI.get 首行 `if not user.is_authenticated: return self.success()`。

F2:旧后端把「是否下发真名」做成 UsernameSerializer(need_real_name=False)
的默认关闭开关,全仓 11 处调用只有比赛榜单一处显式打开;新后端没搬这一层,
真名随用户对象无条件下发,13 个下发点里 8 个匿名可达。

这里补回同一层:helpers.ts 新增 sampleUser(),realName 默认不下发,
需要的地方显式传 { includeRealName: true }。12 个下发点改为走这个函数,
只有比赛榜单一处打开(对齐 contest/serializers.py:84 的 is_contest_admin)。
没有逐处删字段 —— 那样下次新增端点还会重犯。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5 committed 2026-08-07 01:59:05 -06:00
1 parent 9c04b00e3f
commit b4b61af6b0
6 files changed
+52 -27

No files matched your search

+4 -4
View File
@@ -33,7 +33,7 @@ import { publishAchievementNotification } from "../events"
import { failure, success } from "../http"
import { JudgeStatus } from "../judge/status"
import { updateAchievementsForProblemSet } from "../services/achievements"
import { isTeacherOrAbove, objectValue, queryInteger } from "./helpers"
import { isTeacherOrAbove, objectValue, queryInteger, sampleUser } from "./helpers"
export const problemsetRoutes = new Hono<AppEnv>()
@@ -59,7 +59,7 @@ async function problemSetCreator(id: number) {
const [row] = await db.select({ id: schema.user.id, username: schema.user.username, realName: schema.userProfile.realName })
.from(schema.user).leftJoin(schema.userProfile, eq(schema.userProfile.userId, schema.user.id))
.where(eq(schema.user.id, id)).limit(1)
return row ?? { id, username: "", realName: null }
return sampleUser(row ?? { id, username: "" }, row?.realName)
}
function badgeData(badge: typeof schema.problemsetBadge.$inferSelect, earned?: boolean) {
@@ -166,7 +166,7 @@ problemsetRoutes.get("/problem-sets/:id/problems", optionalAuth, async (c) => {
submissionNumber: problem.submissionNumber,
acceptedNumber: problem.acceptedNumber,
difficulty: problem.difficulty,
createdBy: { id: user.id, username: user.username, realName },
createdBy: sampleUser(user, realName),
tags: tags.get(problem.id) ?? [],
contestId: problem.contestId,
allowFlowchart: problem.allowFlowchart,
@@ -395,7 +395,7 @@ problemsetRoutes.get("/problem-sets/:id/user-progress", requireAuth, async (c) =
const results = rows.map(({ progress, user: progressUser, realName }) => problemSetProgressSchema.parse({
id: progress.id,
problemsetId: progress.problemsetId,
user: { id: progressUser.id, username: progressUser.username, realName },
user: sampleUser(progressUser, realName),
joinTime: progress.joinTime,
completeTime: progress.completeTime,
isCompleted: progress.isCompleted,