update
Deploy / deploy (push) Canceled after 0s

This commit is contained in:
xuyue committed 2026-09-27 19:36:28 +08:00
1 parent 23844ae301
commit d25b5dbe6d
268 files changed
+3684 -8861

No files matched your search

+6 -16
View File
@@ -45,33 +45,24 @@ export const requireAuth: MiddlewareHandler<AppEnv> = async (c, next) => {
* 与旧 `BasePermissionDecorator._permission_error` 的两分支一致 —— 前端 `utils/api.ts`
* 的拦截器就是按这两个 code 分别弹登录框和弹提示的。禁用账号走第三个码,见 denied()。
*/
function requireRole(
allowed: (user: AuthUser) => boolean,
): MiddlewareHandler<AppEnv> {
function requireRole(allowed: (user: AuthUser) => boolean): MiddlewareHandler<AppEnv> {
return async (c, next) => {
const session = await resolveSession(c)
if (!session.user) return denied(c, session.reason)
if (!allowed(session.user))
return failure(c, 403, "permission-denied", "权限不足")
if (!allowed(session.user)) return failure(c, 403, "permission-denied", "权限不足")
c.set("user", session.user)
await next()
}
}
/** 旧 `@admin_role_required` */
export const requireAdmin = requireRole((user) =>
ADMIN_ROLES.includes(user.adminType),
)
export const requireAdmin = requireRole((user) => ADMIN_ROLES.includes(user.adminType))
/** 旧 `@teacher_admin_required` */
export const requireTeacher = requireRole((user) =>
TEACHER_ROLES.includes(user.adminType),
)
export const requireTeacher = requireRole((user) => TEACHER_ROLES.includes(user.adminType))
/** 旧 `@super_admin_required` */
export const requireSuperAdmin = requireRole(
(user) => user.adminType === "Super Admin",
)
export const requireSuperAdmin = requireRole((user) => user.adminType === "Super Admin")
/**
* 旧 `@problem_permission_required`:先要是管理员,再要 problem_permission 不为 None。
@@ -79,6 +70,5 @@ export const requireSuperAdmin = requireRole(
* created_by 过滤 —— 旧后端也是这么分工的,别把两件事混在一起。
*/
export const requireProblemPermission = requireRole(
(user) =>
ADMIN_ROLES.includes(user.adminType) && user.problemPermission !== "None",
(user) => ADMIN_ROLES.includes(user.adminType) && user.problemPermission !== "None",
)
+3 -18
View File
@@ -5,32 +5,17 @@ const pbkdf2Async = promisify(pbkdf2)
async function verifyDjangoPbkdf2(password: string, encoded: string) {
const [algorithm, iterationsText, salt, digestText] = encoded.split("$")
if (
algorithm !== "pbkdf2_sha256" ||
!iterationsText ||
!salt ||
!digestText
) {
if (algorithm !== "pbkdf2_sha256" || !iterationsText || !salt || !digestText) {
return false
}
const iterations = Number(iterationsText)
const expected = Buffer.from(digestText, "base64")
if (
!Number.isSafeInteger(iterations) ||
iterations <= 0 ||
expected.length === 0
) {
if (!Number.isSafeInteger(iterations) || iterations <= 0 || expected.length === 0) {
return false
}
const actual = await pbkdf2Async(
password,
salt,
iterations,
expected.length,
"sha256",
)
const actual = await pbkdf2Async(password, salt, iterations, expected.length, "sha256")
return timingSafeEqual(actual, expected)
}
+5 -20
View File
@@ -77,12 +77,7 @@ export async function createSession(
// 全压在登录这一下上
const pipeline = redis
.pipeline()
.set(
sessionKey(token),
JSON.stringify(value),
"EX",
config.sessionTtlSeconds,
)
.set(sessionKey(token), JSON.stringify(value), "EX", config.sessionTtlSeconds)
.sadd(userSessionsKey(userId), token)
.expire(userSessionsKey(userId), config.sessionTtlSeconds)
markOnline(pipeline, userId)
@@ -131,10 +126,7 @@ async function sessionUserId(token: string) {
* 对已经被别人登着的账号毫无作用 —— 而学生密码是明文存着给老师查的,
* 改密码正是发现密码泄露之后唯一的补救手段。
*/
export async function revokeUserSessions(
userId: number,
reason: SessionRevokedReason,
) {
export async function revokeUserSessions(userId: number, reason: SessionRevokedReason) {
const tokens = await redis.smembers(userSessionsKey(userId))
if (tokens.length) await redis.del(...tokens.map(sessionKey))
await redis.del(userSessionsKey(userId))
@@ -161,9 +153,7 @@ export type SessionResult =
| { user: AuthUser; reason?: undefined }
| { user: null; reason: "anonymous" | "disabled" }
async function authenticateToken(
token: string | undefined,
): Promise<SessionResult> {
async function authenticateToken(token: string | undefined): Promise<SessionResult> {
if (!token) return { user: null, reason: "anonymous" }
const raw = await redis.get(sessionKey(token))
@@ -237,8 +227,7 @@ export async function getSessionUser(c: Context) {
}
export async function getRequestSessionUser(request: Request) {
return (await authenticateToken(readCookie(request, config.sessionCookie)))
.user
return (await authenticateToken(readCookie(request, config.sessionCookie))).user
}
/**
@@ -293,11 +282,7 @@ async function getStoredSession(c: Context) {
}
}
export async function setContestPassword(
c: Context,
contestId: number,
password: string,
) {
export async function setContestPassword(c: Context, contestId: number, password: string) {
const session = await getStoredSession(c)
if (!session) return false
session.value.contestPasswords[String(contestId)] = password