From 3f02395d228f96c6ca66e4c99a2394a0a9aa2c46 Mon Sep 17 00:00:00 2001 From: cweinberger Date: Fri, 29 Jun 2018 18:15:51 +0200 Subject: [PATCH] Fix StrongPassword validation and refactor it (+readability) Addresses https://github.com/nodes-vapor/sugar/issues/48 --- Sources/Sugar/Validators/StrongPassword.swift | 33 ++++++++++++++----- Tests/SugarTests/ValidatorsTests.swift | 8 ++++- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/Sources/Sugar/Validators/StrongPassword.swift b/Sources/Sugar/Validators/StrongPassword.swift index 9dabac3..8380e7e 100644 --- a/Sources/Sugar/Validators/StrongPassword.swift +++ b/Sources/Sugar/Validators/StrongPassword.swift @@ -1,30 +1,47 @@ import Vapor import Validation -private let regex = "^.*(?=.{3,})(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[\\d\\X])(?=.*[^a-zA-Z\\d\\s:]).*$" - /*The password contains characters from at least three of the following four categories: English uppercase characters (A – Z) English lowercase characters (a – z) Base 10 digits (0 – 9) Non-alphanumeric (For example: !, $, #, or %) - Unicode characters + */ public struct StrongPassword: Validator { + + static let lowerCaseRegex = "^.*[a-z].*$" + static let upperCaseRegex = "^.*[A-Z].*$" + static let digitsRegex = "^.*[0-9].*$" + static let specialCharRegex = "^.*[^a-zA-Z0-9].*$" + + static let standardRegexes = [lowerCaseRegex, upperCaseRegex, digitsRegex, specialCharRegex] + internal let minLength: Int + internal let minMatchingRegexes: Int + internal let regexes: [String] + + public init(minLength: Int = 6, + regexes: [String] = standardRegexes, + minMatchingRegexes: Int = 3) { - public init(minLength: Int = 6) { self.minLength = minLength + self.regexes = regexes + self.minMatchingRegexes = minMatchingRegexes } public func validate(_ input: String) throws { + + let matchingRegexesCount = regexes + .reduce(0, {$0 + ((input.range(of: $1, options: .regularExpression) == nil) ? 0 : 1) }) + guard - input.range(of: regex, options: .regularExpression) != nil - && input.count >= minLength - else { - throw error("Password is not strong enough. It must be be at least \(minLength) characters long and contain a number, a capital letter and a small letter.") + input.count >= minLength, + matchingRegexesCount >= minMatchingRegexes + else { + throw error("Password is not strong enough. It must be be at least \(minLength) characters long and contain a number, a capital letter and a small letter.") } } } diff --git a/Tests/SugarTests/ValidatorsTests.swift b/Tests/SugarTests/ValidatorsTests.swift index ed91268..d2da0d9 100644 --- a/Tests/SugarTests/ValidatorsTests.swift +++ b/Tests/SugarTests/ValidatorsTests.swift @@ -21,7 +21,13 @@ class ValidatorsTests: XCTestCase { XCTAssertThrowsError(try StrongPassword().validate("p4ssw0rd")) // Only two categories XCTAssertThrowsError(try StrongPassword().validate("Aa1!")) // Three categories, but too short XCTAssertNoThrow(try StrongPassword().validate("p@ssw0rd")) // Three categories, and long enough - + XCTAssertNoThrow(try StrongPassword().validate("Qwer123")) // Three categories, and long enough + XCTAssertThrowsError(try StrongPassword(minLength: 10).validate("Qwer123")) // Too short + XCTAssertNoThrow(try StrongPassword(minMatchingRegexes: 3).validate("Qwer123")) // 3/3 should work + XCTAssertNoThrow(try StrongPassword(minMatchingRegexes: 2).validate("123_pp")) // 2/3 should work + XCTAssertNoThrow(try StrongPassword(minMatchingRegexes: 1).validate("!234DD")) // 1/3 should work + XCTAssertThrowsError(try StrongPassword(minMatchingRegexes: 4).validate("werQ1c")) // 4/3 should not work + XCTAssertThrowsError(try StrongPassword(regexes: [StrongPassword.specialCharRegex], minMatchingRegexes: 4).validate("aA1🔥🔥🔥")) // Emoji as special char } func testThatURLValidatorWorks() {