From b830f3a5c63e556e6c85c1687fbf8804b2739f4b Mon Sep 17 00:00:00 2001 From: Tim Beck Date: Sun, 7 Jun 2020 13:16:08 -0700 Subject: [PATCH 01/18] Add isStrongPassword method --- src/index.js | 2 + src/lib/isStrongPassword.js | 74 +++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 src/lib/isStrongPassword.js diff --git a/src/index.js b/src/index.js index 6fa974e5c..64c2fbacc 100644 --- a/src/index.js +++ b/src/index.js @@ -114,6 +114,7 @@ import isWhitelisted from './lib/isWhitelisted'; import normalizeEmail from './lib/normalizeEmail'; import isSlug from './lib/isSlug'; +import isStrongPassword from './lib/isStrongPassword'; const version = '13.0.0'; @@ -211,6 +212,7 @@ const validator = { normalizeEmail, toString, isSlug, + isStrongPassword, isTaxID, isDate, }; diff --git a/src/lib/isStrongPassword.js b/src/lib/isStrongPassword.js new file mode 100644 index 000000000..7548d3de2 --- /dev/null +++ b/src/lib/isStrongPassword.js @@ -0,0 +1,74 @@ +import assertString from './util/assertString'; + +const upperCaseRegex = /^[A-Z]$/; +const lowerCaseRegex = /^[a-z]$/; +const numberRegex = /^[0-9]$/; +const symbolRegex = /^[-#!$%^&*()_+|~=`{}\[\]:";'<>?,.\/ ]$/; + +/* Counts number of occurances of each char in a string + * could be moved to util/ ? +*/ +function countFrom(str) { + let result = {}; + Array.from(str).forEach((char) => { + let curVal = result[char]; + if (curVal) { + result[char] += 1; + } else { + result[char] = 1; + } + }); + return result; +} + +/* Return information about a password */ +function analyzePassword(password) { + let charMap = countFrom(password); + let analysis = { + length: password.length, + uniqueChars: Object.keys(charMap).length, + uppercaseCount: 0, + lowercaseCount: 0, + numberCount: 0, + symbolCount: 0, + }; + Object.keys(charMap).forEach((char) => { + if (upperCaseRegex.test(char)) { + analysis.uppercaseCount += charMap[char]; + } else if (lowerCaseRegex.test(char)) { + analysis.lowercaseCount += charMap[char]; + } else if (numberRegex.test(char)) { + analysis.numberCount += charMap[char]; + } else if (symbolRegex.test(char)) { + analysis.symbolCount += charMap[char]; + } + }); + return analysis; +} + +/* Scores passwords + * Optional Parameters for isStrongPassword: + * score : if true, will return the calculated score rather than true/false + * strongThreshold : replace the default threshold for what score a strong password receives +*/ + +export default function isStrongPassword(password, score = false, strongThreshold = 50) { + assertString(password); + let analysis = analyzePassword(password); + let points = analysis.uniqueChars; + points += (analysis.length - analysis.uniqueChars) * 0.2; + if (analysis.lowercaseCount > 0) { + points += 10; + } + if (analysis.uppercaseCount > 0) { + points += 10; + } + if (analysis.numberCount > 0) { + points += 10; + } + if (analysis.symbolCount > 0) { + points += 10; + } + console.log(`isStrongPassword: ${password} scored ${points}`); + return score ? points : points >= strongThreshold; +} From f815e4bfe46e24fc14c19ebd42e106d6ff124b7d Mon Sep 17 00:00:00 2001 From: Tim Beck Date: Sun, 7 Jun 2020 13:16:22 -0700 Subject: [PATCH 02/18] Add tests --- test/validators.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/validators.js b/test/validators.js index 4219bd047..2528dd8cd 100755 --- a/test/validators.js +++ b/test/validators.js @@ -8475,6 +8475,28 @@ describe('Validators', () => { }); }); + it('should validate strong passwords', () => { + test({ + validator: 'isStrongPassword', + valid: [ + '%2%k{7BsL"M%Kd6e', + 'EXAMPLE of very long_password!', + 'mxH_+2vs&54_+H3P', + '+&DxJ=X7-4L8jRCD', + 'etV*p%Nr6w&H%FeF', + ], + invalid: [ + '', + 'password', + 'hunter2', + 'hello world', + 'passw0rd', + 'password!', + 'PASSWORD!', + ], + }); + }); + it('should validate base64URL', () => { test({ validator: 'isBase64', From 5a112a0919d2964588ef993e308e61203e571237 Mon Sep 17 00:00:00 2001 From: Tim Beck Date: Sun, 7 Jun 2020 13:16:52 -0700 Subject: [PATCH 03/18] update README.md with isStrongPassword --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8d018f19a..dc24a7055 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,7 @@ Validator | Description **isSurrogatePair(str)** | check if the string contains any surrogate pairs chars. **isUppercase(str)** | check if the string is uppercase. **isSlug** | Check if the string is of type slug. `Options` allow a single hyphen between string. e.g. [`cn-cn`, `cn-c-c`] +**isStrongPassword(str [, options])** | Check if a password is strong or not based on containing a lowercase letter, uppercase letter, number, symbol, as well as the number of uniqe characters and overall length.

Options:
`score`: if true, will return the score calculated for the password rather than true/false.
`strongThreshold`: overrides the default minimum score for a password to be considered strong. **isTaxID(str, locale)** | Check if the given value is a valid Tax Identification Number. Default locale is `en-US` **isURL(str [, options])** | check if the string is an URL.

`options` is an object which defaults to `{ protocols: ['http','https','ftp'], require_tld: true, require_protocol: false, require_host: true, require_valid_protocol: true, allow_underscores: false, host_whitelist: false, host_blacklist: false, allow_trailing_dot: false, allow_protocol_relative_urls: false, disallow_auth: false }`.

require_protocol - if set as true isURL will return false if protocol is not present in the URL.
require_valid_protocol - isURL will check if the URL's protocol is present in the protocols option.
protocols - valid protocols can be modified with this option.
require_host - if set as false isURL will not check if host is present in the URL.
allow_protocol_relative_urls - if set as true protocol relative URLs will be allowed. **isUUID(str [, version])** | check if the string is a UUID (version 3, 4 or 5). From 78ae1163b4541febcf004f56f3b25d77722107f2 Mon Sep 17 00:00:00 2001 From: Tim Beck Date: Sun, 7 Jun 2020 13:20:19 -0700 Subject: [PATCH 04/18] remove console.log --- src/lib/isStrongPassword.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/isStrongPassword.js b/src/lib/isStrongPassword.js index 7548d3de2..12b70c2e2 100644 --- a/src/lib/isStrongPassword.js +++ b/src/lib/isStrongPassword.js @@ -69,6 +69,5 @@ export default function isStrongPassword(password, score = false, strongThreshol if (analysis.symbolCount > 0) { points += 10; } - console.log(`isStrongPassword: ${password} scored ${points}`); return score ? points : points >= strongThreshold; } From 7100af8152f15f21d9e23b9cf7f415d7e73956cd Mon Sep 17 00:00:00 2001 From: Tim Beck Date: Thu, 11 Jun 2020 12:35:49 -0700 Subject: [PATCH 05/18] add tests --- test/validators.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/test/validators.js b/test/validators.js index 47b46f124..ad95841d8 100755 --- a/test/validators.js +++ b/test/validators.js @@ -8532,7 +8532,27 @@ describe('Validators', () => { validator: 'isStrongPassword', valid: [ '%2%k{7BsL"M%Kd6e', - 'EXAMPLE of very long_password!', + 'EXAMPLE of very long_password123!', + 'mxH_+2vs&54_+H3P', + '+&DxJ=X7-4L8jRCD', + 'etV*p%Nr6w&H%FeF', + ], + invalid: [ + '', + 'password', + 'hunter2', + 'hello world', + 'passw0rd', + 'password!', + 'PASSWORD!', + ], + }); + test({ + validator: 'isStrongPassword', + args: [{}, {}], // Test scoring + valid: [ + '%2%k{7BsL"M%Kd6e', + 'EXAMPLE of very long_password123!', 'mxH_+2vs&54_+H3P', '+&DxJ=X7-4L8jRCD', 'etV*p%Nr6w&H%FeF', From 2efe12bf6391d21036c56731afcf8022ffc3c593 Mon Sep 17 00:00:00 2001 From: Tim Beck Date: Thu, 11 Jun 2020 12:36:39 -0700 Subject: [PATCH 06/18] allow either scoring or minimum requirements --- src/lib/isStrongPassword.js | 65 +++++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/src/lib/isStrongPassword.js b/src/lib/isStrongPassword.js index 12b70c2e2..a6c8bce25 100644 --- a/src/lib/isStrongPassword.js +++ b/src/lib/isStrongPassword.js @@ -1,3 +1,4 @@ +import merge from './util/merge'; import assertString from './util/assertString'; const upperCaseRegex = /^[A-Z]$/; @@ -5,10 +6,29 @@ const lowerCaseRegex = /^[a-z]$/; const numberRegex = /^[0-9]$/; const symbolRegex = /^[-#!$%^&*()_+|~=`{}\[\]:";'<>?,.\/ ]$/; +const defaultRequirementOptions = { + minLength: 8, + minLowercase: 1, + minUppercase: 1, + minNumbers: 1, + minSymbols: 1, +}; + +const defaultScoringOptions = { + returnScore: false, + pointsPerUnique: 1, + pointsPerRepeat: 0.5, + pointsForContainingLower: 10, + pointsForContainingUpper: 10, + pointsForContainingNumber: 10, + pointsForContainingSymbol: 10, + strengthThreshold: 50, +}; + /* Counts number of occurances of each char in a string * could be moved to util/ ? */ -function countFrom(str) { +function countChars(str) { let result = {}; Array.from(str).forEach((char) => { let curVal = result[char]; @@ -23,7 +43,7 @@ function countFrom(str) { /* Return information about a password */ function analyzePassword(password) { - let charMap = countFrom(password); + let charMap = countChars(password); let analysis = { length: password.length, uniqueChars: Object.keys(charMap).length, @@ -46,28 +66,37 @@ function analyzePassword(password) { return analysis; } -/* Scores passwords - * Optional Parameters for isStrongPassword: - * score : if true, will return the calculated score rather than true/false - * strongThreshold : replace the default threshold for what score a strong password receives -*/ - -export default function isStrongPassword(password, score = false, strongThreshold = 50) { - assertString(password); - let analysis = analyzePassword(password); - let points = analysis.uniqueChars; - points += (analysis.length - analysis.uniqueChars) * 0.2; +function scorePassword(analysis, scoringOptions) { + let points = 0; + points += analysis.uniqueChars * scoringOptions.pointsPerUnique; + points += (analysis.length - analysis.uniqueChars) * scoringOptions.pointsPerRepeat; if (analysis.lowercaseCount > 0) { - points += 10; + points += scoringOptions.pointsForContainingLower; } if (analysis.uppercaseCount > 0) { - points += 10; + points += scoringOptions.pointsForContainingUpper; } if (analysis.numberCount > 0) { - points += 10; + points += scoringOptions.pointsForContainingNumber; } if (analysis.symbolCount > 0) { - points += 10; + points += scoringOptions.pointsForContainingSymbol; + } + return points; +} + +export default function isStrongPassword(password, requirementOptions = {}, scoringOptions = null) { + assertString(password); + const analysis = analyzePassword(password); + if (scoringOptions) { + scoringOptions = merge(scoringOptions, defaultScoringOptions); + const score = scorePassword(analysis, defaultScoringOptions); + return scoringOptions.returnScore ? score : score >= scoringOptions.strengthThreshold; } - return score ? points : points >= strongThreshold; + requirementOptions = merge(requirementOptions, defaultRequirementOptions); + return analysis.length >= requirementOptions.minLength && + analysis.lowercaseCount >= requirementOptions.minLowercase && + analysis.uppercaseCount >= requirementOptions.minUppercase && + analysis.numberCount >= requirementOptions.minNumbers && + analysis.symbolCount >= requirementOptions.minSymbols; } From aed4a2222e5631b12521aaa068ea182a7abea42b Mon Sep 17 00:00:00 2001 From: Tim Beck Date: Thu, 11 Jun 2020 12:52:36 -0700 Subject: [PATCH 07/18] rename threshold to be more descriptive --- src/lib/isStrongPassword.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/isStrongPassword.js b/src/lib/isStrongPassword.js index a6c8bce25..8ad37a5ee 100644 --- a/src/lib/isStrongPassword.js +++ b/src/lib/isStrongPassword.js @@ -22,7 +22,7 @@ const defaultScoringOptions = { pointsForContainingUpper: 10, pointsForContainingNumber: 10, pointsForContainingSymbol: 10, - strengthThreshold: 50, + minStrongScore: 50, }; /* Counts number of occurances of each char in a string @@ -91,7 +91,7 @@ export default function isStrongPassword(password, requirementOptions = {}, scor if (scoringOptions) { scoringOptions = merge(scoringOptions, defaultScoringOptions); const score = scorePassword(analysis, defaultScoringOptions); - return scoringOptions.returnScore ? score : score >= scoringOptions.strengthThreshold; + return scoringOptions.returnScore ? score : score >= scoringOptions.minStrongScore; } requirementOptions = merge(requirementOptions, defaultRequirementOptions); return analysis.length >= requirementOptions.minLength && From 21f0b262196a36d5d01e1a2ea7c02793523f4691 Mon Sep 17 00:00:00 2001 From: Tim Beck Date: Thu, 11 Jun 2020 12:56:02 -0700 Subject: [PATCH 08/18] update isStrongPassword doc --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f661f89f8..74f32024b 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ Validator | Description **isSurrogatePair(str)** | check if the string contains any surrogate pairs chars. **isUppercase(str)** | check if the string is uppercase. **isSlug** | Check if the string is of type slug. `Options` allow a single hyphen between string. e.g. [`cn-cn`, `cn-c-c`] -**isStrongPassword(str [, options])** | Check if a password is strong or not based on containing a lowercase letter, uppercase letter, number, symbol, as well as the number of uniqe characters and overall length.

Options:
`score`: if true, will return the score calculated for the password rather than true/false.
`strongThreshold`: overrides the default minimum score for a password to be considered strong. +**isStrongPassword(str, requirementOptions?, scoringOptions?)** | Check if a password is strong or not. Allows for custom requirements or scoring. Defaults to checking length/content requirements. When `scoringOptions` is specified, it will score the password and test it against a minimum score `minStrongScore` unless `returnScore` is true.
Default parameters:
`requirementOptions = { minLength: 8, minLowercase: 1, minUppercase: 1, minNumbers: 1, minSymbols: 1 }`
`defaultScoringOptions = { returnScore: false, pointsPerUnique: 1, pointsPerRepeat: 0.5, pointsForContainingLower: 10, pointsForContainingUpper: 10, pointsForContainingNumber: 10, pointsForContainingSymbol: 10, minStrongScore: 50 }` **isTaxID(str, locale)** | Check if the given value is a valid Tax Identification Number. Default locale is `en-US` **isURL(str [, options])** | check if the string is an URL.

`options` is an object which defaults to `{ protocols: ['http','https','ftp'], require_tld: true, require_protocol: false, require_host: true, require_valid_protocol: true, allow_underscores: false, host_whitelist: false, host_blacklist: false, allow_trailing_dot: false, allow_protocol_relative_urls: false, disallow_auth: false }`.

require_protocol - if set as true isURL will return false if protocol is not present in the URL.
require_valid_protocol - isURL will check if the URL's protocol is present in the protocols option.
protocols - valid protocols can be modified with this option.
require_host - if set as false isURL will not check if host is present in the URL.
allow_protocol_relative_urls - if set as true protocol relative URLs will be allowed. **isUUID(str [, version])** | check if the string is a UUID (version 3, 4 or 5). From a55d712cc0e140b982aed62beb7e33d2fb2b8fd3 Mon Sep 17 00:00:00 2001 From: Tim Beck Date: Thu, 11 Jun 2020 13:12:44 -0700 Subject: [PATCH 09/18] shorten function declaration --- src/lib/isStrongPassword.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/isStrongPassword.js b/src/lib/isStrongPassword.js index 8ad37a5ee..84133a859 100644 --- a/src/lib/isStrongPassword.js +++ b/src/lib/isStrongPassword.js @@ -85,9 +85,9 @@ function scorePassword(analysis, scoringOptions) { return points; } -export default function isStrongPassword(password, requirementOptions = {}, scoringOptions = null) { - assertString(password); - const analysis = analyzePassword(password); +export default function isStrongPassword(str, requirementOptions = {}, scoringOptions = null) { + assertString(str); + const analysis = analyzePassword(str); if (scoringOptions) { scoringOptions = merge(scoringOptions, defaultScoringOptions); const score = scorePassword(analysis, defaultScoringOptions); From 97205aa65ff39e54faea8c4799b26272595810e5 Mon Sep 17 00:00:00 2001 From: Tim Beck Date: Thu, 11 Jun 2020 13:16:25 -0700 Subject: [PATCH 10/18] fix confusing parameters --- src/lib/isStrongPassword.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/isStrongPassword.js b/src/lib/isStrongPassword.js index 84133a859..28a487d87 100644 --- a/src/lib/isStrongPassword.js +++ b/src/lib/isStrongPassword.js @@ -85,7 +85,7 @@ function scorePassword(analysis, scoringOptions) { return points; } -export default function isStrongPassword(str, requirementOptions = {}, scoringOptions = null) { +export default function isStrongPassword(str, requirementOptions = null, scoringOptions = null) { assertString(str); const analysis = analyzePassword(str); if (scoringOptions) { @@ -93,7 +93,7 @@ export default function isStrongPassword(str, requirementOptions = {}, scoringOp const score = scorePassword(analysis, defaultScoringOptions); return scoringOptions.returnScore ? score : score >= scoringOptions.minStrongScore; } - requirementOptions = merge(requirementOptions, defaultRequirementOptions); + requirementOptions = merge(requirementOptions || {}, defaultRequirementOptions); return analysis.length >= requirementOptions.minLength && analysis.lowercaseCount >= requirementOptions.minLowercase && analysis.uppercaseCount >= requirementOptions.minUppercase && From 1669bfa4ad2d53d32e68ef0acd39b58db212b876 Mon Sep 17 00:00:00 2001 From: Tim Beck Date: Sun, 28 Jun 2020 21:15:35 -0700 Subject: [PATCH 11/18] combine separate options for more simple usage --- src/lib/isStrongPassword.js | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/lib/isStrongPassword.js b/src/lib/isStrongPassword.js index 28a487d87..91175322e 100644 --- a/src/lib/isStrongPassword.js +++ b/src/lib/isStrongPassword.js @@ -6,15 +6,12 @@ const lowerCaseRegex = /^[a-z]$/; const numberRegex = /^[0-9]$/; const symbolRegex = /^[-#!$%^&*()_+|~=`{}\[\]:";'<>?,.\/ ]$/; -const defaultRequirementOptions = { +const defaultOptions = { minLength: 8, minLowercase: 1, minUppercase: 1, minNumbers: 1, minSymbols: 1, -}; - -const defaultScoringOptions = { returnScore: false, pointsPerUnique: 1, pointsPerRepeat: 0.5, @@ -85,18 +82,16 @@ function scorePassword(analysis, scoringOptions) { return points; } -export default function isStrongPassword(str, requirementOptions = null, scoringOptions = null) { +export default function isStrongPassword(str, options = null) { assertString(str); const analysis = analyzePassword(str); - if (scoringOptions) { - scoringOptions = merge(scoringOptions, defaultScoringOptions); - const score = scorePassword(analysis, defaultScoringOptions); - return scoringOptions.returnScore ? score : score >= scoringOptions.minStrongScore; + options = merge(options || {}, defaultOptions); + if (options.returnScore) { + return scorePassword(analysis, options); } - requirementOptions = merge(requirementOptions || {}, defaultRequirementOptions); - return analysis.length >= requirementOptions.minLength && - analysis.lowercaseCount >= requirementOptions.minLowercase && - analysis.uppercaseCount >= requirementOptions.minUppercase && - analysis.numberCount >= requirementOptions.minNumbers && - analysis.symbolCount >= requirementOptions.minSymbols; + return analysis.length >= options.minLength + && analysis.lowercaseCount >= options.minLowercase + && analysis.uppercaseCount >= options.minUppercase + && analysis.numberCount >= options.minNumbers + && analysis.symbolCount >= options.minSymbols; } From d1aedcc839034fc41d4d26e75b28357c1b520c18 Mon Sep 17 00:00:00 2001 From: Tim Beck Date: Sun, 28 Jun 2020 21:19:25 -0700 Subject: [PATCH 12/18] remove obsolete tests --- test/validators.js | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/test/validators.js b/test/validators.js index 1367728b5..aaafb6bc5 100755 --- a/test/validators.js +++ b/test/validators.js @@ -8581,26 +8581,6 @@ describe('Validators', () => { 'PASSWORD!', ], }); - test({ - validator: 'isStrongPassword', - args: [{}, {}], // Test scoring - valid: [ - '%2%k{7BsL"M%Kd6e', - 'EXAMPLE of very long_password123!', - 'mxH_+2vs&54_+H3P', - '+&DxJ=X7-4L8jRCD', - 'etV*p%Nr6w&H%FeF', - ], - invalid: [ - '', - 'password', - 'hunter2', - 'hello world', - 'passw0rd', - 'password!', - 'PASSWORD!', - ], - }); }); it('should validate base64URL', () => { From 23ebf8288669f1b8a4e61824c550c604f17bde4a Mon Sep 17 00:00:00 2001 From: Tim Beck Date: Sun, 28 Jun 2020 21:20:20 -0700 Subject: [PATCH 13/18] remove minstrongscore option --- src/lib/isStrongPassword.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/isStrongPassword.js b/src/lib/isStrongPassword.js index 91175322e..3b34e4c7d 100644 --- a/src/lib/isStrongPassword.js +++ b/src/lib/isStrongPassword.js @@ -19,7 +19,6 @@ const defaultOptions = { pointsForContainingUpper: 10, pointsForContainingNumber: 10, pointsForContainingSymbol: 10, - minStrongScore: 50, }; /* Counts number of occurances of each char in a string From 883f544e0580d50f4cfb9a4f4504a56fcde4a0c2 Mon Sep 17 00:00:00 2001 From: Tim Beck Date: Sun, 28 Jun 2020 21:23:17 -0700 Subject: [PATCH 14/18] update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1c6949d47..15477d060 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ Validator | Description **isSurrogatePair(str)** | check if the string contains any surrogate pairs chars. **isUppercase(str)** | check if the string is uppercase. **isSlug** | Check if the string is of type slug. `Options` allow a single hyphen between string. e.g. [`cn-cn`, `cn-c-c`] -**isStrongPassword(str, requirementOptions?, scoringOptions?)** | Check if a password is strong or not. Allows for custom requirements or scoring. Defaults to checking length/content requirements. When `scoringOptions` is specified, it will score the password and test it against a minimum score `minStrongScore` unless `returnScore` is true.
Default parameters:
`requirementOptions = { minLength: 8, minLowercase: 1, minUppercase: 1, minNumbers: 1, minSymbols: 1 }`
`defaultScoringOptions = { returnScore: false, pointsPerUnique: 1, pointsPerRepeat: 0.5, pointsForContainingLower: 10, pointsForContainingUpper: 10, pointsForContainingNumber: 10, pointsForContainingSymbol: 10, minStrongScore: 50 }` +**isStrongPassword(str, requirementOptions?, scoringOptions?)** | Check if a password is strong or not. Allows for custom requirements or scoring rules. If `returnScore` is true, then the function returns an integer score for the password rather than a boolean.
Default options:
`{ minLength: 8, minLowercase: 1, minUppercase: 1, minNumbers: 1, minSymbols: 1, returnScore: false, pointsPerUnique: 1, pointsPerRepeat: 0.5, pointsForContainingLower: 10, pointsForContainingUpper: 10, pointsForContainingNumber: 10, pointsForContainingSymbol: 10 }` **isTaxID(str, locale)** | Check if the given value is a valid Tax Identification Number. Default locale is `en-US` **isURL(str [, options])** | check if the string is an URL.

`options` is an object which defaults to `{ protocols: ['http','https','ftp'], require_tld: true, require_protocol: false, require_host: true, require_valid_protocol: true, allow_underscores: false, host_whitelist: false, host_blacklist: false, allow_trailing_dot: false, allow_protocol_relative_urls: false, disallow_auth: false }`.

require_protocol - if set as true isURL will return false if protocol is not present in the URL.
require_valid_protocol - isURL will check if the URL's protocol is present in the protocols option.
protocols - valid protocols can be modified with this option.
require_host - if set as false isURL will not check if host is present in the URL.
allow_protocol_relative_urls - if set as true protocol relative URLs will be allowed. **isUUID(str [, version])** | check if the string is a UUID (version 3, 4 or 5). From a85ea30be971e14782f7a4d588622c8bc938cf5e Mon Sep 17 00:00:00 2001 From: Timothy Beck Date: Tue, 17 Nov 2020 14:47:20 -0800 Subject: [PATCH 15/18] update isStrongPassword signature --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 15477d060..128ca6b4d 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ Validator | Description **isSurrogatePair(str)** | check if the string contains any surrogate pairs chars. **isUppercase(str)** | check if the string is uppercase. **isSlug** | Check if the string is of type slug. `Options` allow a single hyphen between string. e.g. [`cn-cn`, `cn-c-c`] -**isStrongPassword(str, requirementOptions?, scoringOptions?)** | Check if a password is strong or not. Allows for custom requirements or scoring rules. If `returnScore` is true, then the function returns an integer score for the password rather than a boolean.
Default options:
`{ minLength: 8, minLowercase: 1, minUppercase: 1, minNumbers: 1, minSymbols: 1, returnScore: false, pointsPerUnique: 1, pointsPerRepeat: 0.5, pointsForContainingLower: 10, pointsForContainingUpper: 10, pointsForContainingNumber: 10, pointsForContainingSymbol: 10 }` +**isStrongPassword(str [, options])** | Check if a password is strong or not. Allows for custom requirements or scoring rules. If `returnScore` is true, then the function returns an integer score for the password rather than a boolean.
Default options:
`{ minLength: 8, minLowercase: 1, minUppercase: 1, minNumbers: 1, minSymbols: 1, returnScore: false, pointsPerUnique: 1, pointsPerRepeat: 0.5, pointsForContainingLower: 10, pointsForContainingUpper: 10, pointsForContainingNumber: 10, pointsForContainingSymbol: 10 }` **isTaxID(str, locale)** | Check if the given value is a valid Tax Identification Number. Default locale is `en-US` **isURL(str [, options])** | check if the string is an URL.

`options` is an object which defaults to `{ protocols: ['http','https','ftp'], require_tld: true, require_protocol: false, require_host: true, require_valid_protocol: true, allow_underscores: false, host_whitelist: false, host_blacklist: false, allow_trailing_dot: false, allow_protocol_relative_urls: false, disallow_auth: false }`.

require_protocol - if set as true isURL will return false if protocol is not present in the URL.
require_valid_protocol - isURL will check if the URL's protocol is present in the protocols option.
protocols - valid protocols can be modified with this option.
require_host - if set as false isURL will not check if host is present in the URL.
allow_protocol_relative_urls - if set as true protocol relative URLs will be allowed. **isUUID(str [, version])** | check if the string is a UUID (version 3, 4 or 5). From 19baf6d453c2c5f1d9bbb5e4a80e1f32c29eced5 Mon Sep 17 00:00:00 2001 From: Timothy Beck Date: Tue, 17 Nov 2020 14:49:21 -0800 Subject: [PATCH 16/18] Add default args for clarity --- test/validators.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/validators.js b/test/validators.js index aaafb6bc5..a5eb274c4 100755 --- a/test/validators.js +++ b/test/validators.js @@ -8564,6 +8564,13 @@ describe('Validators', () => { it('should validate strong passwords', () => { test({ validator: 'isStrongPassword', + args: [{ + minLength: 8, + minLowercase: 1, + minUppercase: 1, + minNumbers: 1, + minSymbols: 1, + }], valid: [ '%2%k{7BsL"M%Kd6e', 'EXAMPLE of very long_password123!', From 9e36980a96ea6f6a8bdc2d54e0d3caf5f85f1688 Mon Sep 17 00:00:00 2001 From: Timothy Beck Date: Tue, 17 Nov 2020 14:49:45 -0800 Subject: [PATCH 17/18] Add tests for isStrongPassword scoring --- test/sanitizers.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/sanitizers.js b/test/sanitizers.js index 677742731..505e11f95 100644 --- a/test/sanitizers.js +++ b/test/sanitizers.js @@ -246,6 +246,28 @@ describe('Sanitizers', () => { }); }); + it('should score passwords', () => { + test({ + sanitizer: 'isStrongPassword', + args: [{ + returnScore: true, + pointsPerUnique: 1, + pointsPerRepeat: 0.5, + pointsForContainingLower: 10, + pointsForContainingUpper: 10, + pointsForContainingNumber: 10, + pointsForContainingSymbol: 10, + }], + expect: { + abc: 13, + abcc: 13.5, + aBc: 23, + 'Abc123!': 47, + '!@#$%^&*()': 20, + }, + }); + }); + it('should normalize an email based on domain', () => { test({ sanitizer: 'normalizeEmail', From 8ab1d3629d091c406598f1de637a74969077e942 Mon Sep 17 00:00:00 2001 From: Timothy Beck Date: Tue, 17 Nov 2020 14:49:53 -0800 Subject: [PATCH 18/18] Fix typo --- src/lib/isStrongPassword.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/isStrongPassword.js b/src/lib/isStrongPassword.js index 3b34e4c7d..7433703ad 100644 --- a/src/lib/isStrongPassword.js +++ b/src/lib/isStrongPassword.js @@ -21,7 +21,7 @@ const defaultOptions = { pointsForContainingSymbol: 10, }; -/* Counts number of occurances of each char in a string +/* Counts number of occurrences of each char in a string * could be moved to util/ ? */ function countChars(str) {