修复认证挑战与可信代理安全边界(07-17-auth-challenge-trusted-proxy-hardening)
这个提交包含在:
+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({
|
||||
|
||||
在新工单中引用
屏蔽一个用户