修复认证挑战与可信代理安全边界(07-17-auth-challenge-trusted-proxy-hardening)

这个提交包含在:
2026-07-17 14:24:58 +08:00
父节点 bbb5c89825
当前提交 6ddedf1e0e
共修改 31 个文件,包含 3801 行新增和 419 行删除
+696 -316
查看文件
文件差异内容过多而无法显示 加载差异
+29 -5
查看文件
@@ -46,11 +46,18 @@ const listPaginationSchema = z.object({
pageSize: z.coerce.number().int().min(1).max(100).optional(),
});
const normalizedEmailSchema = z
.string()
.trim()
.email()
.max(200)
.transform((value) => value.toLowerCase());
const createBodySchema = z.object({
employeeNo: z.string().trim().min(1).max(50).optional(),
name: z.string().min(1).max(100),
phone: z.string().min(6).max(30),
email: z.string().email().max(200),
email: normalizedEmailSchema,
departmentId: z.string().uuid().optional(),
title: z.string().min(1).max(100).optional(),
directManagerId: z.string().uuid().nullable().optional(),
@@ -63,7 +70,7 @@ const createBodySchema = z.object({
const updateBodySchema = z.object({
name: z.string().trim().min(1).max(100).optional(),
phone: z.string().trim().min(6).max(30).optional(),
email: z.string().trim().email().max(200).optional(),
email: normalizedEmailSchema.optional(),
departmentId: z.string().uuid().optional(),
title: z.string().trim().min(1).max(100).optional(),
directManagerId: z.string().uuid().nullable().optional(),
@@ -544,7 +551,7 @@ function buildImportRowDraft(rawRow, defaultHireDate) {
employeeNo: normalizeImportCell(rawRow.data.employeeNo),
name: normalizeImportCell(rawRow.data.name),
phone: normalizeImportCell(rawRow.data.phone),
email: normalizeImportCell(rawRow.data.email),
email: normalizeImportCell(rawRow.data.email).toLowerCase(),
departmentName: normalizeImportCell(rawRow.data.departmentName),
title: normalizeImportCell(rawRow.data.title) || "员工",
directManagerEmployeeNo: normalizeImportCell(rawRow.data.directManagerEmployeeNo),
@@ -934,7 +941,7 @@ async function createSystemUser(tx, options) {
: {}),
name: options.name,
phone: options.phone,
email: options.email,
email: options.email.trim().toLowerCase(),
department_id: options.departmentId,
title: options.title ?? "员工",
direct_manager_id: options.directManagerId ?? null,
@@ -2137,6 +2144,15 @@ async function usersRoutes(fastify, opts = {}) {
login_failed_count: 0,
login_failed_first_at: null,
login_locked_until: null,
email_login_code_failed_count: 0,
email_login_code_failed_first_at: null,
email_login_code_locked_until: null,
password_reset_failed_count: 0,
password_reset_failed_first_at: null,
password_reset_locked_until: null,
password_reset_request_count: 0,
password_reset_request_first_at: null,
password_reset_last_requested_at: null,
token_version: { increment: 1 },
updated_by: request.employee.id,
},
@@ -2144,7 +2160,15 @@ async function usersRoutes(fastify, opts = {}) {
await tx.authSession.updateMany({
where: { employee_id: userId, revoked_at: null },
data: { revoked_at: now },
data: { revoked_at: now, last_seen_at: now },
});
await tx.passwordResetCode.updateMany({
where: { employee_id: userId, invalidated_at: null },
data: { invalidated_at: now },
});
await tx.emailLoginCode.updateMany({
where: { employee_id: userId, used_at: null },
data: { used_at: now },
});
await tx.auditLog.create({