@zxcvbn-ts/core
Advanced tools
Comparing version 2.1.0 to 2.2.0
@@ -10,7 +10,7 @@ import dateSplits from './dateSplits.esm.js'; | ||
const MIN_SUBMATCH_GUESSES_MULTI_CHAR = 50; | ||
const MIN_YEAR_SPACE = 20; // \xbf-\xdf is a range for almost all special uppercase letter like Ä and so on | ||
const MIN_YEAR_SPACE = 20; | ||
// \xbf-\xdf is a range for almost all special uppercase letter like Ä and so on | ||
const START_UPPER = /^[A-Z\xbf-\xdf][^A-Z\xbf-\xdf]+$/; | ||
const END_UPPER = /^[^A-Z\xbf-\xdf]+[A-Z\xbf-\xdf]$/; // \xdf-\xff is a range for almost all special lowercase letter like ä and so on | ||
const END_UPPER = /^[^A-Z\xbf-\xdf]+[A-Z\xbf-\xdf]$/; | ||
// \xdf-\xff is a range for almost all special lowercase letter like ä and so on | ||
const ALL_UPPER = /^[A-Z\xbf-\xdf]+$/; | ||
@@ -17,0 +17,0 @@ const ALL_UPPER_INVERTED = /^[^a-z\xdf-\xff]+$/; |
'use strict'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
var dateSplits = require('./dateSplits.js'); | ||
@@ -14,7 +12,7 @@ | ||
const MIN_SUBMATCH_GUESSES_MULTI_CHAR = 50; | ||
const MIN_YEAR_SPACE = 20; // \xbf-\xdf is a range for almost all special uppercase letter like Ä and so on | ||
const MIN_YEAR_SPACE = 20; | ||
// \xbf-\xdf is a range for almost all special uppercase letter like Ä and so on | ||
const START_UPPER = /^[A-Z\xbf-\xdf][^A-Z\xbf-\xdf]+$/; | ||
const END_UPPER = /^[^A-Z\xbf-\xdf]+[A-Z\xbf-\xdf]$/; // \xdf-\xff is a range for almost all special lowercase letter like ä and so on | ||
const END_UPPER = /^[^A-Z\xbf-\xdf]+[A-Z\xbf-\xdf]$/; | ||
// \xdf-\xff is a range for almost all special lowercase letter like ä and so on | ||
const ALL_UPPER = /^[A-Z\xbf-\xdf]+$/; | ||
@@ -21,0 +19,0 @@ const ALL_UPPER_INVERTED = /^[^a-z\xdf-\xff]+$/; |
var dateSplits = { | ||
4: [// for length-4 strings, eg 1191 or 9111, two ways to split: | ||
4: [ | ||
// for length-4 strings, eg 1191 or 9111, two ways to split: | ||
[1, 2], [2, 3] // 91 1 1 | ||
], | ||
5: [[1, 3], [2, 3], // [2, 3], // 91 1 11 <- duplicate previous one | ||
5: [[1, 3], [2, 3], | ||
// [2, 3], // 91 1 11 <- duplicate previous one | ||
[2, 4] // 91 11 1 <- New and must be added as bug fix | ||
], | ||
6: [[1, 2], [2, 4], [4, 5] // 1991 1 1 | ||
], | ||
// 1111991 | ||
7: [[1, 3], [2, 3], [4, 5], [4, 6] // 1991 11 1 | ||
], | ||
8: [[2, 4], [4, 6] // 1991 11 11 | ||
@@ -14,0 +20,0 @@ ] |
'use strict'; | ||
var dateSplits = { | ||
4: [// for length-4 strings, eg 1191 or 9111, two ways to split: | ||
4: [ | ||
// for length-4 strings, eg 1191 or 9111, two ways to split: | ||
[1, 2], [2, 3] // 91 1 1 | ||
], | ||
5: [[1, 3], [2, 3], // [2, 3], // 91 1 11 <- duplicate previous one | ||
5: [[1, 3], [2, 3], | ||
// [2, 3], // 91 1 11 <- duplicate previous one | ||
[2, 4] // 91 11 1 <- New and must be added as bug fix | ||
], | ||
6: [[1, 2], [2, 4], [4, 5] // 1991 1 1 | ||
], | ||
// 1111991 | ||
7: [[1, 3], [2, 3], [4, 5], [4, 6] // 1991 11 1 | ||
], | ||
8: [[2, 4], [4, 6] // 1991 11 11 | ||
@@ -16,0 +22,0 @@ ] |
@@ -1,2 +0,2 @@ | ||
export declare type Procedure = (...args: any[]) => void; | ||
export type Procedure = (...args: any[]) => void; | ||
/** | ||
@@ -3,0 +3,0 @@ * @link https://davidwalsh.name/javascript-debounce-function |
@@ -8,6 +8,4 @@ /** | ||
const context = this; | ||
const later = () => { | ||
timeout = undefined; | ||
if (!isImmediate) { | ||
@@ -17,15 +15,10 @@ func.apply(context, args); | ||
}; | ||
const shouldCallNow = isImmediate && !timeout; | ||
if (timeout !== undefined) { | ||
clearTimeout(timeout); | ||
} | ||
timeout = setTimeout(later, wait); | ||
if (shouldCallNow) { | ||
return func.apply(context, args); | ||
} | ||
return undefined; | ||
@@ -32,0 +25,0 @@ }; |
@@ -10,6 +10,4 @@ 'use strict'; | ||
const context = this; | ||
const later = () => { | ||
timeout = undefined; | ||
if (!isImmediate) { | ||
@@ -19,15 +17,10 @@ func.apply(context, args); | ||
}; | ||
const shouldCallNow = isImmediate && !timeout; | ||
if (timeout !== undefined) { | ||
clearTimeout(timeout); | ||
} | ||
timeout = setTimeout(later, wait); | ||
if (shouldCallNow) { | ||
return func.apply(context, args); | ||
} | ||
return undefined; | ||
@@ -34,0 +27,0 @@ }; |
import { DefaultFeedbackFunction, FeedbackType, MatchEstimated } from './types'; | ||
declare type Matchers = { | ||
type Matchers = { | ||
[key: string]: DefaultFeedbackFunction; | ||
@@ -12,4 +12,4 @@ }; | ||
getLongestMatch(sequence: MatchEstimated[]): MatchEstimated; | ||
getMatchFeedback(match: MatchEstimated, isSoleMatch: Boolean): FeedbackType | null; | ||
getMatchFeedback(match: MatchEstimated, isSoleMatch: boolean): FeedbackType | null; | ||
} | ||
export default Feedback; |
@@ -19,3 +19,2 @@ import zxcvbnOptions from './Options.esm.js'; | ||
*/ | ||
class Feedback { | ||
@@ -38,7 +37,5 @@ constructor() { | ||
} | ||
setDefaultSuggestions() { | ||
this.defaultFeedback.suggestions.push(zxcvbnOptions.translations.suggestions.useWords, zxcvbnOptions.translations.suggestions.noNeed); | ||
} | ||
getFeedback(score, sequence) { | ||
@@ -48,14 +45,10 @@ if (sequence.length === 0) { | ||
} | ||
if (score > 2) { | ||
return defaultFeedback; | ||
} | ||
const extraFeedback = zxcvbnOptions.translations.suggestions.anotherWord; | ||
const longestMatch = this.getLongestMatch(sequence); | ||
let feedback = this.getMatchFeedback(longestMatch, sequence.length === 1); | ||
if (feedback !== null && feedback !== undefined) { | ||
feedback.suggestions.unshift(extraFeedback); | ||
if (feedback.warning == null) { | ||
@@ -70,6 +63,4 @@ feedback.warning = ''; | ||
} | ||
return feedback; | ||
} | ||
getLongestMatch(sequence) { | ||
@@ -85,3 +76,2 @@ let longestMatch = sequence[0]; | ||
} | ||
getMatchFeedback(match, isSoleMatch) { | ||
@@ -91,10 +81,7 @@ if (this.matchers[match.pattern]) { | ||
} | ||
if (zxcvbnOptions.matchers[match.pattern] && 'feedback' in zxcvbnOptions.matchers[match.pattern]) { | ||
return zxcvbnOptions.matchers[match.pattern].feedback(match, isSoleMatch); | ||
} | ||
return defaultFeedback; | ||
} | ||
} | ||
@@ -101,0 +88,0 @@ |
@@ -21,3 +21,2 @@ 'use strict'; | ||
*/ | ||
class Feedback { | ||
@@ -40,7 +39,5 @@ constructor() { | ||
} | ||
setDefaultSuggestions() { | ||
this.defaultFeedback.suggestions.push(Options["default"].translations.suggestions.useWords, Options["default"].translations.suggestions.noNeed); | ||
this.defaultFeedback.suggestions.push(Options.default.translations.suggestions.useWords, Options.default.translations.suggestions.noNeed); | ||
} | ||
getFeedback(score, sequence) { | ||
@@ -50,14 +47,10 @@ if (sequence.length === 0) { | ||
} | ||
if (score > 2) { | ||
return defaultFeedback; | ||
} | ||
const extraFeedback = Options["default"].translations.suggestions.anotherWord; | ||
const extraFeedback = Options.default.translations.suggestions.anotherWord; | ||
const longestMatch = this.getLongestMatch(sequence); | ||
let feedback = this.getMatchFeedback(longestMatch, sequence.length === 1); | ||
if (feedback !== null && feedback !== undefined) { | ||
feedback.suggestions.unshift(extraFeedback); | ||
if (feedback.warning == null) { | ||
@@ -72,6 +65,4 @@ feedback.warning = ''; | ||
} | ||
return feedback; | ||
} | ||
getLongestMatch(sequence) { | ||
@@ -87,3 +78,2 @@ let longestMatch = sequence[0]; | ||
} | ||
getMatchFeedback(match, isSoleMatch) { | ||
@@ -93,10 +83,7 @@ if (this.matchers[match.pattern]) { | ||
} | ||
if (Options["default"].matchers[match.pattern] && 'feedback' in Options["default"].matchers[match.pattern]) { | ||
return Options["default"].matchers[match.pattern].feedback(match, isSoleMatch); | ||
if (Options.default.matchers[match.pattern] && 'feedback' in Options.default.matchers[match.pattern]) { | ||
return Options.default.matchers[match.pattern].feedback(match, isSoleMatch); | ||
} | ||
return defaultFeedback; | ||
} | ||
} | ||
@@ -103,0 +90,0 @@ |
const empty = obj => Object.keys(obj).length === 0; | ||
const extend = (listToExtend, list) => // eslint-disable-next-line prefer-spread | ||
const extend = (listToExtend, list) => | ||
// eslint-disable-next-line prefer-spread | ||
listToExtend.push.apply(listToExtend, list); | ||
@@ -7,4 +8,4 @@ const translate = (string, chrMap) => { | ||
return tempArray.map(char => chrMap[char] || char).join(''); | ||
}; // mod implementation that works for negative numbers | ||
}; | ||
// sort on i primary, j secondary | ||
const sorted = matches => matches.sort((m1, m2) => m1.i - m2.i || m1.j - m2.j); | ||
@@ -14,3 +15,2 @@ const buildRankedDictionary = orderedList => { | ||
let counter = 1; // rank starts at 1, not 0 | ||
orderedList.forEach(word => { | ||
@@ -17,0 +17,0 @@ result[word] = counter; |
'use strict'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
const empty = obj => Object.keys(obj).length === 0; | ||
const extend = (listToExtend, list) => // eslint-disable-next-line prefer-spread | ||
const extend = (listToExtend, list) => | ||
// eslint-disable-next-line prefer-spread | ||
listToExtend.push.apply(listToExtend, list); | ||
@@ -11,4 +10,4 @@ const translate = (string, chrMap) => { | ||
return tempArray.map(char => chrMap[char] || char).join(''); | ||
}; // mod implementation that works for negative numbers | ||
}; | ||
// sort on i primary, j secondary | ||
const sorted = matches => matches.sort((m1, m2) => m1.i - m2.i || m1.j - m2.j); | ||
@@ -18,3 +17,2 @@ const buildRankedDictionary = orderedList => { | ||
let counter = 1; // rank starts at 1, not 0 | ||
orderedList.forEach(word => { | ||
@@ -21,0 +19,0 @@ result[word] = counter; |
@@ -1,6 +0,6 @@ | ||
import zxcvbnOptions from './Options'; | ||
import zxcvbnOptions, { Options } from './Options'; | ||
import debounce from './debounce'; | ||
import { ZxcvbnResult } from './types'; | ||
import { MatchExtended, ZxcvbnResult, Matcher, MatchOptions } from './types'; | ||
export declare const zxcvbn: (password: string, userInputs?: (string | number)[]) => ZxcvbnResult; | ||
export declare const zxcvbnAsync: (password: string, userInputs?: (string | number)[]) => Promise<ZxcvbnResult>; | ||
export { zxcvbnOptions, ZxcvbnResult, debounce }; | ||
export { zxcvbnOptions, ZxcvbnResult, debounce, Options, Matcher, MatchOptions, MatchExtended, }; |
@@ -6,7 +6,6 @@ import Matching from './Matching.esm.js'; | ||
import zxcvbnOptions from './Options.esm.js'; | ||
export { default as zxcvbnOptions } from './Options.esm.js'; | ||
export { Options } from './Options.esm.js'; | ||
export { default as debounce } from './debounce.esm.js'; | ||
const time = () => new Date().getTime(); | ||
const createReturnValue = (resolvedMatches, password, start) => { | ||
@@ -25,3 +24,2 @@ const feedback = new Feedback(); | ||
}; | ||
const main = (password, userInputs) => { | ||
@@ -31,15 +29,11 @@ if (userInputs) { | ||
} | ||
const matching = new Matching(); | ||
return matching.match(password); | ||
}; | ||
const zxcvbn = (password, userInputs) => { | ||
const start = time(); | ||
const matches = main(password, userInputs); | ||
if (matches instanceof Promise) { | ||
throw new Error('You are using a Promised matcher, please use `zxcvbnAsync` for it.'); | ||
} | ||
return createReturnValue(matches, password, start); | ||
@@ -53,3 +47,3 @@ }; | ||
export { zxcvbn, zxcvbnAsync }; | ||
export { zxcvbn, zxcvbnAsync, zxcvbnOptions }; | ||
//# sourceMappingURL=index.esm.js.map |
'use strict'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
var Matching = require('./Matching.js'); | ||
@@ -13,3 +11,2 @@ var index = require('./scoring/index.js'); | ||
const time = () => new Date().getTime(); | ||
const createReturnValue = (resolvedMatches, password, start) => { | ||
@@ -28,20 +25,15 @@ const feedback = new Feedback(); | ||
}; | ||
const main = (password, userInputs) => { | ||
if (userInputs) { | ||
Options["default"].extendUserInputsDictionary(userInputs); | ||
Options.default.extendUserInputsDictionary(userInputs); | ||
} | ||
const matching = new Matching(); | ||
return matching.match(password); | ||
}; | ||
const zxcvbn = (password, userInputs) => { | ||
const start = time(); | ||
const matches = main(password, userInputs); | ||
if (matches instanceof Promise) { | ||
throw new Error('You are using a Promised matcher, please use `zxcvbnAsync` for it.'); | ||
} | ||
return createReturnValue(matches, password, start); | ||
@@ -55,3 +47,4 @@ }; | ||
exports.zxcvbnOptions = Options["default"]; | ||
exports.Options = Options.Options; | ||
exports.zxcvbnOptions = Options.default; | ||
exports.debounce = debounce; | ||
@@ -58,0 +51,0 @@ exports.zxcvbn = zxcvbn; |
@@ -1,2 +0,2 @@ | ||
import { distance } from './vendor/fastest-levenshtein.esm.js'; | ||
import { distance } from 'fastest-levenshtein'; | ||
@@ -6,7 +6,6 @@ const getUsedThreshold = (password, entry, threshold) => { | ||
const isThresholdLongerThanPassword = password.length <= threshold; | ||
const shouldUsePasswordLength = isPasswordToShort || isThresholdLongerThanPassword; // if password is too small use the password length divided by 4 while the threshold needs to be at least 1 | ||
const shouldUsePasswordLength = isPasswordToShort || isThresholdLongerThanPassword; | ||
// if password is too small use the password length divided by 4 while the threshold needs to be at least 1 | ||
return shouldUsePasswordLength ? Math.ceil(password.length / 4) : threshold; | ||
}; | ||
const findLevenshteinDistance = (password, rankedDictionary, threshold) => { | ||
@@ -18,10 +17,7 @@ let foundDistance = 0; | ||
const isInThreshold = foundEntryDistance <= usedThreshold; | ||
if (isInThreshold) { | ||
foundDistance = foundEntryDistance; | ||
} | ||
return isInThreshold; | ||
}); | ||
if (found) { | ||
@@ -33,3 +29,2 @@ return { | ||
} | ||
return {}; | ||
@@ -36,0 +31,0 @@ }; |
'use strict'; | ||
var fastestLevenshtein = require('./vendor/fastest-levenshtein.js'); | ||
var fastestLevenshtein = require('fastest-levenshtein'); | ||
@@ -8,7 +8,6 @@ const getUsedThreshold = (password, entry, threshold) => { | ||
const isThresholdLongerThanPassword = password.length <= threshold; | ||
const shouldUsePasswordLength = isPasswordToShort || isThresholdLongerThanPassword; // if password is too small use the password length divided by 4 while the threshold needs to be at least 1 | ||
const shouldUsePasswordLength = isPasswordToShort || isThresholdLongerThanPassword; | ||
// if password is too small use the password length divided by 4 while the threshold needs to be at least 1 | ||
return shouldUsePasswordLength ? Math.ceil(password.length / 4) : threshold; | ||
}; | ||
const findLevenshteinDistance = (password, rankedDictionary, threshold) => { | ||
@@ -20,10 +19,7 @@ let foundDistance = 0; | ||
const isInThreshold = foundEntryDistance <= usedThreshold; | ||
if (isInThreshold) { | ||
foundDistance = foundEntryDistance; | ||
} | ||
return isInThreshold; | ||
}); | ||
if (found) { | ||
@@ -35,3 +31,2 @@ return { | ||
} | ||
return {}; | ||
@@ -38,0 +33,0 @@ }; |
@@ -7,10 +7,8 @@ import { BRUTEFORCE_CARDINALITY, MIN_SUBMATCH_GUESSES_SINGLE_CHAR, MIN_SUBMATCH_GUESSES_MULTI_CHAR } from '../../data/const.esm.js'; | ||
let guesses = BRUTEFORCE_CARDINALITY ** token.length; | ||
if (guesses === Number.POSITIVE_INFINITY) { | ||
guesses = Number.MAX_VALUE; | ||
} | ||
let minGuesses; // small detail: make bruteforce matches at minimum one guess bigger than smallest allowed | ||
let minGuesses; | ||
// small detail: make bruteforce matches at minimum one guess bigger than smallest allowed | ||
// submatch guesses, such that non-bruteforce submatches over the same [i..j] take precedence. | ||
if (token.length === 1) { | ||
@@ -21,3 +19,2 @@ minGuesses = MIN_SUBMATCH_GUESSES_SINGLE_CHAR + 1; | ||
} | ||
return Math.max(guesses, minGuesses); | ||
@@ -24,0 +21,0 @@ }); |
@@ -9,10 +9,8 @@ 'use strict'; | ||
let guesses = _const.BRUTEFORCE_CARDINALITY ** token.length; | ||
if (guesses === Number.POSITIVE_INFINITY) { | ||
guesses = Number.MAX_VALUE; | ||
} | ||
let minGuesses; // small detail: make bruteforce matches at minimum one guess bigger than smallest allowed | ||
let minGuesses; | ||
// small detail: make bruteforce matches at minimum one guess bigger than smallest allowed | ||
// submatch guesses, such that non-bruteforce submatches over the same [i..j] take precedence. | ||
if (token.length === 1) { | ||
@@ -23,3 +21,2 @@ minGuesses = _const.MIN_SUBMATCH_GUESSES_SINGLE_CHAR + 1; | ||
} | ||
return Math.max(guesses, minGuesses); | ||
@@ -26,0 +23,0 @@ }); |
@@ -7,4 +7,4 @@ 'use strict'; | ||
return { | ||
warning: Options["default"].translations.warnings.dates, | ||
suggestions: [Options["default"].translations.suggestions.dates] | ||
warning: Options.default.translations.warnings.dates, | ||
suggestions: [Options.default.translations.suggestions.dates] | ||
}; | ||
@@ -11,0 +11,0 @@ }); |
@@ -9,3 +9,2 @@ import { DATE_MIN_YEAR, DATE_MAX_YEAR, REFERENCE_YEAR, DATE_SPLITS } from '../../data/const.esm.js'; | ||
*/ | ||
class MatchDate { | ||
@@ -39,7 +38,6 @@ /* | ||
} | ||
getMatchesWithSeparator(password) { | ||
const matches = []; | ||
const maybeDateWithSeparator = /^(\d{1,4})([\s/\\_.-])(\d{1,2})\2(\d{1,4})$/; // # dates with separators are between length 6 '1/1/91' and 10 '11/11/1991' | ||
const maybeDateWithSeparator = /^(\d{1,4})([\s/\\_.-])(\d{1,2})\2(\d{1,4})$/; | ||
// # dates with separators are between length 6 '1/1/91' and 10 '11/11/1991' | ||
for (let i = 0; i <= Math.abs(password.length - 6); i += 1) { | ||
@@ -50,9 +48,6 @@ for (let j = i + 5; j <= i + 9; j += 1) { | ||
} | ||
const token = password.slice(i, +j + 1 || 9e9); | ||
const regexMatch = maybeDateWithSeparator.exec(token); | ||
if (regexMatch != null) { | ||
const dmy = this.mapIntegersToDayMonthYear([parseInt(regexMatch[1], 10), parseInt(regexMatch[3], 10), parseInt(regexMatch[4], 10)]); | ||
if (dmy != null) { | ||
@@ -73,14 +68,10 @@ matches.push({ | ||
} | ||
return matches; | ||
} // eslint-disable-next-line max-statements | ||
} | ||
// eslint-disable-next-line max-statements | ||
getMatchesWithoutSeparator(password) { | ||
const matches = []; | ||
const maybeDateNoSeparator = /^\d{4,8}$/; | ||
const metric = candidate => Math.abs(candidate.year - REFERENCE_YEAR); // # dates without separators are between length 4 '1191' and 8 '11111991' | ||
const metric = candidate => Math.abs(candidate.year - REFERENCE_YEAR); | ||
// # dates without separators are between length 4 '1191' and 8 '11111991' | ||
for (let i = 0; i <= Math.abs(password.length - 4); i += 1) { | ||
@@ -91,5 +82,3 @@ for (let j = i + 3; j <= i + 7; j += 1) { | ||
} | ||
const token = password.slice(i, +j + 1 || 9e9); | ||
if (maybeDateNoSeparator.exec(token)) { | ||
@@ -101,3 +90,2 @@ const candidates = []; | ||
const dmy = this.mapIntegersToDayMonthYear([parseInt(token.slice(0, k), 10), parseInt(token.slice(k, l), 10), parseInt(token.slice(l), 10)]); | ||
if (dmy != null) { | ||
@@ -107,3 +95,2 @@ candidates.push(dmy); | ||
}); | ||
if (candidates.length > 0) { | ||
@@ -123,3 +110,2 @@ /* | ||
const distance = metric(candidate); | ||
if (distance < minDistance) { | ||
@@ -144,3 +130,2 @@ bestCandidate = candidate; | ||
} | ||
return matches; | ||
@@ -157,4 +142,2 @@ } | ||
*/ | ||
filterNoise(matches) { | ||
@@ -164,6 +147,4 @@ return matches.filter(match => { | ||
const matchesLength = matches.length; | ||
for (let o = 0; o < matchesLength; o += 1) { | ||
const otherMatch = matches[o]; | ||
if (match !== otherMatch) { | ||
@@ -176,3 +157,2 @@ if (otherMatch.i <= match.i && otherMatch.j >= match.j) { | ||
} | ||
return !isSubmatch; | ||
@@ -192,4 +172,2 @@ }); | ||
// eslint-disable-next-line complexity, max-statements | ||
mapIntegersToDayMonthYear(integers) { | ||
@@ -199,22 +177,16 @@ if (integers[1] > 31 || integers[1] <= 0) { | ||
} | ||
let over12 = 0; | ||
let over31 = 0; | ||
let under1 = 0; | ||
for (let o = 0, len1 = integers.length; o < len1; o += 1) { | ||
const int = integers[o]; | ||
if (int > 99 && int < DATE_MIN_YEAR || int > DATE_MAX_YEAR) { | ||
return null; | ||
} | ||
if (int > 31) { | ||
over31 += 1; | ||
} | ||
if (int > 12) { | ||
over12 += 1; | ||
} | ||
if (int <= 0) { | ||
@@ -224,11 +196,8 @@ under1 += 1; | ||
} | ||
if (over31 >= 2 || over12 === 3 || under1 >= 2) { | ||
return null; | ||
} | ||
return this.getDayMonth(integers); | ||
} // eslint-disable-next-line max-statements | ||
} | ||
// eslint-disable-next-line max-statements | ||
getDayMonth(integers) { | ||
@@ -238,10 +207,8 @@ // first look for a four digit year: yyyy + daymonth or daymonth + yyyy | ||
]; | ||
const possibleYearSplitsLength = possibleYearSplits.length; | ||
for (let j = 0; j < possibleYearSplitsLength; j += 1) { | ||
const [y, rest] = possibleYearSplits[j]; | ||
if (DATE_MIN_YEAR <= y && y <= DATE_MAX_YEAR) { | ||
const dm = this.mapIntegersToDayMonth(rest); | ||
if (dm != null) { | ||
@@ -259,14 +226,10 @@ return { | ||
*/ | ||
return null; | ||
} | ||
} // given no four-digit year, two digit years are the most flexible int to match, so | ||
} | ||
// given no four-digit year, two digit years are the most flexible int to match, so | ||
// try to parse a day-month out of integers[0..1] or integers[1..0] | ||
for (let k = 0; k < possibleYearSplitsLength; k += 1) { | ||
const [y, rest] = possibleYearSplits[k]; | ||
const dm = this.mapIntegersToDayMonth(rest); | ||
if (dm != null) { | ||
@@ -280,9 +243,6 @@ return { | ||
} | ||
return null; | ||
} | ||
mapIntegersToDayMonth(integers) { | ||
const temp = [integers, integers.slice().reverse()]; | ||
for (let i = 0; i < temp.length; i += 1) { | ||
@@ -292,3 +252,2 @@ const data = temp[i]; | ||
const month = data[1]; | ||
if (day >= 1 && day <= 31 && month >= 1 && month <= 12) { | ||
@@ -301,6 +260,4 @@ return { | ||
} | ||
return null; | ||
} | ||
twoToFourDigitYear(year) { | ||
@@ -310,12 +267,9 @@ if (year > 99) { | ||
} | ||
if (year > 50) { | ||
// 87 -> 1987 | ||
return year + 1900; | ||
} // 15 -> 2015 | ||
} | ||
// 15 -> 2015 | ||
return year + 2000; | ||
} | ||
} | ||
@@ -322,0 +276,0 @@ |
@@ -11,3 +11,2 @@ 'use strict'; | ||
*/ | ||
class MatchDate { | ||
@@ -41,7 +40,6 @@ /* | ||
} | ||
getMatchesWithSeparator(password) { | ||
const matches = []; | ||
const maybeDateWithSeparator = /^(\d{1,4})([\s/\\_.-])(\d{1,2})\2(\d{1,4})$/; // # dates with separators are between length 6 '1/1/91' and 10 '11/11/1991' | ||
const maybeDateWithSeparator = /^(\d{1,4})([\s/\\_.-])(\d{1,2})\2(\d{1,4})$/; | ||
// # dates with separators are between length 6 '1/1/91' and 10 '11/11/1991' | ||
for (let i = 0; i <= Math.abs(password.length - 6); i += 1) { | ||
@@ -52,9 +50,6 @@ for (let j = i + 5; j <= i + 9; j += 1) { | ||
} | ||
const token = password.slice(i, +j + 1 || 9e9); | ||
const regexMatch = maybeDateWithSeparator.exec(token); | ||
if (regexMatch != null) { | ||
const dmy = this.mapIntegersToDayMonthYear([parseInt(regexMatch[1], 10), parseInt(regexMatch[3], 10), parseInt(regexMatch[4], 10)]); | ||
if (dmy != null) { | ||
@@ -75,14 +70,10 @@ matches.push({ | ||
} | ||
return matches; | ||
} // eslint-disable-next-line max-statements | ||
} | ||
// eslint-disable-next-line max-statements | ||
getMatchesWithoutSeparator(password) { | ||
const matches = []; | ||
const maybeDateNoSeparator = /^\d{4,8}$/; | ||
const metric = candidate => Math.abs(candidate.year - _const.REFERENCE_YEAR); // # dates without separators are between length 4 '1191' and 8 '11111991' | ||
const metric = candidate => Math.abs(candidate.year - _const.REFERENCE_YEAR); | ||
// # dates without separators are between length 4 '1191' and 8 '11111991' | ||
for (let i = 0; i <= Math.abs(password.length - 4); i += 1) { | ||
@@ -93,5 +84,3 @@ for (let j = i + 3; j <= i + 7; j += 1) { | ||
} | ||
const token = password.slice(i, +j + 1 || 9e9); | ||
if (maybeDateNoSeparator.exec(token)) { | ||
@@ -103,3 +92,2 @@ const candidates = []; | ||
const dmy = this.mapIntegersToDayMonthYear([parseInt(token.slice(0, k), 10), parseInt(token.slice(k, l), 10), parseInt(token.slice(l), 10)]); | ||
if (dmy != null) { | ||
@@ -109,3 +97,2 @@ candidates.push(dmy); | ||
}); | ||
if (candidates.length > 0) { | ||
@@ -125,3 +112,2 @@ /* | ||
const distance = metric(candidate); | ||
if (distance < minDistance) { | ||
@@ -146,3 +132,2 @@ bestCandidate = candidate; | ||
} | ||
return matches; | ||
@@ -159,4 +144,2 @@ } | ||
*/ | ||
filterNoise(matches) { | ||
@@ -166,6 +149,4 @@ return matches.filter(match => { | ||
const matchesLength = matches.length; | ||
for (let o = 0; o < matchesLength; o += 1) { | ||
const otherMatch = matches[o]; | ||
if (match !== otherMatch) { | ||
@@ -178,3 +159,2 @@ if (otherMatch.i <= match.i && otherMatch.j >= match.j) { | ||
} | ||
return !isSubmatch; | ||
@@ -194,4 +174,2 @@ }); | ||
// eslint-disable-next-line complexity, max-statements | ||
mapIntegersToDayMonthYear(integers) { | ||
@@ -201,22 +179,16 @@ if (integers[1] > 31 || integers[1] <= 0) { | ||
} | ||
let over12 = 0; | ||
let over31 = 0; | ||
let under1 = 0; | ||
for (let o = 0, len1 = integers.length; o < len1; o += 1) { | ||
const int = integers[o]; | ||
if (int > 99 && int < _const.DATE_MIN_YEAR || int > _const.DATE_MAX_YEAR) { | ||
return null; | ||
} | ||
if (int > 31) { | ||
over31 += 1; | ||
} | ||
if (int > 12) { | ||
over12 += 1; | ||
} | ||
if (int <= 0) { | ||
@@ -226,11 +198,8 @@ under1 += 1; | ||
} | ||
if (over31 >= 2 || over12 === 3 || under1 >= 2) { | ||
return null; | ||
} | ||
return this.getDayMonth(integers); | ||
} // eslint-disable-next-line max-statements | ||
} | ||
// eslint-disable-next-line max-statements | ||
getDayMonth(integers) { | ||
@@ -240,10 +209,8 @@ // first look for a four digit year: yyyy + daymonth or daymonth + yyyy | ||
]; | ||
const possibleYearSplitsLength = possibleYearSplits.length; | ||
for (let j = 0; j < possibleYearSplitsLength; j += 1) { | ||
const [y, rest] = possibleYearSplits[j]; | ||
if (_const.DATE_MIN_YEAR <= y && y <= _const.DATE_MAX_YEAR) { | ||
const dm = this.mapIntegersToDayMonth(rest); | ||
if (dm != null) { | ||
@@ -261,14 +228,10 @@ return { | ||
*/ | ||
return null; | ||
} | ||
} // given no four-digit year, two digit years are the most flexible int to match, so | ||
} | ||
// given no four-digit year, two digit years are the most flexible int to match, so | ||
// try to parse a day-month out of integers[0..1] or integers[1..0] | ||
for (let k = 0; k < possibleYearSplitsLength; k += 1) { | ||
const [y, rest] = possibleYearSplits[k]; | ||
const dm = this.mapIntegersToDayMonth(rest); | ||
if (dm != null) { | ||
@@ -282,9 +245,6 @@ return { | ||
} | ||
return null; | ||
} | ||
mapIntegersToDayMonth(integers) { | ||
const temp = [integers, integers.slice().reverse()]; | ||
for (let i = 0; i < temp.length; i += 1) { | ||
@@ -294,3 +254,2 @@ const data = temp[i]; | ||
const month = data[1]; | ||
if (day >= 1 && day <= 31 && month >= 1 && month <= 12) { | ||
@@ -303,6 +262,4 @@ return { | ||
} | ||
return null; | ||
} | ||
twoToFourDigitYear(year) { | ||
@@ -312,12 +269,9 @@ if (year > 99) { | ||
} | ||
if (year > 50) { | ||
// 87 -> 1987 | ||
return year + 1900; | ||
} // 15 -> 2015 | ||
} | ||
// 15 -> 2015 | ||
return year + 2000; | ||
} | ||
} | ||
@@ -324,0 +278,0 @@ |
@@ -9,8 +9,7 @@ import { REFERENCE_YEAR, MIN_YEAR_SPACE } from '../../data/const.esm.js'; | ||
const yearSpace = Math.max(Math.abs(year - REFERENCE_YEAR), MIN_YEAR_SPACE); | ||
let guesses = yearSpace * 365; // add factor of 4 for separator selection (one of ~4 choices) | ||
let guesses = yearSpace * 365; | ||
// add factor of 4 for separator selection (one of ~4 choices) | ||
if (separator) { | ||
guesses *= 4; | ||
} | ||
return guesses; | ||
@@ -17,0 +16,0 @@ }); |
@@ -11,8 +11,7 @@ 'use strict'; | ||
const yearSpace = Math.max(Math.abs(year - _const.REFERENCE_YEAR), _const.MIN_YEAR_SPACE); | ||
let guesses = yearSpace * 365; // add factor of 4 for separator selection (one of ~4 choices) | ||
let guesses = yearSpace * 365; | ||
// add factor of 4 for separator selection (one of ~4 choices) | ||
if (separator) { | ||
guesses *= 4; | ||
} | ||
return guesses; | ||
@@ -19,0 +18,0 @@ }); |
import { MatchEstimated } from '../../types'; | ||
declare const _default: (match: MatchEstimated, isSoleMatch?: Boolean) => { | ||
declare const _default: (match: MatchEstimated, isSoleMatch?: boolean) => { | ||
warning: string; | ||
@@ -4,0 +4,0 @@ suggestions: string[]; |
@@ -6,3 +6,2 @@ import zxcvbnOptions from '../../Options.esm.js'; | ||
let warning = ''; | ||
if (isSoleMatch && !match.l33t && !match.reversed) { | ||
@@ -19,16 +18,11 @@ if (match.rank <= 10) { | ||
} | ||
return warning; | ||
}; | ||
const getDictionaryWarningWikipedia = (match, isSoleMatch) => { | ||
let warning = ''; | ||
if (isSoleMatch) { | ||
warning = zxcvbnOptions.translations.warnings.wordByItself; | ||
} | ||
return warning; | ||
}; | ||
const getDictionaryWarningNames = (match, isSoleMatch) => { | ||
@@ -38,6 +32,4 @@ if (isSoleMatch) { | ||
} | ||
return zxcvbnOptions.translations.warnings.commonNames; | ||
}; | ||
const getDictionaryWarning = (match, isSoleMatch) => { | ||
@@ -47,3 +39,2 @@ let warning = ''; | ||
const isAName = dictName === 'lastnames' || dictName.toLowerCase().includes('firstnames'); | ||
if (dictName === 'passwords') { | ||
@@ -58,6 +49,4 @@ warning = getDictionaryWarningPassword(match, isSoleMatch); | ||
} | ||
return warning; | ||
}; | ||
var dictionaryMatcher = ((match, isSoleMatch) => { | ||
@@ -67,3 +56,2 @@ const warning = getDictionaryWarning(match, isSoleMatch); | ||
const word = match.token; | ||
if (word.match(START_UPPER)) { | ||
@@ -74,11 +62,8 @@ suggestions.push(zxcvbnOptions.translations.suggestions.capitalization); | ||
} | ||
if (match.reversed && match.token.length >= 4) { | ||
suggestions.push(zxcvbnOptions.translations.suggestions.reverseWords); | ||
} | ||
if (match.l33t) { | ||
suggestions.push(zxcvbnOptions.translations.suggestions.l33t); | ||
} | ||
return { | ||
@@ -85,0 +70,0 @@ warning, |
@@ -8,36 +8,28 @@ 'use strict'; | ||
let warning = ''; | ||
if (isSoleMatch && !match.l33t && !match.reversed) { | ||
if (match.rank <= 10) { | ||
warning = Options["default"].translations.warnings.topTen; | ||
warning = Options.default.translations.warnings.topTen; | ||
} else if (match.rank <= 100) { | ||
warning = Options["default"].translations.warnings.topHundred; | ||
warning = Options.default.translations.warnings.topHundred; | ||
} else { | ||
warning = Options["default"].translations.warnings.common; | ||
warning = Options.default.translations.warnings.common; | ||
} | ||
} else if (match.guessesLog10 <= 4) { | ||
warning = Options["default"].translations.warnings.similarToCommon; | ||
warning = Options.default.translations.warnings.similarToCommon; | ||
} | ||
return warning; | ||
}; | ||
const getDictionaryWarningWikipedia = (match, isSoleMatch) => { | ||
let warning = ''; | ||
if (isSoleMatch) { | ||
warning = Options["default"].translations.warnings.wordByItself; | ||
warning = Options.default.translations.warnings.wordByItself; | ||
} | ||
return warning; | ||
}; | ||
const getDictionaryWarningNames = (match, isSoleMatch) => { | ||
if (isSoleMatch) { | ||
return Options["default"].translations.warnings.namesByThemselves; | ||
return Options.default.translations.warnings.namesByThemselves; | ||
} | ||
return Options["default"].translations.warnings.commonNames; | ||
return Options.default.translations.warnings.commonNames; | ||
}; | ||
const getDictionaryWarning = (match, isSoleMatch) => { | ||
@@ -47,3 +39,2 @@ let warning = ''; | ||
const isAName = dictName === 'lastnames' || dictName.toLowerCase().includes('firstnames'); | ||
if (dictName === 'passwords') { | ||
@@ -56,8 +47,6 @@ warning = getDictionaryWarningPassword(match, isSoleMatch); | ||
} else if (dictName === 'userInputs') { | ||
warning = Options["default"].translations.warnings.userInputs; | ||
warning = Options.default.translations.warnings.userInputs; | ||
} | ||
return warning; | ||
}; | ||
var dictionaryMatcher = ((match, isSoleMatch) => { | ||
@@ -67,17 +56,13 @@ const warning = getDictionaryWarning(match, isSoleMatch); | ||
const word = match.token; | ||
if (word.match(_const.START_UPPER)) { | ||
suggestions.push(Options["default"].translations.suggestions.capitalization); | ||
suggestions.push(Options.default.translations.suggestions.capitalization); | ||
} else if (word.match(_const.ALL_UPPER_INVERTED) && word.toLowerCase() !== word) { | ||
suggestions.push(Options["default"].translations.suggestions.allUppercase); | ||
suggestions.push(Options.default.translations.suggestions.allUppercase); | ||
} | ||
if (match.reversed && match.token.length >= 4) { | ||
suggestions.push(Options["default"].translations.suggestions.reverseWords); | ||
suggestions.push(Options.default.translations.suggestions.reverseWords); | ||
} | ||
if (match.l33t) { | ||
suggestions.push(Options["default"].translations.suggestions.l33t); | ||
suggestions.push(Options.default.translations.suggestions.l33t); | ||
} | ||
return { | ||
@@ -84,0 +69,0 @@ warning, |
import { DictionaryMatch } from '../../types'; | ||
import Reverse from './variants/matching/reverse'; | ||
import L33t from './variants/matching/l33t'; | ||
interface DictionaryMatchOptions { | ||
password: string; | ||
} | ||
import { DictionaryMatchOptions } from './types'; | ||
declare class MatchDictionary { | ||
@@ -8,0 +6,0 @@ l33t: L33t; |
import findLevenshteinDistance from '../../levenshtein.esm.js'; | ||
import { sorted } from '../../helper.esm.js'; | ||
import zxcvbnOptions from '../../Options.esm.js'; | ||
import MatchL33t$1 from './variants/matching/reverse.esm.js'; | ||
import MatchReverse from './variants/matching/reverse.esm.js'; | ||
import MatchL33t from './variants/matching/l33t.esm.js'; | ||
@@ -10,5 +10,4 @@ | ||
this.l33t = new MatchL33t(this.defaultMatch); | ||
this.reverse = new MatchL33t$1(this.defaultMatch); | ||
this.reverse = new MatchReverse(this.defaultMatch); | ||
} | ||
match({ | ||
@@ -26,3 +25,2 @@ password | ||
} | ||
defaultMatch({ | ||
@@ -33,22 +31,21 @@ password | ||
const passwordLength = password.length; | ||
const passwordLower = password.toLowerCase(); // eslint-disable-next-line complexity | ||
const passwordLower = password.toLowerCase(); | ||
// eslint-disable-next-line complexity,max-statements | ||
Object.keys(zxcvbnOptions.rankedDictionaries).forEach(dictionaryName => { | ||
const rankedDict = zxcvbnOptions.rankedDictionaries[dictionaryName]; | ||
const longestDictionaryWordSize = zxcvbnOptions.rankedDictionariesMaxWordSize[dictionaryName]; | ||
const searchWidth = Math.min(longestDictionaryWordSize, passwordLength); | ||
for (let i = 0; i < passwordLength; i += 1) { | ||
for (let j = i; j < passwordLength; j += 1) { | ||
const searchEnd = Math.min(i + searchWidth, passwordLength); | ||
for (let j = i; j < searchEnd; j += 1) { | ||
const usedPassword = passwordLower.slice(i, +j + 1 || 9e9); | ||
const isInDictionary = (usedPassword in rankedDict); | ||
let foundLevenshteinDistance = {}; // only use levenshtein distance on full password to minimize the performance drop | ||
let foundLevenshteinDistance = {}; | ||
// only use levenshtein distance on full password to minimize the performance drop | ||
// and because otherwise there would be to many false positives | ||
const isFullPassword = i === 0 && j === passwordLength - 1; | ||
if (zxcvbnOptions.useLevenshteinDistance && isFullPassword && !isInDictionary) { | ||
foundLevenshteinDistance = findLevenshteinDistance(usedPassword, rankedDict, zxcvbnOptions.levenshteinThreshold); | ||
} | ||
const isLevenshteinMatch = Object.keys(foundLevenshteinDistance).length !== 0; | ||
if (isInDictionary || isLevenshteinMatch) { | ||
@@ -75,3 +72,2 @@ const usedRankPassword = isLevenshteinMatch ? foundLevenshteinDistance.levenshteinDistanceEntry : usedPassword; | ||
} | ||
} | ||
@@ -78,0 +74,0 @@ |
@@ -14,3 +14,2 @@ 'use strict'; | ||
} | ||
match({ | ||
@@ -28,3 +27,2 @@ password | ||
} | ||
defaultMatch({ | ||
@@ -35,22 +33,21 @@ password | ||
const passwordLength = password.length; | ||
const passwordLower = password.toLowerCase(); // eslint-disable-next-line complexity | ||
Object.keys(Options["default"].rankedDictionaries).forEach(dictionaryName => { | ||
const rankedDict = Options["default"].rankedDictionaries[dictionaryName]; | ||
const passwordLower = password.toLowerCase(); | ||
// eslint-disable-next-line complexity,max-statements | ||
Object.keys(Options.default.rankedDictionaries).forEach(dictionaryName => { | ||
const rankedDict = Options.default.rankedDictionaries[dictionaryName]; | ||
const longestDictionaryWordSize = Options.default.rankedDictionariesMaxWordSize[dictionaryName]; | ||
const searchWidth = Math.min(longestDictionaryWordSize, passwordLength); | ||
for (let i = 0; i < passwordLength; i += 1) { | ||
for (let j = i; j < passwordLength; j += 1) { | ||
const searchEnd = Math.min(i + searchWidth, passwordLength); | ||
for (let j = i; j < searchEnd; j += 1) { | ||
const usedPassword = passwordLower.slice(i, +j + 1 || 9e9); | ||
const isInDictionary = (usedPassword in rankedDict); | ||
let foundLevenshteinDistance = {}; // only use levenshtein distance on full password to minimize the performance drop | ||
let foundLevenshteinDistance = {}; | ||
// only use levenshtein distance on full password to minimize the performance drop | ||
// and because otherwise there would be to many false positives | ||
const isFullPassword = i === 0 && j === passwordLength - 1; | ||
if (Options["default"].useLevenshteinDistance && isFullPassword && !isInDictionary) { | ||
foundLevenshteinDistance = levenshtein(usedPassword, rankedDict, Options["default"].levenshteinThreshold); | ||
if (Options.default.useLevenshteinDistance && isFullPassword && !isInDictionary) { | ||
foundLevenshteinDistance = levenshtein(usedPassword, rankedDict, Options.default.levenshteinThreshold); | ||
} | ||
const isLevenshteinMatch = Object.keys(foundLevenshteinDistance).length !== 0; | ||
if (isInDictionary || isLevenshteinMatch) { | ||
@@ -77,3 +74,2 @@ const usedRankPassword = isLevenshteinMatch ? foundLevenshteinDistance.levenshteinDistanceEntry : usedPassword; | ||
} | ||
} | ||
@@ -80,0 +76,0 @@ |
@@ -12,3 +12,2 @@ import uppercaseVariant from './variants/scoring/uppercase.esm.js'; | ||
const baseGuesses = rank; // keep these as properties for display purposes | ||
const uppercaseVariations = uppercaseVariant(token); | ||
@@ -15,0 +14,0 @@ const l33tVariations = l33tVariant({ |
@@ -14,3 +14,2 @@ 'use strict'; | ||
const baseGuesses = rank; // keep these as properties for display purposes | ||
const uppercaseVariations = uppercase(token); | ||
@@ -17,0 +16,0 @@ const l33tVariations = l33t({ |
import { L33tMatch, LooseObject, OptionsL33tTable } from '../../../../types'; | ||
declare type Subs = string[][][]; | ||
import { DefaultMatch } from '../../types'; | ||
type Subs = string[][][]; | ||
declare class MatchL33t { | ||
defaultMatch: Function; | ||
constructor(defaultMatch: Function); | ||
defaultMatch: DefaultMatch; | ||
constructor(defaultMatch: DefaultMatch); | ||
match({ password }: { | ||
@@ -7,0 +8,0 @@ password: string; |
@@ -9,3 +9,2 @@ import { empty, translate } from '../../../../helper.esm.js'; | ||
*/ | ||
class MatchL33t { | ||
@@ -15,3 +14,2 @@ constructor(defaultMatch) { | ||
} | ||
match({ | ||
@@ -22,10 +20,9 @@ password | ||
const enumeratedSubs = this.enumerateL33tSubs(this.relevantL33tSubtable(password, zxcvbnOptions.l33tTable)); | ||
for (let i = 0; i < enumeratedSubs.length; i += 1) { | ||
const sub = enumeratedSubs[i]; // corner case: password has no relevant subs. | ||
const length = Math.min(enumeratedSubs.length, zxcvbnOptions.l33tMaxSubstitutions); | ||
for (let i = 0; i < length; i += 1) { | ||
const sub = enumeratedSubs[i]; | ||
// corner case: password has no relevant subs. | ||
if (empty(sub)) { | ||
break; | ||
} | ||
const subbedPassword = translate(password, sub); | ||
@@ -36,4 +33,4 @@ const matchedDictionary = this.defaultMatch({ | ||
matchedDictionary.forEach(match => { | ||
const token = password.slice(match.i, +match.j + 1 || 9e9); // only return the matches that contain an actual substitution | ||
const token = password.slice(match.i, +match.j + 1 || 9e9); | ||
// only return the matches that contain an actual substitution | ||
if (token.toLowerCase() !== match.matchedWord) { | ||
@@ -44,3 +41,2 @@ // subset of mappings in sub that are in use for this match | ||
const chr = sub[subbedChr]; | ||
if (token.indexOf(subbedChr) !== -1) { | ||
@@ -51,3 +47,4 @@ matchSub[subbedChr] = chr; | ||
const subDisplay = Object.keys(matchSub).map(k => `${k} -> ${matchSub[k]}`).join(', '); | ||
matches.push({ ...match, | ||
matches.push({ | ||
...match, | ||
l33t: true, | ||
@@ -60,11 +57,9 @@ token, | ||
}); | ||
} // filter single-character l33t matches to reduce noise. | ||
} | ||
// filter single-character l33t matches to reduce noise. | ||
// otherwise '1' matches 'i', '4' matches 'a', both very common English words | ||
// with low dictionary rank. | ||
return matches.filter(match => match.token.length > 1); | ||
} // makes a pruned copy of l33t_table that only includes password's possible substitutions | ||
} | ||
// makes a pruned copy of l33t_table that only includes password's possible substitutions | ||
relevantL33tSubtable(password, table) { | ||
@@ -79,3 +74,2 @@ const passwordChars = {}; | ||
const relevantSubs = subs.filter(sub => sub in passwordChars); | ||
if (relevantSubs.length > 0) { | ||
@@ -86,9 +80,8 @@ subTable[letter] = relevantSubs; | ||
return subTable; | ||
} // returns the list of possible 1337 replacement dictionaries for a given password | ||
} | ||
// returns the list of possible 1337 replacement dictionaries for a given password | ||
enumerateL33tSubs(table) { | ||
const tableKeys = Object.keys(table); | ||
const subs = this.getSubs(tableKeys, [[]], table); // convert from assoc lists to dicts | ||
const subs = this.getSubs(tableKeys, [[]], table); | ||
// convert from assoc lists to dicts | ||
return subs.map(sub => { | ||
@@ -102,3 +95,2 @@ const subDict = {}; | ||
} | ||
getSubs(keys, subs, table) { | ||
@@ -108,3 +100,2 @@ if (!keys.length) { | ||
} | ||
const firstKey = keys[0]; | ||
@@ -116,3 +107,2 @@ const restKeys = keys.slice(1); | ||
let dupL33tIndex = -1; | ||
for (let i = 0; i < sub.length; i += 1) { | ||
@@ -124,3 +114,2 @@ if (sub[i][0] === l33tChr) { | ||
} | ||
if (dupL33tIndex === -1) { | ||
@@ -139,10 +128,7 @@ const subExtension = sub.concat([[l33tChr, firstKey]]); | ||
const newSubs = this.dedup(nextSubs); | ||
if (restKeys.length) { | ||
return this.getSubs(restKeys, newSubs, table); | ||
} | ||
return newSubs; | ||
} | ||
dedup(subs) { | ||
@@ -155,3 +141,2 @@ const deduped = []; | ||
const label = assoc.map(([k, v]) => `${k},${v}`).join('-'); | ||
if (!(label in members)) { | ||
@@ -164,3 +149,2 @@ members[label] = true; | ||
} | ||
} | ||
@@ -167,0 +151,0 @@ |
@@ -11,3 +11,2 @@ 'use strict'; | ||
*/ | ||
class MatchL33t { | ||
@@ -17,3 +16,2 @@ constructor(defaultMatch) { | ||
} | ||
match({ | ||
@@ -23,11 +21,10 @@ password | ||
const matches = []; | ||
const enumeratedSubs = this.enumerateL33tSubs(this.relevantL33tSubtable(password, Options["default"].l33tTable)); | ||
for (let i = 0; i < enumeratedSubs.length; i += 1) { | ||
const sub = enumeratedSubs[i]; // corner case: password has no relevant subs. | ||
const enumeratedSubs = this.enumerateL33tSubs(this.relevantL33tSubtable(password, Options.default.l33tTable)); | ||
const length = Math.min(enumeratedSubs.length, Options.default.l33tMaxSubstitutions); | ||
for (let i = 0; i < length; i += 1) { | ||
const sub = enumeratedSubs[i]; | ||
// corner case: password has no relevant subs. | ||
if (helper.empty(sub)) { | ||
break; | ||
} | ||
const subbedPassword = helper.translate(password, sub); | ||
@@ -38,4 +35,4 @@ const matchedDictionary = this.defaultMatch({ | ||
matchedDictionary.forEach(match => { | ||
const token = password.slice(match.i, +match.j + 1 || 9e9); // only return the matches that contain an actual substitution | ||
const token = password.slice(match.i, +match.j + 1 || 9e9); | ||
// only return the matches that contain an actual substitution | ||
if (token.toLowerCase() !== match.matchedWord) { | ||
@@ -46,3 +43,2 @@ // subset of mappings in sub that are in use for this match | ||
const chr = sub[subbedChr]; | ||
if (token.indexOf(subbedChr) !== -1) { | ||
@@ -53,3 +49,4 @@ matchSub[subbedChr] = chr; | ||
const subDisplay = Object.keys(matchSub).map(k => `${k} -> ${matchSub[k]}`).join(', '); | ||
matches.push({ ...match, | ||
matches.push({ | ||
...match, | ||
l33t: true, | ||
@@ -62,11 +59,9 @@ token, | ||
}); | ||
} // filter single-character l33t matches to reduce noise. | ||
} | ||
// filter single-character l33t matches to reduce noise. | ||
// otherwise '1' matches 'i', '4' matches 'a', both very common English words | ||
// with low dictionary rank. | ||
return matches.filter(match => match.token.length > 1); | ||
} // makes a pruned copy of l33t_table that only includes password's possible substitutions | ||
} | ||
// makes a pruned copy of l33t_table that only includes password's possible substitutions | ||
relevantL33tSubtable(password, table) { | ||
@@ -81,3 +76,2 @@ const passwordChars = {}; | ||
const relevantSubs = subs.filter(sub => sub in passwordChars); | ||
if (relevantSubs.length > 0) { | ||
@@ -88,9 +82,8 @@ subTable[letter] = relevantSubs; | ||
return subTable; | ||
} // returns the list of possible 1337 replacement dictionaries for a given password | ||
} | ||
// returns the list of possible 1337 replacement dictionaries for a given password | ||
enumerateL33tSubs(table) { | ||
const tableKeys = Object.keys(table); | ||
const subs = this.getSubs(tableKeys, [[]], table); // convert from assoc lists to dicts | ||
const subs = this.getSubs(tableKeys, [[]], table); | ||
// convert from assoc lists to dicts | ||
return subs.map(sub => { | ||
@@ -104,3 +97,2 @@ const subDict = {}; | ||
} | ||
getSubs(keys, subs, table) { | ||
@@ -110,3 +102,2 @@ if (!keys.length) { | ||
} | ||
const firstKey = keys[0]; | ||
@@ -118,3 +109,2 @@ const restKeys = keys.slice(1); | ||
let dupL33tIndex = -1; | ||
for (let i = 0; i < sub.length; i += 1) { | ||
@@ -126,3 +116,2 @@ if (sub[i][0] === l33tChr) { | ||
} | ||
if (dupL33tIndex === -1) { | ||
@@ -141,10 +130,7 @@ const subExtension = sub.concat([[l33tChr, firstKey]]); | ||
const newSubs = this.dedup(nextSubs); | ||
if (restKeys.length) { | ||
return this.getSubs(restKeys, newSubs, table); | ||
} | ||
return newSubs; | ||
} | ||
dedup(subs) { | ||
@@ -157,3 +143,2 @@ const deduped = []; | ||
const label = assoc.map(([k, v]) => `${k},${v}`).join('-'); | ||
if (!(label in members)) { | ||
@@ -166,3 +151,2 @@ members[label] = true; | ||
} | ||
} | ||
@@ -169,0 +153,0 @@ |
@@ -1,8 +0,19 @@ | ||
declare class MatchL33t { | ||
defaultMatch: Function; | ||
constructor(defaultMatch: Function); | ||
import { DefaultMatch } from '../../types'; | ||
declare class MatchReverse { | ||
defaultMatch: DefaultMatch; | ||
constructor(defaultMatch: DefaultMatch); | ||
match({ password }: { | ||
password: string; | ||
}): any; | ||
}): { | ||
token: string; | ||
reversed: boolean; | ||
i: number; | ||
j: number; | ||
pattern: "dictionary"; | ||
matchedWord: string; | ||
rank: number; | ||
dictionaryName: import("../../../../types").DictionaryNames; | ||
l33t: boolean; | ||
}[]; | ||
} | ||
export default MatchL33t; | ||
export default MatchReverse; |
@@ -6,7 +6,6 @@ /* | ||
*/ | ||
class MatchL33t { | ||
class MatchReverse { | ||
constructor(defaultMatch) { | ||
this.defaultMatch = defaultMatch; | ||
} | ||
match({ | ||
@@ -18,3 +17,4 @@ password | ||
password: passwordReversed | ||
}).map(match => ({ ...match, | ||
}).map(match => ({ | ||
...match, | ||
token: match.token.split('').reverse().join(''), | ||
@@ -27,6 +27,5 @@ reversed: true, | ||
} | ||
} | ||
export { MatchL33t as default }; | ||
export { MatchReverse as default }; | ||
//# sourceMappingURL=reverse.esm.js.map |
@@ -8,7 +8,6 @@ 'use strict'; | ||
*/ | ||
class MatchL33t { | ||
class MatchReverse { | ||
constructor(defaultMatch) { | ||
this.defaultMatch = defaultMatch; | ||
} | ||
match({ | ||
@@ -20,3 +19,4 @@ password | ||
password: passwordReversed | ||
}).map(match => ({ ...match, | ||
}).map(match => ({ | ||
...match, | ||
token: match.token.split('').reverse().join(''), | ||
@@ -29,6 +29,5 @@ reversed: true, | ||
} | ||
} | ||
module.exports = MatchL33t; | ||
module.exports = MatchReverse; | ||
//# sourceMappingURL=reverse.js.map |
@@ -8,8 +8,8 @@ import utils from '../../../../scoring/utils.esm.js'; | ||
}) => { | ||
const unsubbed = subs[subbed]; // lower-case match.token before calculating: capitalization shouldn't affect l33t calc. | ||
const chrs = token.toLowerCase().split(''); // num of subbed chars | ||
const subbedCount = chrs.filter(char => char === subbed).length; // num of unsubbed chars | ||
const unsubbed = subs[subbed]; | ||
// lower-case match.token before calculating: capitalization shouldn't affect l33t calc. | ||
const chrs = token.toLowerCase().split(''); | ||
// num of subbed chars | ||
const subbedCount = chrs.filter(char => char === subbed).length; | ||
// num of unsubbed chars | ||
const unsubbedCount = chrs.filter(char => char === unsubbed).length; | ||
@@ -21,3 +21,2 @@ return { | ||
}; | ||
var l33tVariant = (({ | ||
@@ -31,3 +30,2 @@ l33t, | ||
} | ||
let variations = 1; | ||
@@ -44,3 +42,2 @@ const subs = sub; | ||
}); | ||
if (subbedCount === 0 || unsubbedCount === 0) { | ||
@@ -56,7 +53,5 @@ // for this sub, password is either fully subbed (444) or fully unsubbed (aaa) | ||
let possibilities = 0; | ||
for (let i = 1; i <= p; i += 1) { | ||
possibilities += utils.nCk(unsubbedCount + subbedCount, i); | ||
} | ||
variations *= possibilities; | ||
@@ -63,0 +58,0 @@ } |
@@ -10,8 +10,8 @@ 'use strict'; | ||
}) => { | ||
const unsubbed = subs[subbed]; // lower-case match.token before calculating: capitalization shouldn't affect l33t calc. | ||
const chrs = token.toLowerCase().split(''); // num of subbed chars | ||
const subbedCount = chrs.filter(char => char === subbed).length; // num of unsubbed chars | ||
const unsubbed = subs[subbed]; | ||
// lower-case match.token before calculating: capitalization shouldn't affect l33t calc. | ||
const chrs = token.toLowerCase().split(''); | ||
// num of subbed chars | ||
const subbedCount = chrs.filter(char => char === subbed).length; | ||
// num of unsubbed chars | ||
const unsubbedCount = chrs.filter(char => char === unsubbed).length; | ||
@@ -23,3 +23,2 @@ return { | ||
}; | ||
var l33tVariant = (({ | ||
@@ -33,3 +32,2 @@ l33t, | ||
} | ||
let variations = 1; | ||
@@ -46,3 +44,2 @@ const subs = sub; | ||
}); | ||
if (subbedCount === 0 || unsubbedCount === 0) { | ||
@@ -58,7 +55,5 @@ // for this sub, password is either fully subbed (444) or fully unsubbed (aaa) | ||
let possibilities = 0; | ||
for (let i = 1; i <= p; i += 1) { | ||
possibilities += utils.nCk(unsubbedCount + subbedCount, i); | ||
} | ||
variations *= possibilities; | ||
@@ -65,0 +60,0 @@ } |
@@ -10,35 +10,27 @@ import utils from '../../../../scoring/utils.esm.js'; | ||
const variationLength = Math.min(upperCaseCount, lowerCaseCount); | ||
for (let i = 1; i <= variationLength; i += 1) { | ||
variations += utils.nCk(upperCaseCount + lowerCaseCount, i); | ||
} | ||
return variations; | ||
}; | ||
var uppercaseVariant = (word => { | ||
// clean words of non alpha characters to remove the reward effekt to capitalize the first letter https://github.com/dropbox/zxcvbn/issues/232 | ||
const cleanedWord = word.replace(ALPHA_INVERTED, ''); | ||
if (cleanedWord.match(ALL_LOWER_INVERTED) || cleanedWord.toLowerCase() === cleanedWord) { | ||
return 1; | ||
} // a capitalized word is the most common capitalization scheme, | ||
} | ||
// a capitalized word is the most common capitalization scheme, | ||
// so it only doubles the search space (uncapitalized + capitalized). | ||
// all caps and end-capitalized are common enough too, underestimate as 2x factor to be safe. | ||
const commonCases = [START_UPPER, END_UPPER, ALL_UPPER_INVERTED]; | ||
const commonCasesLength = commonCases.length; | ||
for (let i = 0; i < commonCasesLength; i += 1) { | ||
const regex = commonCases[i]; | ||
if (cleanedWord.match(regex)) { | ||
return 2; | ||
} | ||
} // otherwise calculate the number of ways to capitalize U+L uppercase+lowercase letters | ||
} | ||
// otherwise calculate the number of ways to capitalize U+L uppercase+lowercase letters | ||
// with U uppercase letters or less. or, if there's more uppercase than lower (for eg. PASSwORD), | ||
// the number of ways to lowercase U+L letters with L lowercase letters or less. | ||
return getVariations(cleanedWord); | ||
@@ -45,0 +37,0 @@ }); |
@@ -12,35 +12,27 @@ 'use strict'; | ||
const variationLength = Math.min(upperCaseCount, lowerCaseCount); | ||
for (let i = 1; i <= variationLength; i += 1) { | ||
variations += utils.nCk(upperCaseCount + lowerCaseCount, i); | ||
} | ||
return variations; | ||
}; | ||
var uppercaseVariant = (word => { | ||
// clean words of non alpha characters to remove the reward effekt to capitalize the first letter https://github.com/dropbox/zxcvbn/issues/232 | ||
const cleanedWord = word.replace(_const.ALPHA_INVERTED, ''); | ||
if (cleanedWord.match(_const.ALL_LOWER_INVERTED) || cleanedWord.toLowerCase() === cleanedWord) { | ||
return 1; | ||
} // a capitalized word is the most common capitalization scheme, | ||
} | ||
// a capitalized word is the most common capitalization scheme, | ||
// so it only doubles the search space (uncapitalized + capitalized). | ||
// all caps and end-capitalized are common enough too, underestimate as 2x factor to be safe. | ||
const commonCases = [_const.START_UPPER, _const.END_UPPER, _const.ALL_UPPER_INVERTED]; | ||
const commonCasesLength = commonCases.length; | ||
for (let i = 0; i < commonCasesLength; i += 1) { | ||
const regex = commonCases[i]; | ||
if (cleanedWord.match(regex)) { | ||
return 2; | ||
} | ||
} // otherwise calculate the number of ways to capitalize U+L uppercase+lowercase letters | ||
} | ||
// otherwise calculate the number of ways to capitalize U+L uppercase+lowercase letters | ||
// with U uppercase letters or less. or, if there's more uppercase than lower (for eg. PASSwORD), | ||
// the number of ways to lowercase U+L letters with L lowercase letters or less. | ||
return getVariations(cleanedWord); | ||
@@ -47,0 +39,0 @@ }); |
@@ -10,3 +10,2 @@ import zxcvbnOptions from '../../Options.esm.js'; | ||
} | ||
return { | ||
@@ -13,0 +12,0 @@ warning: '', |
@@ -8,7 +8,6 @@ 'use strict'; | ||
return { | ||
warning: Options["default"].translations.warnings.recentYears, | ||
suggestions: [Options["default"].translations.suggestions.recentYears, Options["default"].translations.suggestions.associatedYears] | ||
warning: Options.default.translations.warnings.recentYears, | ||
suggestions: [Options.default.translations.suggestions.recentYears, Options.default.translations.suggestions.associatedYears] | ||
}; | ||
} | ||
return { | ||
@@ -15,0 +14,0 @@ warning: '', |
@@ -9,3 +9,2 @@ import { REGEXEN } from '../../data/const.esm.js'; | ||
*/ | ||
class MatchRegex { | ||
@@ -20,5 +19,3 @@ match({ | ||
regex.lastIndex = 0; // keeps regexMatch stateless | ||
const regexMatch = regex.exec(password); | ||
if (regexMatch) { | ||
@@ -38,3 +35,2 @@ const token = regexMatch[0]; | ||
} | ||
} | ||
@@ -41,0 +37,0 @@ |
@@ -11,3 +11,2 @@ 'use strict'; | ||
*/ | ||
class MatchRegex { | ||
@@ -22,5 +21,3 @@ match({ | ||
regex.lastIndex = 0; // keeps regexMatch stateless | ||
const regexMatch = regex.exec(password); | ||
if (regexMatch) { | ||
@@ -40,3 +37,2 @@ const token = regexMatch[0]; | ||
} | ||
} | ||
@@ -43,0 +39,0 @@ |
@@ -16,9 +16,7 @@ import { REFERENCE_YEAR, MIN_YEAR_SPACE } from '../../data/const.esm.js'; | ||
}; | ||
if (regexName in charClassBases) { | ||
return charClassBases[regexName] ** token.length; | ||
} // TODO add more regex types for example special dates like 09.11 | ||
} | ||
// TODO add more regex types for example special dates like 09.11 | ||
// eslint-disable-next-line default-case | ||
switch (regexName) { | ||
@@ -30,3 +28,2 @@ case 'recentYear': | ||
} | ||
return 0; | ||
@@ -33,0 +30,0 @@ }); |
@@ -18,9 +18,7 @@ 'use strict'; | ||
}; | ||
if (regexName in charClassBases) { | ||
return charClassBases[regexName] ** token.length; | ||
} // TODO add more regex types for example special dates like 09.11 | ||
} | ||
// TODO add more regex types for example special dates like 09.11 | ||
// eslint-disable-next-line default-case | ||
switch (regexName) { | ||
@@ -32,3 +30,2 @@ case 'recentYear': | ||
} | ||
return 0; | ||
@@ -35,0 +32,0 @@ }); |
@@ -5,7 +5,5 @@ import zxcvbnOptions from '../../Options.esm.js'; | ||
let warning = zxcvbnOptions.translations.warnings.extendedRepeat; | ||
if (match.baseToken.length === 1) { | ||
warning = zxcvbnOptions.translations.warnings.simpleRepeat; | ||
} | ||
return { | ||
@@ -12,0 +10,0 @@ warning, |
@@ -6,11 +6,9 @@ 'use strict'; | ||
var repeatMatcher = (match => { | ||
let warning = Options["default"].translations.warnings.extendedRepeat; | ||
let warning = Options.default.translations.warnings.extendedRepeat; | ||
if (match.baseToken.length === 1) { | ||
warning = Options["default"].translations.warnings.simpleRepeat; | ||
warning = Options.default.translations.warnings.simpleRepeat; | ||
} | ||
return { | ||
warning, | ||
suggestions: [Options["default"].translations.suggestions.repeated] | ||
suggestions: [Options.default.translations.suggestions.repeated] | ||
}; | ||
@@ -17,0 +15,0 @@ }); |
@@ -8,3 +8,2 @@ import scoring from '../../scoring/index.esm.js'; | ||
*/ | ||
class MatchRepeat { | ||
@@ -18,11 +17,8 @@ // eslint-disable-next-line max-statements | ||
let lastIndex = 0; | ||
while (lastIndex < password.length) { | ||
const greedyMatch = this.getGreedyMatch(password, lastIndex); | ||
const lazyMatch = this.getLazyMatch(password, lastIndex); | ||
if (greedyMatch == null) { | ||
break; | ||
} | ||
const { | ||
@@ -32,3 +28,2 @@ match, | ||
} = this.setMatchToken(greedyMatch, lazyMatch); | ||
if (match) { | ||
@@ -41,15 +36,11 @@ const j = match.index + match[0].length - 1; | ||
} | ||
const hasPromises = matches.some(match => { | ||
return match instanceof Promise; | ||
}); | ||
if (hasPromises) { | ||
return Promise.all(matches); | ||
} | ||
return matches; | ||
} // eslint-disable-next-line max-params | ||
} | ||
// eslint-disable-next-line max-params | ||
normalizeMatch(baseToken, j, match, baseGuesses) { | ||
@@ -65,6 +56,6 @@ const baseMatch = { | ||
}; | ||
if (baseGuesses instanceof Promise) { | ||
return baseGuesses.then(resolvedBaseGuesses => { | ||
return { ...baseMatch, | ||
return { | ||
...baseMatch, | ||
baseGuesses: resolvedBaseGuesses | ||
@@ -74,8 +65,7 @@ }; | ||
} | ||
return { ...baseMatch, | ||
return { | ||
...baseMatch, | ||
baseGuesses | ||
}; | ||
} | ||
getGreedyMatch(password, lastIndex) { | ||
@@ -86,3 +76,2 @@ const greedy = /(.+)\1+/g; | ||
} | ||
getLazyMatch(password, lastIndex) { | ||
@@ -93,3 +82,2 @@ const lazy = /(.+?)\1+/g; | ||
} | ||
setMatchToken(greedyMatch, lazyMatch) { | ||
@@ -99,3 +87,2 @@ const lazyAnchored = /^(.+?)\1+$/; | ||
let baseToken = ''; | ||
if (lazyMatch && greedyMatch[0].length > lazyMatch[0].length) { | ||
@@ -105,9 +92,8 @@ // greedy beats lazy for 'aabaab' | ||
// lazy: [aa, a] | ||
match = greedyMatch; // greedy's repeated string might itself be repeated, eg. | ||
match = greedyMatch; | ||
// greedy's repeated string might itself be repeated, eg. | ||
// aabaab in aabaabaabaab. | ||
// run an anchored lazy match on greedy's repeated string | ||
// to find the shortest repeated string | ||
const temp = lazyAnchored.exec(match[0]); | ||
if (temp) { | ||
@@ -121,3 +107,2 @@ baseToken = temp[1]; | ||
match = lazyMatch; | ||
if (match) { | ||
@@ -127,3 +112,2 @@ baseToken = match[1]; | ||
} | ||
return { | ||
@@ -134,6 +118,4 @@ match, | ||
} | ||
getBaseGuesses(baseToken, omniMatch) { | ||
const matches = omniMatch.match(baseToken); | ||
if (matches instanceof Promise) { | ||
@@ -145,7 +127,5 @@ return matches.then(resolvedMatches => { | ||
} | ||
const baseAnalysis = scoring.mostGuessableMatchSequence(baseToken, matches); | ||
return baseAnalysis.guesses; | ||
} | ||
} | ||
@@ -152,0 +132,0 @@ |
@@ -10,3 +10,2 @@ 'use strict'; | ||
*/ | ||
class MatchRepeat { | ||
@@ -20,11 +19,8 @@ // eslint-disable-next-line max-statements | ||
let lastIndex = 0; | ||
while (lastIndex < password.length) { | ||
const greedyMatch = this.getGreedyMatch(password, lastIndex); | ||
const lazyMatch = this.getLazyMatch(password, lastIndex); | ||
if (greedyMatch == null) { | ||
break; | ||
} | ||
const { | ||
@@ -34,3 +30,2 @@ match, | ||
} = this.setMatchToken(greedyMatch, lazyMatch); | ||
if (match) { | ||
@@ -43,15 +38,11 @@ const j = match.index + match[0].length - 1; | ||
} | ||
const hasPromises = matches.some(match => { | ||
return match instanceof Promise; | ||
}); | ||
if (hasPromises) { | ||
return Promise.all(matches); | ||
} | ||
return matches; | ||
} // eslint-disable-next-line max-params | ||
} | ||
// eslint-disable-next-line max-params | ||
normalizeMatch(baseToken, j, match, baseGuesses) { | ||
@@ -67,6 +58,6 @@ const baseMatch = { | ||
}; | ||
if (baseGuesses instanceof Promise) { | ||
return baseGuesses.then(resolvedBaseGuesses => { | ||
return { ...baseMatch, | ||
return { | ||
...baseMatch, | ||
baseGuesses: resolvedBaseGuesses | ||
@@ -76,8 +67,7 @@ }; | ||
} | ||
return { ...baseMatch, | ||
return { | ||
...baseMatch, | ||
baseGuesses | ||
}; | ||
} | ||
getGreedyMatch(password, lastIndex) { | ||
@@ -88,3 +78,2 @@ const greedy = /(.+)\1+/g; | ||
} | ||
getLazyMatch(password, lastIndex) { | ||
@@ -95,3 +84,2 @@ const lazy = /(.+?)\1+/g; | ||
} | ||
setMatchToken(greedyMatch, lazyMatch) { | ||
@@ -101,3 +89,2 @@ const lazyAnchored = /^(.+?)\1+$/; | ||
let baseToken = ''; | ||
if (lazyMatch && greedyMatch[0].length > lazyMatch[0].length) { | ||
@@ -107,9 +94,8 @@ // greedy beats lazy for 'aabaab' | ||
// lazy: [aa, a] | ||
match = greedyMatch; // greedy's repeated string might itself be repeated, eg. | ||
match = greedyMatch; | ||
// greedy's repeated string might itself be repeated, eg. | ||
// aabaab in aabaabaabaab. | ||
// run an anchored lazy match on greedy's repeated string | ||
// to find the shortest repeated string | ||
const temp = lazyAnchored.exec(match[0]); | ||
if (temp) { | ||
@@ -123,3 +109,2 @@ baseToken = temp[1]; | ||
match = lazyMatch; | ||
if (match) { | ||
@@ -129,3 +114,2 @@ baseToken = match[1]; | ||
} | ||
return { | ||
@@ -136,6 +120,4 @@ match, | ||
} | ||
getBaseGuesses(baseToken, omniMatch) { | ||
const matches = omniMatch.match(baseToken); | ||
if (matches instanceof Promise) { | ||
@@ -147,7 +129,5 @@ return matches.then(resolvedMatches => { | ||
} | ||
const baseAnalysis = index.mostGuessableMatchSequence(baseToken, matches); | ||
return baseAnalysis.guesses; | ||
} | ||
} | ||
@@ -154,0 +134,0 @@ |
@@ -7,4 +7,4 @@ 'use strict'; | ||
return { | ||
warning: Options["default"].translations.warnings.sequences, | ||
suggestions: [Options["default"].translations.suggestions.sequences] | ||
warning: Options.default.translations.warnings.sequences, | ||
suggestions: [Options.default.translations.suggestions.sequences] | ||
}; | ||
@@ -11,0 +11,0 @@ }); |
import { SequenceMatch } from '../../types'; | ||
declare type UpdateParams = { | ||
type UpdateParams = { | ||
i: number; | ||
@@ -4,0 +4,0 @@ j: number; |
@@ -8,9 +8,7 @@ import { ALL_LOWER, ALL_UPPER, ALL_DIGIT } from '../../data/const.esm.js'; | ||
*/ | ||
class MatchSequence { | ||
constructor() { | ||
this.MAX_DELTA = 5; | ||
} // eslint-disable-next-line max-statements | ||
} | ||
// eslint-disable-next-line max-statements | ||
match({ | ||
@@ -34,18 +32,13 @@ password | ||
const result = []; | ||
if (password.length === 1) { | ||
return []; | ||
} | ||
let i = 0; | ||
let lastDelta = null; | ||
const passwordLength = password.length; | ||
for (let k = 1; k < passwordLength; k += 1) { | ||
const delta = password.charCodeAt(k) - password.charCodeAt(k - 1); | ||
if (lastDelta == null) { | ||
lastDelta = delta; | ||
} | ||
if (delta !== lastDelta) { | ||
@@ -64,3 +57,2 @@ const j = k - 1; | ||
} | ||
this.update({ | ||
@@ -75,3 +67,2 @@ i, | ||
} | ||
update({ | ||
@@ -86,3 +77,2 @@ i, | ||
const absoluteDelta = Math.abs(delta); | ||
if (absoluteDelta > 0 && absoluteDelta <= this.MAX_DELTA) { | ||
@@ -105,6 +95,4 @@ const token = password.slice(i, +j + 1 || 9e9); | ||
} | ||
return null; | ||
} | ||
getSequence(token) { | ||
@@ -115,3 +103,2 @@ // TODO conservatively stick with roman alphabet size. | ||
let sequenceSpace = 26; | ||
if (ALL_LOWER.test(token)) { | ||
@@ -127,3 +114,2 @@ sequenceName = 'lower'; | ||
} | ||
return { | ||
@@ -134,3 +120,2 @@ sequenceName, | ||
} | ||
} | ||
@@ -137,0 +122,0 @@ |
@@ -10,9 +10,7 @@ 'use strict'; | ||
*/ | ||
class MatchSequence { | ||
constructor() { | ||
this.MAX_DELTA = 5; | ||
} // eslint-disable-next-line max-statements | ||
} | ||
// eslint-disable-next-line max-statements | ||
match({ | ||
@@ -36,18 +34,13 @@ password | ||
const result = []; | ||
if (password.length === 1) { | ||
return []; | ||
} | ||
let i = 0; | ||
let lastDelta = null; | ||
const passwordLength = password.length; | ||
for (let k = 1; k < passwordLength; k += 1) { | ||
const delta = password.charCodeAt(k) - password.charCodeAt(k - 1); | ||
if (lastDelta == null) { | ||
lastDelta = delta; | ||
} | ||
if (delta !== lastDelta) { | ||
@@ -66,3 +59,2 @@ const j = k - 1; | ||
} | ||
this.update({ | ||
@@ -77,3 +69,2 @@ i, | ||
} | ||
update({ | ||
@@ -88,3 +79,2 @@ i, | ||
const absoluteDelta = Math.abs(delta); | ||
if (absoluteDelta > 0 && absoluteDelta <= this.MAX_DELTA) { | ||
@@ -107,6 +97,4 @@ const token = password.slice(i, +j + 1 || 9e9); | ||
} | ||
return null; | ||
} | ||
getSequence(token) { | ||
@@ -117,3 +105,2 @@ // TODO conservatively stick with roman alphabet size. | ||
let sequenceSpace = 26; | ||
if (_const.ALL_LOWER.test(token)) { | ||
@@ -129,3 +116,2 @@ sequenceName = 'lower'; | ||
} | ||
return { | ||
@@ -136,3 +122,2 @@ sequenceName, | ||
} | ||
} | ||
@@ -139,0 +124,0 @@ |
@@ -7,4 +7,4 @@ var sequenceMatcher = (({ | ||
let baseGuesses = 0; | ||
const startingPoints = ['a', 'A', 'z', 'Z', '0', '1', '9']; // lower guesses for obvious starting points | ||
const startingPoints = ['a', 'A', 'z', 'Z', '0', '1', '9']; | ||
// lower guesses for obvious starting points | ||
if (startingPoints.includes(firstChr)) { | ||
@@ -18,10 +18,8 @@ baseGuesses = 4; | ||
baseGuesses = 26; | ||
} // need to try a descending sequence in addition to every ascending sequence -> | ||
} | ||
// need to try a descending sequence in addition to every ascending sequence -> | ||
// 2x guesses | ||
if (!ascending) { | ||
baseGuesses *= 2; | ||
} | ||
return baseGuesses * token.length; | ||
@@ -28,0 +26,0 @@ }); |
@@ -9,4 +9,4 @@ 'use strict'; | ||
let baseGuesses = 0; | ||
const startingPoints = ['a', 'A', 'z', 'Z', '0', '1', '9']; // lower guesses for obvious starting points | ||
const startingPoints = ['a', 'A', 'z', 'Z', '0', '1', '9']; | ||
// lower guesses for obvious starting points | ||
if (startingPoints.includes(firstChr)) { | ||
@@ -20,10 +20,8 @@ baseGuesses = 4; | ||
baseGuesses = 26; | ||
} // need to try a descending sequence in addition to every ascending sequence -> | ||
} | ||
// need to try a descending sequence in addition to every ascending sequence -> | ||
// 2x guesses | ||
if (!ascending) { | ||
baseGuesses *= 2; | ||
} | ||
return baseGuesses * token.length; | ||
@@ -30,0 +28,0 @@ }); |
@@ -5,7 +5,5 @@ import zxcvbnOptions from '../../Options.esm.js'; | ||
let warning = zxcvbnOptions.translations.warnings.keyPattern; | ||
if (match.turns === 1) { | ||
warning = zxcvbnOptions.translations.warnings.straightRow; | ||
} | ||
return { | ||
@@ -12,0 +10,0 @@ warning, |
@@ -6,11 +6,9 @@ 'use strict'; | ||
var spatialMatcher = (match => { | ||
let warning = Options["default"].translations.warnings.keyPattern; | ||
let warning = Options.default.translations.warnings.keyPattern; | ||
if (match.turns === 1) { | ||
warning = Options["default"].translations.warnings.straightRow; | ||
warning = Options.default.translations.warnings.straightRow; | ||
} | ||
return { | ||
warning, | ||
suggestions: [Options["default"].translations.suggestions.longerKeyboardPattern] | ||
suggestions: [Options.default.translations.suggestions.longerKeyboardPattern] | ||
}; | ||
@@ -17,0 +15,0 @@ }); |
@@ -9,3 +9,2 @@ import { extend, sorted } from '../../helper.esm.js'; | ||
*/ | ||
class MatchSpatial { | ||
@@ -15,3 +14,2 @@ constructor() { | ||
} | ||
match({ | ||
@@ -27,13 +25,11 @@ password | ||
} | ||
checkIfShifted(graphName, password, index) { | ||
if (!graphName.includes('keypad') && // initial character is shifted | ||
if (!graphName.includes('keypad') && | ||
// initial character is shifted | ||
this.SHIFTED_RX.test(password.charAt(index))) { | ||
return 1; | ||
} | ||
return 0; | ||
} // eslint-disable-next-line complexity, max-statements | ||
} | ||
// eslint-disable-next-line complexity, max-statements | ||
helper(password, graph, graphName) { | ||
@@ -44,3 +40,2 @@ let shiftedCount; | ||
const passwordLength = password.length; | ||
while (i < passwordLength - 1) { | ||
@@ -50,4 +45,4 @@ let j = i + 1; | ||
let turns = 0; | ||
shiftedCount = this.checkIfShifted(graphName, password, i); // eslint-disable-next-line no-constant-condition | ||
shiftedCount = this.checkIfShifted(graphName, password, i); | ||
// eslint-disable-next-line no-constant-condition | ||
while (true) { | ||
@@ -58,19 +53,18 @@ const prevChar = password.charAt(j - 1); | ||
let foundDirection = -1; | ||
let curDirection = -1; // consider growing pattern by one character if j hasn't gone over the edge. | ||
let curDirection = -1; | ||
// consider growing pattern by one character if j hasn't gone over the edge. | ||
if (j < passwordLength) { | ||
const curChar = password.charAt(j); | ||
const adjacentsLength = adjacents.length; | ||
for (let k = 0; k < adjacentsLength; k += 1) { | ||
const adjacent = adjacents[k]; | ||
curDirection += 1; // eslint-disable-next-line max-depth | ||
curDirection += 1; | ||
// eslint-disable-next-line max-depth | ||
if (adjacent) { | ||
const adjacentIndex = adjacent.indexOf(curChar); // eslint-disable-next-line max-depth | ||
const adjacentIndex = adjacent.indexOf(curChar); | ||
// eslint-disable-next-line max-depth | ||
if (adjacentIndex !== -1) { | ||
found = true; | ||
foundDirection = curDirection; // eslint-disable-next-line max-depth | ||
foundDirection = curDirection; | ||
// eslint-disable-next-line max-depth | ||
if (adjacentIndex === 1) { | ||
@@ -82,5 +76,4 @@ // # index 1 in the adjacency means the key is shifted, | ||
shiftedCount += 1; | ||
} // eslint-disable-next-line max-depth | ||
} | ||
// eslint-disable-next-line max-depth | ||
if (lastDirection !== foundDirection) { | ||
@@ -93,3 +86,2 @@ // # adding a turn is correct even in the initial | ||
} | ||
break; | ||
@@ -99,7 +91,7 @@ } | ||
} | ||
} // if the current pattern continued, extend j and try to grow again | ||
} | ||
// if the current pattern continued, extend j and try to grow again | ||
if (found) { | ||
j += 1; // otherwise push the pattern discovered so far, if any... | ||
j += 1; | ||
// otherwise push the pattern discovered so far, if any... | ||
} else { | ||
@@ -117,5 +109,4 @@ // don't consider length 1 or 2 chains. | ||
}); | ||
} // ...and then start a new search for the rest of the password. | ||
} | ||
// ...and then start a new search for the rest of the password. | ||
i = j; | ||
@@ -126,6 +117,4 @@ break; | ||
} | ||
return matches; | ||
} | ||
} | ||
@@ -132,0 +121,0 @@ |
@@ -11,3 +11,2 @@ 'use strict'; | ||
*/ | ||
class MatchSpatial { | ||
@@ -17,3 +16,2 @@ constructor() { | ||
} | ||
match({ | ||
@@ -23,4 +21,4 @@ password | ||
const matches = []; | ||
Object.keys(Options["default"].graphs).forEach(graphName => { | ||
const graph = Options["default"].graphs[graphName]; | ||
Object.keys(Options.default.graphs).forEach(graphName => { | ||
const graph = Options.default.graphs[graphName]; | ||
helper.extend(matches, this.helper(password, graph, graphName)); | ||
@@ -30,13 +28,11 @@ }); | ||
} | ||
checkIfShifted(graphName, password, index) { | ||
if (!graphName.includes('keypad') && // initial character is shifted | ||
if (!graphName.includes('keypad') && | ||
// initial character is shifted | ||
this.SHIFTED_RX.test(password.charAt(index))) { | ||
return 1; | ||
} | ||
return 0; | ||
} // eslint-disable-next-line complexity, max-statements | ||
} | ||
// eslint-disable-next-line complexity, max-statements | ||
helper(password, graph, graphName) { | ||
@@ -47,3 +43,2 @@ let shiftedCount; | ||
const passwordLength = password.length; | ||
while (i < passwordLength - 1) { | ||
@@ -53,4 +48,4 @@ let j = i + 1; | ||
let turns = 0; | ||
shiftedCount = this.checkIfShifted(graphName, password, i); // eslint-disable-next-line no-constant-condition | ||
shiftedCount = this.checkIfShifted(graphName, password, i); | ||
// eslint-disable-next-line no-constant-condition | ||
while (true) { | ||
@@ -61,19 +56,18 @@ const prevChar = password.charAt(j - 1); | ||
let foundDirection = -1; | ||
let curDirection = -1; // consider growing pattern by one character if j hasn't gone over the edge. | ||
let curDirection = -1; | ||
// consider growing pattern by one character if j hasn't gone over the edge. | ||
if (j < passwordLength) { | ||
const curChar = password.charAt(j); | ||
const adjacentsLength = adjacents.length; | ||
for (let k = 0; k < adjacentsLength; k += 1) { | ||
const adjacent = adjacents[k]; | ||
curDirection += 1; // eslint-disable-next-line max-depth | ||
curDirection += 1; | ||
// eslint-disable-next-line max-depth | ||
if (adjacent) { | ||
const adjacentIndex = adjacent.indexOf(curChar); // eslint-disable-next-line max-depth | ||
const adjacentIndex = adjacent.indexOf(curChar); | ||
// eslint-disable-next-line max-depth | ||
if (adjacentIndex !== -1) { | ||
found = true; | ||
foundDirection = curDirection; // eslint-disable-next-line max-depth | ||
foundDirection = curDirection; | ||
// eslint-disable-next-line max-depth | ||
if (adjacentIndex === 1) { | ||
@@ -85,5 +79,4 @@ // # index 1 in the adjacency means the key is shifted, | ||
shiftedCount += 1; | ||
} // eslint-disable-next-line max-depth | ||
} | ||
// eslint-disable-next-line max-depth | ||
if (lastDirection !== foundDirection) { | ||
@@ -96,3 +89,2 @@ // # adding a turn is correct even in the initial | ||
} | ||
break; | ||
@@ -102,7 +94,7 @@ } | ||
} | ||
} // if the current pattern continued, extend j and try to grow again | ||
} | ||
// if the current pattern continued, extend j and try to grow again | ||
if (found) { | ||
j += 1; // otherwise push the pattern discovered so far, if any... | ||
j += 1; | ||
// otherwise push the pattern discovered so far, if any... | ||
} else { | ||
@@ -120,5 +112,4 @@ // don't consider length 1 or 2 chains. | ||
}); | ||
} // ...and then start a new search for the rest of the password. | ||
} | ||
// ...and then start a new search for the rest of the password. | ||
i = j; | ||
@@ -129,6 +120,4 @@ break; | ||
} | ||
return matches; | ||
} | ||
} | ||
@@ -135,0 +124,0 @@ |
@@ -13,3 +13,2 @@ import utils from '../../scoring/utils.esm.js'; | ||
}; | ||
const estimatePossiblePatterns = ({ | ||
@@ -23,7 +22,6 @@ token, | ||
let guesses = 0; | ||
const tokenLength = token.length; // # estimate the number of possible patterns w/ tokenLength or less with turns or less. | ||
const tokenLength = token.length; | ||
// # estimate the number of possible patterns w/ tokenLength or less with turns or less. | ||
for (let i = 2; i <= tokenLength; i += 1) { | ||
const possibleTurns = Math.min(turns, i - 1); | ||
for (let j = 1; j <= possibleTurns; j += 1) { | ||
@@ -33,6 +31,4 @@ guesses += utils.nCk(i - 1, j - 1) * startingPosition * averageDegree ** j; | ||
} | ||
return guesses; | ||
}; | ||
var spatialMatcher = (({ | ||
@@ -48,8 +44,7 @@ graph, | ||
turns | ||
}); // add extra guesses for shifted keys. (% instead of 5, A instead of a.) | ||
}); | ||
// add extra guesses for shifted keys. (% instead of 5, A instead of a.) | ||
// math is similar to extra guesses of l33t substitutions in dictionary matches. | ||
if (shiftedCount) { | ||
const unShiftedCount = token.length - shiftedCount; | ||
if (shiftedCount === 0 || unShiftedCount === 0) { | ||
@@ -59,11 +54,8 @@ guesses *= 2; | ||
let shiftedVariations = 0; | ||
for (let i = 1; i <= Math.min(shiftedCount, unShiftedCount); i += 1) { | ||
shiftedVariations += utils.nCk(shiftedCount + unShiftedCount, i); | ||
} | ||
guesses *= shiftedVariations; | ||
} | ||
} | ||
return Math.round(guesses); | ||
@@ -70,0 +62,0 @@ }); |
@@ -15,3 +15,2 @@ 'use strict'; | ||
}; | ||
const estimatePossiblePatterns = ({ | ||
@@ -22,10 +21,9 @@ token, | ||
}) => { | ||
const startingPosition = Object.keys(Options["default"].graphs[graph]).length; | ||
const averageDegree = calcAverageDegree(Options["default"].graphs[graph]); | ||
const startingPosition = Object.keys(Options.default.graphs[graph]).length; | ||
const averageDegree = calcAverageDegree(Options.default.graphs[graph]); | ||
let guesses = 0; | ||
const tokenLength = token.length; // # estimate the number of possible patterns w/ tokenLength or less with turns or less. | ||
const tokenLength = token.length; | ||
// # estimate the number of possible patterns w/ tokenLength or less with turns or less. | ||
for (let i = 2; i <= tokenLength; i += 1) { | ||
const possibleTurns = Math.min(turns, i - 1); | ||
for (let j = 1; j <= possibleTurns; j += 1) { | ||
@@ -35,6 +33,4 @@ guesses += utils.nCk(i - 1, j - 1) * startingPosition * averageDegree ** j; | ||
} | ||
return guesses; | ||
}; | ||
var spatialMatcher = (({ | ||
@@ -50,8 +46,7 @@ graph, | ||
turns | ||
}); // add extra guesses for shifted keys. (% instead of 5, A instead of a.) | ||
}); | ||
// add extra guesses for shifted keys. (% instead of 5, A instead of a.) | ||
// math is similar to extra guesses of l33t substitutions in dictionary matches. | ||
if (shiftedCount) { | ||
const unShiftedCount = token.length - shiftedCount; | ||
if (shiftedCount === 0 || unShiftedCount === 0) { | ||
@@ -61,11 +56,8 @@ guesses *= 2; | ||
let shiftedVariations = 0; | ||
for (let i = 1; i <= Math.min(shiftedCount, unShiftedCount); i += 1) { | ||
shiftedVariations += utils.nCk(shiftedCount + unShiftedCount, i); | ||
} | ||
guesses *= shiftedVariations; | ||
} | ||
} | ||
return Math.round(guesses); | ||
@@ -72,0 +64,0 @@ }); |
import { MatchExtended, MatchingType } from './types'; | ||
declare type Matchers = { | ||
type Matchers = { | ||
[key: string]: MatchingType; | ||
@@ -4,0 +4,0 @@ }; |
@@ -22,3 +22,2 @@ import { extend, sorted } from './helper.esm.js'; | ||
} | ||
match(password) { | ||
@@ -32,3 +31,2 @@ const matches = []; | ||
} | ||
const Matcher = this.matchers[key] ? this.matchers[key] : zxcvbnOptions.matchers[key].Matching; | ||
@@ -40,3 +38,2 @@ const usedMatcher = new Matcher(); | ||
}); | ||
if (result instanceof Promise) { | ||
@@ -51,3 +48,2 @@ result.then(response => { | ||
}); | ||
if (promises.length > 0) { | ||
@@ -60,6 +56,4 @@ return new Promise(resolve => { | ||
} | ||
return sorted(matches); | ||
} | ||
} | ||
@@ -66,0 +60,0 @@ |
@@ -24,13 +24,11 @@ 'use strict'; | ||
} | ||
match(password) { | ||
const matches = []; | ||
const promises = []; | ||
const matchers = [...Object.keys(this.matchers), ...Object.keys(Options["default"].matchers)]; | ||
const matchers = [...Object.keys(this.matchers), ...Object.keys(Options.default.matchers)]; | ||
matchers.forEach(key => { | ||
if (!this.matchers[key] && !Options["default"].matchers[key]) { | ||
if (!this.matchers[key] && !Options.default.matchers[key]) { | ||
return; | ||
} | ||
const Matcher = this.matchers[key] ? this.matchers[key] : Options["default"].matchers[key].Matching; | ||
const Matcher = this.matchers[key] ? this.matchers[key] : Options.default.matchers[key].Matching; | ||
const usedMatcher = new Matcher(); | ||
@@ -41,3 +39,2 @@ const result = usedMatcher.match({ | ||
}); | ||
if (result instanceof Promise) { | ||
@@ -52,3 +49,2 @@ result.then(response => { | ||
}); | ||
if (promises.length > 0) { | ||
@@ -61,6 +57,4 @@ return new Promise(resolve => { | ||
} | ||
return helper.sorted(matches); | ||
} | ||
} | ||
@@ -67,0 +61,0 @@ |
@@ -7,2 +7,3 @@ import { TranslationKeys, OptionsType, OptionsDictionary, OptionsL33tTable, OptionsGraph, RankedDictionaries, Matchers, Matcher } from './types'; | ||
rankedDictionaries: RankedDictionaries; | ||
rankedDictionariesMaxWordSize: Record<string, number>; | ||
translations: TranslationKeys; | ||
@@ -13,2 +14,3 @@ graphs: OptionsGraph; | ||
levenshteinThreshold: number; | ||
l33tMaxSubstitutions: number; | ||
constructor(); | ||
@@ -19,2 +21,3 @@ setOptions(options?: OptionsType): void; | ||
setRankedDictionaries(): void; | ||
getRankedDictionariesMaxWordSize(name: string): number; | ||
getRankedDictionary(name: string): import("./types").LooseObject; | ||
@@ -21,0 +24,0 @@ extendUserInputsDictionary(dictionary: (string | number)[]): void; |
@@ -13,2 +13,3 @@ import { buildRankedDictionary } from './helper.esm.js'; | ||
this.rankedDictionaries = {}; | ||
this.rankedDictionariesMaxWordSize = {}; | ||
this.translations = translationKeys; | ||
@@ -19,5 +20,6 @@ this.graphs = {}; | ||
this.levenshteinThreshold = 2; | ||
this.l33tMaxSubstitutions = 100; | ||
this.setRankedDictionaries(); | ||
} | ||
// eslint-disable-next-line max-statements | ||
setOptions(options = {}) { | ||
@@ -27,3 +29,2 @@ if (options.l33tTable) { | ||
} | ||
if (options.dictionary) { | ||
@@ -33,20 +34,18 @@ this.dictionary = options.dictionary; | ||
} | ||
if (options.translations) { | ||
this.setTranslations(options.translations); | ||
} | ||
if (options.graphs) { | ||
this.graphs = options.graphs; | ||
} | ||
if (options.useLevenshteinDistance !== undefined) { | ||
this.useLevenshteinDistance = options.useLevenshteinDistance; | ||
} | ||
if (options.levenshteinThreshold !== undefined) { | ||
this.levenshteinThreshold = options.levenshteinThreshold; | ||
} | ||
if (options.l33tMaxSubstitutions !== undefined) { | ||
this.l33tMaxSubstitutions = options.l33tMaxSubstitutions; | ||
} | ||
} | ||
setTranslations(translations) { | ||
@@ -59,3 +58,2 @@ if (this.checkCustomTranslations(translations)) { | ||
} | ||
checkCustomTranslations(translations) { | ||
@@ -77,14 +75,24 @@ let valid = true; | ||
} | ||
setRankedDictionaries() { | ||
const rankedDictionaries = {}; | ||
const rankedDictionariesMaxWorkSize = {}; | ||
Object.keys(this.dictionary).forEach(name => { | ||
rankedDictionaries[name] = this.getRankedDictionary(name); | ||
rankedDictionariesMaxWorkSize[name] = this.getRankedDictionariesMaxWordSize(name); | ||
}); | ||
this.rankedDictionaries = rankedDictionaries; | ||
this.rankedDictionariesMaxWordSize = rankedDictionariesMaxWorkSize; | ||
} | ||
getRankedDictionariesMaxWordSize(name) { | ||
const data = this.dictionary[name].map(el => { | ||
if (typeof el !== 'string') { | ||
return el.toString().length; | ||
} | ||
return el.length; | ||
}); | ||
const result = data.length === 0 ? 0 : Math.max(...data); | ||
return result; | ||
} | ||
getRankedDictionary(name) { | ||
const list = this.dictionary[name]; | ||
if (name === 'userInputs') { | ||
@@ -94,3 +102,2 @@ const sanitizedInputs = []; | ||
const inputType = typeof input; | ||
if (inputType === 'string' || inputType === 'number' || inputType === 'boolean') { | ||
@@ -102,6 +109,4 @@ sanitizedInputs.push(input.toString().toLowerCase()); | ||
} | ||
return buildRankedDictionary(list); | ||
} | ||
extendUserInputsDictionary(dictionary) { | ||
@@ -113,6 +118,5 @@ if (this.dictionary.userInputs) { | ||
} | ||
this.rankedDictionaries.userInputs = this.getRankedDictionary('userInputs'); | ||
this.rankedDictionariesMaxWordSize.userInputs = this.getRankedDictionariesMaxWordSize('userInputs'); | ||
} | ||
addMatcher(name, matcher) { | ||
@@ -125,3 +129,2 @@ if (this.matchers[name]) { | ||
} | ||
} | ||
@@ -128,0 +131,0 @@ const zxcvbnOptions = new Options(); |
@@ -17,2 +17,3 @@ 'use strict'; | ||
this.rankedDictionaries = {}; | ||
this.rankedDictionariesMaxWordSize = {}; | ||
this.translations = translationKeys; | ||
@@ -23,5 +24,6 @@ this.graphs = {}; | ||
this.levenshteinThreshold = 2; | ||
this.l33tMaxSubstitutions = 100; | ||
this.setRankedDictionaries(); | ||
} | ||
// eslint-disable-next-line max-statements | ||
setOptions(options = {}) { | ||
@@ -31,3 +33,2 @@ if (options.l33tTable) { | ||
} | ||
if (options.dictionary) { | ||
@@ -37,20 +38,18 @@ this.dictionary = options.dictionary; | ||
} | ||
if (options.translations) { | ||
this.setTranslations(options.translations); | ||
} | ||
if (options.graphs) { | ||
this.graphs = options.graphs; | ||
} | ||
if (options.useLevenshteinDistance !== undefined) { | ||
this.useLevenshteinDistance = options.useLevenshteinDistance; | ||
} | ||
if (options.levenshteinThreshold !== undefined) { | ||
this.levenshteinThreshold = options.levenshteinThreshold; | ||
} | ||
if (options.l33tMaxSubstitutions !== undefined) { | ||
this.l33tMaxSubstitutions = options.l33tMaxSubstitutions; | ||
} | ||
} | ||
setTranslations(translations) { | ||
@@ -63,3 +62,2 @@ if (this.checkCustomTranslations(translations)) { | ||
} | ||
checkCustomTranslations(translations) { | ||
@@ -81,14 +79,24 @@ let valid = true; | ||
} | ||
setRankedDictionaries() { | ||
const rankedDictionaries = {}; | ||
const rankedDictionariesMaxWorkSize = {}; | ||
Object.keys(this.dictionary).forEach(name => { | ||
rankedDictionaries[name] = this.getRankedDictionary(name); | ||
rankedDictionariesMaxWorkSize[name] = this.getRankedDictionariesMaxWordSize(name); | ||
}); | ||
this.rankedDictionaries = rankedDictionaries; | ||
this.rankedDictionariesMaxWordSize = rankedDictionariesMaxWorkSize; | ||
} | ||
getRankedDictionariesMaxWordSize(name) { | ||
const data = this.dictionary[name].map(el => { | ||
if (typeof el !== 'string') { | ||
return el.toString().length; | ||
} | ||
return el.length; | ||
}); | ||
const result = data.length === 0 ? 0 : Math.max(...data); | ||
return result; | ||
} | ||
getRankedDictionary(name) { | ||
const list = this.dictionary[name]; | ||
if (name === 'userInputs') { | ||
@@ -98,3 +106,2 @@ const sanitizedInputs = []; | ||
const inputType = typeof input; | ||
if (inputType === 'string' || inputType === 'number' || inputType === 'boolean') { | ||
@@ -106,6 +113,4 @@ sanitizedInputs.push(input.toString().toLowerCase()); | ||
} | ||
return helper.buildRankedDictionary(list); | ||
} | ||
extendUserInputsDictionary(dictionary) { | ||
@@ -117,6 +122,5 @@ if (this.dictionary.userInputs) { | ||
} | ||
this.rankedDictionaries.userInputs = this.getRankedDictionary('userInputs'); | ||
this.rankedDictionariesMaxWordSize.userInputs = this.getRankedDictionariesMaxWordSize('userInputs'); | ||
} | ||
addMatcher(name, matcher) { | ||
@@ -129,3 +133,2 @@ if (this.matchers[name]) { | ||
} | ||
} | ||
@@ -135,3 +138,3 @@ const zxcvbnOptions = new Options(); | ||
exports.Options = Options; | ||
exports["default"] = zxcvbnOptions; | ||
exports.default = zxcvbnOptions; | ||
//# sourceMappingURL=Options.js.map |
@@ -14,3 +14,2 @@ import { MIN_SUBMATCH_GUESSES_SINGLE_CHAR, MIN_SUBMATCH_GUESSES_MULTI_CHAR } from '../data/const.esm.js'; | ||
let minGuesses = 1; | ||
if (match.token.length < password.length) { | ||
@@ -23,6 +22,4 @@ if (match.token.length === 1) { | ||
} | ||
return minGuesses; | ||
}; | ||
const matchers = { | ||
@@ -37,3 +34,2 @@ bruteforce: bruteforceMatcher, | ||
}; | ||
const getScoring = (name, match) => { | ||
@@ -43,24 +39,19 @@ if (matchers[name]) { | ||
} | ||
if (zxcvbnOptions.matchers[name] && 'scoring' in zxcvbnOptions.matchers[name]) { | ||
return zxcvbnOptions.matchers[name].scoring(match); | ||
} | ||
return 0; | ||
}; // ------------------------------------------------------------------------------ | ||
}; | ||
// ------------------------------------------------------------------------------ | ||
// guess estimation -- one function per match pattern --------------------------- | ||
// ------------------------------------------------------------------------------ | ||
var estimateGuesses = ((match, password) => { | ||
const extraData = {}; // a match's guess estimate doesn't change. cache it. | ||
const extraData = {}; | ||
// a match's guess estimate doesn't change. cache it. | ||
if ('guesses' in match && match.guesses != null) { | ||
return match; | ||
} | ||
const minGuesses = getMinGuesses(match, password); | ||
const estimationResult = getScoring(match.pattern, match); | ||
let guesses = 0; | ||
if (typeof estimationResult === 'number') { | ||
@@ -74,5 +65,5 @@ guesses = estimationResult; | ||
} | ||
const matchGuesses = Math.max(guesses, minGuesses); | ||
return { ...match, | ||
return { | ||
...match, | ||
...extraData, | ||
@@ -79,0 +70,0 @@ guesses: matchGuesses, |
@@ -16,3 +16,2 @@ 'use strict'; | ||
let minGuesses = 1; | ||
if (match.token.length < password.length) { | ||
@@ -25,6 +24,4 @@ if (match.token.length === 1) { | ||
} | ||
return minGuesses; | ||
}; | ||
const matchers = { | ||
@@ -39,3 +36,2 @@ bruteforce: scoring, | ||
}; | ||
const getScoring = (name, match) => { | ||
@@ -45,24 +41,19 @@ if (matchers[name]) { | ||
} | ||
if (Options["default"].matchers[name] && 'scoring' in Options["default"].matchers[name]) { | ||
return Options["default"].matchers[name].scoring(match); | ||
if (Options.default.matchers[name] && 'scoring' in Options.default.matchers[name]) { | ||
return Options.default.matchers[name].scoring(match); | ||
} | ||
return 0; | ||
}; // ------------------------------------------------------------------------------ | ||
}; | ||
// ------------------------------------------------------------------------------ | ||
// guess estimation -- one function per match pattern --------------------------- | ||
// ------------------------------------------------------------------------------ | ||
var estimateGuesses = ((match, password) => { | ||
const extraData = {}; // a match's guess estimate doesn't change. cache it. | ||
const extraData = {}; | ||
// a match's guess estimate doesn't change. cache it. | ||
if ('guesses' in match && match.guesses != null) { | ||
return match; | ||
} | ||
const minGuesses = getMinGuesses(match, password); | ||
const estimationResult = getScoring(match.pattern, match); | ||
let guesses = 0; | ||
if (typeof estimationResult === 'number') { | ||
@@ -76,5 +67,5 @@ guesses = estimationResult; | ||
} | ||
const matchGuesses = Math.max(guesses, minGuesses); | ||
return { ...match, | ||
return { | ||
...match, | ||
...extraData, | ||
@@ -81,0 +72,0 @@ guesses: matchGuesses, |
@@ -9,19 +9,13 @@ import utils from './utils.esm.js'; | ||
excludeAdditive: false, | ||
fillArray(size, valueType) { | ||
const result = []; | ||
for (let i = 0; i < size; i += 1) { | ||
let value = []; | ||
if (valueType === 'object') { | ||
value = {}; | ||
} | ||
result.push(value); | ||
} | ||
return result; | ||
}, | ||
// helper: make bruteforce match objects spanning i to j, inclusive. | ||
@@ -36,3 +30,2 @@ makeBruteforceMatch(i, j) { | ||
}, | ||
// helper: considers whether a length-sequenceLength | ||
@@ -45,3 +38,2 @@ // sequence ending at match m is better (fewer guesses) | ||
let pi = estimatedMatch.guesses; | ||
if (sequenceLength > 1) { | ||
@@ -53,19 +45,15 @@ // we're considering a length-sequenceLength sequence ending with match m: | ||
pi *= this.optimal.pi[estimatedMatch.i - 1][sequenceLength - 1]; | ||
} // calculate the minimization func | ||
} | ||
// calculate the minimization func | ||
let g = utils.factorial(sequenceLength) * pi; | ||
if (!this.excludeAdditive) { | ||
g += MIN_GUESSES_BEFORE_GROWING_SEQUENCE ** (sequenceLength - 1); | ||
} // update state if new best. | ||
} | ||
// update state if new best. | ||
// first see if any competing sequences covering this prefix, | ||
// with sequenceLength or fewer matches, | ||
// fare better than this sequence. if so, skip it and return. | ||
let shouldSkip = false; | ||
Object.keys(this.optimal.g[k]).forEach(competingPatternLength => { | ||
const competingMetricMatch = this.optimal.g[k][competingPatternLength]; | ||
if (parseInt(competingPatternLength, 10) <= sequenceLength) { | ||
@@ -77,3 +65,2 @@ if (competingMetricMatch <= g) { | ||
}); | ||
if (!shouldSkip) { | ||
@@ -86,3 +73,2 @@ // this sequence might be part of the final optimal sequence. | ||
}, | ||
// helper: evaluate bruteforce matches ending at passwordCharIndex. | ||
@@ -93,3 +79,2 @@ bruteforceUpdate(passwordCharIndex) { | ||
this.update(match, 1); | ||
for (let i = 1; i <= passwordCharIndex; i += 1) { | ||
@@ -100,10 +85,10 @@ // generate passwordCharIndex bruteforce matches, spanning from (i=1, j=passwordCharIndex) up to (i=passwordCharIndex, j=passwordCharIndex). | ||
match = this.makeBruteforceMatch(i, passwordCharIndex); | ||
const tmp = this.optimal.m[i - 1]; // eslint-disable-next-line no-loop-func | ||
const tmp = this.optimal.m[i - 1]; | ||
// eslint-disable-next-line no-loop-func | ||
Object.keys(tmp).forEach(sequenceLength => { | ||
const lastMatch = tmp[sequenceLength]; // corner: an optimal sequence will never have two adjacent bruteforce matches. | ||
const lastMatch = tmp[sequenceLength]; | ||
// corner: an optimal sequence will never have two adjacent bruteforce matches. | ||
// it is strictly better to have a single bruteforce match spanning the same region: | ||
// same contribution to the guess product with a lower length. | ||
// --> safe to skip those cases. | ||
if (lastMatch.pattern !== 'bruteforce') { | ||
@@ -116,3 +101,2 @@ // try adding m to this length-sequenceLength sequence. | ||
}, | ||
// helper: step backwards through optimal.m starting at the end, | ||
@@ -122,13 +106,12 @@ // constructing the final optimal match sequence. | ||
const optimalMatchSequence = []; | ||
let k = passwordLength - 1; // find the final best sequence length and score | ||
let sequenceLength = 0; // eslint-disable-next-line no-loss-of-precision | ||
let k = passwordLength - 1; | ||
// find the final best sequence length and score | ||
let sequenceLength = 0; | ||
// eslint-disable-next-line no-loss-of-precision | ||
let g = 2e308; | ||
const temp = this.optimal.g[k]; // safety check for empty passwords | ||
const temp = this.optimal.g[k]; | ||
// safety check for empty passwords | ||
if (temp) { | ||
Object.keys(temp).forEach(candidateSequenceLength => { | ||
const candidateMetricMatch = temp[candidateSequenceLength]; | ||
if (candidateMetricMatch < g) { | ||
@@ -140,3 +123,2 @@ sequenceLength = parseInt(candidateSequenceLength, 10); | ||
} | ||
while (k >= 0) { | ||
@@ -148,6 +130,4 @@ const match = this.optimal.m[k][sequenceLength]; | ||
} | ||
return optimalMatchSequence; | ||
} | ||
}; | ||
@@ -190,9 +170,9 @@ var scoring = { | ||
scoringHelper.excludeAdditive = excludeAdditive; | ||
const passwordLength = password.length; // partition matches into sublists according to ending index j | ||
const passwordLength = password.length; | ||
// partition matches into sublists according to ending index j | ||
let matchesByCoordinateJ = scoringHelper.fillArray(passwordLength, 'array'); | ||
matches.forEach(match => { | ||
matchesByCoordinateJ[match.j].push(match); | ||
}); // small detail: for deterministic output, sort each sublist by i. | ||
}); | ||
// small detail: for deterministic output, sort each sublist by i. | ||
matchesByCoordinateJ = matchesByCoordinateJ.map(match => match.sort((m1, m2) => m1.i - m2.i)); | ||
@@ -213,3 +193,2 @@ scoringHelper.optimal = { | ||
}; | ||
for (let k = 0; k < passwordLength; k += 1) { | ||
@@ -227,3 +206,2 @@ matchesByCoordinateJ[k].forEach(match => { | ||
} | ||
const optimalMatchSequence = scoringHelper.unwind(passwordLength); | ||
@@ -239,7 +217,5 @@ const optimalSequenceLength = optimalMatchSequence.length; | ||
}, | ||
getGuesses(password, optimalSequenceLength) { | ||
const passwordLength = password.length; | ||
let guesses = 0; | ||
if (password.length === 0) { | ||
@@ -250,6 +226,4 @@ guesses = 1; | ||
} | ||
return guesses; | ||
} | ||
}; | ||
@@ -256,0 +230,0 @@ |
@@ -11,19 +11,13 @@ 'use strict'; | ||
excludeAdditive: false, | ||
fillArray(size, valueType) { | ||
const result = []; | ||
for (let i = 0; i < size; i += 1) { | ||
let value = []; | ||
if (valueType === 'object') { | ||
value = {}; | ||
} | ||
result.push(value); | ||
} | ||
return result; | ||
}, | ||
// helper: make bruteforce match objects spanning i to j, inclusive. | ||
@@ -38,3 +32,2 @@ makeBruteforceMatch(i, j) { | ||
}, | ||
// helper: considers whether a length-sequenceLength | ||
@@ -47,3 +40,2 @@ // sequence ending at match m is better (fewer guesses) | ||
let pi = estimatedMatch.guesses; | ||
if (sequenceLength > 1) { | ||
@@ -55,19 +47,15 @@ // we're considering a length-sequenceLength sequence ending with match m: | ||
pi *= this.optimal.pi[estimatedMatch.i - 1][sequenceLength - 1]; | ||
} // calculate the minimization func | ||
} | ||
// calculate the minimization func | ||
let g = utils.factorial(sequenceLength) * pi; | ||
if (!this.excludeAdditive) { | ||
g += _const.MIN_GUESSES_BEFORE_GROWING_SEQUENCE ** (sequenceLength - 1); | ||
} // update state if new best. | ||
} | ||
// update state if new best. | ||
// first see if any competing sequences covering this prefix, | ||
// with sequenceLength or fewer matches, | ||
// fare better than this sequence. if so, skip it and return. | ||
let shouldSkip = false; | ||
Object.keys(this.optimal.g[k]).forEach(competingPatternLength => { | ||
const competingMetricMatch = this.optimal.g[k][competingPatternLength]; | ||
if (parseInt(competingPatternLength, 10) <= sequenceLength) { | ||
@@ -79,3 +67,2 @@ if (competingMetricMatch <= g) { | ||
}); | ||
if (!shouldSkip) { | ||
@@ -88,3 +75,2 @@ // this sequence might be part of the final optimal sequence. | ||
}, | ||
// helper: evaluate bruteforce matches ending at passwordCharIndex. | ||
@@ -95,3 +81,2 @@ bruteforceUpdate(passwordCharIndex) { | ||
this.update(match, 1); | ||
for (let i = 1; i <= passwordCharIndex; i += 1) { | ||
@@ -102,10 +87,10 @@ // generate passwordCharIndex bruteforce matches, spanning from (i=1, j=passwordCharIndex) up to (i=passwordCharIndex, j=passwordCharIndex). | ||
match = this.makeBruteforceMatch(i, passwordCharIndex); | ||
const tmp = this.optimal.m[i - 1]; // eslint-disable-next-line no-loop-func | ||
const tmp = this.optimal.m[i - 1]; | ||
// eslint-disable-next-line no-loop-func | ||
Object.keys(tmp).forEach(sequenceLength => { | ||
const lastMatch = tmp[sequenceLength]; // corner: an optimal sequence will never have two adjacent bruteforce matches. | ||
const lastMatch = tmp[sequenceLength]; | ||
// corner: an optimal sequence will never have two adjacent bruteforce matches. | ||
// it is strictly better to have a single bruteforce match spanning the same region: | ||
// same contribution to the guess product with a lower length. | ||
// --> safe to skip those cases. | ||
if (lastMatch.pattern !== 'bruteforce') { | ||
@@ -118,3 +103,2 @@ // try adding m to this length-sequenceLength sequence. | ||
}, | ||
// helper: step backwards through optimal.m starting at the end, | ||
@@ -124,13 +108,12 @@ // constructing the final optimal match sequence. | ||
const optimalMatchSequence = []; | ||
let k = passwordLength - 1; // find the final best sequence length and score | ||
let sequenceLength = 0; // eslint-disable-next-line no-loss-of-precision | ||
let k = passwordLength - 1; | ||
// find the final best sequence length and score | ||
let sequenceLength = 0; | ||
// eslint-disable-next-line no-loss-of-precision | ||
let g = 2e308; | ||
const temp = this.optimal.g[k]; // safety check for empty passwords | ||
const temp = this.optimal.g[k]; | ||
// safety check for empty passwords | ||
if (temp) { | ||
Object.keys(temp).forEach(candidateSequenceLength => { | ||
const candidateMetricMatch = temp[candidateSequenceLength]; | ||
if (candidateMetricMatch < g) { | ||
@@ -142,3 +125,2 @@ sequenceLength = parseInt(candidateSequenceLength, 10); | ||
} | ||
while (k >= 0) { | ||
@@ -150,6 +132,4 @@ const match = this.optimal.m[k][sequenceLength]; | ||
} | ||
return optimalMatchSequence; | ||
} | ||
}; | ||
@@ -192,9 +172,9 @@ var scoring = { | ||
scoringHelper.excludeAdditive = excludeAdditive; | ||
const passwordLength = password.length; // partition matches into sublists according to ending index j | ||
const passwordLength = password.length; | ||
// partition matches into sublists according to ending index j | ||
let matchesByCoordinateJ = scoringHelper.fillArray(passwordLength, 'array'); | ||
matches.forEach(match => { | ||
matchesByCoordinateJ[match.j].push(match); | ||
}); // small detail: for deterministic output, sort each sublist by i. | ||
}); | ||
// small detail: for deterministic output, sort each sublist by i. | ||
matchesByCoordinateJ = matchesByCoordinateJ.map(match => match.sort((m1, m2) => m1.i - m2.i)); | ||
@@ -215,3 +195,2 @@ scoringHelper.optimal = { | ||
}; | ||
for (let k = 0; k < passwordLength; k += 1) { | ||
@@ -229,3 +208,2 @@ matchesByCoordinateJ[k].forEach(match => { | ||
} | ||
const optimalMatchSequence = scoringHelper.unwind(passwordLength); | ||
@@ -241,7 +219,5 @@ const optimalSequenceLength = optimalMatchSequence.length; | ||
}, | ||
getGuesses(password, optimalSequenceLength) { | ||
const passwordLength = password.length; | ||
let guesses = 0; | ||
if (password.length === 0) { | ||
@@ -252,6 +228,4 @@ guesses = 1; | ||
} | ||
return guesses; | ||
} | ||
}; | ||
@@ -258,0 +232,0 @@ |
@@ -6,13 +6,9 @@ var utils = { | ||
let count = n; | ||
if (k > count) { | ||
return 0; | ||
} | ||
if (k === 0) { | ||
return 1; | ||
} | ||
let coEff = 1; | ||
for (let i = 1; i <= k; i += 1) { | ||
@@ -23,6 +19,4 @@ coEff *= count; | ||
} | ||
return coEff; | ||
}, | ||
log10(n) { | ||
@@ -35,11 +29,7 @@ return Math.log(n) / Math.log(10); // IE doesn't support Math.log10 :( | ||
}, | ||
factorial(num) { | ||
let rval = 1; | ||
for (let i = 2; i <= num; i += 1) rval *= i; | ||
return rval; | ||
} | ||
}; | ||
@@ -46,0 +36,0 @@ |
@@ -8,13 +8,9 @@ 'use strict'; | ||
let count = n; | ||
if (k > count) { | ||
return 0; | ||
} | ||
if (k === 0) { | ||
return 1; | ||
} | ||
let coEff = 1; | ||
for (let i = 1; i <= k; i += 1) { | ||
@@ -25,6 +21,4 @@ coEff *= count; | ||
} | ||
return coEff; | ||
}, | ||
log10(n) { | ||
@@ -37,11 +31,7 @@ return Math.log(n) / Math.log(10); // IE doesn't support Math.log10 :( | ||
}, | ||
factorial(num) { | ||
let rval = 1; | ||
for (let i = 2; i <= num; i += 1) rval *= i; | ||
return rval; | ||
} | ||
}; | ||
@@ -48,0 +38,0 @@ |
@@ -1,2 +0,2 @@ | ||
import { CrackTimesDisplay, CrackTimesSeconds } from './types'; | ||
import { CrackTimesDisplay, CrackTimesSeconds, Score } from './types'; | ||
declare class TimeEstimates { | ||
@@ -7,7 +7,7 @@ translate(displayStr: string, value: number | undefined): string; | ||
crackTimesDisplay: CrackTimesDisplay; | ||
score: number; | ||
score: Score; | ||
}; | ||
guessesToScore(guesses: number): 1 | 2 | 3 | 4 | 0; | ||
guessesToScore(guesses: number): Score; | ||
displayTime(seconds: number): string; | ||
} | ||
export default TimeEstimates; |
@@ -24,11 +24,8 @@ import zxcvbnOptions from './Options.esm.js'; | ||
*/ | ||
class TimeEstimates { | ||
translate(displayStr, value) { | ||
let key = displayStr; | ||
if (value !== undefined && value !== 1) { | ||
key += 's'; | ||
} | ||
const { | ||
@@ -39,3 +36,2 @@ timeEstimation | ||
} | ||
estimateAttackTimes(guesses) { | ||
@@ -64,6 +60,4 @@ const crackTimesSeconds = { | ||
} | ||
guessesToScore(guesses) { | ||
const DELTA = 5; | ||
if (guesses < 1e3 + DELTA) { | ||
@@ -73,3 +67,2 @@ // risky password: "too guessable" | ||
} | ||
if (guesses < 1e6 + DELTA) { | ||
@@ -79,3 +72,2 @@ // modest protection from throttled online attacks: "very guessable" | ||
} | ||
if (guesses < 1e8 + DELTA) { | ||
@@ -85,3 +77,2 @@ // modest protection from unthrottled online attacks: "somewhat guessable" | ||
} | ||
if (guesses < 1e10 + DELTA) { | ||
@@ -91,8 +82,6 @@ // modest protection from offline attacks: "safely unguessable" | ||
return 3; | ||
} // strong protection from offline attacks under same scenario: "very unguessable" | ||
} | ||
// strong protection from offline attacks under same scenario: "very unguessable" | ||
return 4; | ||
} | ||
displayTime(seconds) { | ||
@@ -103,6 +92,4 @@ let displayStr = 'centuries'; | ||
const foundIndex = timeKeys.findIndex(time => seconds < times[time]); | ||
if (foundIndex > -1) { | ||
displayStr = timeKeys[foundIndex - 1]; | ||
if (foundIndex !== 0) { | ||
@@ -114,6 +101,4 @@ base = Math.round(seconds / times[displayStr]); | ||
} | ||
return this.translate(displayStr, base); | ||
} | ||
} | ||
@@ -120,0 +105,0 @@ |
@@ -26,17 +26,13 @@ 'use strict'; | ||
*/ | ||
class TimeEstimates { | ||
translate(displayStr, value) { | ||
let key = displayStr; | ||
if (value !== undefined && value !== 1) { | ||
key += 's'; | ||
} | ||
const { | ||
timeEstimation | ||
} = Options["default"].translations; | ||
} = Options.default.translations; | ||
return timeEstimation[key].replace('{base}', `${value}`); | ||
} | ||
estimateAttackTimes(guesses) { | ||
@@ -65,6 +61,4 @@ const crackTimesSeconds = { | ||
} | ||
guessesToScore(guesses) { | ||
const DELTA = 5; | ||
if (guesses < 1e3 + DELTA) { | ||
@@ -74,3 +68,2 @@ // risky password: "too guessable" | ||
} | ||
if (guesses < 1e6 + DELTA) { | ||
@@ -80,3 +73,2 @@ // modest protection from throttled online attacks: "very guessable" | ||
} | ||
if (guesses < 1e8 + DELTA) { | ||
@@ -86,3 +78,2 @@ // modest protection from unthrottled online attacks: "somewhat guessable" | ||
} | ||
if (guesses < 1e10 + DELTA) { | ||
@@ -92,8 +83,6 @@ // modest protection from offline attacks: "safely unguessable" | ||
return 3; | ||
} // strong protection from offline attacks under same scenario: "very unguessable" | ||
} | ||
// strong protection from offline attacks under same scenario: "very unguessable" | ||
return 4; | ||
} | ||
displayTime(seconds) { | ||
@@ -104,6 +93,4 @@ let displayStr = 'centuries'; | ||
const foundIndex = timeKeys.findIndex(time => seconds < times[time]); | ||
if (foundIndex > -1) { | ||
displayStr = timeKeys[foundIndex - 1]; | ||
if (foundIndex !== 0) { | ||
@@ -115,6 +102,4 @@ base = Math.round(seconds / times[displayStr]); | ||
} | ||
return this.translate(displayStr, base); | ||
} | ||
} | ||
@@ -121,0 +106,0 @@ |
@@ -6,9 +6,9 @@ import translationKeys from './data/translationKeys'; | ||
import Matching from './Matching'; | ||
export declare type TranslationKeys = typeof translationKeys; | ||
export declare type L33tTableDefault = typeof l33tTableDefault; | ||
export type TranslationKeys = typeof translationKeys; | ||
export type L33tTableDefault = typeof l33tTableDefault; | ||
export interface LooseObject { | ||
[key: string]: any; | ||
} | ||
export declare type Pattern = 'dictionary' | 'spatial' | 'repeat' | 'sequence' | 'regex' | 'date' | 'bruteforce' | string; | ||
export declare type DictionaryNames = 'passwords' | 'commonWords' | 'firstnames' | 'lastnames' | 'wikipedia' | 'userInputs'; | ||
export type Pattern = 'dictionary' | 'spatial' | 'repeat' | 'sequence' | 'regex' | 'date' | 'bruteforce' | string; | ||
export type DictionaryNames = 'passwords' | 'commonWords' | 'firstnames' | 'lastnames' | 'wikipedia' | 'userInputs'; | ||
export interface Match { | ||
@@ -66,3 +66,3 @@ pattern: Pattern; | ||
} | ||
export declare type MatchExtended = DictionaryMatch | L33tMatch | SpatialMatch | RepeatMatch | SequenceMatch | RegexMatch | DateMatch | BruteForceMatch | Match; | ||
export type MatchExtended = DictionaryMatch | L33tMatch | SpatialMatch | RepeatMatch | SequenceMatch | RegexMatch | DateMatch | BruteForceMatch | Match; | ||
export interface Estimate { | ||
@@ -75,3 +75,3 @@ guesses: number; | ||
} | ||
export declare type MatchEstimated = MatchExtended & Estimate; | ||
export type MatchEstimated = MatchExtended & Estimate; | ||
export interface Optimal { | ||
@@ -98,6 +98,6 @@ m: Match; | ||
} | ||
export declare type OptionsL33tTable = L33tTableDefault | { | ||
export type OptionsL33tTable = L33tTableDefault | { | ||
[key: string]: string[]; | ||
}; | ||
export declare type OptionsDictionary = { | ||
export type OptionsDictionary = { | ||
[key: string]: (string | number)[]; | ||
@@ -118,2 +118,3 @@ }; | ||
levenshteinThreshold?: number; | ||
l33tMaxSubstitutions?: number; | ||
} | ||
@@ -126,4 +127,4 @@ export interface RankedDictionary { | ||
} | ||
export declare type DefaultFeedbackFunction = (match: MatchEstimated, isSoleMatch?: Boolean) => FeedbackType | null; | ||
export declare type DefaultScoringFunction = (match: MatchExtended | MatchEstimated) => number | DictionaryReturn; | ||
export type DefaultFeedbackFunction = (match: MatchEstimated, isSoleMatch?: boolean) => FeedbackType | null; | ||
export type DefaultScoringFunction = (match: MatchExtended | MatchEstimated) => number | DictionaryReturn; | ||
export interface MatchOptions { | ||
@@ -133,3 +134,3 @@ password: string; | ||
} | ||
export declare type MatchingType = new () => { | ||
export type MatchingType = new () => { | ||
match({ password, omniMatch, }: MatchOptions): MatchExtended[] | Promise<MatchExtended[]>; | ||
@@ -145,2 +146,3 @@ }; | ||
} | ||
export type Score = 0 | 1 | 2 | 3 | 4; | ||
export interface ZxcvbnResult { | ||
@@ -150,3 +152,3 @@ feedback: FeedbackType; | ||
crackTimesDisplay: CrackTimesDisplay; | ||
score: number; | ||
score: Score; | ||
password: string; | ||
@@ -153,0 +155,0 @@ guesses: number; |
{ | ||
"name": "@zxcvbn-ts/core", | ||
"version": "2.1.0", | ||
"version": "2.2.0", | ||
"main": "dist/index.js", | ||
@@ -22,2 +22,5 @@ "module": "dist/index.esm.js", | ||
}, | ||
"dependencies": { | ||
"fastest-levenshtein": "1.0.16" | ||
}, | ||
"keywords": [ | ||
@@ -38,3 +41,3 @@ "password", | ||
], | ||
"gitHead": "9f30e0aaab33ff51e3c3f4d4bbab23186e569008" | ||
"gitHead": "a0315646a0e1f82771d9ac26ad0cac21f48ac0fe" | ||
} |
@@ -86,3 +86,3 @@ import zxcvbnOptions from './Options' | ||
getMatchFeedback(match: MatchEstimated, isSoleMatch: Boolean) { | ||
getMatchFeedback(match: MatchEstimated, isSoleMatch: boolean) { | ||
if (this.matchers[match.pattern]) { | ||
@@ -89,0 +89,0 @@ return this.matchers[match.pattern](match, isSoleMatch) |
@@ -5,5 +5,5 @@ import Matching from './Matching' | ||
import Feedback from './Feedback' | ||
import zxcvbnOptions from './Options' | ||
import zxcvbnOptions, { Options } from './Options' | ||
import debounce from './debounce' | ||
import { MatchExtended, ZxcvbnResult } from './types' | ||
import { MatchExtended, ZxcvbnResult, Matcher, MatchOptions } from './types' | ||
@@ -66,2 +66,10 @@ const time = () => new Date().getTime() | ||
export { zxcvbnOptions, ZxcvbnResult, debounce } | ||
export { | ||
zxcvbnOptions, | ||
ZxcvbnResult, | ||
debounce, | ||
Options, | ||
Matcher, | ||
MatchOptions, | ||
MatchExtended, | ||
} |
@@ -1,2 +0,2 @@ | ||
import { distance } from './vendor/fastest-levenshtein' | ||
import { distance } from 'fastest-levenshtein' | ||
import { LooseObject } from './types' | ||
@@ -3,0 +3,0 @@ |
@@ -7,3 +7,3 @@ import zxcvbnOptions from '../../Options' | ||
match: MatchEstimated, | ||
isSoleMatch?: Boolean, | ||
isSoleMatch?: boolean, | ||
) => { | ||
@@ -27,3 +27,3 @@ let warning = '' | ||
match: MatchEstimated, | ||
isSoleMatch?: Boolean, | ||
isSoleMatch?: boolean, | ||
) => { | ||
@@ -39,3 +39,3 @@ let warning = '' | ||
match: MatchEstimated, | ||
isSoleMatch?: Boolean, | ||
isSoleMatch?: boolean, | ||
) => { | ||
@@ -48,3 +48,3 @@ if (isSoleMatch) { | ||
const getDictionaryWarning = (match: MatchEstimated, isSoleMatch?: Boolean) => { | ||
const getDictionaryWarning = (match: MatchEstimated, isSoleMatch?: boolean) => { | ||
let warning = '' | ||
@@ -66,3 +66,3 @@ const dictName = match.dictionaryName | ||
export default (match: MatchEstimated, isSoleMatch?: Boolean) => { | ||
export default (match: MatchEstimated, isSoleMatch?: boolean) => { | ||
const warning = getDictionaryWarning(match, isSoleMatch) | ||
@@ -69,0 +69,0 @@ const suggestions: string[] = [] |
@@ -9,7 +9,4 @@ import findLevenshteinDistance, { | ||
import L33t from './variants/matching/l33t' | ||
import { DictionaryMatchOptions } from './types' | ||
interface DictionaryMatchOptions { | ||
password: string | ||
} | ||
class MatchDictionary { | ||
@@ -39,8 +36,12 @@ l33t: L33t | ||
// eslint-disable-next-line complexity | ||
// eslint-disable-next-line complexity,max-statements | ||
Object.keys(zxcvbnOptions.rankedDictionaries).forEach((dictionaryName) => { | ||
const rankedDict = | ||
zxcvbnOptions.rankedDictionaries[dictionaryName as DictionaryNames] | ||
const longestDictionaryWordSize = | ||
zxcvbnOptions.rankedDictionariesMaxWordSize[dictionaryName] | ||
const searchWidth = Math.min(longestDictionaryWordSize, passwordLength) | ||
for (let i = 0; i < passwordLength; i += 1) { | ||
for (let j = i; j < passwordLength; j += 1) { | ||
const searchEnd = Math.min(i + searchWidth, passwordLength) | ||
for (let j = i; j < searchEnd; j += 1) { | ||
const usedPassword = passwordLower.slice(i, +j + 1 || 9e9) | ||
@@ -47,0 +48,0 @@ const isInDictionary = usedPassword in rankedDict |
@@ -9,2 +9,3 @@ import { empty, translate } from '../../../../helper' | ||
} from '../../../../types' | ||
import { DefaultMatch } from '../../types' | ||
@@ -19,5 +20,5 @@ type Subs = string[][][] | ||
class MatchL33t { | ||
defaultMatch: Function | ||
defaultMatch: DefaultMatch | ||
constructor(defaultMatch: Function) { | ||
constructor(defaultMatch: DefaultMatch) { | ||
this.defaultMatch = defaultMatch | ||
@@ -31,3 +32,7 @@ } | ||
) | ||
for (let i = 0; i < enumeratedSubs.length; i += 1) { | ||
const length = Math.min( | ||
enumeratedSubs.length, | ||
zxcvbnOptions.l33tMaxSubstitutions, | ||
) | ||
for (let i = 0; i < length; i += 1) { | ||
const sub = enumeratedSubs[i] | ||
@@ -34,0 +39,0 @@ // corner case: password has no relevant subs. |
import { DictionaryMatch } from '../../../../types' | ||
import { DefaultMatch } from '../../types' | ||
@@ -8,6 +9,6 @@ /* | ||
*/ | ||
class MatchL33t { | ||
defaultMatch: Function | ||
class MatchReverse { | ||
defaultMatch: DefaultMatch | ||
constructor(defaultMatch: Function) { | ||
constructor(defaultMatch: DefaultMatch) { | ||
this.defaultMatch = defaultMatch | ||
@@ -31,2 +32,2 @@ } | ||
export default MatchL33t | ||
export default MatchReverse |
@@ -26,2 +26,4 @@ import { buildRankedDictionary } from './helper' | ||
rankedDictionariesMaxWordSize: Record<string, number> = {} | ||
translations: TranslationKeys = translationKeys | ||
@@ -37,2 +39,4 @@ | ||
l33tMaxSubstitutions: number = 100 | ||
constructor() { | ||
@@ -42,2 +46,3 @@ this.setRankedDictionaries() | ||
// eslint-disable-next-line max-statements | ||
setOptions(options: OptionsType = {}) { | ||
@@ -69,2 +74,6 @@ if (options.l33tTable) { | ||
} | ||
if (options.l33tMaxSubstitutions !== undefined) { | ||
this.l33tMaxSubstitutions = options.l33tMaxSubstitutions | ||
} | ||
} | ||
@@ -99,8 +108,24 @@ | ||
const rankedDictionaries: RankedDictionaries = {} | ||
const rankedDictionariesMaxWorkSize: Record<string, number> = {} | ||
Object.keys(this.dictionary).forEach((name) => { | ||
rankedDictionaries[name] = this.getRankedDictionary(name) | ||
rankedDictionariesMaxWorkSize[name] = | ||
this.getRankedDictionariesMaxWordSize(name) | ||
}) | ||
this.rankedDictionaries = rankedDictionaries | ||
this.rankedDictionariesMaxWordSize = rankedDictionariesMaxWorkSize | ||
} | ||
getRankedDictionariesMaxWordSize(name: string) { | ||
const data = this.dictionary[name].map((el) => { | ||
if (typeof el !== 'string') { | ||
return el.toString().length | ||
} | ||
return el.length | ||
}) | ||
const result = data.length === 0 ? 0 : Math.max(...data) | ||
return result | ||
} | ||
getRankedDictionary(name: string) { | ||
@@ -138,2 +163,4 @@ const list = this.dictionary[name] | ||
this.rankedDictionaries.userInputs = this.getRankedDictionary('userInputs') | ||
this.rankedDictionariesMaxWordSize.userInputs = | ||
this.getRankedDictionariesMaxWordSize('userInputs') | ||
} | ||
@@ -140,0 +167,0 @@ |
import utils from './utils' | ||
import estimateGuesses from './estimate' | ||
import { MIN_GUESSES_BEFORE_GROWING_SEQUENCE } from '../data/const' | ||
import { MatchExtended, BruteForceMatch, MatchEstimated } from '../types' | ||
import { | ||
MatchExtended, | ||
BruteForceMatch, | ||
MatchEstimated, | ||
LooseObject, | ||
} from '../types' | ||
@@ -11,5 +16,6 @@ const scoringHelper = { | ||
fillArray(size: number, valueType: 'object' | 'array') { | ||
const result: typeof valueType extends 'array' ? string[] : {}[] = [] | ||
const result: typeof valueType extends 'array' ? string[] : LooseObject[] = | ||
[] | ||
for (let i = 0; i < size; i += 1) { | ||
let value: [] | {} = [] | ||
let value: [] | LooseObject = [] | ||
if (valueType === 'object') { | ||
@@ -16,0 +22,0 @@ value = {} |
import zxcvbnOptions from './Options' | ||
import { CrackTimesDisplay, CrackTimesSeconds } from './types' | ||
import { CrackTimesDisplay, CrackTimesSeconds, Score } from './types' | ||
@@ -65,3 +65,3 @@ const SECOND = 1 | ||
guessesToScore(guesses: number) { | ||
guessesToScore(guesses: number): Score { | ||
const DELTA = 5 | ||
@@ -68,0 +68,0 @@ if (guesses < 1e3 + DELTA) { |
@@ -163,2 +163,3 @@ import translationKeys from './data/translationKeys' | ||
levenshteinThreshold?: number | ||
l33tMaxSubstitutions?: number | ||
} | ||
@@ -176,3 +177,3 @@ | ||
match: MatchEstimated, | ||
isSoleMatch?: Boolean, | ||
isSoleMatch?: boolean, | ||
) => FeedbackType | null | ||
@@ -206,2 +207,4 @@ | ||
export type Score = 0 | 1 | 2 | 3 | 4 | ||
export interface ZxcvbnResult { | ||
@@ -211,3 +214,3 @@ feedback: FeedbackType | ||
crackTimesDisplay: CrackTimesDisplay | ||
score: number | ||
score: Score | ||
password: string | ||
@@ -214,0 +217,0 @@ guesses: number |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
580157
1
243
9683
+ Addedfastest-levenshtein@1.0.16
+ Addedfastest-levenshtein@1.0.16(transitive)