Files
OJ2/packages/contract/src/account.ts
yuetsh 61f74054f9 fix(Minor M3): blog / github 加回 URL 校验
旧 account/serializers.py:125,127 是 serializers.URLField,迁移时降级成了
z.string().max(256)。这两个字段会被前端渲染成可点击链接,放任自由字符串等于
允许写入 javascript: 一类伪协议。

只放行 http(s)://,空串表示清空。实测 javascript:alert(1) 与 not a url 均 400,
https://example.com/x 与空串 200。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 07:01:13 -06:00

68 lines
2.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { z } from "zod"
import { paginatedSchema, sampleUserSchema } from "./common"
import { userProfileSchema } from "./auth"
export const registerRequestSchema = z.object({
username: z.string().trim().min(1).max(32),
email: z.email().max(64),
password: z.string().min(6).max(20),
})
/**
* 对齐旧后端 `account/serializers.py:125,127` 的 `serializers.URLField`
* 这两个字段会被前端渲染成可点击链接,放任自由字符串等于允许写入
* `javascript:` 一类的伪协议。只放行 http/https空串与 null 表示「清空」。
*/
const linkField = z
.string()
.max(256)
.refine(
(value) => value === "" || /^https?:\/\/\S+$/i.test(value),
"必须是以 http:// 或 https:// 开头的网址",
)
export const updateProfileRequestSchema = z.object({
realName: z.string().max(32).nullable().optional(),
avatar: z.string().max(256).optional(),
blog: linkField.nullable().optional(),
mood: z.string().max(256).nullable().optional(),
github: linkField.nullable().optional(),
school: z.string().max(64).nullable().optional(),
major: z.string().max(64).nullable().optional(),
language: z.string().max(32).nullable().optional(),
})
export const metricsSchema = z.object({
now: z.string(),
latest: z.string(),
first: z.string(),
})
export const rankProfileSchema = z.object({
id: z.number().int(),
user: sampleUserSchema,
acceptedNumber: z.number().int(),
submissionNumber: z.number().int(),
mood: z.string().nullable(),
})
export const userRankSchema = paginatedSchema(rankProfileSchema)
export const activityRankItemSchema = z.object({
username: z.string(),
count: z.number().int().nonnegative(),
})
export const problemRankSchema = z.object({
className: z.string(),
rank: z.number().int(),
classAcCount: z.number().int().nonnegative(),
allAcCount: z.number().int().nonnegative(),
})
export const publicProfileSchema = userProfileSchema
export type RegisterRequest = z.infer<typeof registerRequestSchema>
export type UpdateProfileRequest = z.infer<typeof updateProfileRequestSchema>