-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add isStrongPassword method #1348
Changes from 19 commits
b830f3a
f815e4b
5a112a0
78ae116
f9330e6
7100af8
2efe12b
aed4a22
21f0b26
a55d712
97205aa
16e98a2
d78eae1
9f9e68e
a5971b1
1669bfa
d1aedcc
23ebf82
883f544
a85ea30
19baf6d
9e36980
8ab1d36
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import merge from './util/merge'; | ||
import assertString from './util/assertString'; | ||
|
||
const upperCaseRegex = /^[A-Z]$/; | ||
const lowerCaseRegex = /^[a-z]$/; | ||
const numberRegex = /^[0-9]$/; | ||
const symbolRegex = /^[-#!$%^&*()_+|~=`{}\[\]:";'<>?,.\/ ]$/; | ||
|
||
const defaultOptions = { | ||
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, | ||
}; | ||
|
||
/* Counts number of occurances of each char in a string | ||
* could be moved to util/ ? | ||
*/ | ||
function countChars(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 = countChars(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; | ||
} | ||
|
||
function scorePassword(analysis, scoringOptions) { | ||
let points = 0; | ||
points += analysis.uniqueChars * scoringOptions.pointsPerUnique; | ||
points += (analysis.length - analysis.uniqueChars) * scoringOptions.pointsPerRepeat; | ||
if (analysis.lowercaseCount > 0) { | ||
points += scoringOptions.pointsForContainingLower; | ||
} | ||
if (analysis.uppercaseCount > 0) { | ||
points += scoringOptions.pointsForContainingUpper; | ||
} | ||
if (analysis.numberCount > 0) { | ||
points += scoringOptions.pointsForContainingNumber; | ||
} | ||
if (analysis.symbolCount > 0) { | ||
points += scoringOptions.pointsForContainingSymbol; | ||
} | ||
return points; | ||
} | ||
|
||
export default function isStrongPassword(str, options = null) { | ||
assertString(str); | ||
const analysis = analyzePassword(str); | ||
options = merge(options || {}, defaultOptions); | ||
if (options.returnScore) { | ||
return scorePassword(analysis, options); | ||
} | ||
return analysis.length >= options.minLength | ||
&& analysis.lowercaseCount >= options.minLowercase | ||
&& analysis.uppercaseCount >= options.minUppercase | ||
&& analysis.numberCount >= options.minNumbers | ||
&& analysis.symbolCount >= options.minSymbols; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8561,6 +8561,28 @@ describe('Validators', () => { | |
}); | ||
}); | ||
|
||
it('should validate strong passwords', () => { | ||
test({ | ||
validator: 'isStrongPassword', | ||
valid: [ | ||
'%2%k{7BsL"M%Kd6e', | ||
'EXAMPLE of very long_password123!', | ||
'mxH_+2vs&54_+H3P', | ||
'+&DxJ=X7-4L8jRCD', | ||
'etV*p%Nr6w&H%FeF', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a need for tests that pass There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added the tests for this case in the |
||
], | ||
invalid: [ | ||
'', | ||
'password', | ||
'hunter2', | ||
'hello world', | ||
'passw0rd', | ||
'password!', | ||
'PASSWORD!', | ||
], | ||
}); | ||
}); | ||
|
||
it('should validate base64URL', () => { | ||
test({ | ||
validator: 'isBase64', | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From my look of the eye, I thought, I needed to pass 3 parameters, though 2 are optional.
But, from function arguments call, it takes
str & option
.I expected something like below...
It is not a priority but my suggestion for consistency and easy readability/usage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missed this, thanks!