2

I need to validate a user's password against the following requirements:

  • 8 or more characters.

  • Contains 1 character and 1 number

  • Can enter letters, numbers, and symbols

Does anyone know how I can accomplish this using a RegEx?

I've made attempts to solve this problem on my own, but nothing I've tried so far as worked. The code for my latest attempt is below.

func isPasswordHasEightCharacter(password: String) -> Bool {
    let passWordRegEx = "^.{8,}$"
    let passwordTest = NSPredicate(format: "SELF MATCHES %@", passWordRegEx)
    return passwordTest.evaluate(with: password)
}

func isPasswordHasNumberAndCharacter(password: String) -> Bool {
    let passRegEx = "^(?=.*[a-z])(?=.*[0-9])"
    let passwordTest = NSPredicate(format: "SELF MATCHES %@", passRegEx)
    return passwordTest.evaluate(with: password)
}

func isPasswordHasNumberAndCharacterSign(password: String) -> Bool {
    let passWordRegEx = "^(?!.[^a-zA-Z0-9@#${'$'}^+=])"
    let passwordTest = NSPredicate(format: "SELF MATCHES %@", passWordRegEx)
    return passwordTest.evaluate(with: password)
}
IronFlare
  • 2,287
  • 2
  • 17
  • 27

2 Answers2

2

The main issue is that NSPredicate with MATCHES requires the full string to match and consume the whole input. Lookarounds - you are using lookaheads - do not consume text, that is, the texts they match are not added to the match value and the regex index stays where it was before attempting to match a lookaround pattern.

The last two parts can be thus fixed this way:

func isPasswordHasNumberAndCharacter(password: String) -> Bool {
    let passRegEx = "(?=[^a-z]*[a-z])[^0-9]*[0-9].*"
    let passwordTest = NSPredicate(format: "SELF MATCHES %@", passRegEx)
    return passwordTest.evaluate(with: password)
}

func isPasswordHasNumberAndCharacterSign(password: String) -> Bool {
    let passWordRegEx = "[a-zA-Z0-9!@#$%^&*]+"
    let passwordTest = NSPredicate(format: "SELF MATCHES %@", passWordRegEx)
    return passwordTest.evaluate(with: password)
}

The first part is OK, though you do not need the ^ and $ anchors (as the whole string input must match the pattern). However, to check a string length you do not even need a regex: see Get the length of a String.

Note:

  • ^(?=[^a-z]*[a-z])[^0-9]*[0-9].*\z matches a string that contains at least one lowercase ASCII letter and at least one ASCII digit
  • ^[a-zA-Z0-9!@#$%^&*]+$ will match a string that only contains ASCII letters, digits and some specific symbols. If you inlude a - make sure it is at the end. Escape both [ and ] if you need to add them, too.

If you want to combine all that into 1 regex you could use

let passRegEx = "(?=[^a-z]*[a-z])(?=[^0-9]*[0-9])[a-zA-Z0-9!@#$%^&*]{8,}"

Or, if you are not using the regex with the NSPredicate MATCHES, with anchors:

let passRegEx = "\\A(?=[^a-z]*[a-z])(?=[^0-9]*[0-9])[a-zA-Z0-9!@#$%^&*]{8,}\\z"
Wiktor Stribiżew
  • 607,720
  • 39
  • 448
  • 563
  • The last function I want only containt letters, numbers, and symbols like (!@#$%^&*). when I enter Special characters like " | ; , or katakana Japanese then return false – Hiệp Chelsea Sep 03 '19 at 13:05
  • @HiệpChelsea So, do you mean to only match ASCII letters, numbers and some specific symbols then? I updated the answer. Please check. – Wiktor Stribiżew Sep 03 '19 at 13:08
  • yes, when I enter some characters I want show the message : "An invalid value has been entered " – Hiệp Chelsea Sep 03 '19 at 13:13
  • @HiệpChelsea So, show it. The functions above should work now. Now you know why your regexps failed. The rest is up to you and does not pertain to the current question. – Wiktor Stribiżew Sep 03 '19 at 13:14
  • now I want white space in between text, top and last non-white space. I try : ^(?=\\S*)(?=\\s*)[a-zA-Z0-9!@#$%^&*]+(?=\\S*)$ but not work. can you help me? – Hiệp Chelsea Sep 04 '19 at 11:40
  • @HiệpChelsea `(?=\\S*)` and `(?=\\s*)` are meaningless as you require an empty string to be present - it is always there. Can you please provide some example strings you want to pass and fail? Update https://regex101.com/r/g2OI5K/1 please with those examples. – Wiktor Stribiżew Sep 04 '19 at 11:43
  • @HiệpChelsea Try `"\\A(?=.{8})(?=.*[a-z])(?=.*[0-9])[a-zA-Z0-9!@#$%^&*]+(?:\\s[a-zA-Z0-9!@#$%^&*]+)*\\z"`, see [demo](https://regex101.com/r/g2OI5K/4) – Wiktor Stribiżew Sep 04 '19 at 11:58
1

In this solution each requirement is checked individually to avoid complex regular expressions. This solution supports variants of characters like ôöệż etc

func validatePassword(_ password: String) -> Bool {
    //At least 8 characters
    if password.count < 8 {
        return false
    }

    //At least one digit
    if password.range(of: #"\d+"#, options: .regularExpression) == nil {
        return false
    }

    //At least one letter
    if password.range(of: #"\p{Alphabetic}+"#, options: .regularExpression) == nil {
        return false
    }

    //No whitespace charcters
    if password.range(of: #"\s+"#, options: .regularExpression) != nil {
        return false
    }

    return true
}

Some test cases

print(validatePassword("abc"))        // --> false
print(validatePassword("abcdefgh"))   // --> false
print(validatePassword("abcde fgh1")) // --> false
print(validatePassword("abcdefgh1"))  // --> true
print(validatePassword("abcåäö123"))  // --> true
print(validatePassword("ABC123€%&"))  // --> true
print(validatePassword("@èệżôøö123")) // --> true
Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52