在 TypeScript 中使用 `class-validator` 确认密码

Posted

技术标签:

【中文标题】在 TypeScript 中使用 `class-validator` 确认密码【英文标题】:Password confirmation in TypeScript with `class-validator` 【发布时间】:2020-06-12 12:50:27 【问题描述】:

今天,我试图弄清楚如何在应用程序的后端 (NestJS) 中验证注册表单。我只是想知道是否存在验证passwordpasswordConfirm 匹配的方法,使用class-validator 包来构建自定义验证器或利用提供的验证器。我正在考虑一个类验证器,而不是一个字段。

// Maybe validator here
export class SignUpDto 
    @IsString()
    @MinLength(4)
    @MaxLength(20)
    username: string;

    @IsString()
    @MinLength(4)
    @MaxLength(20)
    @Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, message: 'password too weak')
    password: string;

    @IsString()
    @MinLength(4)
    @MaxLength(20)
    passwordConfirm: string;

你有什么建议?

【问题讨论】:

不认为它支持这个:github.com/typestack/class-validator/issues/486 @AndreiTătar 真可惜!感谢您的回答! @piero:如前所述,尚不支持。但这里有一个示例装饰器 (@IsLongerThan):github.com/typestack/class-validator/tree/master/sample/… .... 它检查一个属性是否比另一个长。因此,可以将一个属性与另一个属性进行比较。你可以使用这个例子来创建一个装饰器来做你想做的事情。 @ChristopheGeers 我会尽快尝试一下。感谢您的评论! @PieroMacaluso 我不会设置密码的最大长度,请参阅:***.com/questions/98768/… 我也希望你对密码进行哈希处理 【参考方案1】:

接受的答案对我来说非常好,但我们可能会犯如下拼写错误:

@Match('passwordd')
//              ?

所以我想对Generics更严格

@Match(SignUpDto, (s) => s.password)

ma​​tch.decorator.ts

import  ClassConstructor  from "class-transformer";

export const Match = <T>(
  type: ClassConstructor<T>,
  property: (o: T) => any,
  validationOptions?: ValidationOptions,
) => 
  return (object: any, propertyName: string) => 
    registerDecorator(
      target: object.constructor,
      propertyName,
      options: validationOptions,
      constraints: [property],
      validator: MatchConstraint,
    );
  ;
;

@ValidatorConstraint( name: "Match" )
export class MatchConstraint implements ValidatorConstraintInterface 
  validate(value: any, args: ValidationArguments) 
    const [fn] = args.constraints;
    return fn(args.object) === value;
  

  defaultMessage(args: ValidationArguments) 
    const [constraintProperty]: (() => any)[] = args.constraints;
    return `$constraintProperty and $args.property does not match`;
  

所以我们可以像这样使用Match 装饰器:

export class SignUpDto 
  // ...
  password: string;

  // finally, we have ?
  @Match(SignUpDto, (s) => s.password)
  passwordConfirm: string;

【讨论】:

ClassConstructor 来自哪个模块?我似乎在任何地方都找不到它。 你需要从class-transformer中导入:import ClassConstructor from "class-transformer";【参考方案2】:

我喜欢接受的答案,但我认为我们可以通过将要验证的属性作为约束数组中的字符串传递来简化流程。

示例:

@ValidatorConstraint( name: 'CustomMatchPasswords', async: false )
export class CustomMatchPasswords implements ValidatorConstraintInterface 
   validate(password: string, args: ValidationArguments) 

      if (password !== (args.object as any)[args.constraints[0]]) return false;
      return true;
   

   defaultMessage(args: ValidationArguments) 
      return "Passwords do not match!";
   

然后我们可以使用验证器而不需要创建装饰器:

    @IsString()
    @MinLength(4)
    @MaxLength(20)
    @Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, message: 'password too weak')
    password: string;

    @IsString()
    @MinLength(4)
    @MaxLength(20)
    @Validate(CustomMatchPasswords, ['password'])
    passwordConfirm: string;   

【讨论】:

【参考方案3】:

我喜欢 IsEqualTo 装饰器的方法,但我也担心使用不在我的 Dto 中的属性时的拼写错误,所以我最终得到了这个:

import 
  registerDecorator,
  ValidationArguments,
  ValidationOptions,
 from 'class-validator';

export function IsEqualTo<T>(
  property: keyof T,
  validationOptions?: ValidationOptions,
) 
  return (object: any, propertyName: string) => 
    registerDecorator(
      name: 'isEqualTo',
      target: object.constructor,
      propertyName,
      constraints: [property],
      options: validationOptions,
      validator: 
        validate(value: any, args: ValidationArguments) 
          const [relatedPropertyName] = args.constraints;
          const relatedValue = (args.object as any)[relatedPropertyName];
          return value === relatedValue;
        ,

        defaultMessage(args: ValidationArguments) 
          const [relatedPropertyName] = args.constraints;
          return `$propertyName must match $relatedPropertyName exactly`;
        ,
      ,
    );
  ;

并像这样使用它:

export class CreateUserDto 
  @IsEqualTo<CreateUserDto>('password')
  readonly password_confirmation: string;

【讨论】:

【参考方案4】:

这是一个扩展示例,它内联验证器并为其提供默认消息。这样您就不必每次使用 @IsEqualTo 装饰器时都输入消息。

import  
    registerDecorator, 
    ValidationArguments, 
    ValidationOptions 
 from 'class-validator';

export function IsEqualTo(property: string, validationOptions?: ValidationOptions) 
    return (object: any, propertyName: string) => 
      registerDecorator(
        name: 'isEqualTo',
        target: object.constructor,
        propertyName,
        constraints: [property],
        options: validationOptions,
        validator: 
          validate(value: any, args: ValidationArguments) 
          const [relatedPropertyName] = args.constraints;
          const relatedValue = (args.object as any)[relatedPropertyName];
          return value === relatedValue;
        ,

        defaultMessage(args: ValidationArguments) 
          const [relatedPropertyName] = args.constraints;
          return `$propertyName must match $relatedPropertyName exactly`;
        ,
      ,
    );
  ;

【讨论】:

【参考方案5】:
@MinLength(requiredlength ex: 5)
 @MaxLength(requiredlength ex:5)

正在使用最新版本,因此我们可以使用它来验证长度。

【讨论】:

什么是requiredlength ex:5【参考方案6】:

感谢@ChristopheGeers在我的问题的cmets中的建议,我终于设法解决了密码匹配问题:

@piero:如前所述,尚不支持。但这里有一个示例装饰器 (@IsLongerThan):LINK .... 它检查一个属性是否比另一个长。因此,可以将一个属性与另一个属性进行比较。你可以使用这个例子来创建一个装饰器来做你想做的事情。

这是我提出的解决方案:

注册.dto.ts

export class SignUpDto 
    @IsString()
    @MinLength(4)
    @MaxLength(20)
    username: string;

    @IsString()
    @MinLength(4)
    @MaxLength(20)
    @Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, message: 'password too weak')
    password: string;

    @IsString()
    @MinLength(4)
    @MaxLength(20)
    @Match('password')
    passwordConfirm: string;

match.decorator.ts

import registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface from 'class-validator';

export function Match(property: string, validationOptions?: ValidationOptions) 
    return (object: any, propertyName: string) => 
        registerDecorator(
            target: object.constructor,
            propertyName,
            options: validationOptions,
            constraints: [property],
            validator: MatchConstraint,
        );
    ;


@ValidatorConstraint(name: 'Match')
export class MatchConstraint implements ValidatorConstraintInterface 

    validate(value: any, args: ValidationArguments) 
        const [relatedPropertyName] = args.constraints;
        const relatedValue = (args.object as any)[relatedPropertyName];
        return value === relatedValue;
    


【讨论】:

这似乎是一个很好的解决方案,但是 TypeScript linting 有很多问题。 您可以将此方法添加到 MatchConstraint 类以获得错误消息:defaultMessage(args: ValidationArguments) return args.property + " must match " + args.constraints[0];

以上是关于在 TypeScript 中使用 `class-validator` 确认密码的主要内容,如果未能解决你的问题,请参考以下文章

深入浅出TypeScript- 在React项目中使用TypeScript

优雅的在vue中使用TypeScript

为啥在 Typescript 泛型中使用 '&'

如何在 Vue (Typescript) 中使用 axios?

5种在TypeScript中使用的类型保护

如何在 Typescript 2.1+ 中使用 Bluebird