Custom validator

The next step is to tidy up the previous solution.

Not only the decorator above the password field is pretty untidy, but if you try to send a badly formatted password, the default error message reflects the regex, being hard to understand. In this section, we'll learn how to make our solution more elegant by using a Custom Validator.

Begin by creating the file common -> decorators -> is-password.decorator with following content

const PASSWORD_REGEX =
  /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[a-zA-Z\d@$!%*?&]{8,20}$/;

const IS_PASSWORD_KEY = 'isPassword';

const isPassword = (value: string): boolean => {
  return matches(value, PASSWORD_REGEX);
};

/**
 * Checks if the value is a string following these rules:
 * 1. 8 to 20 characters
 * 2. At least one
 * - Lowercase letter
 * - Uppercase letter
 * - Number
 * - Special character
 */
export const IsPassword = (
  validationOptions?: ValidationOptions,
): PropertyDecorator =>
  ValidateBy(
    {
      name: IS_PASSWORD_KEY,
      validator: {
        validate: (value): boolean => isPassword(value),
        defaultMessage: buildMessage(
          (eachPrefix) => eachPrefix + '$property must be a valid password',
          validationOptions,
        ),
      },
    },
    validationOptions,
  );

This implementation was adapted from the official validator @IsInt() from Class Validator. In the documentation, it is also explained how to create a custom validation decorator. The official decorator model was used as it was simpler, but you may see both of them to compare their differences.

Let's not dwell too much on trying to understand everything of the code shown above too deeply. What is really important to notice is the isPassword() function, which contains the logic to validate the password. In this case, it is simply comparing the value received against the regex through the matches() function. And then, it is used in the IsPassword() function, which is the actual decorator. There is mostly boilerplate inside it, we should notice however the validate field, which returns the validation function, and the defaultMessage, where we can customize the error message.

Lastly, we can now use the @IsPassword() decorator over the password field.

Commit - Encapsulating password validation in custom validator

Last updated