Merge pull request #3654 from bluewave-labs/fix/set-password

add password and newPassword to editUserValidation
This commit is contained in:
Alexander Holliday authored and GitHub committed 2026-05-15 11:16:34 -07:00
commit d1b5830c3d
3 files changed
+70 -15

No files matched your search

+5
View File
@@ -4,6 +4,11 @@ import { type UserRole } from "@/types/user.js";
export const passwordPattern =
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!?@#$%^&*()\-_=+[\]{};:'",.~`|\\/])[A-Za-z0-9!?@#$%^&*()\-_=+[\]{};:'",.~`|\\/]+$/;
export const passwordValidation = z
.string()
.min(8, "Password must be at least 8 characters")
.regex(passwordPattern, "Password must contain at least one lowercase letter, one uppercase letter, one number, and one special character");
export const nameValidation = z
.string()
.trim()
+19 -15
View File
@@ -1,6 +1,6 @@
import { z } from "zod";
import { UserRoles } from "@/types/user.js";
import { nameValidation, lowercaseEmailValidation, passwordPattern } from "./shared.js";
import { nameValidation, lowercaseEmailValidation, passwordValidation } from "./shared.js";
//****************************************
// User Validations
@@ -27,29 +27,33 @@ export const editSuperadminUserByIdBodyValidation = z.object({
});
export const editUserPasswordByIdBodyValidation = z.object({
password: z
.string()
.min(8, "Password must be at least 8 characters")
.regex(passwordPattern, "Password must contain at least one lowercase letter, one uppercase letter, one number, and one special character"),
password: passwordValidation,
});
export const createUserBodyValidation = z.object({
firstName: nameValidation,
lastName: nameValidation,
email: lowercaseEmailValidation,
password: z
.string()
.min(8, "Password must be at least 8 characters")
.regex(passwordPattern, "Password must contain at least one lowercase letter, one uppercase letter, one number, and one special character"),
password: passwordValidation,
role: z.array(z.enum(UserRoles)).min(1, "At least one role is required"),
});
export const editUserBodyValidation = z.object({
firstName: nameValidation.optional(),
lastName: nameValidation.optional(),
email: lowercaseEmailValidation.optional(),
profilePicture: z.string().optional(),
});
export const editUserBodyValidation = z
.object({
firstName: nameValidation.optional(),
lastName: nameValidation.optional(),
email: lowercaseEmailValidation.optional(),
profilePicture: z.string().optional(),
password: passwordValidation.optional(),
newPassword: passwordValidation.optional(),
})
// Changing a password requires both the current password and the new one.
// The service only runs the change-password flow when both are present, so
// reject a half-filled request
.refine((data) => (data.password === undefined) === (data.newPassword === undefined), {
message: "Both current password and new password are required to change your password",
path: ["newPassword"],
});
// Canonical user shape returned by auth/user endpoints. Keep aligned with what
// the controllers actually serialize (password is intentionally omitted).
@@ -0,0 +1,46 @@
import { describe, expect, it } from "@jest/globals";
import { editUserBodyValidation } from "../../../src/validation/userValidation.ts";
const validPassword = "OldPass1!";
const validNewPassword = "NewPass1!";
describe("editUserBodyValidation", () => {
// Regression: the Joi->Zod validation refactor (commit c3a41272a3) dropped
// `password`/`newPassword` from this schema. Because z.object() strips
// unknown keys, the change-password flow in userService.editUser silently
// became a no-op. Both fields must survive parsing.
it("retains password and newPassword on the parsed body", () => {
const result = editUserBodyValidation.safeParse({
password: validPassword,
newPassword: validNewPassword,
});
expect(result.success).toBe(true);
expect(result.data).toEqual({
password: validPassword,
newPassword: validNewPassword,
});
});
it("accepts a profile-only edit with neither password field", () => {
const result = editUserBodyValidation.safeParse({ firstName: "Ada", lastName: "Lovelace" });
expect(result.success).toBe(true);
});
it("rejects password without newPassword", () => {
const result = editUserBodyValidation.safeParse({ password: validPassword });
expect(result.success).toBe(false);
});
it("rejects newPassword without password", () => {
const result = editUserBodyValidation.safeParse({ newPassword: validNewPassword });
expect(result.success).toBe(false);
});
it("rejects a newPassword that does not meet the strength policy", () => {
const result = editUserBodyValidation.safeParse({
password: validPassword,
newPassword: "weak",
});
expect(result.success).toBe(false);
});
});