Skip to content
This repository has been archived by the owner on Apr 20, 2024. It is now read-only.

Commit

Permalink
Merge pull request #59 from nodes-vapor/feature/vapor-2-fix-password-…
Browse files Browse the repository at this point in the history
…validator

Fix StrongPassword validation and refactor it (+readability)
  • Loading branch information
cweinberger authored Jul 3, 2018
2 parents 0dc7e88 + 6a53e22 commit 9e14536
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 10 deletions.
39 changes: 30 additions & 9 deletions Sources/Sugar/Validators/StrongPassword.swift
Original file line number Diff line number Diff line change
@@ -1,30 +1,51 @@
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]
internal var errorDescription: String?

public init(minLength: Int = 6) {
public init(
minLength: Int = 6,
regexes: [String] = standardRegexes,
minMatchingRegexes: Int = 3,
errorDescription: String? = nil)
{
self.minLength = minLength
self.regexes = regexes
self.minMatchingRegexes = minMatchingRegexes
self.errorDescription = errorDescription
}

public func validate(_ input: String) throws {
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.")

let matchingRegexesCount = regexes
.reduce(0, {
$0 + ((input.range(of: $1, options: .regularExpression) == nil) ? 0 : 1)
})

guard input.count >= minLength, matchingRegexesCount >= minMatchingRegexes else {
throw error(errorDescription ??
"Password is not strong enough. It must be be at least \(minLength) characters long and contain \(minMatchingRegexes) of these: a number, a capital letter, a small letter or a special character.")
}
}
}
8 changes: 7 additions & 1 deletion Tests/SugarTests/ValidatorsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down

0 comments on commit 9e14536

Please sign in to comment.