0

I am trying to include the following password rules via Validators.pattern in my angular 4 project using regex.

My external stakeholder would like a users password to be valid if it included a lowercase letter, uppercase letter and a Number OR Special character.

Below is the regex I am using, Pattern 1 works fine as does pattern2, however when I try to do the OR of them in pattern3 it does not work.

  //passwordPattern1='^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9]).{8,}$';// must include numbers
  //passwordPattern2='^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[#?!@$%^&*-]).{8,}$';//must include special characters
  passwordPattern3='^(((?=.*[a-z])(?=.*[A-Z])(?=.*[0-9]))|((?=.*[a-z])(?=.*[A-Z])(?=.*?[#?!@$%^&*-.])))';//Numbers or Special characters 

Below is how I add it to my formGroup. see line 3

    this.form = this.fb.group({
       username: [null, Validators.compose([Validators.required, Validators.minLength(4), Validators.maxLength(20)])],
       password: [null, Validators.compose([Validators.required,Validators.minLength(8), Validators.maxLength(128), Validators.pattern(this.passwordPattern3)])],
       confirmPassword: [null, Validators.compose([Validators.required, Validators.minLength(8), Validators.maxLength(128)])]
}, {validator: matchingPasswords('password', 'confirmPassword')});

Does anyone know why the third password pattern does not seem to work in angular, and what I could do to make it work?

I tested the REGEX at http://rubular.com/ in passwordPattern3 and it works as desired. Anything I may be missing in Angulars validators.pattern()?

Regarding the security of these requirements- I am aware these rules are not the best approach for the security of a user. For this effort, I, unfortunately, do not have the influence to change the requirements from the external stakeholder. But I am awarewhy such rules may be ineffective

Frank Visaggio
  • 3,642
  • 9
  • 34
  • 71
  • You may benefit from reading [Reference - Password Validation](https://stackoverflow.com/questions/48345922/reference-password-validation/) – ctwheels Mar 19 '18 at 20:49
  • It's probably breaking due to the character set at the end, which contains a hyphen in between two other characters, causing it to define a range rather than a literal hyphen. Try placing it either as the first or last character, or simply escape it with a backslash. – CAustin Mar 19 '18 at 20:53
  • @ctwheels that picture of the horse comic is actually next to my desk... this is one of those moments where stakeholderXYZ wants requirment27 and we cannot change that. See my note because at the end I saw other questions closed for this reason. I know it's not the best practice, however, the desired implementation is out of my/our hands unfortunately. – Frank Visaggio Mar 19 '18 at 20:53
  • @CAustin when can you show me what the escaping would look like? Am i just escaping the hyphen that may be causing the issues? – Frank Visaggio Mar 19 '18 at 20:55
  • This is a hyphen `-` and this is an escaped hyphen `\-`. – CAustin Mar 19 '18 at 20:56
  • @FrankVisaggio did you communicate to your client that any client-side code can be disabled and that you validating passwords client-side is, quite frankly, pointless? – ctwheels Mar 19 '18 at 20:57
  • @FrankVisaggio you have `(?=.*[A-Z])` twice. Also note that `(?=[^a-z][a-z])` is faster than the `.*` alternative. – ctwheels Mar 19 '18 at 20:59
  • @CAustin i tried escaping it and putting it at the front, and also deleting the hyphen and it didnt solve it. Is it possible that it doesnt like the || symbol? – Frank Visaggio Mar 19 '18 at 21:00
  • @ctwheels I believe that is for lowercase and uppercase characters. Thanks for the tip regarding performance I am a novice when it comes to regex so that is helpful. – Frank Visaggio Mar 19 '18 at 21:02
  • It's not pointless to do it on client side, although OP must consider validation on back-end. It reduces steps of user interacting with web page going back and forth to find out how different validations on different form fields work. @ctwheels – revo Mar 19 '18 at 21:03
  • Honestly I can't say what's causing your current issue, but this can be done in a simpler way regardless. Just match the input against `^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9#?!@$%^&*.-]).{8,128}`, then you don't even have to check the length with two separate methods. – CAustin Mar 19 '18 at 21:07
  • @revo agreed that it reduces steps, but that could easily be mitigated using AJAX. The point being: 1. you now have a second regex to take care of. 2. you have to ensure both regexes are compatible. 3. JS doesn't work well with Unicode characters in regex so stuff like `\p{Lu}` can't be *easily* used. 4. JavaScript can be disabled, rendering the validation completely useless. The list goes on and on. Doing this whole thing server-side keeps the environment consistent for all users and helps to prevent bugs as code duplication shouldn't exist if validation is done in one place. – ctwheels Mar 19 '18 at 21:09
  • @ctwheels They're valid points but no one should be worried much: 1) A regex that works on JS %99 percent works on other regex flavors leaving it intact. 2) Refer to #1. 3) It's a language limit. This is a concern that whoever needs it should be aware of. 4) Disabling JS means disabling most possible interactions with that web page. It's a decision from owner whether he / she wants to consider it or not due to how they deliver their services, although it doesn't affect back-end validation. It means client chose the hard way. You may add to your list challenging an ADHD condition to no end. – revo Mar 19 '18 at 21:52

1 Answers1

1

I know this answer doesn't use RegEx, but you could do this with custom validator functions:

function hasUpperCaseChar(val: string): boolean {
  return val.split('')
    .reduce((current, next) => current || next === next.toUpperCase(), false);
}

function hasLowerCaseChar(val: string): boolean {
  return val.split('')
    .reduce((current, next) => current || next === next.toLowerCase(), false);
}

function moreThanOneInteger(val: string): boolean {
  return val.split('')
    .reduce((current, next) => current || !isNaN(parseInt(next)), false);
}

function moreThanOneSpecialCharacter(val: string): boolean {
  return val.split('')
    .reduce((current, next) => current || '#?!@$%^&*-'.split('').includes(next), false);
}

// In another function file (e.g. MY_CUSTOM_VALIDATORS)
export const MY_VALIDATORS: any = {
  password: {
    gtOneInteger: (control: AbstractControl): boolean {
      return hasUpperCaseChar(control.value) && hasLowerCaseChar(control.value) && moreThanOneInteger(control.value)
        ? null 
        : { gtOneInteger: false };
    },
    gtOneSpecialChar: (control: AbstractControl): boolean {
      return hasUpperCaseChar(control.value) && hasLowerCaseChar(control.value) && moreThanOneSpecialCharacter(control.value) 
        ? null 
        : { gtOneSpecialChar: false };
    },
    gtOneIntegerOrSpecialChar: (control: AbstractControl): boolean {
      return hasUpperCaseChar(control.value) && hasLowerCaseChar(control.value) && moreThanOneInteger(control.value) && moreThanOneSpecialCharacter(control.value)
        ? null
        : { gtOneIntegerOrSpecialChar: false };
    }
  }
};

// Updated your current functionality:
this.form = this.fb.group(
  {
    // The Validators.compose() function is not necessary with Angular 4+.
    username: [null, [Validators.required, Validators.minLength(4), Validators.maxLength(20)]],
    // Your previous validation:
    // password: [null, [Validators.required,Validators.minLength(8), Validators.maxLength(128), Validators.pattern(this.passwordPattern3)]],
    
    // New validation (you can use any of the ones defined above):
    password: [null, [Validators.required, Validators.minLength(8), Validators.maxLength(128), MY_VALIDATORS.gtOneIntegerOrSpecialChar]]
    confirmPassword: [null, [Validators.required, Validators.minLength(8), Validators.maxLength(128)]]
  }, 
  {
    validator: matchingPasswords('password', 'confirmPassword')
  }
);
th3n3wguy
  • 3,649
  • 2
  • 23
  • 30
  • In My_Validators the ? null I have a syntax error with. What you are elluding to is that you return the And of each of the 3 states, however, if its null you just set that property (gtOneInteger, gtOneSpecialChar) to false then correct? – Frank Visaggio Mar 20 '18 at 12:49
  • Ended up going this route. Will make some (minor) edits to your code to reflect the approach I used because your logic would have a special characters count as uppercase characters also. Thank you for your time & help. – Frank Visaggio Apr 11 '18 at 13:44
  • @FrankVisaggio => You're very welcome. Glad it worked out for you! – th3n3wguy Apr 11 '18 at 14:34