Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.<br/><br/>Options:<br/>`score`: if true, will return the score calculated for the password rather than true/false.<br/>`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.<br/><br/>`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 }`.<br/><br/>require_protocol - if set as true isURL will return false if protocol is not present in the URL.<br/>require_valid_protocol - isURL will check if the URL's protocol is present in the protocols option.<br/>protocols - valid protocols can be modified with this option.<br/>require_host - if set as false isURL will not check if host is present in the URL.<br/>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).
Expand Down
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -211,6 +212,7 @@ const validator = {
normalizeEmail,
toString,
isSlug,
isStrongPassword,
isTaxID,
isDate,
};
Expand Down
73 changes: 73 additions & 0 deletions src/lib/isStrongPassword.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
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;
}
return score ? points : points >= strongThreshold;
}
22 changes: 22 additions & 0 deletions test/validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a need for tests that pass returnScore as an option

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the tests for this case in the sanitizers.js test file since with this option, the function essentially becomes a sanitizer, turning the string into a number. Hope that makes sense

],
invalid: [
'',
'password',
'hunter2',
'hello world',
'passw0rd',
'password!',
'PASSWORD!',
],
});
});

it('should validate base64URL', () => {
test({
validator: 'isBase64',
Expand Down