修复认证挑战与可信代理安全边界(07-17-auth-challenge-trusted-proxy-hardening)
这个提交包含在:
@@ -58,6 +58,9 @@ async function buildApp({
|
||||
const employeeRoleCreateMany = vi.fn().mockResolvedValue({ count: 1 });
|
||||
const employeeRoleFindMany = vi.fn().mockResolvedValue([{ role_code: "employee" }]);
|
||||
const auditCreate = vi.fn().mockResolvedValue({ id: "audit-1" });
|
||||
const authSessionUpdateMany = vi.fn().mockResolvedValue({ count: 0 });
|
||||
const passwordResetCodeUpdateMany = vi.fn().mockResolvedValue({ count: 0 });
|
||||
const emailLoginCodeUpdateMany = vi.fn().mockResolvedValue({ count: 0 });
|
||||
|
||||
const tx = {
|
||||
department: {
|
||||
@@ -75,7 +78,13 @@ async function buildApp({
|
||||
create: auditCreate,
|
||||
},
|
||||
authSession: {
|
||||
updateMany: vi.fn().mockResolvedValue({ count: 0 }),
|
||||
updateMany: authSessionUpdateMany,
|
||||
},
|
||||
passwordResetCode: {
|
||||
updateMany: passwordResetCodeUpdateMany,
|
||||
},
|
||||
emailLoginCode: {
|
||||
updateMany: emailLoginCodeUpdateMany,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -134,6 +143,10 @@ async function buildApp({
|
||||
employeeUpdate,
|
||||
employeeRoleCreateMany,
|
||||
employeeRoleFindMany,
|
||||
auditCreate,
|
||||
authSessionUpdateMany,
|
||||
passwordResetCodeUpdateMany,
|
||||
emailLoginCodeUpdateMany,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -147,7 +160,7 @@ describe("usersRoutes write flows", () => {
|
||||
payload: {
|
||||
name: "张三",
|
||||
phone: "13800000001",
|
||||
email: "zhangsan@example.com",
|
||||
email: "ZhangSan@Example.COM",
|
||||
roles: ["employee"],
|
||||
},
|
||||
});
|
||||
@@ -155,11 +168,10 @@ describe("usersRoutes write flows", () => {
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(employeeCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: expect.not.objectContaining({
|
||||
employee_no: expect.anything(),
|
||||
}),
|
||||
data: expect.objectContaining({ email: "[email protected]" }),
|
||||
}),
|
||||
);
|
||||
expect(employeeCreate.mock.calls[0][0].data).not.toHaveProperty("employee_no");
|
||||
expect(employeeRoleCreateMany).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: [
|
||||
@@ -210,4 +222,54 @@ describe("usersRoutes write flows", () => {
|
||||
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("revokes sessions and outstanding challenges during an administrator password reset", async () => {
|
||||
const {
|
||||
app,
|
||||
employeeUpdate,
|
||||
authSessionUpdateMany,
|
||||
passwordResetCodeUpdateMany,
|
||||
emailLoginCodeUpdateMany,
|
||||
auditCreate,
|
||||
} = await buildApp();
|
||||
|
||||
const response = await app.inject({
|
||||
method: "POST",
|
||||
url: `/api/users/${USER_ID}/reset-password`,
|
||||
payload: { newPassword: "AdministratorReset1234" },
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.json().data).toEqual({
|
||||
id: USER_ID,
|
||||
oneTimePassword: "AdministratorReset1234",
|
||||
});
|
||||
expect(employeeUpdate).toHaveBeenCalledWith({
|
||||
where: { id: USER_ID },
|
||||
data: expect.objectContaining({
|
||||
must_change_password: true,
|
||||
token_version: { increment: 1 },
|
||||
email_login_code_failed_count: 0,
|
||||
password_reset_failed_count: 0,
|
||||
password_reset_request_count: 0,
|
||||
}),
|
||||
});
|
||||
expect(authSessionUpdateMany).toHaveBeenCalledWith({
|
||||
where: { employee_id: USER_ID, revoked_at: null },
|
||||
data: { revoked_at: expect.any(Date), last_seen_at: expect.any(Date) },
|
||||
});
|
||||
expect(passwordResetCodeUpdateMany).toHaveBeenCalledWith({
|
||||
where: { employee_id: USER_ID, invalidated_at: null },
|
||||
data: { invalidated_at: expect.any(Date) },
|
||||
});
|
||||
expect(emailLoginCodeUpdateMany).toHaveBeenCalledWith({
|
||||
where: { employee_id: USER_ID, used_at: null },
|
||||
data: { used_at: expect.any(Date) },
|
||||
});
|
||||
expect(auditCreate).toHaveBeenCalledWith({
|
||||
data: expect.objectContaining({ action: "SYSTEM_USER_PASSWORD_RESET" }),
|
||||
});
|
||||
|
||||
await app.close();
|
||||
});
|
||||
});
|
||||
|
||||
在新工单中引用
屏蔽一个用户