@zxcvbn-ts/core
Advanced tools
Comparing version 3.0.4 to 4.0.0-beta.0
var dateSplits = { | ||
4: [ | ||
// for length-4 strings, eg 1191 or 9111, two ways to split: | ||
[1, 2], [2, 3] // 91 1 1 | ||
[1, 2], | ||
// 1 1 91 (2nd split starts at index 1, 3rd at index 2) | ||
[2, 3] // 91 1 1 | ||
], | ||
5: [[1, 3], [2, 3], | ||
5: [[1, 3], | ||
// 1 11 91 | ||
[2, 3], | ||
// 11 1 91 | ||
// [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 | ||
6: [[1, 2], | ||
// 1 1 1991 | ||
[2, 4], | ||
// 11 11 91 | ||
[4, 5] // 1991 1 1 | ||
], | ||
// 1111991 | ||
7: [[1, 3], [2, 3], [4, 5], [4, 6] // 1991 11 1 | ||
7: [[1, 3], | ||
// 1 11 1991 | ||
[2, 3], | ||
// 11 1 1991 | ||
[4, 5], | ||
// 1991 1 11 | ||
[4, 6] // 1991 11 1 | ||
], | ||
8: [[2, 4], [4, 6] // 1991 11 11 | ||
8: [[2, 4], | ||
// 11 11 1991 | ||
[4, 6] // 1991 11 11 | ||
] | ||
@@ -21,0 +34,0 @@ }; |
@@ -6,18 +6,31 @@ 'use strict'; | ||
// for length-4 strings, eg 1191 or 9111, two ways to split: | ||
[1, 2], [2, 3] // 91 1 1 | ||
[1, 2], | ||
// 1 1 91 (2nd split starts at index 1, 3rd at index 2) | ||
[2, 3] // 91 1 1 | ||
], | ||
5: [[1, 3], [2, 3], | ||
5: [[1, 3], | ||
// 1 11 91 | ||
[2, 3], | ||
// 11 1 91 | ||
// [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 | ||
6: [[1, 2], | ||
// 1 1 1991 | ||
[2, 4], | ||
// 11 11 91 | ||
[4, 5] // 1991 1 1 | ||
], | ||
// 1111991 | ||
7: [[1, 3], [2, 3], [4, 5], [4, 6] // 1991 11 1 | ||
7: [[1, 3], | ||
// 1 11 1991 | ||
[2, 3], | ||
// 11 1 1991 | ||
[4, 5], | ||
// 1991 1 11 | ||
[4, 6] // 1991 11 1 | ||
], | ||
8: [[2, 4], [4, 6] // 1991 11 11 | ||
8: [[2, 4], | ||
// 11 11 1991 | ||
[4, 6] // 1991 11 11 | ||
] | ||
@@ -24,0 +37,0 @@ }; |
@@ -1,14 +0,13 @@ | ||
import { DefaultFeedbackFunction, FeedbackType, MatchEstimated } from './types'; | ||
type Matchers = { | ||
[key: string]: DefaultFeedbackFunction; | ||
}; | ||
import Options from './Options'; | ||
import { FeedbackType, MatchEstimated } from './types'; | ||
declare class Feedback { | ||
readonly matchers: Matchers; | ||
defaultFeedback: FeedbackType; | ||
constructor(); | ||
setDefaultSuggestions(): void; | ||
private options; | ||
private readonly matchers; | ||
private defaultFeedback; | ||
constructor(options: Options); | ||
private setDefaultSuggestions; | ||
getFeedback(score: number, sequence: MatchEstimated[]): FeedbackType; | ||
getLongestMatch(sequence: MatchEstimated[]): MatchEstimated; | ||
getMatchFeedback(match: MatchEstimated, isSoleMatch: boolean): FeedbackType | null; | ||
private getLongestMatch; | ||
private getMatchFeedback; | ||
} | ||
export default Feedback; |
@@ -1,2 +0,1 @@ | ||
import { zxcvbnOptions } from './Options.esm.js'; | ||
import bruteforceMatcher from './matcher/bruteforce/feedback.esm.js'; | ||
@@ -21,3 +20,4 @@ import dateMatcher from './matcher/date/feedback.esm.js'; | ||
class Feedback { | ||
constructor() { | ||
constructor(options) { | ||
this.options = options; | ||
this.matchers = { | ||
@@ -40,3 +40,3 @@ bruteforce: bruteforceMatcher, | ||
setDefaultSuggestions() { | ||
this.defaultFeedback.suggestions.push(zxcvbnOptions.translations.suggestions.useWords, zxcvbnOptions.translations.suggestions.noNeed); | ||
this.defaultFeedback.suggestions.push(this.options.translations.suggestions.useWords, this.options.translations.suggestions.noNeed); | ||
} | ||
@@ -50,3 +50,3 @@ getFeedback(score, sequence) { | ||
} | ||
const extraFeedback = zxcvbnOptions.translations.suggestions.anotherWord; | ||
const extraFeedback = this.options.translations.suggestions.anotherWord; | ||
const longestMatch = this.getLongestMatch(sequence); | ||
@@ -76,6 +76,6 @@ let feedback = this.getMatchFeedback(longestMatch, sequence.length === 1); | ||
if (this.matchers[match.pattern]) { | ||
return this.matchers[match.pattern](match, isSoleMatch); | ||
return this.matchers[match.pattern](this.options, match, isSoleMatch); | ||
} | ||
if (zxcvbnOptions.matchers[match.pattern] && 'feedback' in zxcvbnOptions.matchers[match.pattern]) { | ||
return zxcvbnOptions.matchers[match.pattern].feedback(match, isSoleMatch); | ||
if (this.options.matchers[match.pattern] && 'feedback' in this.options.matchers[match.pattern]) { | ||
return this.options.matchers[match.pattern].feedback(this.options, match, isSoleMatch); | ||
} | ||
@@ -82,0 +82,0 @@ return defaultFeedback; |
'use strict'; | ||
var Options = require('./Options.js'); | ||
var feedback = require('./matcher/bruteforce/feedback.js'); | ||
@@ -23,3 +22,4 @@ var feedback$1 = require('./matcher/date/feedback.js'); | ||
class Feedback { | ||
constructor() { | ||
constructor(options) { | ||
this.options = options; | ||
this.matchers = { | ||
@@ -42,3 +42,3 @@ bruteforce: feedback, | ||
setDefaultSuggestions() { | ||
this.defaultFeedback.suggestions.push(Options.zxcvbnOptions.translations.suggestions.useWords, Options.zxcvbnOptions.translations.suggestions.noNeed); | ||
this.defaultFeedback.suggestions.push(this.options.translations.suggestions.useWords, this.options.translations.suggestions.noNeed); | ||
} | ||
@@ -52,3 +52,3 @@ getFeedback(score, sequence) { | ||
} | ||
const extraFeedback = Options.zxcvbnOptions.translations.suggestions.anotherWord; | ||
const extraFeedback = this.options.translations.suggestions.anotherWord; | ||
const longestMatch = this.getLongestMatch(sequence); | ||
@@ -78,6 +78,6 @@ let feedback = this.getMatchFeedback(longestMatch, sequence.length === 1); | ||
if (this.matchers[match.pattern]) { | ||
return this.matchers[match.pattern](match, isSoleMatch); | ||
return this.matchers[match.pattern](this.options, match, isSoleMatch); | ||
} | ||
if (Options.zxcvbnOptions.matchers[match.pattern] && 'feedback' in Options.zxcvbnOptions.matchers[match.pattern]) { | ||
return Options.zxcvbnOptions.matchers[match.pattern].feedback(match, isSoleMatch); | ||
if (this.options.matchers[match.pattern] && 'feedback' in this.options.matchers[match.pattern]) { | ||
return this.options.matchers[match.pattern].feedback(this.options, match, isSoleMatch); | ||
} | ||
@@ -84,0 +84,0 @@ return defaultFeedback; |
@@ -1,7 +0,15 @@ | ||
import { zxcvbnOptions, Options } from './Options'; | ||
import debounce from './debounce'; | ||
import { ZxcvbnResult } from './types'; | ||
export declare const zxcvbn: (password: string, userInputs?: (string | number)[]) => ZxcvbnResult; | ||
export declare const zxcvbnAsync: (password: string, userInputs?: (string | number)[]) => Promise<ZxcvbnResult>; | ||
import debounce from './utils/debounce'; | ||
import { Matcher, OptionsType, ZxcvbnResult } from './types'; | ||
declare class ZxcvbnFactory { | ||
private options; | ||
private scoring; | ||
constructor(options?: OptionsType, customMatchers?: Record<string, Matcher>); | ||
private estimateAttackTimes; | ||
private getFeedback; | ||
private createReturnValue; | ||
private main; | ||
check(password: string, userInputs?: (string | number)[]): ZxcvbnResult; | ||
checkAsync(password: string, userInputs?: (string | number)[]): Promise<ZxcvbnResult>; | ||
} | ||
export * from './types'; | ||
export { zxcvbnOptions, Options, debounce }; | ||
export { ZxcvbnFactory, debounce }; |
import Matching from './Matching.esm.js'; | ||
import scoring from './scoring/index.esm.js'; | ||
import TimeEstimates from './TimeEstimates.esm.js'; | ||
import { TimeEstimates } from './TimeEstimates.esm.js'; | ||
import Feedback from './Feedback.esm.js'; | ||
import { zxcvbnOptions } from './Options.esm.js'; | ||
export { Options } from './Options.esm.js'; | ||
export { default as debounce } from './debounce.esm.js'; | ||
import Options from './Options.esm.js'; | ||
export { default as debounce } from './utils/debounce.esm.js'; | ||
import Scoring from './scoring/index.esm.js'; | ||
const time = () => new Date().getTime(); | ||
const createReturnValue = (resolvedMatches, password, start) => { | ||
const feedback = new Feedback(); | ||
const timeEstimates = new TimeEstimates(); | ||
const matchSequence = scoring.mostGuessableMatchSequence(password, resolvedMatches); | ||
const calcTime = time() - start; | ||
const attackTimes = timeEstimates.estimateAttackTimes(matchSequence.guesses); | ||
return { | ||
calcTime, | ||
...matchSequence, | ||
...attackTimes, | ||
feedback: feedback.getFeedback(attackTimes.score, matchSequence.sequence) | ||
}; | ||
}; | ||
const main = (password, userInputs) => { | ||
if (userInputs) { | ||
zxcvbnOptions.extendUserInputsDictionary(userInputs); | ||
class ZxcvbnFactory { | ||
constructor(options = {}, customMatchers = {}) { | ||
this.options = new Options(options, customMatchers); | ||
this.scoring = new Scoring(this.options); | ||
} | ||
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.'); | ||
estimateAttackTimes(guesses) { | ||
const timeEstimates = new TimeEstimates(this.options); | ||
return timeEstimates.estimateAttackTimes(guesses); | ||
} | ||
return createReturnValue(matches, password, start); | ||
}; | ||
const zxcvbnAsync = async (password, userInputs) => { | ||
const usedPassword = password.substring(0, zxcvbnOptions.maxLength); | ||
const start = time(); | ||
const matches = await main(usedPassword, userInputs); | ||
return createReturnValue(matches, usedPassword, start); | ||
}; | ||
getFeedback(score, sequence) { | ||
const feedback = new Feedback(this.options); | ||
return feedback.getFeedback(score, sequence); | ||
} | ||
createReturnValue(resolvedMatches, password, start) { | ||
const matchSequence = this.scoring.mostGuessableMatchSequence(password, resolvedMatches); | ||
const calcTime = time() - start; | ||
const attackTimes = this.estimateAttackTimes(matchSequence.guesses); | ||
return { | ||
calcTime, | ||
...matchSequence, | ||
...attackTimes, | ||
feedback: this.getFeedback(attackTimes.score, matchSequence.sequence) | ||
}; | ||
} | ||
main(password, userInputs) { | ||
const userInputsOptions = this.options.getUserInputsOptions(userInputs); | ||
const matching = new Matching(this.options); | ||
return matching.match(password, userInputsOptions); | ||
} | ||
check(password, userInputs) { | ||
const reducedPassword = password.substring(0, this.options.maxLength); | ||
const start = time(); | ||
const matches = this.main(reducedPassword, userInputs); | ||
if (matches instanceof Promise) { | ||
throw new Error('You are using a Promised matcher, please use `zxcvbnAsync` for it.'); | ||
} | ||
return this.createReturnValue(matches, reducedPassword, start); | ||
} | ||
async checkAsync(password, userInputs) { | ||
const reducedPassword = password.substring(0, this.options.maxLength); | ||
const start = time(); | ||
const matches = await this.main(reducedPassword, userInputs); | ||
return this.createReturnValue(matches, reducedPassword, start); | ||
} | ||
} | ||
export { zxcvbn, zxcvbnAsync, zxcvbnOptions }; | ||
export { ZxcvbnFactory }; | ||
//# sourceMappingURL=index.esm.js.map |
'use strict'; | ||
var Matching = require('./Matching.js'); | ||
var index = require('./scoring/index.js'); | ||
var TimeEstimates = require('./TimeEstimates.js'); | ||
var Feedback = require('./Feedback.js'); | ||
var Options = require('./Options.js'); | ||
var debounce = require('./debounce.js'); | ||
var debounce = require('./utils/debounce.js'); | ||
var index = require('./scoring/index.js'); | ||
const time = () => new Date().getTime(); | ||
const createReturnValue = (resolvedMatches, password, start) => { | ||
const feedback = new Feedback(); | ||
const timeEstimates = new TimeEstimates(); | ||
const matchSequence = index.mostGuessableMatchSequence(password, resolvedMatches); | ||
const calcTime = time() - start; | ||
const attackTimes = timeEstimates.estimateAttackTimes(matchSequence.guesses); | ||
return { | ||
calcTime, | ||
...matchSequence, | ||
...attackTimes, | ||
feedback: feedback.getFeedback(attackTimes.score, matchSequence.sequence) | ||
}; | ||
}; | ||
const main = (password, userInputs) => { | ||
if (userInputs) { | ||
Options.zxcvbnOptions.extendUserInputsDictionary(userInputs); | ||
class ZxcvbnFactory { | ||
constructor(options = {}, customMatchers = {}) { | ||
this.options = new Options(options, customMatchers); | ||
this.scoring = new index(this.options); | ||
} | ||
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.'); | ||
estimateAttackTimes(guesses) { | ||
const timeEstimates = new TimeEstimates.TimeEstimates(this.options); | ||
return timeEstimates.estimateAttackTimes(guesses); | ||
} | ||
return createReturnValue(matches, password, start); | ||
}; | ||
const zxcvbnAsync = async (password, userInputs) => { | ||
const usedPassword = password.substring(0, Options.zxcvbnOptions.maxLength); | ||
const start = time(); | ||
const matches = await main(usedPassword, userInputs); | ||
return createReturnValue(matches, usedPassword, start); | ||
}; | ||
getFeedback(score, sequence) { | ||
const feedback = new Feedback(this.options); | ||
return feedback.getFeedback(score, sequence); | ||
} | ||
createReturnValue(resolvedMatches, password, start) { | ||
const matchSequence = this.scoring.mostGuessableMatchSequence(password, resolvedMatches); | ||
const calcTime = time() - start; | ||
const attackTimes = this.estimateAttackTimes(matchSequence.guesses); | ||
return { | ||
calcTime, | ||
...matchSequence, | ||
...attackTimes, | ||
feedback: this.getFeedback(attackTimes.score, matchSequence.sequence) | ||
}; | ||
} | ||
main(password, userInputs) { | ||
const userInputsOptions = this.options.getUserInputsOptions(userInputs); | ||
const matching = new Matching(this.options); | ||
return matching.match(password, userInputsOptions); | ||
} | ||
check(password, userInputs) { | ||
const reducedPassword = password.substring(0, this.options.maxLength); | ||
const start = time(); | ||
const matches = this.main(reducedPassword, userInputs); | ||
if (matches instanceof Promise) { | ||
throw new Error('You are using a Promised matcher, please use `zxcvbnAsync` for it.'); | ||
} | ||
return this.createReturnValue(matches, reducedPassword, start); | ||
} | ||
async checkAsync(password, userInputs) { | ||
const reducedPassword = password.substring(0, this.options.maxLength); | ||
const start = time(); | ||
const matches = await this.main(reducedPassword, userInputs); | ||
return this.createReturnValue(matches, reducedPassword, start); | ||
} | ||
} | ||
exports.Options = Options.Options; | ||
exports.zxcvbnOptions = Options.zxcvbnOptions; | ||
exports.debounce = debounce; | ||
exports.zxcvbn = zxcvbn; | ||
exports.zxcvbnAsync = zxcvbnAsync; | ||
exports.ZxcvbnFactory = ZxcvbnFactory; | ||
//# sourceMappingURL=index.js.map |
@@ -1,2 +0,3 @@ | ||
declare const _default: () => { | ||
import Options from '../../Options'; | ||
declare const _default: (options: Options) => { | ||
warning: string; | ||
@@ -3,0 +4,0 @@ suggestions: string[]; |
@@ -1,7 +0,5 @@ | ||
import { zxcvbnOptions } from '../../Options.esm.js'; | ||
var dateMatcher = (() => { | ||
var dateMatcher = (options => { | ||
return { | ||
warning: zxcvbnOptions.translations.warnings.dates, | ||
suggestions: [zxcvbnOptions.translations.suggestions.dates] | ||
warning: options.translations.warnings.dates, | ||
suggestions: [options.translations.suggestions.dates] | ||
}; | ||
@@ -8,0 +6,0 @@ }); |
'use strict'; | ||
var Options = require('../../Options.js'); | ||
var dateMatcher = (() => { | ||
var dateMatcher = (options => { | ||
return { | ||
warning: Options.zxcvbnOptions.translations.warnings.dates, | ||
suggestions: [Options.zxcvbnOptions.translations.suggestions.dates] | ||
warning: options.translations.warnings.dates, | ||
suggestions: [options.translations.suggestions.dates] | ||
}; | ||
@@ -10,0 +8,0 @@ }); |
@@ -1,6 +0,7 @@ | ||
import { DateMatch } from '../../types'; | ||
interface DateMatchOptions { | ||
password: string; | ||
} | ||
import { DateMatch, MatchOptions } from '../../types'; | ||
import Options from '../../Options'; | ||
type DateMatchOptions = Pick<MatchOptions, 'password'>; | ||
declare class MatchDate { | ||
private options; | ||
constructor(options: Options); | ||
match({ password }: DateMatchOptions): import("../../types").MatchExtended[]; | ||
@@ -7,0 +8,0 @@ getMatchesWithSeparator(password: string): DateMatch[]; |
import { DATE_MIN_YEAR, DATE_MAX_YEAR, REFERENCE_YEAR, DATE_SPLITS } from '../../data/const.esm.js'; | ||
import { sorted } from '../../helper.esm.js'; | ||
import { sorted } from '../../utils/helper.esm.js'; | ||
@@ -10,2 +10,5 @@ /* | ||
class MatchDate { | ||
constructor(options) { | ||
this.options = options; | ||
} | ||
/* | ||
@@ -191,5 +194,6 @@ * a "date" is recognized as: | ||
// first look for a four digit year: yyyy + daymonth or daymonth + yyyy | ||
const possibleYearSplits = [[integers[2], integers.slice(0, 2)], [integers[0], integers.slice(1, 3)] // year first | ||
const possibleYearSplits = [[integers[2], integers.slice(0, 2)], | ||
// year last | ||
[integers[0], integers.slice(1, 3)] // year first | ||
]; | ||
const possibleYearSplitsLength = possibleYearSplits.length; | ||
@@ -196,0 +200,0 @@ for (let j = 0; j < possibleYearSplitsLength; j += 1) { |
'use strict'; | ||
var _const = require('../../data/const.js'); | ||
var helper = require('../../helper.js'); | ||
var helper = require('../../utils/helper.js'); | ||
@@ -12,2 +12,5 @@ /* | ||
class MatchDate { | ||
constructor(options) { | ||
this.options = options; | ||
} | ||
/* | ||
@@ -193,5 +196,6 @@ * a "date" is recognized as: | ||
// first look for a four digit year: yyyy + daymonth or daymonth + yyyy | ||
const possibleYearSplits = [[integers[2], integers.slice(0, 2)], [integers[0], integers.slice(1, 3)] // year first | ||
const possibleYearSplits = [[integers[2], integers.slice(0, 2)], | ||
// year last | ||
[integers[0], integers.slice(1, 3)] // year first | ||
]; | ||
const possibleYearSplitsLength = possibleYearSplits.length; | ||
@@ -198,0 +202,0 @@ for (let j = 0; j < possibleYearSplitsLength; j += 1) { |
@@ -0,3 +1,4 @@ | ||
import Options from '../../Options'; | ||
import { MatchEstimated } from '../../types'; | ||
declare const _default: (match: MatchEstimated, isSoleMatch?: boolean) => { | ||
declare const _default: (options: Options, match: MatchEstimated, isSoleMatch?: boolean) => { | ||
warning: string | null; | ||
@@ -4,0 +5,0 @@ suggestions: string[]; |
@@ -1,61 +0,62 @@ | ||
import { zxcvbnOptions } from '../../Options.esm.js'; | ||
import { START_UPPER, ALL_UPPER_INVERTED } from '../../data/const.esm.js'; | ||
const getDictionaryWarningPassword = (match, isSoleMatch) => { | ||
const getDictionaryWarningPassword = (options, match, isSoleMatch) => { | ||
let warning = null; | ||
if (isSoleMatch && !match.l33t && !match.reversed) { | ||
if (match.rank <= 10) { | ||
warning = zxcvbnOptions.translations.warnings.topTen; | ||
warning = options.translations.warnings.topTen; | ||
} else if (match.rank <= 100) { | ||
warning = zxcvbnOptions.translations.warnings.topHundred; | ||
warning = options.translations.warnings.topHundred; | ||
} else { | ||
warning = zxcvbnOptions.translations.warnings.common; | ||
warning = options.translations.warnings.common; | ||
} | ||
} else if (match.guessesLog10 <= 4) { | ||
warning = zxcvbnOptions.translations.warnings.similarToCommon; | ||
warning = options.translations.warnings.similarToCommon; | ||
} | ||
return warning; | ||
}; | ||
const getDictionaryWarningWikipedia = (match, isSoleMatch) => { | ||
const getDictionaryWarningWikipedia = (options, match, isSoleMatch) => { | ||
let warning = null; | ||
if (isSoleMatch) { | ||
warning = zxcvbnOptions.translations.warnings.wordByItself; | ||
warning = options.translations.warnings.wordByItself; | ||
} | ||
return warning; | ||
}; | ||
const getDictionaryWarningNames = (match, isSoleMatch) => { | ||
const getDictionaryWarningNames = (options, match, isSoleMatch) => { | ||
if (isSoleMatch) { | ||
return zxcvbnOptions.translations.warnings.namesByThemselves; | ||
return options.translations.warnings.namesByThemselves; | ||
} | ||
return zxcvbnOptions.translations.warnings.commonNames; | ||
return options.translations.warnings.commonNames; | ||
}; | ||
const getDictionaryWarning = (match, isSoleMatch) => { | ||
let warning = null; | ||
const getDictionaryWarning = (options, match, isSoleMatch) => { | ||
const dictName = match.dictionaryName; | ||
const isAName = dictName === 'lastnames' || dictName.toLowerCase().includes('firstnames'); | ||
if (dictName === 'passwords') { | ||
warning = getDictionaryWarningPassword(match, isSoleMatch); | ||
} else if (dictName.includes('wikipedia')) { | ||
warning = getDictionaryWarningWikipedia(match, isSoleMatch); | ||
} else if (isAName) { | ||
warning = getDictionaryWarningNames(match, isSoleMatch); | ||
} else if (dictName === 'userInputs') { | ||
warning = zxcvbnOptions.translations.warnings.userInputs; | ||
const isAName = dictName.toLowerCase().includes('lastnames') || dictName.toLowerCase().includes('firstnames') || dictName.toLowerCase().includes('names'); | ||
if (dictName.includes('passwords')) { | ||
return getDictionaryWarningPassword(options, match, isSoleMatch); | ||
} | ||
return warning; | ||
if (dictName.includes('wikipedia')) { | ||
return getDictionaryWarningWikipedia(options, match, isSoleMatch); | ||
} | ||
if (isAName) { | ||
return getDictionaryWarningNames(options, match, isSoleMatch); | ||
} | ||
if (dictName.includes('userInputs')) { | ||
return options.translations.warnings.userInputs; | ||
} | ||
return null; | ||
}; | ||
var dictionaryMatcher = ((match, isSoleMatch) => { | ||
const warning = getDictionaryWarning(match, isSoleMatch); | ||
var dictionaryMatcher = ((options, match, isSoleMatch) => { | ||
const warning = getDictionaryWarning(options, match, isSoleMatch); | ||
const suggestions = []; | ||
const word = match.token; | ||
if (word.match(START_UPPER)) { | ||
suggestions.push(zxcvbnOptions.translations.suggestions.capitalization); | ||
suggestions.push(options.translations.suggestions.capitalization); | ||
} else if (word.match(ALL_UPPER_INVERTED) && word.toLowerCase() !== word) { | ||
suggestions.push(zxcvbnOptions.translations.suggestions.allUppercase); | ||
suggestions.push(options.translations.suggestions.allUppercase); | ||
} | ||
if (match.reversed && match.token.length >= 4) { | ||
suggestions.push(zxcvbnOptions.translations.suggestions.reverseWords); | ||
suggestions.push(options.translations.suggestions.reverseWords); | ||
} | ||
if (match.l33t) { | ||
suggestions.push(zxcvbnOptions.translations.suggestions.l33t); | ||
suggestions.push(options.translations.suggestions.l33t); | ||
} | ||
@@ -62,0 +63,0 @@ return { |
'use strict'; | ||
var Options = require('../../Options.js'); | ||
var _const = require('../../data/const.js'); | ||
const getDictionaryWarningPassword = (match, isSoleMatch) => { | ||
const getDictionaryWarningPassword = (options, match, isSoleMatch) => { | ||
let warning = null; | ||
if (isSoleMatch && !match.l33t && !match.reversed) { | ||
if (match.rank <= 10) { | ||
warning = Options.zxcvbnOptions.translations.warnings.topTen; | ||
warning = options.translations.warnings.topTen; | ||
} else if (match.rank <= 100) { | ||
warning = Options.zxcvbnOptions.translations.warnings.topHundred; | ||
warning = options.translations.warnings.topHundred; | ||
} else { | ||
warning = Options.zxcvbnOptions.translations.warnings.common; | ||
warning = options.translations.warnings.common; | ||
} | ||
} else if (match.guessesLog10 <= 4) { | ||
warning = Options.zxcvbnOptions.translations.warnings.similarToCommon; | ||
warning = options.translations.warnings.similarToCommon; | ||
} | ||
return warning; | ||
}; | ||
const getDictionaryWarningWikipedia = (match, isSoleMatch) => { | ||
const getDictionaryWarningWikipedia = (options, match, isSoleMatch) => { | ||
let warning = null; | ||
if (isSoleMatch) { | ||
warning = Options.zxcvbnOptions.translations.warnings.wordByItself; | ||
warning = options.translations.warnings.wordByItself; | ||
} | ||
return warning; | ||
}; | ||
const getDictionaryWarningNames = (match, isSoleMatch) => { | ||
const getDictionaryWarningNames = (options, match, isSoleMatch) => { | ||
if (isSoleMatch) { | ||
return Options.zxcvbnOptions.translations.warnings.namesByThemselves; | ||
return options.translations.warnings.namesByThemselves; | ||
} | ||
return Options.zxcvbnOptions.translations.warnings.commonNames; | ||
return options.translations.warnings.commonNames; | ||
}; | ||
const getDictionaryWarning = (match, isSoleMatch) => { | ||
let warning = null; | ||
const getDictionaryWarning = (options, match, isSoleMatch) => { | ||
const dictName = match.dictionaryName; | ||
const isAName = dictName === 'lastnames' || dictName.toLowerCase().includes('firstnames'); | ||
if (dictName === 'passwords') { | ||
warning = getDictionaryWarningPassword(match, isSoleMatch); | ||
} else if (dictName.includes('wikipedia')) { | ||
warning = getDictionaryWarningWikipedia(match, isSoleMatch); | ||
} else if (isAName) { | ||
warning = getDictionaryWarningNames(match, isSoleMatch); | ||
} else if (dictName === 'userInputs') { | ||
warning = Options.zxcvbnOptions.translations.warnings.userInputs; | ||
const isAName = dictName.toLowerCase().includes('lastnames') || dictName.toLowerCase().includes('firstnames') || dictName.toLowerCase().includes('names'); | ||
if (dictName.includes('passwords')) { | ||
return getDictionaryWarningPassword(options, match, isSoleMatch); | ||
} | ||
return warning; | ||
if (dictName.includes('wikipedia')) { | ||
return getDictionaryWarningWikipedia(options, match, isSoleMatch); | ||
} | ||
if (isAName) { | ||
return getDictionaryWarningNames(options, match, isSoleMatch); | ||
} | ||
if (dictName.includes('userInputs')) { | ||
return options.translations.warnings.userInputs; | ||
} | ||
return null; | ||
}; | ||
var dictionaryMatcher = ((match, isSoleMatch) => { | ||
const warning = getDictionaryWarning(match, isSoleMatch); | ||
var dictionaryMatcher = ((options, match, isSoleMatch) => { | ||
const warning = getDictionaryWarning(options, match, isSoleMatch); | ||
const suggestions = []; | ||
const word = match.token; | ||
if (word.match(_const.START_UPPER)) { | ||
suggestions.push(Options.zxcvbnOptions.translations.suggestions.capitalization); | ||
suggestions.push(options.translations.suggestions.capitalization); | ||
} else if (word.match(_const.ALL_UPPER_INVERTED) && word.toLowerCase() !== word) { | ||
suggestions.push(Options.zxcvbnOptions.translations.suggestions.allUppercase); | ||
suggestions.push(options.translations.suggestions.allUppercase); | ||
} | ||
if (match.reversed && match.token.length >= 4) { | ||
suggestions.push(Options.zxcvbnOptions.translations.suggestions.reverseWords); | ||
suggestions.push(options.translations.suggestions.reverseWords); | ||
} | ||
if (match.l33t) { | ||
suggestions.push(Options.zxcvbnOptions.translations.suggestions.l33t); | ||
suggestions.push(options.translations.suggestions.l33t); | ||
} | ||
@@ -64,0 +65,0 @@ return { |
@@ -0,1 +1,2 @@ | ||
import Options from '../../Options'; | ||
import { DictionaryMatch } from '../../types'; | ||
@@ -6,8 +7,9 @@ import Reverse from './variants/matching/reverse'; | ||
declare class MatchDictionary { | ||
private options; | ||
l33t: L33t; | ||
reverse: Reverse; | ||
constructor(); | ||
match({ password }: DictionaryMatchOptions): import("../../types").MatchExtended[]; | ||
defaultMatch({ password, useLevenshtein }: DictionaryMatchOptions): DictionaryMatch[]; | ||
constructor(options: Options); | ||
match(matchOptions: DictionaryMatchOptions): import("../../types").MatchExtended[]; | ||
defaultMatch({ password, userInputsOptions, useLevenshtein, }: DictionaryMatchOptions): DictionaryMatch[]; | ||
} | ||
export default MatchDictionary; |
@@ -1,22 +0,15 @@ | ||
import findLevenshteinDistance from '../../levenshtein.esm.js'; | ||
import { sorted } from '../../helper.esm.js'; | ||
import { zxcvbnOptions } from '../../Options.esm.js'; | ||
import findLevenshteinDistance from '../../utils/levenshtein.esm.js'; | ||
import { sorted } from '../../utils/helper.esm.js'; | ||
import MatchReverse from './variants/matching/reverse.esm.js'; | ||
import MatchL33t from './variants/matching/l33t.esm.js'; | ||
import mergeUserInputDictionary from '../../utils/mergeUserInputDictionary.esm.js'; | ||
class MatchDictionary { | ||
constructor() { | ||
this.l33t = new MatchL33t(this.defaultMatch); | ||
this.reverse = new MatchReverse(this.defaultMatch); | ||
constructor(options) { | ||
this.options = options; | ||
this.l33t = new MatchL33t(options, this.defaultMatch); | ||
this.reverse = new MatchReverse(options, this.defaultMatch); | ||
} | ||
match({ | ||
password | ||
}) { | ||
const matches = [...this.defaultMatch({ | ||
password | ||
}), ...this.reverse.match({ | ||
password | ||
}), ...this.l33t.match({ | ||
password | ||
})]; | ||
match(matchOptions) { | ||
const matches = [...this.defaultMatch(matchOptions), ...this.reverse.match(matchOptions), ...this.l33t.match(matchOptions)]; | ||
return sorted(matches); | ||
@@ -26,2 +19,3 @@ } | ||
password, | ||
userInputsOptions, | ||
useLevenshtein = true | ||
@@ -32,6 +26,10 @@ }) { | ||
const passwordLower = password.toLowerCase(); | ||
const { | ||
rankedDictionaries, | ||
rankedDictionariesMaxWordSize | ||
} = mergeUserInputDictionary(this.options.rankedDictionaries, this.options.rankedDictionariesMaxWordSize, userInputsOptions); | ||
// eslint-disable-next-line complexity,max-statements | ||
Object.keys(zxcvbnOptions.rankedDictionaries).forEach(dictionaryName => { | ||
const rankedDict = zxcvbnOptions.rankedDictionaries[dictionaryName]; | ||
const longestDictionaryWordSize = zxcvbnOptions.rankedDictionariesMaxWordSize[dictionaryName]; | ||
Object.keys(rankedDictionaries).forEach(dictionaryName => { | ||
const rankedDict = rankedDictionaries[dictionaryName]; | ||
const longestDictionaryWordSize = rankedDictionariesMaxWordSize[dictionaryName]; | ||
const searchWidth = Math.min(longestDictionaryWordSize, passwordLength); | ||
@@ -47,4 +45,4 @@ for (let i = 0; i < passwordLength; i += 1) { | ||
const isFullPassword = i === 0 && j === passwordLength - 1; | ||
if (zxcvbnOptions.useLevenshteinDistance && isFullPassword && !isInDictionary && useLevenshtein) { | ||
foundLevenshteinDistance = findLevenshteinDistance(usedPassword, rankedDict, zxcvbnOptions.levenshteinThreshold); | ||
if (this.options.useLevenshteinDistance && isFullPassword && !isInDictionary && useLevenshtein) { | ||
foundLevenshteinDistance = findLevenshteinDistance(usedPassword, rankedDict, this.options.levenshteinThreshold); | ||
} | ||
@@ -51,0 +49,0 @@ const isLevenshteinMatch = Object.keys(foundLevenshteinDistance).length !== 0; |
'use strict'; | ||
var levenshtein = require('../../levenshtein.js'); | ||
var helper = require('../../helper.js'); | ||
var Options = require('../../Options.js'); | ||
var levenshtein = require('../../utils/levenshtein.js'); | ||
var helper = require('../../utils/helper.js'); | ||
var reverse = require('./variants/matching/reverse.js'); | ||
var l33t = require('./variants/matching/l33t.js'); | ||
var mergeUserInputDictionary = require('../../utils/mergeUserInputDictionary.js'); | ||
class MatchDictionary { | ||
constructor() { | ||
this.l33t = new l33t(this.defaultMatch); | ||
this.reverse = new reverse(this.defaultMatch); | ||
constructor(options) { | ||
this.options = options; | ||
this.l33t = new l33t(options, this.defaultMatch); | ||
this.reverse = new reverse(options, this.defaultMatch); | ||
} | ||
match({ | ||
password | ||
}) { | ||
const matches = [...this.defaultMatch({ | ||
password | ||
}), ...this.reverse.match({ | ||
password | ||
}), ...this.l33t.match({ | ||
password | ||
})]; | ||
match(matchOptions) { | ||
const matches = [...this.defaultMatch(matchOptions), ...this.reverse.match(matchOptions), ...this.l33t.match(matchOptions)]; | ||
return helper.sorted(matches); | ||
@@ -28,2 +21,3 @@ } | ||
password, | ||
userInputsOptions, | ||
useLevenshtein = true | ||
@@ -34,6 +28,10 @@ }) { | ||
const passwordLower = password.toLowerCase(); | ||
const { | ||
rankedDictionaries, | ||
rankedDictionariesMaxWordSize | ||
} = mergeUserInputDictionary(this.options.rankedDictionaries, this.options.rankedDictionariesMaxWordSize, userInputsOptions); | ||
// eslint-disable-next-line complexity,max-statements | ||
Object.keys(Options.zxcvbnOptions.rankedDictionaries).forEach(dictionaryName => { | ||
const rankedDict = Options.zxcvbnOptions.rankedDictionaries[dictionaryName]; | ||
const longestDictionaryWordSize = Options.zxcvbnOptions.rankedDictionariesMaxWordSize[dictionaryName]; | ||
Object.keys(rankedDictionaries).forEach(dictionaryName => { | ||
const rankedDict = rankedDictionaries[dictionaryName]; | ||
const longestDictionaryWordSize = rankedDictionariesMaxWordSize[dictionaryName]; | ||
const searchWidth = Math.min(longestDictionaryWordSize, passwordLength); | ||
@@ -49,4 +47,4 @@ for (let i = 0; i < passwordLength; i += 1) { | ||
const isFullPassword = i === 0 && j === passwordLength - 1; | ||
if (Options.zxcvbnOptions.useLevenshteinDistance && isFullPassword && !isInDictionary && useLevenshtein) { | ||
foundLevenshteinDistance = levenshtein(usedPassword, rankedDict, Options.zxcvbnOptions.levenshteinThreshold); | ||
if (this.options.useLevenshteinDistance && isFullPassword && !isInDictionary && useLevenshtein) { | ||
foundLevenshteinDistance = levenshtein(usedPassword, rankedDict, this.options.levenshteinThreshold); | ||
} | ||
@@ -53,0 +51,0 @@ const isLevenshteinMatch = Object.keys(foundLevenshteinDistance).length !== 0; |
@@ -1,6 +0,6 @@ | ||
import { DictionaryMatch } from '../../types'; | ||
export interface DictionaryMatchOptions { | ||
password: string; | ||
import { DictionaryMatch, MatchOptions } from '../../types'; | ||
export interface DictionaryMatchOptionsLevenshtein extends MatchOptions { | ||
useLevenshtein?: boolean; | ||
} | ||
export type DictionaryMatchOptions = Pick<DictionaryMatchOptionsLevenshtein, 'password' | 'userInputsOptions' | 'useLevenshtein'>; | ||
export type DefaultMatch = (options: DictionaryMatchOptions) => DictionaryMatch[]; |
@@ -0,11 +1,11 @@ | ||
import Options from '../../../../Options'; | ||
import { L33tMatch } from '../../../../types'; | ||
import { DefaultMatch } from '../../types'; | ||
import { DefaultMatch, DictionaryMatchOptions } from '../../types'; | ||
declare class MatchL33t { | ||
defaultMatch: DefaultMatch; | ||
constructor(defaultMatch: DefaultMatch); | ||
private options; | ||
private defaultMatch; | ||
constructor(options: Options, defaultMatch: DefaultMatch); | ||
isAlreadyIncluded(matches: L33tMatch[], newMatch: L33tMatch): boolean; | ||
match({ password }: { | ||
password: string; | ||
}): L33tMatch[]; | ||
match(matchOptions: DictionaryMatchOptions): L33tMatch[]; | ||
} | ||
export default MatchL33t; |
@@ -1,2 +0,1 @@ | ||
import { zxcvbnOptions } from '../../../../Options.esm.js'; | ||
import getCleanPasswords from './unmunger/getCleanPasswords.esm.js'; | ||
@@ -44,3 +43,4 @@ | ||
class MatchL33t { | ||
constructor(defaultMatch) { | ||
constructor(options, defaultMatch) { | ||
this.options = options; | ||
this.defaultMatch = defaultMatch; | ||
@@ -55,9 +55,6 @@ } | ||
} | ||
match({ | ||
password | ||
}) { | ||
match(matchOptions) { | ||
const matches = []; | ||
const subbedPasswords = getCleanPasswords(password, zxcvbnOptions.l33tMaxSubstitutions, zxcvbnOptions.trieNodeRoot); | ||
const subbedPasswords = getCleanPasswords(matchOptions.password, this.options.l33tMaxSubstitutions, this.options.trieNodeRoot); | ||
let hasFullMatch = false; | ||
let isFullSubstitution = true; | ||
subbedPasswords.forEach(subbedPassword => { | ||
@@ -68,13 +65,12 @@ if (hasFullMatch) { | ||
const matchedDictionary = this.defaultMatch({ | ||
...matchOptions, | ||
password: subbedPassword.password, | ||
useLevenshtein: isFullSubstitution | ||
useLevenshtein: subbedPassword.isFullSubstitution | ||
}); | ||
// only the first entry has a full substitution | ||
isFullSubstitution = false; | ||
matchedDictionary.forEach(match => { | ||
if (!hasFullMatch) { | ||
hasFullMatch = match.i === 0 && match.j === password.length - 1; | ||
hasFullMatch = match.i === 0 && match.j === matchOptions.password.length - 1; | ||
} | ||
const extras = getExtras(subbedPassword, match.i, match.j); | ||
const token = password.slice(extras.i, +extras.j + 1 || 9e9); | ||
const token = matchOptions.password.slice(extras.i, +extras.j + 1 || 9e9); | ||
const newMatch = { | ||
@@ -81,0 +77,0 @@ ...match, |
'use strict'; | ||
var Options = require('../../../../Options.js'); | ||
var getCleanPasswords = require('./unmunger/getCleanPasswords.js'); | ||
@@ -46,3 +45,4 @@ | ||
class MatchL33t { | ||
constructor(defaultMatch) { | ||
constructor(options, defaultMatch) { | ||
this.options = options; | ||
this.defaultMatch = defaultMatch; | ||
@@ -57,9 +57,6 @@ } | ||
} | ||
match({ | ||
password | ||
}) { | ||
match(matchOptions) { | ||
const matches = []; | ||
const subbedPasswords = getCleanPasswords(password, Options.zxcvbnOptions.l33tMaxSubstitutions, Options.zxcvbnOptions.trieNodeRoot); | ||
const subbedPasswords = getCleanPasswords(matchOptions.password, this.options.l33tMaxSubstitutions, this.options.trieNodeRoot); | ||
let hasFullMatch = false; | ||
let isFullSubstitution = true; | ||
subbedPasswords.forEach(subbedPassword => { | ||
@@ -70,13 +67,12 @@ if (hasFullMatch) { | ||
const matchedDictionary = this.defaultMatch({ | ||
...matchOptions, | ||
password: subbedPassword.password, | ||
useLevenshtein: isFullSubstitution | ||
useLevenshtein: subbedPassword.isFullSubstitution | ||
}); | ||
// only the first entry has a full substitution | ||
isFullSubstitution = false; | ||
matchedDictionary.forEach(match => { | ||
if (!hasFullMatch) { | ||
hasFullMatch = match.i === 0 && match.j === password.length - 1; | ||
hasFullMatch = match.i === 0 && match.j === matchOptions.password.length - 1; | ||
} | ||
const extras = getExtras(subbedPassword, match.i, match.j); | ||
const token = password.slice(extras.i, +extras.j + 1 || 9e9); | ||
const token = matchOptions.password.slice(extras.i, +extras.j + 1 || 9e9); | ||
const newMatch = { | ||
@@ -83,0 +79,0 @@ ...match, |
@@ -1,8 +0,8 @@ | ||
import { DefaultMatch } from '../../types'; | ||
import { DefaultMatch, DictionaryMatchOptions } from '../../types'; | ||
import Options from '../../../../Options'; | ||
declare class MatchReverse { | ||
defaultMatch: DefaultMatch; | ||
constructor(defaultMatch: DefaultMatch); | ||
match({ password }: { | ||
password: string; | ||
}): { | ||
private options; | ||
private defaultMatch; | ||
constructor(options: Options, defaultMatch: DefaultMatch); | ||
match(matchOptions: DictionaryMatchOptions): { | ||
token: string; | ||
@@ -15,3 +15,3 @@ reversed: boolean; | ||
rank: number; | ||
dictionaryName: import("../../../../types").DictionaryNames; | ||
dictionaryName: import("../../../../types").DictionaryNames | string; | ||
l33t: boolean; | ||
@@ -18,0 +18,0 @@ }[]; |
@@ -7,10 +7,10 @@ /* | ||
class MatchReverse { | ||
constructor(defaultMatch) { | ||
constructor(options, defaultMatch) { | ||
this.options = options; | ||
this.defaultMatch = defaultMatch; | ||
} | ||
match({ | ||
password | ||
}) { | ||
const passwordReversed = password.split('').reverse().join(''); | ||
match(matchOptions) { | ||
const passwordReversed = matchOptions.password.split('').reverse().join(''); | ||
return this.defaultMatch({ | ||
...matchOptions, | ||
password: passwordReversed | ||
@@ -20,6 +20,7 @@ }).map(match => ({ | ||
token: match.token.split('').reverse().join(''), | ||
// reverse back | ||
reversed: true, | ||
// map coordinates back to original string | ||
i: password.length - 1 - match.j, | ||
j: password.length - 1 - match.i | ||
i: matchOptions.password.length - 1 - match.j, | ||
j: matchOptions.password.length - 1 - match.i | ||
})); | ||
@@ -26,0 +27,0 @@ } |
@@ -9,10 +9,10 @@ 'use strict'; | ||
class MatchReverse { | ||
constructor(defaultMatch) { | ||
constructor(options, defaultMatch) { | ||
this.options = options; | ||
this.defaultMatch = defaultMatch; | ||
} | ||
match({ | ||
password | ||
}) { | ||
const passwordReversed = password.split('').reverse().join(''); | ||
match(matchOptions) { | ||
const passwordReversed = matchOptions.password.split('').reverse().join(''); | ||
return this.defaultMatch({ | ||
...matchOptions, | ||
password: passwordReversed | ||
@@ -22,6 +22,7 @@ }).map(match => ({ | ||
token: match.token.split('').reverse().join(''), | ||
// reverse back | ||
reversed: true, | ||
// map coordinates back to original string | ||
i: password.length - 1 - match.j, | ||
j: password.length - 1 - match.i | ||
i: matchOptions.password.length - 1 - match.j, | ||
j: matchOptions.password.length - 1 - match.i | ||
})); | ||
@@ -28,0 +29,0 @@ } |
@@ -12,4 +12,5 @@ import TrieNode from './TrieNode'; | ||
changes: IndexedPasswordChanges[]; | ||
isFullSubstitution: boolean; | ||
} | ||
declare const getCleanPasswords: (password: string, limit: number, trieRoot: TrieNode) => PasswordWithSubs[]; | ||
export default getCleanPasswords; |
@@ -43,3 +43,4 @@ class CleanPasswords { | ||
password: this.buffer.join(''), | ||
changes | ||
changes, | ||
isFullSubstitution: onlyFullSub | ||
}); | ||
@@ -55,2 +56,3 @@ } | ||
const cur = nodes[i - index]; | ||
const sub = cur.parents.join(''); | ||
if (cur.isTerminal()) { | ||
@@ -60,3 +62,3 @@ // Skip if this would be a 4th or more consecutive substitution of the same letter | ||
// So we can ignore the rest to save calculation time | ||
if (lastSubLetter === cur.parents.join('') && consecutiveSubCount >= 3) { | ||
if (lastSubLetter === sub && consecutiveSubCount >= 3) { | ||
// eslint-disable-next-line no-continue | ||
@@ -66,10 +68,10 @@ continue; | ||
hasSubs = true; | ||
const subs = cur.subs; | ||
const letters = cur.subs; | ||
// eslint-disable-next-line no-restricted-syntax | ||
for (const sub of subs) { | ||
this.buffer.push(sub); | ||
for (const letter of letters) { | ||
this.buffer.push(letter); | ||
const newSubs = changes.concat({ | ||
i: subIndex, | ||
letter: sub, | ||
substitution: cur.parents.join('') | ||
letter, | ||
substitution: sub | ||
}); | ||
@@ -80,7 +82,7 @@ // recursively build the rest of the string | ||
isFullSub, | ||
index: i + 1, | ||
subIndex: subIndex + sub.length, | ||
index: index + sub.length, | ||
subIndex: subIndex + letter.length, | ||
changes: newSubs, | ||
lastSubLetter: cur.parents.join(''), | ||
consecutiveSubCount: lastSubLetter === cur.parents.join('') ? consecutiveSubCount + 1 : 1 | ||
lastSubLetter: sub, | ||
consecutiveSubCount: lastSubLetter === sub ? consecutiveSubCount + 1 : 1 | ||
}); | ||
@@ -87,0 +89,0 @@ // backtrack by ignoring the added postfix |
@@ -45,3 +45,4 @@ 'use strict'; | ||
password: this.buffer.join(''), | ||
changes | ||
changes, | ||
isFullSubstitution: onlyFullSub | ||
}); | ||
@@ -57,2 +58,3 @@ } | ||
const cur = nodes[i - index]; | ||
const sub = cur.parents.join(''); | ||
if (cur.isTerminal()) { | ||
@@ -62,3 +64,3 @@ // Skip if this would be a 4th or more consecutive substitution of the same letter | ||
// So we can ignore the rest to save calculation time | ||
if (lastSubLetter === cur.parents.join('') && consecutiveSubCount >= 3) { | ||
if (lastSubLetter === sub && consecutiveSubCount >= 3) { | ||
// eslint-disable-next-line no-continue | ||
@@ -68,10 +70,10 @@ continue; | ||
hasSubs = true; | ||
const subs = cur.subs; | ||
const letters = cur.subs; | ||
// eslint-disable-next-line no-restricted-syntax | ||
for (const sub of subs) { | ||
this.buffer.push(sub); | ||
for (const letter of letters) { | ||
this.buffer.push(letter); | ||
const newSubs = changes.concat({ | ||
i: subIndex, | ||
letter: sub, | ||
substitution: cur.parents.join('') | ||
letter, | ||
substitution: sub | ||
}); | ||
@@ -82,7 +84,7 @@ // recursively build the rest of the string | ||
isFullSub, | ||
index: i + 1, | ||
subIndex: subIndex + sub.length, | ||
index: index + sub.length, | ||
subIndex: subIndex + letter.length, | ||
changes: newSubs, | ||
lastSubLetter: cur.parents.join(''), | ||
consecutiveSubCount: lastSubLetter === cur.parents.join('') ? consecutiveSubCount + 1 : 1 | ||
lastSubLetter: sub, | ||
consecutiveSubCount: lastSubLetter === sub ? consecutiveSubCount + 1 : 1 | ||
}); | ||
@@ -89,0 +91,0 @@ // backtrack by ignoring the added postfix |
@@ -0,3 +1,4 @@ | ||
import Options from '../../Options'; | ||
import { MatchEstimated } from '../../types'; | ||
declare const _default: (match: MatchEstimated) => { | ||
declare const _default: (options: Options, match: MatchEstimated) => { | ||
warning: string; | ||
@@ -4,0 +5,0 @@ suggestions: string[]; |
@@ -1,8 +0,6 @@ | ||
import { zxcvbnOptions } from '../../Options.esm.js'; | ||
var regexMatcher = (match => { | ||
var regexMatcher = ((options, match) => { | ||
if (match.regexName === 'recentYear') { | ||
return { | ||
warning: zxcvbnOptions.translations.warnings.recentYears, | ||
suggestions: [zxcvbnOptions.translations.suggestions.recentYears, zxcvbnOptions.translations.suggestions.associatedYears] | ||
warning: options.translations.warnings.recentYears, | ||
suggestions: [options.translations.suggestions.recentYears, options.translations.suggestions.associatedYears] | ||
}; | ||
@@ -9,0 +7,0 @@ } |
'use strict'; | ||
var Options = require('../../Options.js'); | ||
var regexMatcher = (match => { | ||
var regexMatcher = ((options, match) => { | ||
if (match.regexName === 'recentYear') { | ||
return { | ||
warning: Options.zxcvbnOptions.translations.warnings.recentYears, | ||
suggestions: [Options.zxcvbnOptions.translations.suggestions.recentYears, Options.zxcvbnOptions.translations.suggestions.associatedYears] | ||
warning: options.translations.warnings.recentYears, | ||
suggestions: [options.translations.suggestions.recentYears, options.translations.suggestions.associatedYears] | ||
}; | ||
@@ -11,0 +9,0 @@ } |
@@ -1,9 +0,9 @@ | ||
import { REGEXEN } from '../../data/const'; | ||
interface RegexMatchOptions { | ||
password: string; | ||
regexes?: typeof REGEXEN; | ||
} | ||
import { MatchOptions } from '../../types'; | ||
import Options from '../../Options'; | ||
type RegexMatchOptions = Pick<MatchOptions, 'password'>; | ||
declare class MatchRegex { | ||
match({ password, regexes }: RegexMatchOptions): import("../../types").MatchExtended[]; | ||
private options; | ||
constructor(options: Options); | ||
match({ password }: RegexMatchOptions): import("../../types").MatchExtended[]; | ||
} | ||
export default MatchRegex; |
import { REGEXEN } from '../../data/const.esm.js'; | ||
import { sorted } from '../../helper.esm.js'; | ||
import { sorted } from '../../utils/helper.esm.js'; | ||
@@ -10,9 +10,11 @@ /* | ||
class MatchRegex { | ||
constructor(options) { | ||
this.options = options; | ||
} | ||
match({ | ||
password, | ||
regexes = REGEXEN | ||
password | ||
}) { | ||
const matches = []; | ||
Object.keys(regexes).forEach(name => { | ||
const regex = regexes[name]; | ||
Object.keys(REGEXEN).forEach(name => { | ||
const regex = REGEXEN[name]; | ||
regex.lastIndex = 0; // keeps regexMatch stateless | ||
@@ -19,0 +21,0 @@ let regexMatch; |
'use strict'; | ||
var _const = require('../../data/const.js'); | ||
var helper = require('../../helper.js'); | ||
var helper = require('../../utils/helper.js'); | ||
@@ -12,9 +12,11 @@ /* | ||
class MatchRegex { | ||
constructor(options) { | ||
this.options = options; | ||
} | ||
match({ | ||
password, | ||
regexes = _const.REGEXEN | ||
password | ||
}) { | ||
const matches = []; | ||
Object.keys(regexes).forEach(name => { | ||
const regex = regexes[name]; | ||
Object.keys(_const.REGEXEN).forEach(name => { | ||
const regex = _const.REGEXEN[name]; | ||
regex.lastIndex = 0; // keeps regexMatch stateless | ||
@@ -21,0 +23,0 @@ let regexMatch; |
@@ -0,3 +1,4 @@ | ||
import Options from '../../Options'; | ||
import { MatchEstimated } from '../../types'; | ||
declare const _default: (match: MatchEstimated) => { | ||
declare const _default: (options: Options, match: MatchEstimated) => { | ||
warning: string; | ||
@@ -4,0 +5,0 @@ suggestions: string[]; |
@@ -1,11 +0,9 @@ | ||
import { zxcvbnOptions } from '../../Options.esm.js'; | ||
var repeatMatcher = (match => { | ||
let warning = zxcvbnOptions.translations.warnings.extendedRepeat; | ||
var repeatMatcher = ((options, match) => { | ||
let warning = options.translations.warnings.extendedRepeat; | ||
if (match.baseToken.length === 1) { | ||
warning = zxcvbnOptions.translations.warnings.simpleRepeat; | ||
warning = options.translations.warnings.simpleRepeat; | ||
} | ||
return { | ||
warning, | ||
suggestions: [zxcvbnOptions.translations.suggestions.repeated] | ||
suggestions: [options.translations.suggestions.repeated] | ||
}; | ||
@@ -12,0 +10,0 @@ }); |
'use strict'; | ||
var Options = require('../../Options.js'); | ||
var repeatMatcher = (match => { | ||
let warning = Options.zxcvbnOptions.translations.warnings.extendedRepeat; | ||
var repeatMatcher = ((options, match) => { | ||
let warning = options.translations.warnings.extendedRepeat; | ||
if (match.baseToken.length === 1) { | ||
warning = Options.zxcvbnOptions.translations.warnings.simpleRepeat; | ||
warning = options.translations.warnings.simpleRepeat; | ||
} | ||
return { | ||
warning, | ||
suggestions: [Options.zxcvbnOptions.translations.suggestions.repeated] | ||
suggestions: [options.translations.suggestions.repeated] | ||
}; | ||
@@ -14,0 +12,0 @@ }); |
@@ -1,9 +0,9 @@ | ||
import { RepeatMatch } from '../../types'; | ||
import { MatchOptions, RepeatMatch } from '../../types'; | ||
import Matching from '../../Matching'; | ||
interface RepeatMatchOptions { | ||
password: string; | ||
omniMatch: Matching; | ||
} | ||
import Options from '../../Options'; | ||
declare class MatchRepeat { | ||
match({ password, omniMatch }: RepeatMatchOptions): RepeatMatch[] | Promise<RepeatMatch[]>; | ||
private options; | ||
private scoring; | ||
constructor(options: Options); | ||
match({ password, omniMatch }: MatchOptions): RepeatMatch[] | Promise<RepeatMatch[]>; | ||
normalizeMatch(baseToken: string, j: number, match: RegExpExecArray, baseGuesses: number | Promise<number>): RepeatMatch | Promise<RepeatMatch>; | ||
@@ -10,0 +10,0 @@ getGreedyMatch(password: string, lastIndex: number): RegExpExecArray | null; |
@@ -1,2 +0,2 @@ | ||
import scoring from '../../scoring/index.esm.js'; | ||
import Scoring from '../../scoring/index.esm.js'; | ||
@@ -9,2 +9,6 @@ /* | ||
class MatchRepeat { | ||
constructor(options) { | ||
this.options = options; | ||
this.scoring = new Scoring(options); | ||
} | ||
// eslint-disable-next-line max-statements | ||
@@ -111,7 +115,7 @@ match({ | ||
return matches.then(resolvedMatches => { | ||
const baseAnalysis = scoring.mostGuessableMatchSequence(baseToken, resolvedMatches); | ||
const baseAnalysis = this.scoring.mostGuessableMatchSequence(baseToken, resolvedMatches); | ||
return baseAnalysis.guesses; | ||
}); | ||
} | ||
const baseAnalysis = scoring.mostGuessableMatchSequence(baseToken, matches); | ||
const baseAnalysis = this.scoring.mostGuessableMatchSequence(baseToken, matches); | ||
return baseAnalysis.guesses; | ||
@@ -118,0 +122,0 @@ } |
@@ -11,2 +11,6 @@ 'use strict'; | ||
class MatchRepeat { | ||
constructor(options) { | ||
this.options = options; | ||
this.scoring = new index(options); | ||
} | ||
// eslint-disable-next-line max-statements | ||
@@ -113,7 +117,7 @@ match({ | ||
return matches.then(resolvedMatches => { | ||
const baseAnalysis = index.mostGuessableMatchSequence(baseToken, resolvedMatches); | ||
const baseAnalysis = this.scoring.mostGuessableMatchSequence(baseToken, resolvedMatches); | ||
return baseAnalysis.guesses; | ||
}); | ||
} | ||
const baseAnalysis = index.mostGuessableMatchSequence(baseToken, matches); | ||
const baseAnalysis = this.scoring.mostGuessableMatchSequence(baseToken, matches); | ||
return baseAnalysis.guesses; | ||
@@ -120,0 +124,0 @@ } |
@@ -1,6 +0,7 @@ | ||
import { SeparatorMatch } from '../../types'; | ||
interface SeparatorMatchOptions { | ||
password: string; | ||
} | ||
import { MatchOptions, SeparatorMatch } from '../../types'; | ||
import Options from '../../Options'; | ||
type SeparatorMatchOptions = Pick<MatchOptions, 'password'>; | ||
declare class MatchSeparator { | ||
private options; | ||
constructor(options: Options); | ||
static getMostUsedSeparatorChar(password: string): string | undefined; | ||
@@ -7,0 +8,0 @@ static getSeparatorRegex(separator: string): RegExp; |
@@ -10,2 +10,5 @@ import { SEPERATOR_CHARS } from '../../data/const.esm.js'; | ||
class MatchSeparator { | ||
constructor(options) { | ||
this.options = options; | ||
} | ||
static getMostUsedSeparatorChar(password) { | ||
@@ -12,0 +15,0 @@ const mostUsedSeperators = [...password.split('').filter(c => separatorRegex.test(c)).reduce((memo, c) => { |
@@ -12,2 +12,5 @@ 'use strict'; | ||
class MatchSeparator { | ||
constructor(options) { | ||
this.options = options; | ||
} | ||
static getMostUsedSeparatorChar(password) { | ||
@@ -14,0 +17,0 @@ const mostUsedSeperators = [...password.split('').filter(c => separatorRegex.test(c)).reduce((memo, c) => { |
@@ -1,2 +0,3 @@ | ||
declare const _default: () => { | ||
import Options from '../../Options'; | ||
declare const _default: (options: Options) => { | ||
warning: string; | ||
@@ -3,0 +4,0 @@ suggestions: string[]; |
@@ -1,7 +0,5 @@ | ||
import { zxcvbnOptions } from '../../Options.esm.js'; | ||
var sequenceMatcher = (() => { | ||
var sequenceMatcher = (options => { | ||
return { | ||
warning: zxcvbnOptions.translations.warnings.sequences, | ||
suggestions: [zxcvbnOptions.translations.suggestions.sequences] | ||
warning: options.translations.warnings.sequences, | ||
suggestions: [options.translations.suggestions.sequences] | ||
}; | ||
@@ -8,0 +6,0 @@ }); |
'use strict'; | ||
var Options = require('../../Options.js'); | ||
var sequenceMatcher = (() => { | ||
var sequenceMatcher = (options => { | ||
return { | ||
warning: Options.zxcvbnOptions.translations.warnings.sequences, | ||
suggestions: [Options.zxcvbnOptions.translations.suggestions.sequences] | ||
warning: options.translations.warnings.sequences, | ||
suggestions: [options.translations.suggestions.sequences] | ||
}; | ||
@@ -10,0 +8,0 @@ }); |
@@ -1,2 +0,3 @@ | ||
import { SequenceMatch } from '../../types'; | ||
import { MatchOptions, SequenceMatch } from '../../types'; | ||
import Options from '../../Options'; | ||
type UpdateParams = { | ||
@@ -9,7 +10,7 @@ i: number; | ||
}; | ||
interface SequenceMatchOptions { | ||
password: string; | ||
} | ||
type SequenceMatchOptions = Pick<MatchOptions, 'password'>; | ||
declare class MatchSequence { | ||
private options; | ||
MAX_DELTA: number; | ||
constructor(options: Options); | ||
match({ password }: SequenceMatchOptions): SequenceMatch[]; | ||
@@ -16,0 +17,0 @@ update({ i, j, delta, password, result }: UpdateParams): number | null; |
@@ -9,3 +9,4 @@ import { ALL_LOWER, ALL_UPPER, ALL_DIGIT } from '../../data/const.esm.js'; | ||
class MatchSequence { | ||
constructor() { | ||
constructor(options) { | ||
this.options = options; | ||
this.MAX_DELTA = 5; | ||
@@ -12,0 +13,0 @@ } |
@@ -11,3 +11,4 @@ 'use strict'; | ||
class MatchSequence { | ||
constructor() { | ||
constructor(options) { | ||
this.options = options; | ||
this.MAX_DELTA = 5; | ||
@@ -14,0 +15,0 @@ } |
@@ -0,3 +1,4 @@ | ||
import Options from '../../Options'; | ||
import { MatchEstimated } from '../../types'; | ||
declare const _default: (match: MatchEstimated) => { | ||
declare const _default: (options: Options, match: MatchEstimated) => { | ||
warning: string; | ||
@@ -4,0 +5,0 @@ suggestions: string[]; |
@@ -1,11 +0,9 @@ | ||
import { zxcvbnOptions } from '../../Options.esm.js'; | ||
var spatialMatcher = (match => { | ||
let warning = zxcvbnOptions.translations.warnings.keyPattern; | ||
var spatialMatcher = ((options, match) => { | ||
let warning = options.translations.warnings.keyPattern; | ||
if (match.turns === 1) { | ||
warning = zxcvbnOptions.translations.warnings.straightRow; | ||
warning = options.translations.warnings.straightRow; | ||
} | ||
return { | ||
warning, | ||
suggestions: [zxcvbnOptions.translations.suggestions.longerKeyboardPattern] | ||
suggestions: [options.translations.suggestions.longerKeyboardPattern] | ||
}; | ||
@@ -12,0 +10,0 @@ }); |
'use strict'; | ||
var Options = require('../../Options.js'); | ||
var spatialMatcher = (match => { | ||
let warning = Options.zxcvbnOptions.translations.warnings.keyPattern; | ||
var spatialMatcher = ((options, match) => { | ||
let warning = options.translations.warnings.keyPattern; | ||
if (match.turns === 1) { | ||
warning = Options.zxcvbnOptions.translations.warnings.straightRow; | ||
warning = options.translations.warnings.straightRow; | ||
} | ||
return { | ||
warning, | ||
suggestions: [Options.zxcvbnOptions.translations.suggestions.longerKeyboardPattern] | ||
suggestions: [options.translations.suggestions.longerKeyboardPattern] | ||
}; | ||
@@ -14,0 +12,0 @@ }); |
@@ -1,7 +0,8 @@ | ||
import { LooseObject, SpatialMatch } from '../../types'; | ||
interface SpatialMatchOptions { | ||
password: string; | ||
} | ||
import Options from '../../Options'; | ||
import { LooseObject, MatchOptions, SpatialMatch } from '../../types'; | ||
type SpatialMatchOptions = Pick<MatchOptions, 'password'>; | ||
declare class MatchSpatial { | ||
private options; | ||
SHIFTED_RX: RegExp; | ||
constructor(options: Options); | ||
match({ password }: SpatialMatchOptions): import("../../types").MatchExtended[]; | ||
@@ -8,0 +9,0 @@ checkIfShifted(graphName: string, password: string, index: number): 1 | 0; |
@@ -1,3 +0,2 @@ | ||
import { extend, sorted } from '../../helper.esm.js'; | ||
import { zxcvbnOptions } from '../../Options.esm.js'; | ||
import { extend, sorted } from '../../utils/helper.esm.js'; | ||
@@ -10,3 +9,4 @@ /* | ||
class MatchSpatial { | ||
constructor() { | ||
constructor(options) { | ||
this.options = options; | ||
this.SHIFTED_RX = /[~!@#$%^&*()_+QWERTYUIOP{}|ASDFGHJKL:"ZXCVBNM<>?]/; | ||
@@ -18,4 +18,4 @@ } | ||
const matches = []; | ||
Object.keys(zxcvbnOptions.graphs).forEach(graphName => { | ||
const graph = zxcvbnOptions.graphs[graphName]; | ||
Object.keys(this.options.graphs).forEach(graphName => { | ||
const graph = this.options.graphs[graphName]; | ||
extend(matches, this.helper(password, graph, graphName)); | ||
@@ -22,0 +22,0 @@ }); |
'use strict'; | ||
var helper = require('../../helper.js'); | ||
var Options = require('../../Options.js'); | ||
var helper = require('../../utils/helper.js'); | ||
@@ -12,3 +11,4 @@ /* | ||
class MatchSpatial { | ||
constructor() { | ||
constructor(options) { | ||
this.options = options; | ||
this.SHIFTED_RX = /[~!@#$%^&*()_+QWERTYUIOP{}|ASDFGHJKL:"ZXCVBNM<>?]/; | ||
@@ -20,4 +20,4 @@ } | ||
const matches = []; | ||
Object.keys(Options.zxcvbnOptions.graphs).forEach(graphName => { | ||
const graph = Options.zxcvbnOptions.graphs[graphName]; | ||
Object.keys(this.options.graphs).forEach(graphName => { | ||
const graph = this.options.graphs[graphName]; | ||
helper.extend(matches, this.helper(password, graph, graphName)); | ||
@@ -24,0 +24,0 @@ }); |
@@ -0,3 +1,4 @@ | ||
import Options from '../../Options'; | ||
import { MatchEstimated, MatchExtended } from '../../types'; | ||
declare const _default: ({ graph, token, shiftedCount, turns, }: MatchExtended | MatchEstimated) => number; | ||
declare const _default: ({ graph, token, shiftedCount, turns }: MatchExtended | MatchEstimated, options: Options) => number; | ||
export default _default; |
import utils from '../../scoring/utils.esm.js'; | ||
import { zxcvbnOptions } from '../../Options.esm.js'; | ||
@@ -13,9 +12,8 @@ const calcAverageDegree = graph => { | ||
}; | ||
const estimatePossiblePatterns = ({ | ||
const estimatePossiblePatterns = (graphEntry, { | ||
token, | ||
graph, | ||
turns | ||
}) => { | ||
const startingPosition = Object.keys(zxcvbnOptions.graphs[graph]).length; | ||
const averageDegree = calcAverageDegree(zxcvbnOptions.graphs[graph]); | ||
const startingPosition = Object.keys(graphEntry).length; | ||
const averageDegree = calcAverageDegree(graphEntry); | ||
let guesses = 0; | ||
@@ -37,6 +35,5 @@ const tokenLength = token.length; | ||
turns | ||
}) => { | ||
let guesses = estimatePossiblePatterns({ | ||
}, options) => { | ||
let guesses = estimatePossiblePatterns(options.graphs[graph], { | ||
token, | ||
graph, | ||
turns | ||
@@ -43,0 +40,0 @@ }); |
'use strict'; | ||
var utils = require('../../scoring/utils.js'); | ||
var Options = require('../../Options.js'); | ||
@@ -15,9 +14,8 @@ const calcAverageDegree = graph => { | ||
}; | ||
const estimatePossiblePatterns = ({ | ||
const estimatePossiblePatterns = (graphEntry, { | ||
token, | ||
graph, | ||
turns | ||
}) => { | ||
const startingPosition = Object.keys(Options.zxcvbnOptions.graphs[graph]).length; | ||
const averageDegree = calcAverageDegree(Options.zxcvbnOptions.graphs[graph]); | ||
const startingPosition = Object.keys(graphEntry).length; | ||
const averageDegree = calcAverageDegree(graphEntry); | ||
let guesses = 0; | ||
@@ -39,6 +37,5 @@ const tokenLength = token.length; | ||
turns | ||
}) => { | ||
let guesses = estimatePossiblePatterns({ | ||
}, options) => { | ||
let guesses = estimatePossiblePatterns(options.graphs[graph], { | ||
token, | ||
graph, | ||
turns | ||
@@ -45,0 +42,0 @@ }); |
@@ -1,9 +0,12 @@ | ||
import { MatchExtended, MatchingType } from './types'; | ||
type Matchers = { | ||
[key: string]: MatchingType; | ||
}; | ||
import { MatchExtended, UserInputsOptions } from './types'; | ||
import Options from './Options'; | ||
declare class Matching { | ||
readonly matchers: Matchers; | ||
match(password: string): MatchExtended[] | Promise<MatchExtended[]>; | ||
private options; | ||
private readonly matchers; | ||
constructor(options: Options); | ||
private matcherFactory; | ||
private processResult; | ||
private handlePromises; | ||
match(password: string, userInputsOptions?: UserInputsOptions): MatchExtended[] | Promise<MatchExtended[]>; | ||
} | ||
export default Matching; |
@@ -1,2 +0,2 @@ | ||
import { extend, sorted } from './helper.esm.js'; | ||
import { extend, sorted } from './utils/helper.esm.js'; | ||
import MatchDate from './matcher/date/matching.esm.js'; | ||
@@ -9,6 +9,6 @@ import MatchDictionary from './matcher/dictionary/matching.esm.js'; | ||
import MatchSeparator from './matcher/separator/matching.esm.js'; | ||
import { zxcvbnOptions } from './Options.esm.js'; | ||
class Matching { | ||
constructor() { | ||
constructor(options) { | ||
this.options = options; | ||
this.matchers = { | ||
@@ -25,25 +25,20 @@ date: MatchDate, | ||
} | ||
match(password) { | ||
const matches = []; | ||
const promises = []; | ||
const matchers = [...Object.keys(this.matchers), ...Object.keys(zxcvbnOptions.matchers)]; | ||
matchers.forEach(key => { | ||
if (!this.matchers[key] && !zxcvbnOptions.matchers[key]) { | ||
return; | ||
} | ||
const Matcher = this.matchers[key] ? this.matchers[key] : zxcvbnOptions.matchers[key].Matching; | ||
const usedMatcher = new Matcher(); | ||
const result = usedMatcher.match({ | ||
password, | ||
omniMatch: this | ||
matcherFactory(name) { | ||
if (!this.matchers[name] && !this.options.matchers[name]) { | ||
return null; | ||
} | ||
const Matcher = this.matchers[name] ? this.matchers[name] : this.options.matchers[name].Matching; | ||
return new Matcher(this.options); | ||
} | ||
processResult(matches, promises, result) { | ||
if (result instanceof Promise) { | ||
result.then(response => { | ||
extend(matches, response); | ||
}); | ||
if (result instanceof Promise) { | ||
result.then(response => { | ||
extend(matches, response); | ||
}); | ||
promises.push(result); | ||
} else { | ||
extend(matches, result); | ||
} | ||
}); | ||
promises.push(result); | ||
} else { | ||
extend(matches, result); | ||
} | ||
} | ||
handlePromises(matches, promises) { | ||
if (promises.length > 0) { | ||
@@ -60,2 +55,21 @@ return new Promise((resolve, reject) => { | ||
} | ||
match(password, userInputsOptions) { | ||
const matches = []; | ||
const promises = []; | ||
const matchers = [...Object.keys(this.matchers), ...Object.keys(this.options.matchers)]; | ||
matchers.forEach(key => { | ||
const matcher = this.matcherFactory(key); | ||
if (!matcher) { | ||
return; | ||
} | ||
const result = matcher.match({ | ||
password, | ||
omniMatch: this, | ||
userInputsOptions | ||
}); | ||
// extends matches and promises by references | ||
this.processResult(matches, promises, result); | ||
}); | ||
return this.handlePromises(matches, promises); | ||
} | ||
} | ||
@@ -62,0 +76,0 @@ |
'use strict'; | ||
var helper = require('./helper.js'); | ||
var helper = require('./utils/helper.js'); | ||
var matching = require('./matcher/date/matching.js'); | ||
@@ -11,6 +11,6 @@ var matching$1 = require('./matcher/dictionary/matching.js'); | ||
var matching$6 = require('./matcher/separator/matching.js'); | ||
var Options = require('./Options.js'); | ||
class Matching { | ||
constructor() { | ||
constructor(options) { | ||
this.options = options; | ||
this.matchers = { | ||
@@ -27,25 +27,20 @@ date: matching, | ||
} | ||
match(password) { | ||
const matches = []; | ||
const promises = []; | ||
const matchers = [...Object.keys(this.matchers), ...Object.keys(Options.zxcvbnOptions.matchers)]; | ||
matchers.forEach(key => { | ||
if (!this.matchers[key] && !Options.zxcvbnOptions.matchers[key]) { | ||
return; | ||
} | ||
const Matcher = this.matchers[key] ? this.matchers[key] : Options.zxcvbnOptions.matchers[key].Matching; | ||
const usedMatcher = new Matcher(); | ||
const result = usedMatcher.match({ | ||
password, | ||
omniMatch: this | ||
matcherFactory(name) { | ||
if (!this.matchers[name] && !this.options.matchers[name]) { | ||
return null; | ||
} | ||
const Matcher = this.matchers[name] ? this.matchers[name] : this.options.matchers[name].Matching; | ||
return new Matcher(this.options); | ||
} | ||
processResult(matches, promises, result) { | ||
if (result instanceof Promise) { | ||
result.then(response => { | ||
helper.extend(matches, response); | ||
}); | ||
if (result instanceof Promise) { | ||
result.then(response => { | ||
helper.extend(matches, response); | ||
}); | ||
promises.push(result); | ||
} else { | ||
helper.extend(matches, result); | ||
} | ||
}); | ||
promises.push(result); | ||
} else { | ||
helper.extend(matches, result); | ||
} | ||
} | ||
handlePromises(matches, promises) { | ||
if (promises.length > 0) { | ||
@@ -62,2 +57,21 @@ return new Promise((resolve, reject) => { | ||
} | ||
match(password, userInputsOptions) { | ||
const matches = []; | ||
const promises = []; | ||
const matchers = [...Object.keys(this.matchers), ...Object.keys(this.options.matchers)]; | ||
matchers.forEach(key => { | ||
const matcher = this.matcherFactory(key); | ||
if (!matcher) { | ||
return; | ||
} | ||
const result = matcher.match({ | ||
password, | ||
omniMatch: this, | ||
userInputsOptions | ||
}); | ||
// extends matches and promises by references | ||
this.processResult(matches, promises, result); | ||
}); | ||
return this.handlePromises(matches, promises); | ||
} | ||
} | ||
@@ -64,0 +78,0 @@ |
@@ -1,4 +0,4 @@ | ||
import { TranslationKeys, OptionsType, OptionsDictionary, OptionsL33tTable, OptionsGraph, RankedDictionaries, Matchers, Matcher } from './types'; | ||
import { TranslationKeys, OptionsType, OptionsDictionary, OptionsL33tTable, OptionsGraph, RankedDictionaries, Matchers, Matcher, UserInputsOptions, TimeEstimationValues } from './types'; | ||
import TrieNode from './matcher/dictionary/variants/matching/unmunger/TrieNode'; | ||
export declare class Options { | ||
export default class Options { | ||
matchers: Matchers; | ||
@@ -16,12 +16,12 @@ l33tTable: OptionsL33tTable; | ||
maxLength: number; | ||
constructor(); | ||
setOptions(options?: OptionsType): void; | ||
setTranslations(translations: TranslationKeys): void; | ||
checkCustomTranslations(translations: TranslationKeys): boolean; | ||
setRankedDictionaries(): void; | ||
getRankedDictionariesMaxWordSize(list: (string | number)[]): number; | ||
buildSanitizedRankedDictionary(list: (string | number)[]): import("./types").LooseObject; | ||
extendUserInputsDictionary(dictionary: (string | number)[]): void; | ||
addMatcher(name: string, matcher: Matcher): void; | ||
timeEstimationValues: TimeEstimationValues; | ||
constructor(options?: OptionsType, customMatchers?: Record<string, Matcher>); | ||
private setOptions; | ||
private setTranslations; | ||
private checkCustomTranslations; | ||
private setRankedDictionaries; | ||
private getRankedDictionariesMaxWordSize; | ||
private buildSanitizedRankedDictionary; | ||
getUserInputsOptions(dictionary?: (string | number)[]): UserInputsOptions; | ||
private addMatcher; | ||
} | ||
export declare const zxcvbnOptions: Options; |
@@ -1,2 +0,2 @@ | ||
import { buildRankedDictionary } from './helper.esm.js'; | ||
import { buildRankedDictionary } from './utils/helper.esm.js'; | ||
import l33tTable from './data/l33tTable.esm.js'; | ||
@@ -6,5 +6,6 @@ import translationKeys from './data/translationKeys.esm.js'; | ||
import l33tTableToTrieNode from './matcher/dictionary/variants/matching/unmunger/l33tTableToTrieNode.esm.js'; | ||
import { timeEstimationValuesDefaults, checkTimeEstimationValues } from './TimeEstimates.esm.js'; | ||
class Options { | ||
constructor() { | ||
constructor(options = {}, customMatchers = {}) { | ||
this.matchers = {}; | ||
@@ -24,3 +25,14 @@ this.l33tTable = l33tTable; | ||
this.maxLength = 256; | ||
this.setRankedDictionaries(); | ||
this.timeEstimationValues = { | ||
scoring: { | ||
...timeEstimationValuesDefaults.scoring | ||
}, | ||
attackTime: { | ||
...timeEstimationValuesDefaults.attackTime | ||
} | ||
}; | ||
this.setOptions(options); | ||
Object.entries(customMatchers).forEach(([name, matcher]) => { | ||
this.addMatcher(name, matcher); | ||
}); | ||
} | ||
@@ -55,2 +67,13 @@ // eslint-disable-next-line max-statements,complexity | ||
} | ||
if (options.timeEstimationValues !== undefined) { | ||
checkTimeEstimationValues(options.timeEstimationValues); | ||
this.timeEstimationValues = { | ||
scoring: { | ||
...options.timeEstimationValues.scoring | ||
}, | ||
attackTime: { | ||
...options.timeEstimationValues.attackTime | ||
} | ||
}; | ||
} | ||
} | ||
@@ -113,9 +136,13 @@ setTranslations(translations) { | ||
} | ||
extendUserInputsDictionary(dictionary) { | ||
if (!this.dictionary.userInputs) { | ||
this.dictionary.userInputs = []; | ||
getUserInputsOptions(dictionary) { | ||
let rankedDictionary = {}; | ||
let rankedDictionaryMaxWordSize = 0; | ||
if (dictionary) { | ||
rankedDictionary = this.buildSanitizedRankedDictionary(dictionary); | ||
rankedDictionaryMaxWordSize = this.getRankedDictionariesMaxWordSize(dictionary); | ||
} | ||
const newList = [...this.dictionary.userInputs, ...dictionary]; | ||
this.rankedDictionaries.userInputs = this.buildSanitizedRankedDictionary(newList); | ||
this.rankedDictionariesMaxWordSize.userInputs = this.getRankedDictionariesMaxWordSize(newList); | ||
return { | ||
rankedDictionary, | ||
rankedDictionaryMaxWordSize | ||
}; | ||
} | ||
@@ -130,5 +157,4 @@ addMatcher(name, matcher) { | ||
} | ||
const zxcvbnOptions = new Options(); | ||
export { Options, zxcvbnOptions }; | ||
export { Options as default }; | ||
//# sourceMappingURL=Options.esm.js.map |
'use strict'; | ||
var helper = require('./helper.js'); | ||
var helper = require('./utils/helper.js'); | ||
var l33tTable = require('./data/l33tTable.js'); | ||
@@ -8,5 +8,6 @@ var translationKeys = require('./data/translationKeys.js'); | ||
var l33tTableToTrieNode = require('./matcher/dictionary/variants/matching/unmunger/l33tTableToTrieNode.js'); | ||
var TimeEstimates = require('./TimeEstimates.js'); | ||
class Options { | ||
constructor() { | ||
constructor(options = {}, customMatchers = {}) { | ||
this.matchers = {}; | ||
@@ -26,3 +27,14 @@ this.l33tTable = l33tTable; | ||
this.maxLength = 256; | ||
this.setRankedDictionaries(); | ||
this.timeEstimationValues = { | ||
scoring: { | ||
...TimeEstimates.timeEstimationValuesDefaults.scoring | ||
}, | ||
attackTime: { | ||
...TimeEstimates.timeEstimationValuesDefaults.attackTime | ||
} | ||
}; | ||
this.setOptions(options); | ||
Object.entries(customMatchers).forEach(([name, matcher]) => { | ||
this.addMatcher(name, matcher); | ||
}); | ||
} | ||
@@ -57,2 +69,13 @@ // eslint-disable-next-line max-statements,complexity | ||
} | ||
if (options.timeEstimationValues !== undefined) { | ||
TimeEstimates.checkTimeEstimationValues(options.timeEstimationValues); | ||
this.timeEstimationValues = { | ||
scoring: { | ||
...options.timeEstimationValues.scoring | ||
}, | ||
attackTime: { | ||
...options.timeEstimationValues.attackTime | ||
} | ||
}; | ||
} | ||
} | ||
@@ -115,9 +138,13 @@ setTranslations(translations) { | ||
} | ||
extendUserInputsDictionary(dictionary) { | ||
if (!this.dictionary.userInputs) { | ||
this.dictionary.userInputs = []; | ||
getUserInputsOptions(dictionary) { | ||
let rankedDictionary = {}; | ||
let rankedDictionaryMaxWordSize = 0; | ||
if (dictionary) { | ||
rankedDictionary = this.buildSanitizedRankedDictionary(dictionary); | ||
rankedDictionaryMaxWordSize = this.getRankedDictionariesMaxWordSize(dictionary); | ||
} | ||
const newList = [...this.dictionary.userInputs, ...dictionary]; | ||
this.rankedDictionaries.userInputs = this.buildSanitizedRankedDictionary(newList); | ||
this.rankedDictionariesMaxWordSize.userInputs = this.getRankedDictionariesMaxWordSize(newList); | ||
return { | ||
rankedDictionary, | ||
rankedDictionaryMaxWordSize | ||
}; | ||
} | ||
@@ -132,6 +159,4 @@ addMatcher(name, matcher) { | ||
} | ||
const zxcvbnOptions = new Options(); | ||
exports.Options = Options; | ||
exports.zxcvbnOptions = zxcvbnOptions; | ||
module.exports = Options; | ||
//# sourceMappingURL=Options.js.map |
@@ -0,3 +1,4 @@ | ||
import Options from '../Options'; | ||
import { MatchEstimated, MatchExtended } from '../types'; | ||
declare const _default: (match: MatchExtended | MatchEstimated, password: string) => import("../types").Match; | ||
declare const _default: (options: Options, match: MatchExtended | MatchEstimated, password: string) => import("../types").Match; | ||
export default _default; |
import { MIN_SUBMATCH_GUESSES_SINGLE_CHAR, MIN_SUBMATCH_GUESSES_MULTI_CHAR } from '../data/const.esm.js'; | ||
import utils from './utils.esm.js'; | ||
import { zxcvbnOptions } from '../Options.esm.js'; | ||
import bruteforceMatcher from '../matcher/bruteforce/scoring.esm.js'; | ||
@@ -34,8 +33,8 @@ import dateMatcher from '../matcher/date/scoring.esm.js'; | ||
}; | ||
const getScoring = (name, match) => { | ||
const getScoring = (options, name, match) => { | ||
if (matchers[name]) { | ||
return matchers[name](match); | ||
return matchers[name](match, options); | ||
} | ||
if (zxcvbnOptions.matchers[name] && 'scoring' in zxcvbnOptions.matchers[name]) { | ||
return zxcvbnOptions.matchers[name].scoring(match); | ||
if (options.matchers[name] && 'scoring' in options.matchers[name]) { | ||
return options.matchers[name].scoring(match, options); | ||
} | ||
@@ -48,3 +47,3 @@ return 0; | ||
// eslint-disable-next-line complexity, max-statements | ||
var estimateGuesses = ((match, password) => { | ||
var estimateGuesses = ((options, match, password) => { | ||
const extraData = {}; | ||
@@ -56,3 +55,3 @@ // a match's guess estimate doesn't change. cache it. | ||
const minGuesses = getMinGuesses(match, password); | ||
const estimationResult = getScoring(match.pattern, match); | ||
const estimationResult = getScoring(options, match.pattern, match); | ||
let guesses = 0; | ||
@@ -59,0 +58,0 @@ if (typeof estimationResult === 'number') { |
@@ -5,3 +5,2 @@ 'use strict'; | ||
var utils = require('./utils.js'); | ||
var Options = require('../Options.js'); | ||
var scoring = require('../matcher/bruteforce/scoring.js'); | ||
@@ -37,8 +36,8 @@ var scoring$1 = require('../matcher/date/scoring.js'); | ||
}; | ||
const getScoring = (name, match) => { | ||
const getScoring = (options, name, match) => { | ||
if (matchers[name]) { | ||
return matchers[name](match); | ||
return matchers[name](match, options); | ||
} | ||
if (Options.zxcvbnOptions.matchers[name] && 'scoring' in Options.zxcvbnOptions.matchers[name]) { | ||
return Options.zxcvbnOptions.matchers[name].scoring(match); | ||
if (options.matchers[name] && 'scoring' in options.matchers[name]) { | ||
return options.matchers[name].scoring(match, options); | ||
} | ||
@@ -51,3 +50,3 @@ return 0; | ||
// eslint-disable-next-line complexity, max-statements | ||
var estimateGuesses = ((match, password) => { | ||
var estimateGuesses = ((options, match, password) => { | ||
const extraData = {}; | ||
@@ -59,3 +58,3 @@ // a match's guess estimate doesn't change. cache it. | ||
const minGuesses = getMinGuesses(match, password); | ||
const estimationResult = getScoring(match.pattern, match); | ||
const estimationResult = getScoring(options, match.pattern, match); | ||
let guesses = 0; | ||
@@ -62,0 +61,0 @@ if (typeof estimationResult === 'number') { |
import { MatchExtended, MatchEstimated } from '../types'; | ||
declare const _default: { | ||
import Options from '../Options'; | ||
export default class Scoring { | ||
private options; | ||
constructor(options: Options); | ||
mostGuessableMatchSequence(password: string, matches: MatchExtended[], excludeAdditive?: boolean): { | ||
@@ -10,3 +13,2 @@ password: string; | ||
getGuesses(password: string, optimalSequenceLength: number): number; | ||
}; | ||
export default _default; | ||
} |
@@ -33,5 +33,5 @@ import utils from './utils.esm.js'; | ||
// than previously encountered sequences, updating state if so. | ||
update(match, sequenceLength) { | ||
update(options, match, sequenceLength) { | ||
const k = match.j; | ||
const estimatedMatch = estimateGuesses(match, this.password); | ||
const estimatedMatch = estimateGuesses(options, match, this.password); | ||
let pi = estimatedMatch.guesses; | ||
@@ -71,6 +71,6 @@ if (sequenceLength > 1) { | ||
// helper: evaluate bruteforce matches ending at passwordCharIndex. | ||
bruteforceUpdate(passwordCharIndex) { | ||
bruteforceUpdate(options, passwordCharIndex) { | ||
// see if a single bruteforce match spanning the passwordCharIndex-prefix is optimal. | ||
let match = this.makeBruteforceMatch(0, passwordCharIndex); | ||
this.update(match, 1); | ||
this.update(options, match, 1); | ||
for (let i = 1; i <= passwordCharIndex; i += 1) { | ||
@@ -91,3 +91,3 @@ // generate passwordCharIndex bruteforce matches, spanning from (i=1, j=passwordCharIndex) up to (i=passwordCharIndex, j=passwordCharIndex). | ||
// try adding m to this length-sequenceLength sequence. | ||
this.update(match, parseInt(sequenceLength, 10) + 1); | ||
this.update(options, match, parseInt(sequenceLength, 10) + 1); | ||
} | ||
@@ -126,3 +126,6 @@ }); | ||
}; | ||
var scoring = { | ||
class Scoring { | ||
constructor(options) { | ||
this.options = options; | ||
} | ||
// ------------------------------------------------------------------------------ | ||
@@ -178,2 +181,3 @@ // search --- most guessable match sequence ------------------------------------- | ||
// optimal.m[k][sequenceLength] is undefined. | ||
// @ts-ignore | ||
m: scoringHelper.fillArray(passwordLength, 'object'), | ||
@@ -190,9 +194,9 @@ // same structure as optimal.m -- holds the product term Prod(m.guesses for m in sequence). | ||
Object.keys(scoringHelper.optimal.m[match.i - 1]).forEach(sequenceLength => { | ||
scoringHelper.update(match, parseInt(sequenceLength, 10) + 1); | ||
scoringHelper.update(this.options, match, parseInt(sequenceLength, 10) + 1); | ||
}); | ||
} else { | ||
scoringHelper.update(match, 1); | ||
scoringHelper.update(this.options, match, 1); | ||
} | ||
}); | ||
scoringHelper.bruteforceUpdate(k); | ||
scoringHelper.bruteforceUpdate(this.options, k); | ||
} | ||
@@ -208,3 +212,3 @@ const optimalMatchSequence = scoringHelper.unwind(passwordLength); | ||
}; | ||
}, | ||
} | ||
getGuesses(password, optimalSequenceLength) { | ||
@@ -220,5 +224,5 @@ const passwordLength = password.length; | ||
} | ||
}; | ||
} | ||
export { scoring as default }; | ||
export { Scoring as default }; | ||
//# sourceMappingURL=index.esm.js.map |
@@ -35,5 +35,5 @@ 'use strict'; | ||
// than previously encountered sequences, updating state if so. | ||
update(match, sequenceLength) { | ||
update(options, match, sequenceLength) { | ||
const k = match.j; | ||
const estimatedMatch = estimate(match, this.password); | ||
const estimatedMatch = estimate(options, match, this.password); | ||
let pi = estimatedMatch.guesses; | ||
@@ -73,6 +73,6 @@ if (sequenceLength > 1) { | ||
// helper: evaluate bruteforce matches ending at passwordCharIndex. | ||
bruteforceUpdate(passwordCharIndex) { | ||
bruteforceUpdate(options, passwordCharIndex) { | ||
// see if a single bruteforce match spanning the passwordCharIndex-prefix is optimal. | ||
let match = this.makeBruteforceMatch(0, passwordCharIndex); | ||
this.update(match, 1); | ||
this.update(options, match, 1); | ||
for (let i = 1; i <= passwordCharIndex; i += 1) { | ||
@@ -93,3 +93,3 @@ // generate passwordCharIndex bruteforce matches, spanning from (i=1, j=passwordCharIndex) up to (i=passwordCharIndex, j=passwordCharIndex). | ||
// try adding m to this length-sequenceLength sequence. | ||
this.update(match, parseInt(sequenceLength, 10) + 1); | ||
this.update(options, match, parseInt(sequenceLength, 10) + 1); | ||
} | ||
@@ -128,3 +128,6 @@ }); | ||
}; | ||
var scoring = { | ||
class Scoring { | ||
constructor(options) { | ||
this.options = options; | ||
} | ||
// ------------------------------------------------------------------------------ | ||
@@ -180,2 +183,3 @@ // search --- most guessable match sequence ------------------------------------- | ||
// optimal.m[k][sequenceLength] is undefined. | ||
// @ts-ignore | ||
m: scoringHelper.fillArray(passwordLength, 'object'), | ||
@@ -192,9 +196,9 @@ // same structure as optimal.m -- holds the product term Prod(m.guesses for m in sequence). | ||
Object.keys(scoringHelper.optimal.m[match.i - 1]).forEach(sequenceLength => { | ||
scoringHelper.update(match, parseInt(sequenceLength, 10) + 1); | ||
scoringHelper.update(this.options, match, parseInt(sequenceLength, 10) + 1); | ||
}); | ||
} else { | ||
scoringHelper.update(match, 1); | ||
scoringHelper.update(this.options, match, 1); | ||
} | ||
}); | ||
scoringHelper.bruteforceUpdate(k); | ||
scoringHelper.bruteforceUpdate(this.options, k); | ||
} | ||
@@ -210,3 +214,3 @@ const optimalMatchSequence = scoringHelper.unwind(passwordLength); | ||
}; | ||
}, | ||
} | ||
getGuesses(password, optimalSequenceLength) { | ||
@@ -222,5 +226,5 @@ const passwordLength = password.length; | ||
} | ||
}; | ||
} | ||
module.exports = scoring; | ||
module.exports = Scoring; | ||
//# sourceMappingURL=index.js.map |
@@ -24,3 +24,2 @@ var utils = { | ||
}, | ||
log2(n) { | ||
@@ -27,0 +26,0 @@ return Math.log(n) / Math.log(2); |
@@ -26,3 +26,2 @@ 'use strict'; | ||
}, | ||
log2(n) { | ||
@@ -29,0 +28,0 @@ return Math.log(n) / Math.log(2); |
@@ -1,12 +0,16 @@ | ||
import { CrackTimesDisplay, CrackTimesSeconds, Score } from './types'; | ||
declare class TimeEstimates { | ||
translate(displayStr: string, value: number | undefined): string; | ||
import Options from './Options'; | ||
import { CrackTimes, Score, TimeEstimationValues } from './types'; | ||
export declare const timeEstimationValuesDefaults: TimeEstimationValues; | ||
export declare const checkTimeEstimationValues: (timeEstimationValues: TimeEstimationValues) => void; | ||
export declare class TimeEstimates { | ||
private options; | ||
constructor(options: Options); | ||
estimateAttackTimes(guesses: number): { | ||
crackTimesSeconds: CrackTimesSeconds; | ||
crackTimesDisplay: CrackTimesDisplay; | ||
crackTimes: CrackTimes; | ||
score: Score; | ||
}; | ||
guessesToScore(guesses: number): Score; | ||
displayTime(seconds: number): string; | ||
private calculateCrackTimesSeconds; | ||
private guessesToScore; | ||
private displayTime; | ||
private translate; | ||
} | ||
export default TimeEstimates; |
@@ -1,3 +0,1 @@ | ||
import { zxcvbnOptions } from './Options.esm.js'; | ||
const SECOND = 1; | ||
@@ -19,2 +17,26 @@ const MINUTE = SECOND * 60; | ||
}; | ||
const timeEstimationValuesDefaults = { | ||
scoring: { | ||
0: 1e3, | ||
1: 1e6, | ||
2: 1e8, | ||
3: 1e10 | ||
}, | ||
attackTime: { | ||
onlineThrottlingXPerHour: 100, | ||
onlineNoThrottlingXPerSecond: 10, | ||
offlineSlowHashingXPerSecond: 1e4, | ||
offlineFastHashingXPerSecond: 1e10 | ||
} | ||
}; | ||
const checkTimeEstimationValues = timeEstimationValues => { | ||
Object.entries(timeEstimationValues).forEach(([key, data]) => { | ||
Object.entries(data).forEach(([subKey, value]) => { | ||
// @ts-ignore | ||
if (value < timeEstimationValuesDefaults[key][subKey]) { | ||
throw new Error('Time estimation values are not to be allowed to be less than default'); | ||
} | ||
}); | ||
}); | ||
}; | ||
/* | ||
@@ -26,50 +48,52 @@ * ------------------------------------------------------------------------------- | ||
class TimeEstimates { | ||
translate(displayStr, value) { | ||
let key = displayStr; | ||
if (value !== undefined && value !== 1) { | ||
key += 's'; | ||
} | ||
const { | ||
timeEstimation | ||
} = zxcvbnOptions.translations; | ||
return timeEstimation[key].replace('{base}', `${value}`); | ||
constructor(options) { | ||
this.options = options; | ||
} | ||
estimateAttackTimes(guesses) { | ||
const crackTimesSeconds = { | ||
onlineThrottling100PerHour: guesses / (100 / 3600), | ||
onlineNoThrottling10PerSecond: guesses / 10, | ||
offlineSlowHashing1e4PerSecond: guesses / 1e4, | ||
offlineFastHashing1e10PerSecond: guesses / 1e10 | ||
}; | ||
const crackTimesDisplay = { | ||
onlineThrottling100PerHour: '', | ||
onlineNoThrottling10PerSecond: '', | ||
offlineSlowHashing1e4PerSecond: '', | ||
offlineFastHashing1e10PerSecond: '' | ||
}; | ||
Object.keys(crackTimesSeconds).forEach(scenario => { | ||
const seconds = crackTimesSeconds[scenario]; | ||
crackTimesDisplay[scenario] = this.displayTime(seconds); | ||
}); | ||
const crackTimesSeconds = this.calculateCrackTimesSeconds(guesses); | ||
const crackTimes = Object.keys(crackTimesSeconds).reduce((previousValue, crackTime) => { | ||
const usedScenario = crackTime; | ||
const seconds = crackTimesSeconds[usedScenario]; | ||
const { | ||
base, | ||
displayStr | ||
} = this.displayTime(seconds); | ||
// eslint-disable-next-line no-param-reassign | ||
previousValue[usedScenario] = { | ||
base, | ||
seconds, | ||
display: this.translate(displayStr, base) | ||
}; | ||
return previousValue; | ||
}, {}); | ||
return { | ||
crackTimesSeconds, | ||
crackTimesDisplay, | ||
crackTimes, | ||
score: this.guessesToScore(guesses) | ||
}; | ||
} | ||
calculateCrackTimesSeconds(guesses) { | ||
const attackTimesOptions = this.options.timeEstimationValues.attackTime; | ||
return { | ||
onlineThrottlingXPerHour: guesses / (attackTimesOptions.onlineThrottlingXPerHour / 3600), | ||
onlineNoThrottlingXPerSecond: guesses / attackTimesOptions.onlineNoThrottlingXPerSecond, | ||
offlineSlowHashingXPerSecond: guesses / attackTimesOptions.offlineSlowHashingXPerSecond, | ||
offlineFastHashingXPerSecond: guesses / attackTimesOptions.offlineFastHashingXPerSecond | ||
}; | ||
} | ||
guessesToScore(guesses) { | ||
const scoringOptions = this.options.timeEstimationValues.scoring; | ||
const DELTA = 5; | ||
if (guesses < 1e3 + DELTA) { | ||
if (guesses < scoringOptions[0] + DELTA) { | ||
// risky password: "too guessable" | ||
return 0; | ||
} | ||
if (guesses < 1e6 + DELTA) { | ||
if (guesses < scoringOptions[1] + DELTA) { | ||
// modest protection from throttled online attacks: "very guessable" | ||
return 1; | ||
} | ||
if (guesses < 1e8 + DELTA) { | ||
if (guesses < scoringOptions[2] + DELTA) { | ||
// modest protection from unthrottled online attacks: "somewhat guessable" | ||
return 2; | ||
} | ||
if (guesses < 1e10 + DELTA) { | ||
if (guesses < scoringOptions[3] + DELTA) { | ||
// modest protection from offline attacks: "safely unguessable" | ||
@@ -84,3 +108,3 @@ // assuming a salted, slow hash function like bcrypt, scrypt, PBKDF2, argon, etc | ||
let displayStr = 'centuries'; | ||
let base; | ||
let base = null; | ||
const timeKeys = Object.keys(times); | ||
@@ -96,7 +120,20 @@ const foundIndex = timeKeys.findIndex(time => seconds < times[time]); | ||
} | ||
return this.translate(displayStr, base); | ||
return { | ||
base, | ||
displayStr | ||
}; | ||
} | ||
translate(displayStr, value) { | ||
let key = displayStr; | ||
if (value !== null && value !== 1) { | ||
key += 's'; | ||
} | ||
const { | ||
timeEstimation | ||
} = this.options.translations; | ||
return timeEstimation[key].replace('{base}', `${value}`); | ||
} | ||
} | ||
export { TimeEstimates as default }; | ||
export { TimeEstimates, checkTimeEstimationValues, timeEstimationValuesDefaults }; | ||
//# sourceMappingURL=TimeEstimates.esm.js.map |
'use strict'; | ||
var Options = require('./Options.js'); | ||
const SECOND = 1; | ||
@@ -21,2 +19,26 @@ const MINUTE = SECOND * 60; | ||
}; | ||
const timeEstimationValuesDefaults = { | ||
scoring: { | ||
0: 1e3, | ||
1: 1e6, | ||
2: 1e8, | ||
3: 1e10 | ||
}, | ||
attackTime: { | ||
onlineThrottlingXPerHour: 100, | ||
onlineNoThrottlingXPerSecond: 10, | ||
offlineSlowHashingXPerSecond: 1e4, | ||
offlineFastHashingXPerSecond: 1e10 | ||
} | ||
}; | ||
const checkTimeEstimationValues = timeEstimationValues => { | ||
Object.entries(timeEstimationValues).forEach(([key, data]) => { | ||
Object.entries(data).forEach(([subKey, value]) => { | ||
// @ts-ignore | ||
if (value < timeEstimationValuesDefaults[key][subKey]) { | ||
throw new Error('Time estimation values are not to be allowed to be less than default'); | ||
} | ||
}); | ||
}); | ||
}; | ||
/* | ||
@@ -28,50 +50,52 @@ * ------------------------------------------------------------------------------- | ||
class TimeEstimates { | ||
translate(displayStr, value) { | ||
let key = displayStr; | ||
if (value !== undefined && value !== 1) { | ||
key += 's'; | ||
} | ||
const { | ||
timeEstimation | ||
} = Options.zxcvbnOptions.translations; | ||
return timeEstimation[key].replace('{base}', `${value}`); | ||
constructor(options) { | ||
this.options = options; | ||
} | ||
estimateAttackTimes(guesses) { | ||
const crackTimesSeconds = { | ||
onlineThrottling100PerHour: guesses / (100 / 3600), | ||
onlineNoThrottling10PerSecond: guesses / 10, | ||
offlineSlowHashing1e4PerSecond: guesses / 1e4, | ||
offlineFastHashing1e10PerSecond: guesses / 1e10 | ||
}; | ||
const crackTimesDisplay = { | ||
onlineThrottling100PerHour: '', | ||
onlineNoThrottling10PerSecond: '', | ||
offlineSlowHashing1e4PerSecond: '', | ||
offlineFastHashing1e10PerSecond: '' | ||
}; | ||
Object.keys(crackTimesSeconds).forEach(scenario => { | ||
const seconds = crackTimesSeconds[scenario]; | ||
crackTimesDisplay[scenario] = this.displayTime(seconds); | ||
}); | ||
const crackTimesSeconds = this.calculateCrackTimesSeconds(guesses); | ||
const crackTimes = Object.keys(crackTimesSeconds).reduce((previousValue, crackTime) => { | ||
const usedScenario = crackTime; | ||
const seconds = crackTimesSeconds[usedScenario]; | ||
const { | ||
base, | ||
displayStr | ||
} = this.displayTime(seconds); | ||
// eslint-disable-next-line no-param-reassign | ||
previousValue[usedScenario] = { | ||
base, | ||
seconds, | ||
display: this.translate(displayStr, base) | ||
}; | ||
return previousValue; | ||
}, {}); | ||
return { | ||
crackTimesSeconds, | ||
crackTimesDisplay, | ||
crackTimes, | ||
score: this.guessesToScore(guesses) | ||
}; | ||
} | ||
calculateCrackTimesSeconds(guesses) { | ||
const attackTimesOptions = this.options.timeEstimationValues.attackTime; | ||
return { | ||
onlineThrottlingXPerHour: guesses / (attackTimesOptions.onlineThrottlingXPerHour / 3600), | ||
onlineNoThrottlingXPerSecond: guesses / attackTimesOptions.onlineNoThrottlingXPerSecond, | ||
offlineSlowHashingXPerSecond: guesses / attackTimesOptions.offlineSlowHashingXPerSecond, | ||
offlineFastHashingXPerSecond: guesses / attackTimesOptions.offlineFastHashingXPerSecond | ||
}; | ||
} | ||
guessesToScore(guesses) { | ||
const scoringOptions = this.options.timeEstimationValues.scoring; | ||
const DELTA = 5; | ||
if (guesses < 1e3 + DELTA) { | ||
if (guesses < scoringOptions[0] + DELTA) { | ||
// risky password: "too guessable" | ||
return 0; | ||
} | ||
if (guesses < 1e6 + DELTA) { | ||
if (guesses < scoringOptions[1] + DELTA) { | ||
// modest protection from throttled online attacks: "very guessable" | ||
return 1; | ||
} | ||
if (guesses < 1e8 + DELTA) { | ||
if (guesses < scoringOptions[2] + DELTA) { | ||
// modest protection from unthrottled online attacks: "somewhat guessable" | ||
return 2; | ||
} | ||
if (guesses < 1e10 + DELTA) { | ||
if (guesses < scoringOptions[3] + DELTA) { | ||
// modest protection from offline attacks: "safely unguessable" | ||
@@ -86,3 +110,3 @@ // assuming a salted, slow hash function like bcrypt, scrypt, PBKDF2, argon, etc | ||
let displayStr = 'centuries'; | ||
let base; | ||
let base = null; | ||
const timeKeys = Object.keys(times); | ||
@@ -98,7 +122,22 @@ const foundIndex = timeKeys.findIndex(time => seconds < times[time]); | ||
} | ||
return this.translate(displayStr, base); | ||
return { | ||
base, | ||
displayStr | ||
}; | ||
} | ||
translate(displayStr, value) { | ||
let key = displayStr; | ||
if (value !== null && value !== 1) { | ||
key += 's'; | ||
} | ||
const { | ||
timeEstimation | ||
} = this.options.translations; | ||
return timeEstimation[key].replace('{base}', `${value}`); | ||
} | ||
} | ||
module.exports = TimeEstimates; | ||
exports.TimeEstimates = TimeEstimates; | ||
exports.checkTimeEstimationValues = checkTimeEstimationValues; | ||
exports.timeEstimationValuesDefaults = timeEstimationValuesDefaults; | ||
//# sourceMappingURL=TimeEstimates.js.map |
@@ -7,2 +7,3 @@ import translationKeys from './data/translationKeys'; | ||
import { PasswordChanges } from './matcher/dictionary/variants/matching/unmunger/getCleanPasswords'; | ||
import Options from './Options'; | ||
export type TranslationKeys = typeof translationKeys; | ||
@@ -38,3 +39,3 @@ export type L33tTableDefault = typeof l33tTableDefault; | ||
rank: number; | ||
dictionaryName: DictionaryNames; | ||
dictionaryName: DictionaryNames | string; | ||
reversed: boolean; | ||
@@ -93,18 +94,23 @@ l33t: boolean; | ||
export interface Optimal { | ||
m: Match; | ||
pi: Match; | ||
g: Match; | ||
m: Match[]; | ||
pi: Record<string, number>[]; | ||
g: Record<string, number>[]; | ||
} | ||
export interface CrackTime { | ||
base: number | null; | ||
seconds: number; | ||
display: string; | ||
} | ||
export interface CrackTimes { | ||
onlineThrottlingXPerHour: CrackTime; | ||
onlineNoThrottlingXPerSecond: CrackTime; | ||
offlineSlowHashingXPerSecond: CrackTime; | ||
offlineFastHashingXPerSecond: CrackTime; | ||
} | ||
export interface CrackTimesSeconds { | ||
onlineThrottling100PerHour: number; | ||
onlineNoThrottling10PerSecond: number; | ||
offlineSlowHashing1e4PerSecond: number; | ||
offlineFastHashing1e10PerSecond: number; | ||
onlineThrottlingXPerHour: number; | ||
onlineNoThrottlingXPerSecond: number; | ||
offlineSlowHashingXPerSecond: number; | ||
offlineFastHashingXPerSecond: number; | ||
} | ||
export interface CrackTimesDisplay { | ||
onlineThrottling100PerHour: string; | ||
onlineNoThrottling10PerSecond: string; | ||
offlineSlowHashing1e4PerSecond: string; | ||
offlineFastHashing1e10PerSecond: string; | ||
} | ||
export interface FeedbackType { | ||
@@ -126,2 +132,16 @@ warning: string | null; | ||
} | ||
export interface TimeEstimationValues { | ||
scoring: { | ||
0: number; | ||
1: number; | ||
2: number; | ||
3: number; | ||
}; | ||
attackTime: { | ||
onlineThrottlingXPerHour: number; | ||
onlineNoThrottlingXPerSecond: number; | ||
offlineSlowHashingXPerSecond: number; | ||
offlineFastHashingXPerSecond: number; | ||
}; | ||
} | ||
export interface OptionsType { | ||
@@ -164,2 +184,6 @@ /** | ||
maxLength?: number; | ||
/** | ||
* @description Define the values to calculate the scoring and attack times. DO NOT CHANGE unless you know what you are doing. The default values are just fine as long as you are using a strong, slow hash function. Can be adjusted to account for increasingly powerful attacker hardware. | ||
*/ | ||
timeEstimationValues?: TimeEstimationValues; | ||
} | ||
@@ -172,4 +196,8 @@ export interface RankedDictionary { | ||
} | ||
export type DefaultFeedbackFunction = (match: MatchEstimated, isSoleMatch?: boolean) => FeedbackType | null; | ||
export type DefaultScoringFunction = (match: MatchExtended | MatchEstimated) => number | DictionaryReturn; | ||
export type DefaultFeedbackFunction = (options: Options, match: MatchEstimated, isSoleMatch?: boolean) => FeedbackType | null; | ||
export type DefaultScoringFunction = (match: MatchExtended | MatchEstimated, options: Options) => number | DictionaryReturn; | ||
export interface UserInputsOptions { | ||
rankedDictionary: RankedDictionary; | ||
rankedDictionaryMaxWordSize: number; | ||
} | ||
export interface MatchOptions { | ||
@@ -181,5 +209,6 @@ password: string; | ||
omniMatch: Matching; | ||
userInputsOptions?: UserInputsOptions; | ||
} | ||
export type MatchingType = new () => { | ||
match({ password, omniMatch, }: MatchOptions): MatchExtended[] | Promise<MatchExtended[]>; | ||
export type MatchingType = new (options: Options) => { | ||
match({ password, omniMatch, userInputsOptions, }: MatchOptions): MatchExtended[] | Promise<MatchExtended[]>; | ||
}; | ||
@@ -197,4 +226,3 @@ export interface Matcher { | ||
feedback: FeedbackType; | ||
crackTimesSeconds: CrackTimesSeconds; | ||
crackTimesDisplay: CrackTimesDisplay; | ||
crackTimes: CrackTimes; | ||
score: Score; | ||
@@ -201,0 +229,0 @@ password: string; |
@@ -1,2 +0,2 @@ | ||
this.zxcvbnts=this.zxcvbnts||{},this.zxcvbnts.core=function(t){"use strict";const e=(t,e)=>t.push.apply(t,e),s=t=>t.sort(((t,e)=>t.i-e.i||t.j-e.j)),n=t=>{const e={};let s=1;return t.forEach((t=>{e[t]=s,s+=1})),e};const r={4:[[1,2],[2,3]],5:[[1,3],[2,3],[2,4]],6:[[1,2],[2,4],[4,5]],7:[[1,3],[2,3],[4,5],[4,6]],8:[[2,4],[4,6]]},a=/^[A-Z\xbf-\xdf][^A-Z\xbf-\xdf]+$/,i=/^[^A-Z\xbf-\xdf]+[A-Z\xbf-\xdf]$/,o=/^[A-Z\xbf-\xdf]+$/,c=/^[^a-z\xdf-\xff]+$/,l=/^[a-z\xdf-\xff]+$/,h=/^[^A-Z\xbf-\xdf]+$/,u=/[a-z\xdf-\xff]/,d=/[A-Z\xbf-\xdf]/,g=/[^A-Za-z\xbf-\xdf]/gi,p=/^\d+$/,f=(new Date).getFullYear(),m={recentYear:/19\d\d|200\d|201\d|202\d/g},b=[" ",",",";",":","|","/","\\","_",".","-"],y=b.length;class k{match({password:t}){const e=[...this.getMatchesWithoutSeparator(t),...this.getMatchesWithSeparator(t)],n=this.filterNoise(e);return s(n)}getMatchesWithSeparator(t){const e=[],s=/^(\d{1,4})([\s/\\_.-])(\d{1,2})\2(\d{1,4})$/;for(let n=0;n<=Math.abs(t.length-6);n+=1)for(let r=n+5;r<=n+9&&!(r>=t.length);r+=1){const a=t.slice(n,+r+1||9e9),i=s.exec(a);if(null!=i){const t=this.mapIntegersToDayMonthYear([parseInt(i[1],10),parseInt(i[3],10),parseInt(i[4],10)]);null!=t&&e.push({pattern:"date",token:a,i:n,j:r,separator:i[2],year:t.year,month:t.month,day:t.day})}}return e}getMatchesWithoutSeparator(t){const e=[],s=/^\d{4,8}$/,n=t=>Math.abs(t.year-f);for(let a=0;a<=Math.abs(t.length-4);a+=1)for(let i=a+3;i<=a+7&&!(i>=t.length);i+=1){const o=t.slice(a,+i+1||9e9);if(s.exec(o)){const t=[],s=o.length;if(r[s].forEach((([e,s])=>{const n=this.mapIntegersToDayMonthYear([parseInt(o.slice(0,e),10),parseInt(o.slice(e,s),10),parseInt(o.slice(s),10)]);null!=n&&t.push(n)})),t.length>0){let s=t[0],r=n(t[0]);t.slice(1).forEach((t=>{const e=n(t);e<r&&(s=t,r=e)})),e.push({pattern:"date",token:o,i:a,j:i,separator:"",year:s.year,month:s.month,day:s.day})}}}return e}filterNoise(t){return t.filter((e=>{let s=!1;const n=t.length;for(let r=0;r<n;r+=1){const n=t[r];if(e!==n&&n.i<=e.i&&n.j>=e.j){s=!0;break}}return!s}))}mapIntegersToDayMonthYear(t){if(t[1]>31||t[1]<=0)return null;let e=0,s=0,n=0;for(let r=0,a=t.length;r<a;r+=1){const a=t[r];if(a>99&&a<1e3||a>2050)return null;a>31&&(s+=1),a>12&&(e+=1),a<=0&&(n+=1)}return s>=2||3===e||n>=2?null:this.getDayMonth(t)}getDayMonth(t){const e=[[t[2],t.slice(0,2)],[t[0],t.slice(1,3)]],s=e.length;for(let t=0;t<s;t+=1){const[s,n]=e[t];if(1e3<=s&&s<=2050){const t=this.mapIntegersToDayMonth(n);return null!=t?{year:s,month:t.month,day:t.day}:null}}for(let t=0;t<s;t+=1){const[s,n]=e[t],r=this.mapIntegersToDayMonth(n);if(null!=r)return{year:this.twoToFourDigitYear(s),month:r.month,day:r.day}}return null}mapIntegersToDayMonth(t){const e=[t,t.slice().reverse()];for(let t=0;t<e.length;t+=1){const s=e[t],n=s[0],r=s[1];if(n>=1&&n<=31&&r>=1&&r<=12)return{day:n,month:r}}return null}twoToFourDigitYear(t){return t>99?t:t>50?t+1900:t+2e3}}const w=new Uint32Array(65536),x=(t,e)=>{if(t.length<e.length){const s=e;e=t,t=s}return 0===e.length?t.length:t.length<=32?((t,e)=>{const s=t.length,n=e.length,r=1<<s-1;let a=-1,i=0,o=s,c=s;for(;c--;)w[t.charCodeAt(c)]|=1<<c;for(c=0;c<n;c++){let t=w[e.charCodeAt(c)];const s=t|i;t|=(t&a)+a^a,i|=~(t|a),a&=t,i&r&&o++,a&r&&o--,i=i<<1|1,a=a<<1|~(s|i),i&=s}for(c=s;c--;)w[t.charCodeAt(c)]=0;return o})(t,e):((t,e)=>{const s=e.length,n=t.length,r=[],a=[],i=Math.ceil(s/32),o=Math.ceil(n/32);for(let t=0;t<i;t++)a[t]=-1,r[t]=0;let c=0;for(;c<o-1;c++){let i=0,o=-1;const l=32*c,h=Math.min(32,n)+l;for(let e=l;e<h;e++)w[t.charCodeAt(e)]|=1<<e;for(let t=0;t<s;t++){const s=w[e.charCodeAt(t)],n=a[t/32|0]>>>t&1,c=r[t/32|0]>>>t&1,l=s|i,h=((s|c)&o)+o^o|s|c;let u=i|~(h|o),d=o&h;u>>>31^n&&(a[t/32|0]^=1<<t),d>>>31^c&&(r[t/32|0]^=1<<t),u=u<<1|n,d=d<<1|c,o=d|~(l|u),i=u&l}for(let e=l;e<h;e++)w[t.charCodeAt(e)]=0}let l=0,h=-1;const u=32*c,d=Math.min(32,n-u)+u;for(let e=u;e<d;e++)w[t.charCodeAt(e)]|=1<<e;let g=n;for(let t=0;t<s;t++){const s=w[e.charCodeAt(t)],i=a[t/32|0]>>>t&1,o=r[t/32|0]>>>t&1,c=s|l,u=((s|o)&h)+h^h|s|o;let d=l|~(u|h),p=h&u;g+=d>>>n-1&1,g-=p>>>n-1&1,d>>>31^i&&(a[t/32|0]^=1<<t),p>>>31^o&&(r[t/32|0]^=1<<t),d=d<<1|i,p=p<<1|o,h=p|~(c|d),l=d&c}for(let e=u;e<d;e++)w[t.charCodeAt(e)]=0;return g})(t,e)},M=(t,e,s)=>{let n=0;const r=Object.keys(e).find((e=>{const r=((t,e,s)=>{const n=t.length<=e.length,r=t.length<=s;return n||r?Math.ceil(t.length/4):s})(t,e,s);if(Math.abs(t.length-e.length)>r)return!1;const a=x(t,e),i=a<=r;return i&&(n=a),i}));return r?{levenshteinDistance:n,levenshteinDistanceEntry:r}:{}};var S={a:["4","@"],b:["8"],c:["(","{","[","<"],d:["6","|)"],e:["3"],f:["#"],g:["6","9","&"],h:["#","|-|"],i:["1","!","|"],k:["<","|<"],l:["!","1","|","7"],m:["^^","nn","2n","/\\\\/\\\\"],n:["//"],o:["0","()"],q:["9"],u:["|_|"],s:["$","5"],t:["+","7"],v:["<",">","/"],w:["^/","uu","vv","2u","2v","\\\\/\\\\/"],x:["%","><"],z:["2"]},v={warnings:{straightRow:"straightRow",keyPattern:"keyPattern",simpleRepeat:"simpleRepeat",extendedRepeat:"extendedRepeat",sequences:"sequences",recentYears:"recentYears",dates:"dates",topTen:"topTen",topHundred:"topHundred",common:"common",similarToCommon:"similarToCommon",wordByItself:"wordByItself",namesByThemselves:"namesByThemselves",commonNames:"commonNames",userInputs:"userInputs",pwned:"pwned"},suggestions:{l33t:"l33t",reverseWords:"reverseWords",allUppercase:"allUppercase",capitalization:"capitalization",dates:"dates",recentYears:"recentYears",associatedYears:"associatedYears",sequences:"sequences",repeated:"repeated",longerKeyboardPattern:"longerKeyboardPattern",anotherWord:"anotherWord",useWords:"useWords",noNeed:"noNeed",pwned:"pwned"},timeEstimation:{ltSecond:"ltSecond",second:"second",seconds:"seconds",minute:"minute",minutes:"minutes",hour:"hour",hours:"hours",day:"day",days:"days",month:"month",months:"months",year:"year",years:"years",centuries:"centuries"}};class T{constructor(t=[]){this.parents=t,this.children=new Map}addSub(t,...e){const s=t.charAt(0);this.children.has(s)||this.children.set(s,new T([...this.parents,s]));let n=this.children.get(s);for(let e=1;e<t.length;e+=1){const s=t.charAt(e);n.hasChild(s)||n.addChild(s),n=n.getChild(s)}return n.subs=(n.subs||[]).concat(e),this}getChild(t){return this.children.get(t)}isTerminal(){return!!this.subs}addChild(t){this.hasChild(t)||this.children.set(t,new T([...this.parents,t]))}hasChild(t){return this.children.has(t)}}var j=(t,e)=>(Object.entries(t).forEach((([t,s])=>{s.forEach((s=>{e.addSub(s,t)}))})),e);class I{constructor(){this.matchers={},this.l33tTable=S,this.trieNodeRoot=j(S,new T),this.dictionary={userInputs:[]},this.rankedDictionaries={},this.rankedDictionariesMaxWordSize={},this.translations=v,this.graphs={},this.useLevenshteinDistance=!1,this.levenshteinThreshold=2,this.l33tMaxSubstitutions=100,this.maxLength=256,this.setRankedDictionaries()}setOptions(t={}){t.l33tTable&&(this.l33tTable=t.l33tTable,this.trieNodeRoot=j(t.l33tTable,new T)),t.dictionary&&(this.dictionary=t.dictionary,this.setRankedDictionaries()),t.translations&&this.setTranslations(t.translations),t.graphs&&(this.graphs=t.graphs),void 0!==t.useLevenshteinDistance&&(this.useLevenshteinDistance=t.useLevenshteinDistance),void 0!==t.levenshteinThreshold&&(this.levenshteinThreshold=t.levenshteinThreshold),void 0!==t.l33tMaxSubstitutions&&(this.l33tMaxSubstitutions=t.l33tMaxSubstitutions),void 0!==t.maxLength&&(this.maxLength=t.maxLength)}setTranslations(t){if(!this.checkCustomTranslations(t))throw new Error("Invalid translations object fallback to keys");this.translations=t}checkCustomTranslations(t){let e=!0;return Object.keys(v).forEach((s=>{if(s in t){const n=s;Object.keys(v[n]).forEach((s=>{s in t[n]||(e=!1)}))}else e=!1})),e}setRankedDictionaries(){const t={},e={};Object.keys(this.dictionary).forEach((s=>{t[s]=n(this.dictionary[s]),e[s]=this.getRankedDictionariesMaxWordSize(this.dictionary[s])})),this.rankedDictionaries=t,this.rankedDictionariesMaxWordSize=e}getRankedDictionariesMaxWordSize(t){const e=t.map((t=>"string"!=typeof t?t.toString().length:t.length));return 0===e.length?0:e.reduce(((t,e)=>Math.max(t,e)),-1/0)}buildSanitizedRankedDictionary(t){const e=[];return t.forEach((t=>{const s=typeof t;"string"!==s&&"number"!==s&&"boolean"!==s||e.push(t.toString().toLowerCase())})),n(e)}extendUserInputsDictionary(t){this.dictionary.userInputs||(this.dictionary.userInputs=[]);const e=[...this.dictionary.userInputs,...t];this.rankedDictionaries.userInputs=this.buildSanitizedRankedDictionary(e),this.rankedDictionariesMaxWordSize.userInputs=this.getRankedDictionariesMaxWordSize(e)}addMatcher(t,e){this.matchers[t]?console.info(`Matcher ${t} already exists`):this.matchers[t]=e}}const A=new I;class C{constructor(t){this.defaultMatch=t}match({password:t}){const e=t.split("").reverse().join("");return this.defaultMatch({password:e}).map((e=>({...e,token:e.token.split("").reverse().join(""),reversed:!0,i:t.length-1-e.j,j:t.length-1-e.i})))}}class D{constructor({substr:t,limit:e,trieRoot:s}){this.buffer=[],this.finalPasswords=[],this.substr=t,this.limit=e,this.trieRoot=s}getAllPossibleSubsAtIndex(t){const e=[];let s=this.trieRoot;for(let n=t;n<this.substr.length;n+=1){const t=this.substr.charAt(n);if(s=s.getChild(t),!s)break;e.push(s)}return e}helper({onlyFullSub:t,isFullSub:e,index:s,subIndex:n,changes:r,lastSubLetter:a,consecutiveSubCount:i}){if(this.finalPasswords.length>=this.limit)return;if(s===this.substr.length)return void(t===e&&this.finalPasswords.push({password:this.buffer.join(""),changes:r}));const o=[...this.getAllPossibleSubsAtIndex(s)];let c=!1;for(let l=s+o.length-1;l>=s;l-=1){const h=o[l-s];if(h.isTerminal()){if(a===h.parents.join("")&&i>=3)continue;c=!0;const s=h.subs;for(const o of s){this.buffer.push(o);const s=r.concat({i:n,letter:o,substitution:h.parents.join("")});if(this.helper({onlyFullSub:t,isFullSub:e,index:l+1,subIndex:n+o.length,changes:s,lastSubLetter:h.parents.join(""),consecutiveSubCount:a===h.parents.join("")?i+1:1}),this.buffer.pop(),this.finalPasswords.length>=this.limit)return}}}if(!t||!c){const o=this.substr.charAt(s);this.buffer.push(o),this.helper({onlyFullSub:t,isFullSub:e&&!c,index:s+1,subIndex:n+1,changes:r,lastSubLetter:a,consecutiveSubCount:i}),this.buffer.pop()}}getAll(){return this.helper({onlyFullSub:!0,isFullSub:!0,index:0,subIndex:0,changes:[],lastSubLetter:void 0,consecutiveSubCount:0}),this.helper({onlyFullSub:!1,isFullSub:!0,index:0,subIndex:0,changes:[],lastSubLetter:void 0,consecutiveSubCount:0}),this.finalPasswords}}class E{constructor(t){this.defaultMatch=t}isAlreadyIncluded(t,e){return t.some((t=>Object.entries(t).every((([t,s])=>"subs"===t||s===e[t]))))}match({password:t}){const e=[],s=((t,e,s)=>new D({substr:t,limit:e,trieRoot:s}).getAll())(t,A.l33tMaxSubstitutions,A.trieNodeRoot);let n=!1,r=!0;return s.forEach((s=>{if(n)return;const a=this.defaultMatch({password:s.password,useLevenshtein:r});r=!1,a.forEach((r=>{n||(n=0===r.i&&r.j===t.length-1);const a=((t,e,s)=>{const n=t.changes.filter((t=>t.i<e)).reduce(((t,e)=>t-e.letter.length+e.substitution.length),e),r=t.changes.filter((t=>t.i>=e&&t.i<=s)),a=r.reduce(((t,e)=>t-e.letter.length+e.substitution.length),s-e+n),i=[],o=[];return r.forEach((t=>{i.findIndex((e=>e.letter===t.letter&&e.substitution===t.substitution))<0&&(i.push({letter:t.letter,substitution:t.substitution}),o.push(`${t.substitution} -> ${t.letter}`))})),{i:n,j:a,subs:i,subDisplay:o.join(", ")}})(s,r.i,r.j),i=t.slice(a.i,+a.j+1||9e9),o={...r,l33t:!0,token:i,...a},c=this.isAlreadyIncluded(e,o);i.toLowerCase()===r.matchedWord||c||e.push(o)}))})),e.filter((t=>t.token.length>1))}}class L{constructor(){this.l33t=new E(this.defaultMatch),this.reverse=new C(this.defaultMatch)}match({password:t}){const e=[...this.defaultMatch({password:t}),...this.reverse.match({password:t}),...this.l33t.match({password:t})];return s(e)}defaultMatch({password:t,useLevenshtein:e=!0}){const s=[],n=t.length,r=t.toLowerCase();return Object.keys(A.rankedDictionaries).forEach((a=>{const i=A.rankedDictionaries[a],o=A.rankedDictionariesMaxWordSize[a],c=Math.min(o,n);for(let o=0;o<n;o+=1){const l=Math.min(o+c,n);for(let c=o;c<l;c+=1){const l=r.slice(o,+c+1||9e9),h=l in i;let u={};const d=0===o&&c===n-1;A.useLevenshteinDistance&&d&&!h&&e&&(u=M(l,i,A.levenshteinThreshold));const g=0!==Object.keys(u).length;if(h||g){const e=i[g?u.levenshteinDistanceEntry:l];s.push({pattern:"dictionary",i:o,j:c,token:t.slice(o,+c+1||9e9),matchedWord:l,rank:e,dictionaryName:a,reversed:!1,l33t:!1,...u})}}}})),s}}class P{match({password:t,regexes:e=m}){const n=[];return Object.keys(e).forEach((s=>{const r=e[s];let a;for(r.lastIndex=0;a=r.exec(t);)if(a){const t=a[0];n.push({pattern:"regex",token:t,i:a.index,j:a.index+a[0].length-1,regexName:s,regexMatch:a})}})),s(n)}}var R={nCk(t,e){let s=t;if(e>s)return 0;if(0===e)return 1;let n=1;for(let t=1;t<=e;t+=1)n*=s,n/=t,s-=1;return n},log10:t=>0===t?0:Math.log(t)/Math.log(10),log2:t=>Math.log(t)/Math.log(2),factorial(t){let e=1;for(let s=2;s<=t;s+=1)e*=s;return e}};var z=t=>{const e=t.replace(g,"");if(e.match(h)||e.toLowerCase()===e)return 1;const s=[a,i,c],n=s.length;for(let t=0;t<n;t+=1){const n=s[t];if(e.match(n))return 2}return(t=>{const e=t.split(""),s=e.filter((t=>t.match(d))).length,n=e.filter((t=>t.match(u))).length;let r=0;const a=Math.min(s,n);for(let t=1;t<=a;t+=1)r+=R.nCk(s+n,t);return r})(e)};const O=(t,e)=>{let s=0,n=t.indexOf(e);for(;n>=0;)s+=1,n=t.indexOf(e,n+e.length);return s};var N=({l33t:t,subs:e,token:s})=>{if(!t)return 1;let n=1;return e.forEach((t=>{const{subbedCount:e,unsubbedCount:r}=(({sub:t,token:e})=>{const s=e.toLowerCase();return{subbedCount:O(s,t.substitution),unsubbedCount:O(s,t.letter)}})({sub:t,token:s});if(0===e||0===r)n*=2;else{const t=Math.min(r,e);let s=0;for(let n=1;n<=t;n+=1)s+=R.nCk(r+e,n);n*=s}})),n};const F=({token:t,graph:e,turns:s})=>{const n=Object.keys(A.graphs[e]).length,r=(t=>{let e=0;return Object.keys(t).forEach((s=>{const n=t[s];e+=n.filter((t=>!!t)).length})),e/=Object.entries(t).length,e})(A.graphs[e]);let a=0;const i=t.length;for(let t=2;t<=i;t+=1){const e=Math.min(s,t-1);for(let s=1;s<=e;s+=1)a+=R.nCk(t-1,s-1)*n*r**s}return a};const q={bruteforce:({token:t})=>{let e,s=10**t.length;return s===Number.POSITIVE_INFINITY&&(s=Number.MAX_VALUE),e=1===t.length?11:51,Math.max(s,e)},date:({year:t,separator:e})=>{let s=365*Math.max(Math.abs(t-f),20);return e&&(s*=4),s},dictionary:({rank:t,reversed:e,l33t:s,subs:n,token:r,dictionaryName:a})=>{const i=t,o=z(r),c=N({l33t:s,subs:n,token:r});let l;return l="diceware"===a?3888:i*o*c*(e?2:1),{baseGuesses:i,uppercaseVariations:o,l33tVariations:c,calculation:l}},regex:({regexName:t,regexMatch:e,token:s})=>{const n={alphaLower:26,alphaUpper:26,alpha:52,alphanumeric:62,digits:10,symbols:33};return t in n?n[t]**s.length:"recentYear"===t?Math.max(Math.abs(parseInt(e[0],10)-f),20):0},repeat:({baseGuesses:t,repeatCount:e})=>t*e,sequence:({token:t,ascending:e})=>{const s=t.charAt(0);let n=0;return n=["a","A","z","Z","0","1","9"].includes(s)?4:s.match(/\d/)?10:26,e||(n*=2),n*t.length},spatial:({graph:t,token:e,shiftedCount:s,turns:n})=>{let r=F({token:e,graph:t,turns:n});if(s){const t=e.length-s;if(0===s||0===t)r*=2;else{let e=0;for(let n=1;n<=Math.min(s,t);n+=1)e+=R.nCk(s+t,n);r*=e}}return Math.round(r)},separator:()=>y};var W=(t,e)=>{const s={};if("guesses"in t&&null!=t.guesses)return t;const n=((t,e)=>{let s=1;return t.token.length<e.length&&(s=1===t.token.length?10:50),s})(t,e),r=((t,e)=>q[t]?q[t](e):A.matchers[t]&&"scoring"in A.matchers[t]?A.matchers[t].scoring(e):0)(t.pattern,t);let a=0;"number"==typeof r?a=r:"dictionary"===t.pattern&&(a=r.calculation,s.baseGuesses=r.baseGuesses,s.uppercaseVariations=r.uppercaseVariations,s.l33tVariations=r.l33tVariations);const i=Math.max(a,n);return{...t,...s,guesses:i,guessesLog10:R.log10(i)}};const Y={password:"",optimal:{},excludeAdditive:!1,separatorRegex:void 0,fillArray(t,e){const s=[];for(let n=0;n<t;n+=1){let t=[];"object"===e&&(t={}),s.push(t)}return s},makeBruteforceMatch(t,e){return{pattern:"bruteforce",token:this.password.slice(t,+e+1||9e9),i:t,j:e}},update(t,e){const s=t.j,n=W(t,this.password);let r=n.guesses;e>1&&(r*=this.optimal.pi[n.i-1][e-1]);let a=R.factorial(e)*r;this.excludeAdditive||(a+=1e4**(e-1));let i=!1;Object.keys(this.optimal.g[s]).forEach((t=>{const n=this.optimal.g[s][t];parseInt(t,10)<=e&&n<=a&&(i=!0)})),i||(this.optimal.g[s][e]=a,this.optimal.m[s][e]=n,this.optimal.pi[s][e]=r)},bruteforceUpdate(t){let e=this.makeBruteforceMatch(0,t);this.update(e,1);for(let s=1;s<=t;s+=1){e=this.makeBruteforceMatch(s,t);const n=this.optimal.m[s-1];Object.keys(n).forEach((t=>{"bruteforce"!==n[t].pattern&&this.update(e,parseInt(t,10)+1)}))}},unwind(t){const e=[];let s=t-1,n=0,r=Infinity;const a=this.optimal.g[s];for(a&&Object.keys(a).forEach((t=>{const e=a[t];e<r&&(n=parseInt(t,10),r=e)}));s>=0;){const t=this.optimal.m[s][n];e.unshift(t),s=t.i-1,n-=1}return e}};var $={mostGuessableMatchSequence(t,e,s=!1){Y.password=t,Y.excludeAdditive=s;const n=t.length;let r=Y.fillArray(n,"array");e.forEach((t=>{r[t.j].push(t)})),r=r.map((t=>t.sort(((t,e)=>t.i-e.i)))),Y.optimal={m:Y.fillArray(n,"object"),pi:Y.fillArray(n,"object"),g:Y.fillArray(n,"object")};for(let t=0;t<n;t+=1)r[t].forEach((t=>{t.i>0?Object.keys(Y.optimal.m[t.i-1]).forEach((e=>{Y.update(t,parseInt(e,10)+1)})):Y.update(t,1)})),Y.bruteforceUpdate(t);const a=Y.unwind(n),i=a.length,o=this.getGuesses(t,i);return{password:t,guesses:o,guessesLog10:R.log10(o),sequence:a}},getGuesses(t,e){const s=t.length;let n=0;return n=0===t.length?1:Y.optimal.g[s-1][e],n}};class G{match({password:t,omniMatch:e}){const s=[];let n=0;for(;n<t.length;){const r=this.getGreedyMatch(t,n),a=this.getLazyMatch(t,n);if(null==r)break;const{match:i,baseToken:o}=this.setMatchToken(r,a);if(i){const t=i.index+i[0].length-1,r=this.getBaseGuesses(o,e);s.push(this.normalizeMatch(o,t,i,r)),n=t+1}}return s.some((t=>t instanceof Promise))?Promise.all(s):s}normalizeMatch(t,e,s,n){const r={pattern:"repeat",i:s.index,j:e,token:s[0],baseToken:t,baseGuesses:0,repeatCount:s[0].length/t.length};return n instanceof Promise?n.then((t=>({...r,baseGuesses:t}))):{...r,baseGuesses:n}}getGreedyMatch(t,e){const s=/(.+)\1+/g;return s.lastIndex=e,s.exec(t)}getLazyMatch(t,e){const s=/(.+?)\1+/g;return s.lastIndex=e,s.exec(t)}setMatchToken(t,e){const s=/^(.+?)\1+$/;let n,r="";if(e&&t[0].length>e[0].length){n=t;const e=s.exec(n[0]);e&&(r=e[1])}else n=e,n&&(r=n[1]);return{match:n,baseToken:r}}getBaseGuesses(t,e){const s=e.match(t);if(s instanceof Promise)return s.then((e=>$.mostGuessableMatchSequence(t,e).guesses));return $.mostGuessableMatchSequence(t,s).guesses}}class U{constructor(){this.MAX_DELTA=5}match({password:t}){const e=[];if(1===t.length)return[];let s=0,n=null;const r=t.length;for(let a=1;a<r;a+=1){const r=t.charCodeAt(a)-t.charCodeAt(a-1);if(null==n&&(n=r),r!==n){const i=a-1;this.update({i:s,j:i,delta:n,password:t,result:e}),s=i,n=r}}return this.update({i:s,j:r-1,delta:n,password:t,result:e}),e}update({i:t,j:e,delta:s,password:n,result:r}){if(e-t>1||1===Math.abs(s)){const a=Math.abs(s);if(a>0&&a<=this.MAX_DELTA){const a=n.slice(t,+e+1||9e9),{sequenceName:i,sequenceSpace:o}=this.getSequence(a);return r.push({pattern:"sequence",i:t,j:e,token:n.slice(t,+e+1||9e9),sequenceName:i,sequenceSpace:o,ascending:s>0})}}return null}getSequence(t){let e="unicode",s=26;return l.test(t)?(e="lower",s=26):o.test(t)?(e="upper",s=26):p.test(t)&&(e="digits",s=10),{sequenceName:e,sequenceSpace:s}}}class B{constructor(){this.SHIFTED_RX=/[~!@#$%^&*()_+QWERTYUIOP{}|ASDFGHJKL:"ZXCVBNM<>?]/}match({password:t}){const n=[];return Object.keys(A.graphs).forEach((s=>{const r=A.graphs[s];e(n,this.helper(t,r,s))})),s(n)}checkIfShifted(t,e,s){return!t.includes("keypad")&&this.SHIFTED_RX.test(e.charAt(s))?1:0}helper(t,e,s){let n;const r=[];let a=0;const i=t.length;for(;a<i-1;){let o=a+1,c=null,l=0;for(n=this.checkIfShifted(s,t,a);;){const h=e[t.charAt(o-1)]||[];let u=!1,d=-1,g=-1;if(o<i){const e=t.charAt(o),s=h.length;for(let t=0;t<s;t+=1){const s=h[t];if(g+=1,s){const t=s.indexOf(e);if(-1!==t){u=!0,d=g,1===t&&(n+=1),c!==d&&(l+=1,c=d);break}}}}if(!u){o-a>2&&r.push({pattern:"spatial",i:a,j:o-1,token:t.slice(a,o),graph:s,turns:l,shiftedCount:n}),a=o;break}o+=1}}return r}}const H=new RegExp(`[${b.join("")}]`);class Z{static getMostUsedSeparatorChar(t){const e=[...t.split("").filter((t=>H.test(t))).reduce(((t,e)=>{const s=t.get(e);return s?t.set(e,s+1):t.set(e,1),t}),new Map).entries()].sort((([t,e],[s,n])=>n-e));if(!e.length)return;const s=e[0];return s[1]<2?void 0:s[0]}static getSeparatorRegex(t){return new RegExp(`([^${t}\n])(${t})(?!${t})`,"g")}match({password:t}){const e=[];if(0===t.length)return e;const s=Z.getMostUsedSeparatorChar(t);if(void 0===s)return e;const n=Z.getSeparatorRegex(s);for(const r of t.matchAll(n)){if(void 0===r.index)continue;const t=r.index+1;e.push({pattern:"separator",token:s,i:t,j:t})}return e}}class _{constructor(){this.matchers={date:k,dictionary:L,regex:P,repeat:G,sequence:U,spatial:B,separator:Z}}match(t){const n=[],r=[];return[...Object.keys(this.matchers),...Object.keys(A.matchers)].forEach((s=>{if(!this.matchers[s]&&!A.matchers[s])return;const a=(new(this.matchers[s]?this.matchers[s]:A.matchers[s].Matching)).match({password:t,omniMatch:this});a instanceof Promise?(a.then((t=>{e(n,t)})),r.push(a)):e(n,a)})),r.length>0?new Promise(((t,e)=>{Promise.all(r).then((()=>{t(s(n))})).catch((t=>{e(t)}))})):s(n)}}const V=2678400,X=32140800,K={second:1,minute:60,hour:3600,day:86400,month:V,year:X,century:321408e4};class J{translate(t,e){let s=t;void 0!==e&&1!==e&&(s+="s");const{timeEstimation:n}=A.translations;return n[s].replace("{base}",`${e}`)}estimateAttackTimes(t){const e={onlineThrottling100PerHour:t/(100/3600),onlineNoThrottling10PerSecond:t/10,offlineSlowHashing1e4PerSecond:t/1e4,offlineFastHashing1e10PerSecond:t/1e10},s={onlineThrottling100PerHour:"",onlineNoThrottling10PerSecond:"",offlineSlowHashing1e4PerSecond:"",offlineFastHashing1e10PerSecond:""};return Object.keys(e).forEach((t=>{const n=e[t];s[t]=this.displayTime(n)})),{crackTimesSeconds:e,crackTimesDisplay:s,score:this.guessesToScore(t)}}guessesToScore(t){return t<1005?0:t<1000005?1:t<100000005?2:t<10000000005?3:4}displayTime(t){let e,s="centuries";const n=Object.keys(K),r=n.findIndex((e=>t<K[e]));return r>-1&&(s=n[r-1],0!==r?e=Math.round(t/K[s]):s="ltSecond"),this.translate(s,e)}}var Q=()=>null,tt=()=>({warning:A.translations.warnings.dates,suggestions:[A.translations.suggestions.dates]});const et=(t,e)=>{let s=null;const n=t.dictionaryName,r="lastnames"===n||n.toLowerCase().includes("firstnames");return"passwords"===n?s=((t,e)=>{let s=null;return!e||t.l33t||t.reversed?t.guessesLog10<=4&&(s=A.translations.warnings.similarToCommon):s=t.rank<=10?A.translations.warnings.topTen:t.rank<=100?A.translations.warnings.topHundred:A.translations.warnings.common,s})(t,e):n.includes("wikipedia")?s=((t,e)=>{let s=null;return e&&(s=A.translations.warnings.wordByItself),s})(0,e):r?s=((t,e)=>e?A.translations.warnings.namesByThemselves:A.translations.warnings.commonNames)(0,e):"userInputs"===n&&(s=A.translations.warnings.userInputs),s};var st=(t,e)=>{const s=et(t,e),n=[],r=t.token;return r.match(a)?n.push(A.translations.suggestions.capitalization):r.match(c)&&r.toLowerCase()!==r&&n.push(A.translations.suggestions.allUppercase),t.reversed&&t.token.length>=4&&n.push(A.translations.suggestions.reverseWords),t.l33t&&n.push(A.translations.suggestions.l33t),{warning:s,suggestions:n}},nt=t=>"recentYear"===t.regexName?{warning:A.translations.warnings.recentYears,suggestions:[A.translations.suggestions.recentYears,A.translations.suggestions.associatedYears]}:{warning:null,suggestions:[]},rt=t=>{let e=A.translations.warnings.extendedRepeat;return 1===t.baseToken.length&&(e=A.translations.warnings.simpleRepeat),{warning:e,suggestions:[A.translations.suggestions.repeated]}},at=()=>({warning:A.translations.warnings.sequences,suggestions:[A.translations.suggestions.sequences]}),it=t=>{let e=A.translations.warnings.keyPattern;return 1===t.turns&&(e=A.translations.warnings.straightRow),{warning:e,suggestions:[A.translations.suggestions.longerKeyboardPattern]}},ot=()=>null;const ct={warning:null,suggestions:[]};class lt{constructor(){this.matchers={bruteforce:Q,date:tt,dictionary:st,regex:nt,repeat:rt,sequence:at,spatial:it,separator:ot},this.defaultFeedback={warning:null,suggestions:[]},this.setDefaultSuggestions()}setDefaultSuggestions(){this.defaultFeedback.suggestions.push(A.translations.suggestions.useWords,A.translations.suggestions.noNeed)}getFeedback(t,e){if(0===e.length)return this.defaultFeedback;if(t>2)return ct;const s=A.translations.suggestions.anotherWord,n=this.getLongestMatch(e);let r=this.getMatchFeedback(n,1===e.length);return null!=r?r.suggestions.unshift(s):r={warning:null,suggestions:[s]},r}getLongestMatch(t){let e=t[0];return t.slice(1).forEach((t=>{t.token.length>e.token.length&&(e=t)})),e}getMatchFeedback(t,e){return this.matchers[t.pattern]?this.matchers[t.pattern](t,e):A.matchers[t.pattern]&&"feedback"in A.matchers[t.pattern]?A.matchers[t.pattern].feedback(t,e):ct}}const ht=()=>(new Date).getTime(),ut=(t,e,s)=>{const n=new lt,r=new J,a=$.mostGuessableMatchSequence(e,t),i=ht()-s,o=r.estimateAttackTimes(a.guesses);return{calcTime:i,...a,...o,feedback:n.getFeedback(o.score,a.sequence)}},dt=(t,e)=>{e&&A.extendUserInputsDictionary(e);return(new _).match(t)};return t.Options=I,t.debounce=(t,e,s)=>{let n;return function(...r){const a=this,i=s&&!n;if(void 0!==n&&clearTimeout(n),n=setTimeout((()=>{n=void 0,s||t.apply(a,r)}),e),i)return t.apply(a,r)}},t.zxcvbn=(t,e)=>{const s=ht(),n=dt(t,e);if(n instanceof Promise)throw new Error("You are using a Promised matcher, please use `zxcvbnAsync` for it.");return ut(n,t,s)},t.zxcvbnAsync=async(t,e)=>{const s=t.substring(0,A.maxLength),n=ht(),r=await dt(s,e);return ut(r,s,n)},t.zxcvbnOptions=A,t}({}); | ||
this.zxcvbnts=this.zxcvbnts||{},this.zxcvbnts.core=function(t){"use strict";const e=(t,e)=>t.push.apply(t,e),s=t=>t.sort(((t,e)=>t.i-e.i||t.j-e.j)),n=t=>{const e={};let s=1;return t.forEach((t=>{e[t]=s,s+=1})),e};const r={4:[[1,2],[2,3]],5:[[1,3],[2,3],[2,4]],6:[[1,2],[2,4],[4,5]],7:[[1,3],[2,3],[4,5],[4,6]],8:[[2,4],[4,6]]},i=/^[A-Z\xbf-\xdf][^A-Z\xbf-\xdf]+$/,o=/^[^A-Z\xbf-\xdf]+[A-Z\xbf-\xdf]$/,a=/^[A-Z\xbf-\xdf]+$/,c=/^[^a-z\xdf-\xff]+$/,l=/^[a-z\xdf-\xff]+$/,h=/^[^A-Z\xbf-\xdf]+$/,u=/[a-z\xdf-\xff]/,d=/[A-Z\xbf-\xdf]/,g=/[^A-Za-z\xbf-\xdf]/gi,p=/^\d+$/,f=(new Date).getFullYear(),m={recentYear:/19\d\d|200\d|201\d|202\d/g},b=[" ",",",";",":","|","/","\\","_",".","-"],k=b.length;class y{constructor(t){this.options=t}match({password:t}){const e=[...this.getMatchesWithoutSeparator(t),...this.getMatchesWithSeparator(t)],n=this.filterNoise(e);return s(n)}getMatchesWithSeparator(t){const e=[],s=/^(\d{1,4})([\s/\\_.-])(\d{1,2})\2(\d{1,4})$/;for(let n=0;n<=Math.abs(t.length-6);n+=1)for(let r=n+5;r<=n+9&&!(r>=t.length);r+=1){const i=t.slice(n,+r+1||9e9),o=s.exec(i);if(null!=o){const t=this.mapIntegersToDayMonthYear([parseInt(o[1],10),parseInt(o[3],10),parseInt(o[4],10)]);null!=t&&e.push({pattern:"date",token:i,i:n,j:r,separator:o[2],year:t.year,month:t.month,day:t.day})}}return e}getMatchesWithoutSeparator(t){const e=[],s=/^\d{4,8}$/,n=t=>Math.abs(t.year-f);for(let i=0;i<=Math.abs(t.length-4);i+=1)for(let o=i+3;o<=i+7&&!(o>=t.length);o+=1){const a=t.slice(i,+o+1||9e9);if(s.exec(a)){const t=[],s=a.length;if(r[s].forEach((([e,s])=>{const n=this.mapIntegersToDayMonthYear([parseInt(a.slice(0,e),10),parseInt(a.slice(e,s),10),parseInt(a.slice(s),10)]);null!=n&&t.push(n)})),t.length>0){let s=t[0],r=n(t[0]);t.slice(1).forEach((t=>{const e=n(t);e<r&&(s=t,r=e)})),e.push({pattern:"date",token:a,i:i,j:o,separator:"",year:s.year,month:s.month,day:s.day})}}}return e}filterNoise(t){return t.filter((e=>{let s=!1;const n=t.length;for(let r=0;r<n;r+=1){const n=t[r];if(e!==n&&n.i<=e.i&&n.j>=e.j){s=!0;break}}return!s}))}mapIntegersToDayMonthYear(t){if(t[1]>31||t[1]<=0)return null;let e=0,s=0,n=0;for(let r=0,i=t.length;r<i;r+=1){const i=t[r];if(i>99&&i<1e3||i>2050)return null;i>31&&(s+=1),i>12&&(e+=1),i<=0&&(n+=1)}return s>=2||3===e||n>=2?null:this.getDayMonth(t)}getDayMonth(t){const e=[[t[2],t.slice(0,2)],[t[0],t.slice(1,3)]],s=e.length;for(let t=0;t<s;t+=1){const[s,n]=e[t];if(1e3<=s&&s<=2050){const t=this.mapIntegersToDayMonth(n);return null!=t?{year:s,month:t.month,day:t.day}:null}}for(let t=0;t<s;t+=1){const[s,n]=e[t],r=this.mapIntegersToDayMonth(n);if(null!=r)return{year:this.twoToFourDigitYear(s),month:r.month,day:r.day}}return null}mapIntegersToDayMonth(t){const e=[t,t.slice().reverse()];for(let t=0;t<e.length;t+=1){const s=e[t],n=s[0],r=s[1];if(n>=1&&n<=31&&r>=1&&r<=12)return{day:n,month:r}}return null}twoToFourDigitYear(t){return t>99?t:t>50?t+1900:t+2e3}}const w=new Uint32Array(65536),x=(t,e)=>{if(t.length<e.length){const s=e;e=t,t=s}return 0===e.length?t.length:t.length<=32?((t,e)=>{const s=t.length,n=e.length,r=1<<s-1;let i=-1,o=0,a=s,c=s;for(;c--;)w[t.charCodeAt(c)]|=1<<c;for(c=0;c<n;c++){let t=w[e.charCodeAt(c)];const s=t|o;t|=(t&i)+i^i,o|=~(t|i),i&=t,o&r&&a++,i&r&&a--,o=o<<1|1,i=i<<1|~(s|o),o&=s}for(c=s;c--;)w[t.charCodeAt(c)]=0;return a})(t,e):((t,e)=>{const s=e.length,n=t.length,r=[],i=[],o=Math.ceil(s/32),a=Math.ceil(n/32);for(let t=0;t<o;t++)i[t]=-1,r[t]=0;let c=0;for(;c<a-1;c++){let o=0,a=-1;const l=32*c,h=Math.min(32,n)+l;for(let e=l;e<h;e++)w[t.charCodeAt(e)]|=1<<e;for(let t=0;t<s;t++){const s=w[e.charCodeAt(t)],n=i[t/32|0]>>>t&1,c=r[t/32|0]>>>t&1,l=s|o,h=((s|c)&a)+a^a|s|c;let u=o|~(h|a),d=a&h;u>>>31^n&&(i[t/32|0]^=1<<t),d>>>31^c&&(r[t/32|0]^=1<<t),u=u<<1|n,d=d<<1|c,a=d|~(l|u),o=u&l}for(let e=l;e<h;e++)w[t.charCodeAt(e)]=0}let l=0,h=-1;const u=32*c,d=Math.min(32,n-u)+u;for(let e=u;e<d;e++)w[t.charCodeAt(e)]|=1<<e;let g=n;for(let t=0;t<s;t++){const s=w[e.charCodeAt(t)],o=i[t/32|0]>>>t&1,a=r[t/32|0]>>>t&1,c=s|l,u=((s|a)&h)+h^h|s|a;let d=l|~(u|h),p=h&u;g+=d>>>n-1&1,g-=p>>>n-1&1,d>>>31^o&&(i[t/32|0]^=1<<t),p>>>31^a&&(r[t/32|0]^=1<<t),d=d<<1|o,p=p<<1|a,h=p|~(c|d),l=d&c}for(let e=u;e<d;e++)w[t.charCodeAt(e)]=0;return g})(t,e)},M=(t,e,s)=>{let n=0;const r=Object.keys(e).find((e=>{const r=((t,e,s)=>{const n=t.length<=e.length,r=t.length<=s;return n||r?Math.ceil(t.length/4):s})(t,e,s);if(Math.abs(t.length-e.length)>r)return!1;const i=x(t,e),o=i<=r;return o&&(n=i),o}));return r?{levenshteinDistance:n,levenshteinDistanceEntry:r}:{}};class S{constructor(t,e){this.options=t,this.defaultMatch=e}match(t){const e=t.password.split("").reverse().join("");return this.defaultMatch({...t,password:e}).map((e=>({...e,token:e.token.split("").reverse().join(""),reversed:!0,i:t.password.length-1-e.j,j:t.password.length-1-e.i})))}}class v{constructor({substr:t,limit:e,trieRoot:s}){this.buffer=[],this.finalPasswords=[],this.substr=t,this.limit=e,this.trieRoot=s}getAllPossibleSubsAtIndex(t){const e=[];let s=this.trieRoot;for(let n=t;n<this.substr.length;n+=1){const t=this.substr.charAt(n);if(s=s.getChild(t),!s)break;e.push(s)}return e}helper({onlyFullSub:t,isFullSub:e,index:s,subIndex:n,changes:r,lastSubLetter:i,consecutiveSubCount:o}){if(this.finalPasswords.length>=this.limit)return;if(s===this.substr.length)return void(t===e&&this.finalPasswords.push({password:this.buffer.join(""),changes:r,isFullSubstitution:t}));const a=[...this.getAllPossibleSubsAtIndex(s)];let c=!1;for(let l=s+a.length-1;l>=s;l-=1){const h=a[l-s],u=h.parents.join("");if(h.isTerminal()){if(i===u&&o>=3)continue;c=!0;const a=h.subs;for(const c of a){this.buffer.push(c);const a=r.concat({i:n,letter:c,substitution:u});if(this.helper({onlyFullSub:t,isFullSub:e,index:s+u.length,subIndex:n+c.length,changes:a,lastSubLetter:u,consecutiveSubCount:i===u?o+1:1}),this.buffer.pop(),this.finalPasswords.length>=this.limit)return}}}if(!t||!c){const a=this.substr.charAt(s);this.buffer.push(a),this.helper({onlyFullSub:t,isFullSub:e&&!c,index:s+1,subIndex:n+1,changes:r,lastSubLetter:i,consecutiveSubCount:o}),this.buffer.pop()}}getAll(){return this.helper({onlyFullSub:!0,isFullSub:!0,index:0,subIndex:0,changes:[],lastSubLetter:void 0,consecutiveSubCount:0}),this.helper({onlyFullSub:!1,isFullSub:!0,index:0,subIndex:0,changes:[],lastSubLetter:void 0,consecutiveSubCount:0}),this.finalPasswords}}class T{constructor(t,e){this.options=t,this.defaultMatch=e}isAlreadyIncluded(t,e){return t.some((t=>Object.entries(t).every((([t,s])=>"subs"===t||s===e[t]))))}match(t){const e=[],s=(n=t.password,r=this.options.l33tMaxSubstitutions,i=this.options.trieNodeRoot,new v({substr:n,limit:r,trieRoot:i}).getAll());var n,r,i;let o=!1;return s.forEach((s=>{if(o)return;this.defaultMatch({...t,password:s.password,useLevenshtein:s.isFullSubstitution}).forEach((n=>{o||(o=0===n.i&&n.j===t.password.length-1);const r=((t,e,s)=>{const n=t.changes.filter((t=>t.i<e)).reduce(((t,e)=>t-e.letter.length+e.substitution.length),e),r=t.changes.filter((t=>t.i>=e&&t.i<=s)),i=r.reduce(((t,e)=>t-e.letter.length+e.substitution.length),s-e+n),o=[],a=[];return r.forEach((t=>{o.findIndex((e=>e.letter===t.letter&&e.substitution===t.substitution))<0&&(o.push({letter:t.letter,substitution:t.substitution}),a.push(`${t.substitution} -> ${t.letter}`))})),{i:n,j:i,subs:o,subDisplay:a.join(", ")}})(s,n.i,n.j),i=t.password.slice(r.i,+r.j+1||9e9),a={...n,l33t:!0,token:i,...r},c=this.isAlreadyIncluded(e,a);i.toLowerCase()===n.matchedWord||c||e.push(a)}))})),e.filter((t=>t.token.length>1))}}class j{constructor(t){this.options=t,this.l33t=new T(t,this.defaultMatch),this.reverse=new S(t,this.defaultMatch)}match(t){const e=[...this.defaultMatch(t),...this.reverse.match(t),...this.l33t.match(t)];return s(e)}defaultMatch({password:t,userInputsOptions:e,useLevenshtein:s=!0}){const n=[],r=t.length,i=t.toLowerCase(),{rankedDictionaries:o,rankedDictionariesMaxWordSize:a}=((t,e,s)=>{const n={...t},r={...e};return s?(n.userInputs={...n.userInputs||{},...s.rankedDictionary},r.userInputs=Math.max(s.rankedDictionaryMaxWordSize,r.userInputs||0),{rankedDictionaries:n,rankedDictionariesMaxWordSize:r}):{rankedDictionaries:n,rankedDictionariesMaxWordSize:r}})(this.options.rankedDictionaries,this.options.rankedDictionariesMaxWordSize,e);return Object.keys(o).forEach((e=>{const c=o[e],l=a[e],h=Math.min(l,r);for(let o=0;o<r;o+=1){const a=Math.min(o+h,r);for(let l=o;l<a;l+=1){const a=i.slice(o,+l+1||9e9),h=a in c;let u={};const d=0===o&&l===r-1;this.options.useLevenshteinDistance&&d&&!h&&s&&(u=M(a,c,this.options.levenshteinThreshold));const g=0!==Object.keys(u).length;if(h||g){const s=c[g?u.levenshteinDistanceEntry:a];n.push({pattern:"dictionary",i:o,j:l,token:t.slice(o,+l+1||9e9),matchedWord:a,rank:s,dictionaryName:e,reversed:!1,l33t:!1,...u})}}}})),n}}class A{constructor(t){this.options=t}match({password:t}){const e=[];return Object.keys(m).forEach((s=>{const n=m[s];let r;for(n.lastIndex=0;r=n.exec(t);)if(r){const t=r[0];e.push({pattern:"regex",token:t,i:r.index,j:r.index+r[0].length-1,regexName:s,regexMatch:r})}})),s(e)}}var I={nCk(t,e){let s=t;if(e>s)return 0;if(0===e)return 1;let n=1;for(let t=1;t<=e;t+=1)n*=s,n/=t,s-=1;return n},log10:t=>0===t?0:Math.log(t)/Math.log(10),log2:t=>Math.log(t)/Math.log(2),factorial(t){let e=1;for(let s=2;s<=t;s+=1)e*=s;return e}};var C=t=>{const e=t.replace(g,"");if(e.match(h)||e.toLowerCase()===e)return 1;const s=[i,o,c],n=s.length;for(let t=0;t<n;t+=1){const n=s[t];if(e.match(n))return 2}return(t=>{const e=t.split(""),s=e.filter((t=>t.match(d))).length,n=e.filter((t=>t.match(u))).length;let r=0;const i=Math.min(s,n);for(let t=1;t<=i;t+=1)r+=I.nCk(s+n,t);return r})(e)};const E=(t,e)=>{let s=0,n=t.indexOf(e);for(;n>=0;)s+=1,n=t.indexOf(e,n+e.length);return s};var D=({l33t:t,subs:e,token:s})=>{if(!t)return 1;let n=1;return e.forEach((t=>{const{subbedCount:e,unsubbedCount:r}=(({sub:t,token:e})=>{const s=e.toLowerCase();return{subbedCount:E(s,t.substitution),unsubbedCount:E(s,t.letter)}})({sub:t,token:s});if(0===e||0===r)n*=2;else{const t=Math.min(r,e);let s=0;for(let n=1;n<=t;n+=1)s+=I.nCk(r+e,n);n*=s}})),n};const L=(t,{token:e,turns:s})=>{const n=Object.keys(t).length,r=(t=>{let e=0;return Object.keys(t).forEach((s=>{const n=t[s];e+=n.filter((t=>!!t)).length})),e/=Object.entries(t).length,e})(t);let i=0;const o=e.length;for(let t=2;t<=o;t+=1){const e=Math.min(s,t-1);for(let s=1;s<=e;s+=1)i+=I.nCk(t-1,s-1)*n*r**s}return i};const P={bruteforce:({token:t})=>{let e,s=10**t.length;return s===Number.POSITIVE_INFINITY&&(s=Number.MAX_VALUE),e=1===t.length?11:51,Math.max(s,e)},date:({year:t,separator:e})=>{let s=365*Math.max(Math.abs(t-f),20);return e&&(s*=4),s},dictionary:({rank:t,reversed:e,l33t:s,subs:n,token:r,dictionaryName:i})=>{const o=t,a=C(r),c=D({l33t:s,subs:n,token:r});let l;return l="diceware"===i?3888:o*a*c*(e?2:1),{baseGuesses:o,uppercaseVariations:a,l33tVariations:c,calculation:l}},regex:({regexName:t,regexMatch:e,token:s})=>{const n={alphaLower:26,alphaUpper:26,alpha:52,alphanumeric:62,digits:10,symbols:33};return t in n?n[t]**s.length:"recentYear"===t?Math.max(Math.abs(parseInt(e[0],10)-f),20):0},repeat:({baseGuesses:t,repeatCount:e})=>t*e,sequence:({token:t,ascending:e})=>{const s=t.charAt(0);let n=0;return n=["a","A","z","Z","0","1","9"].includes(s)?4:s.match(/\d/)?10:26,e||(n*=2),n*t.length},spatial:({graph:t,token:e,shiftedCount:s,turns:n},r)=>{let i=L(r.graphs[t],{token:e,turns:n});if(s){const t=e.length-s;if(0===s||0===t)i*=2;else{let e=0;for(let n=1;n<=Math.min(s,t);n+=1)e+=I.nCk(s+t,n);i*=e}}return Math.round(i)},separator:()=>k};var R=(t,e,s)=>{const n={};if("guesses"in e&&null!=e.guesses)return e;const r=((t,e)=>{let s=1;return t.token.length<e.length&&(s=1===t.token.length?10:50),s})(e,s),i=((t,e,s)=>P[e]?P[e](s,t):t.matchers[e]&&"scoring"in t.matchers[e]?t.matchers[e].scoring(s,t):0)(t,e.pattern,e);let o=0;"number"==typeof i?o=i:"dictionary"===e.pattern&&(o=i.calculation,n.baseGuesses=i.baseGuesses,n.uppercaseVariations=i.uppercaseVariations,n.l33tVariations=i.l33tVariations);const a=Math.max(o,r);return{...e,...n,guesses:a,guessesLog10:I.log10(a)}};const O={password:"",optimal:{},excludeAdditive:!1,separatorRegex:void 0,fillArray(t,e){const s=[];for(let n=0;n<t;n+=1){let t=[];"object"===e&&(t={}),s.push(t)}return s},makeBruteforceMatch(t,e){return{pattern:"bruteforce",token:this.password.slice(t,+e+1||9e9),i:t,j:e}},update(t,e,s){const n=e.j,r=R(t,e,this.password);let i=r.guesses;s>1&&(i*=this.optimal.pi[r.i-1][s-1]);let o=I.factorial(s)*i;this.excludeAdditive||(o+=1e4**(s-1));let a=!1;Object.keys(this.optimal.g[n]).forEach((t=>{const e=this.optimal.g[n][t];parseInt(t,10)<=s&&e<=o&&(a=!0)})),a||(this.optimal.g[n][s]=o,this.optimal.m[n][s]=r,this.optimal.pi[n][s]=i)},bruteforceUpdate(t,e){let s=this.makeBruteforceMatch(0,e);this.update(t,s,1);for(let n=1;n<=e;n+=1){s=this.makeBruteforceMatch(n,e);const r=this.optimal.m[n-1];Object.keys(r).forEach((e=>{"bruteforce"!==r[e].pattern&&this.update(t,s,parseInt(e,10)+1)}))}},unwind(t){const e=[];let s=t-1,n=0,r=Infinity;const i=this.optimal.g[s];for(i&&Object.keys(i).forEach((t=>{const e=i[t];e<r&&(n=parseInt(t,10),r=e)}));s>=0;){const t=this.optimal.m[s][n];e.unshift(t),s=t.i-1,n-=1}return e}};class F{constructor(t){this.options=t}mostGuessableMatchSequence(t,e,s=!1){O.password=t,O.excludeAdditive=s;const n=t.length;let r=O.fillArray(n,"array");e.forEach((t=>{r[t.j].push(t)})),r=r.map((t=>t.sort(((t,e)=>t.i-e.i)))),O.optimal={m:O.fillArray(n,"object"),pi:O.fillArray(n,"object"),g:O.fillArray(n,"object")};for(let t=0;t<n;t+=1)r[t].forEach((t=>{t.i>0?Object.keys(O.optimal.m[t.i-1]).forEach((e=>{O.update(this.options,t,parseInt(e,10)+1)})):O.update(this.options,t,1)})),O.bruteforceUpdate(this.options,t);const i=O.unwind(n),o=i.length,a=this.getGuesses(t,o);return{password:t,guesses:a,guessesLog10:I.log10(a),sequence:i}}getGuesses(t,e){const s=t.length;let n=0;return n=0===t.length?1:O.optimal.g[s-1][e],n}}class z{constructor(t){this.options=t,this.scoring=new F(t)}match({password:t,omniMatch:e}){const s=[];let n=0;for(;n<t.length;){const r=this.getGreedyMatch(t,n),i=this.getLazyMatch(t,n);if(null==r)break;const{match:o,baseToken:a}=this.setMatchToken(r,i);if(o){const t=o.index+o[0].length-1,r=this.getBaseGuesses(a,e);s.push(this.normalizeMatch(a,t,o,r)),n=t+1}}return s.some((t=>t instanceof Promise))?Promise.all(s):s}normalizeMatch(t,e,s,n){const r={pattern:"repeat",i:s.index,j:e,token:s[0],baseToken:t,baseGuesses:0,repeatCount:s[0].length/t.length};return n instanceof Promise?n.then((t=>({...r,baseGuesses:t}))):{...r,baseGuesses:n}}getGreedyMatch(t,e){const s=/(.+)\1+/g;return s.lastIndex=e,s.exec(t)}getLazyMatch(t,e){const s=/(.+?)\1+/g;return s.lastIndex=e,s.exec(t)}setMatchToken(t,e){const s=/^(.+?)\1+$/;let n,r="";if(e&&t[0].length>e[0].length){n=t;const e=s.exec(n[0]);e&&(r=e[1])}else n=e,n&&(r=n[1]);return{match:n,baseToken:r}}getBaseGuesses(t,e){const s=e.match(t);if(s instanceof Promise)return s.then((e=>this.scoring.mostGuessableMatchSequence(t,e).guesses));return this.scoring.mostGuessableMatchSequence(t,s).guesses}}class N{constructor(t){this.options=t,this.MAX_DELTA=5}match({password:t}){const e=[];if(1===t.length)return[];let s=0,n=null;const r=t.length;for(let i=1;i<r;i+=1){const r=t.charCodeAt(i)-t.charCodeAt(i-1);if(null==n&&(n=r),r!==n){const o=i-1;this.update({i:s,j:o,delta:n,password:t,result:e}),s=o,n=r}}return this.update({i:s,j:r-1,delta:n,password:t,result:e}),e}update({i:t,j:e,delta:s,password:n,result:r}){if(e-t>1||1===Math.abs(s)){const i=Math.abs(s);if(i>0&&i<=this.MAX_DELTA){const i=n.slice(t,+e+1||9e9),{sequenceName:o,sequenceSpace:a}=this.getSequence(i);return r.push({pattern:"sequence",i:t,j:e,token:n.slice(t,+e+1||9e9),sequenceName:o,sequenceSpace:a,ascending:s>0})}}return null}getSequence(t){let e="unicode",s=26;return l.test(t)?(e="lower",s=26):a.test(t)?(e="upper",s=26):p.test(t)&&(e="digits",s=10),{sequenceName:e,sequenceSpace:s}}}class W{constructor(t){this.options=t,this.SHIFTED_RX=/[~!@#$%^&*()_+QWERTYUIOP{}|ASDFGHJKL:"ZXCVBNM<>?]/}match({password:t}){const n=[];return Object.keys(this.options.graphs).forEach((s=>{const r=this.options.graphs[s];e(n,this.helper(t,r,s))})),s(n)}checkIfShifted(t,e,s){return!t.includes("keypad")&&this.SHIFTED_RX.test(e.charAt(s))?1:0}helper(t,e,s){let n;const r=[];let i=0;const o=t.length;for(;i<o-1;){let a=i+1,c=null,l=0;for(n=this.checkIfShifted(s,t,i);;){const h=e[t.charAt(a-1)]||[];let u=!1,d=-1,g=-1;if(a<o){const e=t.charAt(a),s=h.length;for(let t=0;t<s;t+=1){const s=h[t];if(g+=1,s){const t=s.indexOf(e);if(-1!==t){u=!0,d=g,1===t&&(n+=1),c!==d&&(l+=1,c=d);break}}}}if(!u){a-i>2&&r.push({pattern:"spatial",i:i,j:a-1,token:t.slice(i,a),graph:s,turns:l,shiftedCount:n}),i=a;break}a+=1}}return r}}const q=new RegExp(`[${b.join("")}]`);class Y{constructor(t){this.options=t}static getMostUsedSeparatorChar(t){const e=[...t.split("").filter((t=>q.test(t))).reduce(((t,e)=>{const s=t.get(e);return s?t.set(e,s+1):t.set(e,1),t}),new Map).entries()].sort((([t,e],[s,n])=>n-e));if(!e.length)return;const s=e[0];return s[1]<2?void 0:s[0]}static getSeparatorRegex(t){return new RegExp(`([^${t}\n])(${t})(?!${t})`,"g")}match({password:t}){const e=[];if(0===t.length)return e;const s=Y.getMostUsedSeparatorChar(t);if(void 0===s)return e;const n=Y.getSeparatorRegex(s);for(const r of t.matchAll(n)){if(void 0===r.index)continue;const t=r.index+1;e.push({pattern:"separator",token:s,i:t,j:t})}return e}}class V{constructor(t){this.options=t,this.matchers={date:y,dictionary:j,regex:A,repeat:z,sequence:N,spatial:W,separator:Y}}matcherFactory(t){if(!this.matchers[t]&&!this.options.matchers[t])return null;return new(this.matchers[t]?this.matchers[t]:this.options.matchers[t].Matching)(this.options)}processResult(t,s,n){n instanceof Promise?(n.then((s=>{e(t,s)})),s.push(n)):e(t,n)}handlePromises(t,e){return e.length>0?new Promise(((n,r)=>{Promise.all(e).then((()=>{n(s(t))})).catch((t=>{r(t)}))})):s(t)}match(t,e){const s=[],n=[];return[...Object.keys(this.matchers),...Object.keys(this.options.matchers)].forEach((r=>{const i=this.matcherFactory(r);if(!i)return;const o=i.match({password:t,omniMatch:this,userInputsOptions:e});this.processResult(s,n,o)})),this.handlePromises(s,n)}}const $=2678400,G=32140800,X={second:1,minute:60,hour:3600,day:86400,month:$,year:G,century:100*G},H={scoring:{0:1e3,1:1e6,2:1e8,3:1e10},attackTime:{onlineThrottlingXPerHour:100,onlineNoThrottlingXPerSecond:10,offlineSlowHashingXPerSecond:1e4,offlineFastHashingXPerSecond:1e10}};class U{constructor(t){this.options=t}estimateAttackTimes(t){const e=this.calculateCrackTimesSeconds(t);return{crackTimes:Object.keys(e).reduce(((t,s)=>{const n=s,r=e[n],{base:i,displayStr:o}=this.displayTime(r);return t[n]={base:i,seconds:r,display:this.translate(o,i)},t}),{}),score:this.guessesToScore(t)}}calculateCrackTimesSeconds(t){const e=this.options.timeEstimationValues.attackTime;return{onlineThrottlingXPerHour:t/(e.onlineThrottlingXPerHour/3600),onlineNoThrottlingXPerSecond:t/e.onlineNoThrottlingXPerSecond,offlineSlowHashingXPerSecond:t/e.offlineSlowHashingXPerSecond,offlineFastHashingXPerSecond:t/e.offlineFastHashingXPerSecond}}guessesToScore(t){const e=this.options.timeEstimationValues.scoring;return t<e[0]+5?0:t<e[1]+5?1:t<e[2]+5?2:t<e[3]+5?3:4}displayTime(t){let e="centuries",s=null;const n=Object.keys(X),r=n.findIndex((e=>t<X[e]));return r>-1&&(e=n[r-1],0!==r?s=Math.round(t/X[e]):e="ltSecond"),{base:s,displayStr:e}}translate(t,e){let s=t;null!==e&&1!==e&&(s+="s");const{timeEstimation:n}=this.options.translations;return n[s].replace("{base}",`${e}`)}}var B=()=>null,Z=t=>({warning:t.translations.warnings.dates,suggestions:[t.translations.suggestions.dates]});var _=(t,e,s)=>{const n=((t,e,s)=>{const n=e.dictionaryName,r=n.toLowerCase().includes("lastnames")||n.toLowerCase().includes("firstnames")||n.toLowerCase().includes("names");return n.includes("passwords")?((t,e,s)=>{let n=null;return!s||e.l33t||e.reversed?e.guessesLog10<=4&&(n=t.translations.warnings.similarToCommon):n=e.rank<=10?t.translations.warnings.topTen:e.rank<=100?t.translations.warnings.topHundred:t.translations.warnings.common,n})(t,e,s):n.includes("wikipedia")?((t,e,s)=>{let n=null;return s&&(n=t.translations.warnings.wordByItself),n})(t,0,s):r?((t,e,s)=>s?t.translations.warnings.namesByThemselves:t.translations.warnings.commonNames)(t,0,s):n.includes("userInputs")?t.translations.warnings.userInputs:null})(t,e,s),r=[],o=e.token;return o.match(i)?r.push(t.translations.suggestions.capitalization):o.match(c)&&o.toLowerCase()!==o&&r.push(t.translations.suggestions.allUppercase),e.reversed&&e.token.length>=4&&r.push(t.translations.suggestions.reverseWords),e.l33t&&r.push(t.translations.suggestions.l33t),{warning:n,suggestions:r}},K=(t,e)=>"recentYear"===e.regexName?{warning:t.translations.warnings.recentYears,suggestions:[t.translations.suggestions.recentYears,t.translations.suggestions.associatedYears]}:{warning:null,suggestions:[]},J=(t,e)=>{let s=t.translations.warnings.extendedRepeat;return 1===e.baseToken.length&&(s=t.translations.warnings.simpleRepeat),{warning:s,suggestions:[t.translations.suggestions.repeated]}},Q=t=>({warning:t.translations.warnings.sequences,suggestions:[t.translations.suggestions.sequences]}),tt=(t,e)=>{let s=t.translations.warnings.keyPattern;return 1===e.turns&&(s=t.translations.warnings.straightRow),{warning:s,suggestions:[t.translations.suggestions.longerKeyboardPattern]}},et=()=>null;const st={warning:null,suggestions:[]};class nt{constructor(t){this.options=t,this.matchers={bruteforce:B,date:Z,dictionary:_,regex:K,repeat:J,sequence:Q,spatial:tt,separator:et},this.defaultFeedback={warning:null,suggestions:[]},this.setDefaultSuggestions()}setDefaultSuggestions(){this.defaultFeedback.suggestions.push(this.options.translations.suggestions.useWords,this.options.translations.suggestions.noNeed)}getFeedback(t,e){if(0===e.length)return this.defaultFeedback;if(t>2)return st;const s=this.options.translations.suggestions.anotherWord,n=this.getLongestMatch(e);let r=this.getMatchFeedback(n,1===e.length);return null!=r?r.suggestions.unshift(s):r={warning:null,suggestions:[s]},r}getLongestMatch(t){let e=t[0];return t.slice(1).forEach((t=>{t.token.length>e.token.length&&(e=t)})),e}getMatchFeedback(t,e){return this.matchers[t.pattern]?this.matchers[t.pattern](this.options,t,e):this.options.matchers[t.pattern]&&"feedback"in this.options.matchers[t.pattern]?this.options.matchers[t.pattern].feedback(this.options,t,e):st}}var rt={a:["4","@"],b:["8"],c:["(","{","[","<"],d:["6","|)"],e:["3"],f:["#"],g:["6","9","&"],h:["#","|-|"],i:["1","!","|"],k:["<","|<"],l:["!","1","|","7"],m:["^^","nn","2n","/\\\\/\\\\"],n:["//"],o:["0","()"],q:["9"],u:["|_|"],s:["$","5"],t:["+","7"],v:["<",">","/"],w:["^/","uu","vv","2u","2v","\\\\/\\\\/"],x:["%","><"],z:["2"]},it={warnings:{straightRow:"straightRow",keyPattern:"keyPattern",simpleRepeat:"simpleRepeat",extendedRepeat:"extendedRepeat",sequences:"sequences",recentYears:"recentYears",dates:"dates",topTen:"topTen",topHundred:"topHundred",common:"common",similarToCommon:"similarToCommon",wordByItself:"wordByItself",namesByThemselves:"namesByThemselves",commonNames:"commonNames",userInputs:"userInputs",pwned:"pwned"},suggestions:{l33t:"l33t",reverseWords:"reverseWords",allUppercase:"allUppercase",capitalization:"capitalization",dates:"dates",recentYears:"recentYears",associatedYears:"associatedYears",sequences:"sequences",repeated:"repeated",longerKeyboardPattern:"longerKeyboardPattern",anotherWord:"anotherWord",useWords:"useWords",noNeed:"noNeed",pwned:"pwned"},timeEstimation:{ltSecond:"ltSecond",second:"second",seconds:"seconds",minute:"minute",minutes:"minutes",hour:"hour",hours:"hours",day:"day",days:"days",month:"month",months:"months",year:"year",years:"years",centuries:"centuries"}};class ot{constructor(t=[]){this.parents=t,this.children=new Map}addSub(t,...e){const s=t.charAt(0);this.children.has(s)||this.children.set(s,new ot([...this.parents,s]));let n=this.children.get(s);for(let e=1;e<t.length;e+=1){const s=t.charAt(e);n.hasChild(s)||n.addChild(s),n=n.getChild(s)}return n.subs=(n.subs||[]).concat(e),this}getChild(t){return this.children.get(t)}isTerminal(){return!!this.subs}addChild(t){this.hasChild(t)||this.children.set(t,new ot([...this.parents,t]))}hasChild(t){return this.children.has(t)}}var at=(t,e)=>(Object.entries(t).forEach((([t,s])=>{s.forEach((s=>{e.addSub(s,t)}))})),e);class ct{constructor(t={},e={}){this.matchers={},this.l33tTable=rt,this.trieNodeRoot=at(rt,new ot),this.dictionary={userInputs:[]},this.rankedDictionaries={},this.rankedDictionariesMaxWordSize={},this.translations=it,this.graphs={},this.useLevenshteinDistance=!1,this.levenshteinThreshold=2,this.l33tMaxSubstitutions=100,this.maxLength=256,this.timeEstimationValues={scoring:{...H.scoring},attackTime:{...H.attackTime}},this.setOptions(t),Object.entries(e).forEach((([t,e])=>{this.addMatcher(t,e)}))}setOptions(t={}){var e;t.l33tTable&&(this.l33tTable=t.l33tTable,this.trieNodeRoot=at(t.l33tTable,new ot)),t.dictionary&&(this.dictionary=t.dictionary,this.setRankedDictionaries()),t.translations&&this.setTranslations(t.translations),t.graphs&&(this.graphs=t.graphs),void 0!==t.useLevenshteinDistance&&(this.useLevenshteinDistance=t.useLevenshteinDistance),void 0!==t.levenshteinThreshold&&(this.levenshteinThreshold=t.levenshteinThreshold),void 0!==t.l33tMaxSubstitutions&&(this.l33tMaxSubstitutions=t.l33tMaxSubstitutions),void 0!==t.maxLength&&(this.maxLength=t.maxLength),void 0!==t.timeEstimationValues&&(e=t.timeEstimationValues,Object.entries(e).forEach((([t,e])=>{Object.entries(e).forEach((([e,s])=>{if(s<H[t][e])throw new Error("Time estimation values are not to be allowed to be less than default")}))})),this.timeEstimationValues={scoring:{...t.timeEstimationValues.scoring},attackTime:{...t.timeEstimationValues.attackTime}})}setTranslations(t){if(!this.checkCustomTranslations(t))throw new Error("Invalid translations object fallback to keys");this.translations=t}checkCustomTranslations(t){let e=!0;return Object.keys(it).forEach((s=>{if(s in t){const n=s;Object.keys(it[n]).forEach((s=>{s in t[n]||(e=!1)}))}else e=!1})),e}setRankedDictionaries(){const t={},e={};Object.keys(this.dictionary).forEach((s=>{t[s]=n(this.dictionary[s]),e[s]=this.getRankedDictionariesMaxWordSize(this.dictionary[s])})),this.rankedDictionaries=t,this.rankedDictionariesMaxWordSize=e}getRankedDictionariesMaxWordSize(t){const e=t.map((t=>"string"!=typeof t?t.toString().length:t.length));return 0===e.length?0:e.reduce(((t,e)=>Math.max(t,e)),-1/0)}buildSanitizedRankedDictionary(t){const e=[];return t.forEach((t=>{const s=typeof t;"string"!==s&&"number"!==s&&"boolean"!==s||e.push(t.toString().toLowerCase())})),n(e)}getUserInputsOptions(t){let e={},s=0;return t&&(e=this.buildSanitizedRankedDictionary(t),s=this.getRankedDictionariesMaxWordSize(t)),{rankedDictionary:e,rankedDictionaryMaxWordSize:s}}addMatcher(t,e){this.matchers[t]?console.info(`Matcher ${t} already exists`):this.matchers[t]=e}}const lt=()=>(new Date).getTime();return t.ZxcvbnFactory=class{constructor(t={},e={}){this.options=new ct(t,e),this.scoring=new F(this.options)}estimateAttackTimes(t){return new U(this.options).estimateAttackTimes(t)}getFeedback(t,e){return new nt(this.options).getFeedback(t,e)}createReturnValue(t,e,s){const n=this.scoring.mostGuessableMatchSequence(e,t),r=lt()-s,i=this.estimateAttackTimes(n.guesses);return{calcTime:r,...n,...i,feedback:this.getFeedback(i.score,n.sequence)}}main(t,e){const s=this.options.getUserInputsOptions(e);return new V(this.options).match(t,s)}check(t,e){const s=t.substring(0,this.options.maxLength),n=lt(),r=this.main(s,e);if(r instanceof Promise)throw new Error("You are using a Promised matcher, please use `zxcvbnAsync` for it.");return this.createReturnValue(r,s,n)}async checkAsync(t,e){const s=t.substring(0,this.options.maxLength),n=lt(),r=await this.main(s,e);return this.createReturnValue(r,s,n)}},t.debounce=(t,e,s)=>{let n;return function(...r){const i=this,o=s&&!n;if(void 0!==n&&clearTimeout(n),n=setTimeout((()=>{n=void 0,s||t.apply(i,r)}),e),o)return t.apply(i,r)}},t}({}); | ||
//# sourceMappingURL=zxcvbn-ts.min.js.map |
{ | ||
"name": "@zxcvbn-ts/core", | ||
"version": "3.0.4", | ||
"version": "4.0.0-beta.0", | ||
"main": "dist/index.js", | ||
@@ -40,3 +40,3 @@ "module": "dist/index.esm.js", | ||
], | ||
"gitHead": "ecb44e737022ff694a063d3e5e5caec94cc2d4fa" | ||
"gitHead": "b7f8950e4a4b03716c55203815d7b58bff93b369" | ||
} |
@@ -21,7 +21,6 @@ # zxcvbn-ts | ||
```js | ||
import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core' | ||
import { ZxcvbnFactory } from '@zxcvbn-ts/core' | ||
import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common' | ||
import * as zxcvbnEnPackage from '@zxcvbn-ts/language-en' | ||
const password = 'somePassword' | ||
const options = { | ||
@@ -35,5 +34,6 @@ dictionary: { | ||
} | ||
zxcvbnOptions.setOptions(options) | ||
const zxcvbn = new ZxcvbnFactory(options) | ||
const password = 'somePassword' | ||
zxcvbn(password) | ||
``` |
@@ -1,2 +0,2 @@ | ||
import { zxcvbnOptions } from './Options' | ||
import Options from './Options' | ||
import { DefaultFeedbackFunction, FeedbackType, MatchEstimated } from './types' | ||
@@ -26,3 +26,3 @@ import bruteforceMatcher from './matcher/bruteforce/feedback' | ||
class Feedback { | ||
readonly matchers: Matchers = { | ||
private readonly matchers: Matchers = { | ||
bruteforce: bruteforceMatcher, | ||
@@ -38,3 +38,3 @@ date: dateMatcher, | ||
defaultFeedback: FeedbackType = { | ||
private defaultFeedback: FeedbackType = { | ||
warning: null, | ||
@@ -44,14 +44,14 @@ suggestions: [], | ||
constructor() { | ||
constructor(private options: Options) { | ||
this.setDefaultSuggestions() | ||
} | ||
setDefaultSuggestions() { | ||
private setDefaultSuggestions() { | ||
this.defaultFeedback.suggestions.push( | ||
zxcvbnOptions.translations.suggestions.useWords, | ||
zxcvbnOptions.translations.suggestions.noNeed, | ||
this.options.translations.suggestions.useWords, | ||
this.options.translations.suggestions.noNeed, | ||
) | ||
} | ||
getFeedback(score: number, sequence: MatchEstimated[]) { | ||
public getFeedback(score: number, sequence: MatchEstimated[]) { | ||
if (sequence.length === 0) { | ||
@@ -63,3 +63,3 @@ return this.defaultFeedback | ||
} | ||
const extraFeedback = zxcvbnOptions.translations.suggestions.anotherWord | ||
const extraFeedback = this.options.translations.suggestions.anotherWord | ||
const longestMatch = this.getLongestMatch(sequence) | ||
@@ -78,3 +78,3 @@ let feedback = this.getMatchFeedback(longestMatch, sequence.length === 1) | ||
getLongestMatch(sequence: MatchEstimated[]) { | ||
private getLongestMatch(sequence: MatchEstimated[]) { | ||
let longestMatch = sequence[0] | ||
@@ -90,11 +90,15 @@ const slicedSequence = sequence.slice(1) | ||
getMatchFeedback(match: MatchEstimated, isSoleMatch: boolean) { | ||
private getMatchFeedback(match: MatchEstimated, isSoleMatch: boolean) { | ||
if (this.matchers[match.pattern]) { | ||
return this.matchers[match.pattern](match, isSoleMatch) | ||
return this.matchers[match.pattern](this.options, match, isSoleMatch) | ||
} | ||
if ( | ||
zxcvbnOptions.matchers[match.pattern] && | ||
'feedback' in zxcvbnOptions.matchers[match.pattern] | ||
this.options.matchers[match.pattern] && | ||
'feedback' in this.options.matchers[match.pattern] | ||
) { | ||
return zxcvbnOptions.matchers[match.pattern].feedback(match, isSoleMatch) | ||
return this.options.matchers[match.pattern].feedback( | ||
this.options, | ||
match, | ||
isSoleMatch, | ||
) | ||
} | ||
@@ -101,0 +105,0 @@ return defaultFeedback |
120
src/index.ts
import Matching from './Matching' | ||
import scoring from './scoring' | ||
import TimeEstimates from './TimeEstimates' | ||
import { TimeEstimates } from './TimeEstimates' | ||
import Feedback from './Feedback' | ||
import { zxcvbnOptions, Options } from './Options' | ||
import debounce from './debounce' | ||
import { MatchExtended, ZxcvbnResult } from './types' | ||
import Options from './Options' | ||
import debounce from './utils/debounce' | ||
import { | ||
Matcher, | ||
MatchEstimated, | ||
MatchExtended, | ||
OptionsType, | ||
ZxcvbnResult, | ||
} from './types' | ||
import Scoring from './scoring' | ||
const time = () => new Date().getTime() | ||
const createReturnValue = ( | ||
resolvedMatches: MatchExtended[], | ||
password: string, | ||
start: number, | ||
): ZxcvbnResult => { | ||
const feedback = new Feedback() | ||
const timeEstimates = new TimeEstimates() | ||
const matchSequence = scoring.mostGuessableMatchSequence( | ||
password, | ||
resolvedMatches, | ||
) | ||
const calcTime = time() - start | ||
const attackTimes = timeEstimates.estimateAttackTimes(matchSequence.guesses) | ||
class ZxcvbnFactory { | ||
private options: Options | ||
return { | ||
calcTime, | ||
...matchSequence, | ||
...attackTimes, | ||
feedback: feedback.getFeedback(attackTimes.score, matchSequence.sequence), | ||
private scoring: Scoring | ||
constructor( | ||
options: OptionsType = {}, | ||
customMatchers: Record<string, Matcher> = {}, | ||
) { | ||
this.options = new Options(options, customMatchers) | ||
this.scoring = new Scoring(this.options) | ||
} | ||
} | ||
const main = (password: string, userInputs?: (string | number)[]) => { | ||
if (userInputs) { | ||
zxcvbnOptions.extendUserInputsDictionary(userInputs) | ||
private estimateAttackTimes(guesses: number) { | ||
const timeEstimates = new TimeEstimates(this.options) | ||
return timeEstimates.estimateAttackTimes(guesses) | ||
} | ||
const matching = new Matching() | ||
private getFeedback(score: number, sequence: MatchEstimated[]) { | ||
const feedback = new Feedback(this.options) | ||
return feedback.getFeedback(score, sequence) | ||
} | ||
return matching.match(password) | ||
} | ||
private createReturnValue( | ||
resolvedMatches: MatchExtended[], | ||
password: string, | ||
start: number, | ||
): ZxcvbnResult { | ||
const matchSequence = this.scoring.mostGuessableMatchSequence( | ||
password, | ||
resolvedMatches, | ||
) | ||
const calcTime = time() - start | ||
const attackTimes = this.estimateAttackTimes(matchSequence.guesses) | ||
export const zxcvbn = (password: string, userInputs?: (string | number)[]) => { | ||
const start = time() | ||
const matches = main(password, userInputs) | ||
return { | ||
calcTime, | ||
...matchSequence, | ||
...attackTimes, | ||
feedback: this.getFeedback(attackTimes.score, matchSequence.sequence), | ||
} | ||
} | ||
if (matches instanceof Promise) { | ||
throw new Error( | ||
'You are using a Promised matcher, please use `zxcvbnAsync` for it.', | ||
) | ||
private main(password: string, userInputs?: (string | number)[]) { | ||
const userInputsOptions = this.options.getUserInputsOptions(userInputs) | ||
const matching = new Matching(this.options) | ||
return matching.match(password, userInputsOptions) | ||
} | ||
return createReturnValue(matches, password, start) | ||
} | ||
export const zxcvbnAsync = async ( | ||
password: string, | ||
userInputs?: (string | number)[], | ||
): Promise<ZxcvbnResult> => { | ||
const usedPassword = password.substring(0, zxcvbnOptions.maxLength) | ||
const start = time() | ||
const matches = await main(usedPassword, userInputs) | ||
public check(password: string, userInputs?: (string | number)[]) { | ||
const reducedPassword = password.substring(0, this.options.maxLength) | ||
const start = time() | ||
const matches = this.main(reducedPassword, userInputs) | ||
return createReturnValue(matches, usedPassword, start) | ||
if (matches instanceof Promise) { | ||
throw new Error( | ||
'You are using a Promised matcher, please use `zxcvbnAsync` for it.', | ||
) | ||
} | ||
return this.createReturnValue(matches, reducedPassword, start) | ||
} | ||
public async checkAsync(password: string, userInputs?: (string | number)[]) { | ||
const reducedPassword = password.substring(0, this.options.maxLength) | ||
const start = time() | ||
const matches = await this.main(reducedPassword, userInputs) | ||
return this.createReturnValue(matches, reducedPassword, start) | ||
} | ||
} | ||
export * from './types' | ||
export { zxcvbnOptions, Options, debounce } | ||
export { ZxcvbnFactory, debounce } |
@@ -1,8 +0,8 @@ | ||
import { zxcvbnOptions } from '../../Options' | ||
import Options from '../../Options' | ||
export default () => { | ||
export default (options: Options) => { | ||
return { | ||
warning: zxcvbnOptions.translations.warnings.dates, | ||
suggestions: [zxcvbnOptions.translations.suggestions.dates], | ||
warning: options.translations.warnings.dates, | ||
suggestions: [options.translations.suggestions.dates], | ||
} | ||
} |
@@ -7,8 +7,7 @@ import { | ||
} from '../../data/const' | ||
import { sorted } from '../../helper' | ||
import { DateMatch } from '../../types' | ||
import { sorted } from '../../utils/helper' | ||
import { DateMatch, MatchOptions } from '../../types' | ||
import Options from '../../Options' | ||
interface DateMatchOptions { | ||
password: string | ||
} | ||
type DateMatchOptions = Pick<MatchOptions, 'password'> | ||
@@ -21,2 +20,4 @@ /* | ||
class MatchDate { | ||
constructor(private options: Options) {} | ||
/* | ||
@@ -23,0 +24,0 @@ * a "date" is recognized as: |
@@ -1,2 +0,2 @@ | ||
import { zxcvbnOptions } from '../../Options' | ||
import Options from '../../Options' | ||
import { MatchEstimated } from '../../types' | ||
@@ -6,2 +6,3 @@ import { ALL_UPPER_INVERTED, START_UPPER } from '../../data/const' | ||
const getDictionaryWarningPassword = ( | ||
options: Options, | ||
match: MatchEstimated, | ||
@@ -13,10 +14,10 @@ isSoleMatch?: boolean, | ||
if (match.rank <= 10) { | ||
warning = zxcvbnOptions.translations.warnings.topTen | ||
warning = options.translations.warnings.topTen | ||
} else if (match.rank <= 100) { | ||
warning = zxcvbnOptions.translations.warnings.topHundred | ||
warning = options.translations.warnings.topHundred | ||
} else { | ||
warning = zxcvbnOptions.translations.warnings.common | ||
warning = options.translations.warnings.common | ||
} | ||
} else if (match.guessesLog10 <= 4) { | ||
warning = zxcvbnOptions.translations.warnings.similarToCommon | ||
warning = options.translations.warnings.similarToCommon | ||
} | ||
@@ -27,2 +28,3 @@ return warning | ||
const getDictionaryWarningWikipedia = ( | ||
options: Options, | ||
match: MatchEstimated, | ||
@@ -33,3 +35,3 @@ isSoleMatch?: boolean, | ||
if (isSoleMatch) { | ||
warning = zxcvbnOptions.translations.warnings.wordByItself | ||
warning = options.translations.warnings.wordByItself | ||
} | ||
@@ -40,2 +42,3 @@ return warning | ||
const getDictionaryWarningNames = ( | ||
options: Options, | ||
match: MatchEstimated, | ||
@@ -45,26 +48,40 @@ isSoleMatch?: boolean, | ||
if (isSoleMatch) { | ||
return zxcvbnOptions.translations.warnings.namesByThemselves | ||
return options.translations.warnings.namesByThemselves | ||
} | ||
return zxcvbnOptions.translations.warnings.commonNames | ||
return options.translations.warnings.commonNames | ||
} | ||
const getDictionaryWarning = (match: MatchEstimated, isSoleMatch?: boolean) => { | ||
let warning: string | null = null | ||
const getDictionaryWarning = ( | ||
options: Options, | ||
match: MatchEstimated, | ||
isSoleMatch?: boolean, | ||
) => { | ||
const dictName = match.dictionaryName | ||
const isAName = | ||
dictName === 'lastnames' || dictName.toLowerCase().includes('firstnames') | ||
if (dictName === 'passwords') { | ||
warning = getDictionaryWarningPassword(match, isSoleMatch) | ||
} else if (dictName.includes('wikipedia')) { | ||
warning = getDictionaryWarningWikipedia(match, isSoleMatch) | ||
} else if (isAName) { | ||
warning = getDictionaryWarningNames(match, isSoleMatch) | ||
} else if (dictName === 'userInputs') { | ||
warning = zxcvbnOptions.translations.warnings.userInputs | ||
dictName.toLowerCase().includes('lastnames') || | ||
dictName.toLowerCase().includes('firstnames') || | ||
dictName.toLowerCase().includes('names') | ||
if (dictName.includes('passwords')) { | ||
return getDictionaryWarningPassword(options, match, isSoleMatch) | ||
} | ||
return warning | ||
if (dictName.includes('wikipedia')) { | ||
return getDictionaryWarningWikipedia(options, match, isSoleMatch) | ||
} | ||
if (isAName) { | ||
return getDictionaryWarningNames(options, match, isSoleMatch) | ||
} | ||
if (dictName.includes('userInputs')) { | ||
return options.translations.warnings.userInputs | ||
} | ||
return null | ||
} | ||
export default (match: MatchEstimated, isSoleMatch?: boolean) => { | ||
const warning = getDictionaryWarning(match, isSoleMatch) | ||
export default ( | ||
options: Options, | ||
match: MatchEstimated, | ||
isSoleMatch?: boolean, | ||
) => { | ||
const warning = getDictionaryWarning(options, match, isSoleMatch) | ||
const suggestions: string[] = [] | ||
@@ -74,11 +91,11 @@ const word = match.token | ||
if (word.match(START_UPPER)) { | ||
suggestions.push(zxcvbnOptions.translations.suggestions.capitalization) | ||
suggestions.push(options.translations.suggestions.capitalization) | ||
} else if (word.match(ALL_UPPER_INVERTED) && word.toLowerCase() !== word) { | ||
suggestions.push(zxcvbnOptions.translations.suggestions.allUppercase) | ||
suggestions.push(options.translations.suggestions.allUppercase) | ||
} | ||
if (match.reversed && match.token.length >= 4) { | ||
suggestions.push(zxcvbnOptions.translations.suggestions.reverseWords) | ||
suggestions.push(options.translations.suggestions.reverseWords) | ||
} | ||
if (match.l33t) { | ||
suggestions.push(zxcvbnOptions.translations.suggestions.l33t) | ||
suggestions.push(options.translations.suggestions.l33t) | ||
} | ||
@@ -85,0 +102,0 @@ return { |
import findLevenshteinDistance, { | ||
FindLevenshteinDistanceResult, | ||
} from '../../levenshtein' | ||
import { sorted } from '../../helper' | ||
import { zxcvbnOptions } from '../../Options' | ||
} from '../../utils/levenshtein' | ||
import { sorted } from '../../utils/helper' | ||
import Options from '../../Options' | ||
import { DictionaryNames, DictionaryMatch, L33tMatch } from '../../types' | ||
@@ -10,2 +10,3 @@ import Reverse from './variants/matching/reverse' | ||
import { DictionaryMatchOptions } from './types' | ||
import mergeUserInputDictionary from '../../utils/mergeUserInputDictionary' | ||
@@ -17,14 +18,12 @@ class MatchDictionary { | ||
constructor() { | ||
this.l33t = new L33t(this.defaultMatch) | ||
this.reverse = new Reverse(this.defaultMatch) | ||
constructor(private options: Options) { | ||
this.l33t = new L33t(options, this.defaultMatch) | ||
this.reverse = new Reverse(options, this.defaultMatch) | ||
} | ||
match({ password }: DictionaryMatchOptions) { | ||
match(matchOptions: DictionaryMatchOptions) { | ||
const matches = [ | ||
...(this.defaultMatch({ | ||
password, | ||
}) as DictionaryMatch[]), | ||
...(this.reverse.match({ password }) as DictionaryMatch[]), | ||
...(this.l33t.match({ password }) as L33tMatch[]), | ||
...(this.defaultMatch(matchOptions) as DictionaryMatch[]), | ||
...(this.reverse.match(matchOptions) as DictionaryMatch[]), | ||
...(this.l33t.match(matchOptions) as L33tMatch[]), | ||
] | ||
@@ -34,3 +33,7 @@ return sorted(matches) | ||
defaultMatch({ password, useLevenshtein = true }: DictionaryMatchOptions) { | ||
defaultMatch({ | ||
password, | ||
userInputsOptions, | ||
useLevenshtein = true, | ||
}: DictionaryMatchOptions) { | ||
const matches: DictionaryMatch[] = [] | ||
@@ -40,8 +43,13 @@ const passwordLength = password.length | ||
const { rankedDictionaries, rankedDictionariesMaxWordSize } = | ||
mergeUserInputDictionary( | ||
this.options.rankedDictionaries, | ||
this.options.rankedDictionariesMaxWordSize, | ||
userInputsOptions, | ||
) | ||
// eslint-disable-next-line complexity,max-statements | ||
Object.keys(zxcvbnOptions.rankedDictionaries).forEach((dictionaryName) => { | ||
const rankedDict = | ||
zxcvbnOptions.rankedDictionaries[dictionaryName as DictionaryNames] | ||
Object.keys(rankedDictionaries).forEach((dictionaryName) => { | ||
const rankedDict = rankedDictionaries[dictionaryName as DictionaryNames] | ||
const longestDictionaryWordSize = | ||
zxcvbnOptions.rankedDictionariesMaxWordSize[dictionaryName] | ||
rankedDictionariesMaxWordSize[dictionaryName] | ||
const searchWidth = Math.min(longestDictionaryWordSize, passwordLength) | ||
@@ -59,3 +67,3 @@ for (let i = 0; i < passwordLength; i += 1) { | ||
if ( | ||
zxcvbnOptions.useLevenshteinDistance && | ||
this.options.useLevenshteinDistance && | ||
isFullPassword && | ||
@@ -68,3 +76,3 @@ !isInDictionary && | ||
rankedDict, | ||
zxcvbnOptions.levenshteinThreshold, | ||
this.options.levenshteinThreshold, | ||
) | ||
@@ -71,0 +79,0 @@ } |
@@ -1,10 +0,14 @@ | ||
import { DictionaryMatch } from '../../types' | ||
import { DictionaryMatch, MatchOptions } from '../../types' | ||
export interface DictionaryMatchOptions { | ||
password: string | ||
export interface DictionaryMatchOptionsLevenshtein extends MatchOptions { | ||
useLevenshtein?: boolean | ||
} | ||
export type DictionaryMatchOptions = Pick< | ||
DictionaryMatchOptionsLevenshtein, | ||
'password' | 'userInputsOptions' | 'useLevenshtein' | ||
> | ||
export type DefaultMatch = ( | ||
options: DictionaryMatchOptions, | ||
) => DictionaryMatch[] |
@@ -1,4 +0,4 @@ | ||
import { zxcvbnOptions } from '../../../../Options' | ||
import Options from '../../../../Options' | ||
import { DictionaryMatch, L33tMatch } from '../../../../types' | ||
import { DefaultMatch } from '../../types' | ||
import { DefaultMatch, DictionaryMatchOptions } from '../../types' | ||
import getCleanPasswords, { | ||
@@ -57,8 +57,7 @@ PasswordChanges, | ||
class MatchL33t { | ||
defaultMatch: DefaultMatch | ||
constructor( | ||
private options: Options, | ||
private defaultMatch: DefaultMatch, | ||
) {} | ||
constructor(defaultMatch: DefaultMatch) { | ||
this.defaultMatch = defaultMatch | ||
} | ||
isAlreadyIncluded(matches: L33tMatch[], newMatch: L33tMatch) { | ||
@@ -72,11 +71,10 @@ return matches.some((l33tMatch) => { | ||
match({ password }: { password: string }) { | ||
match(matchOptions: DictionaryMatchOptions) { | ||
const matches: L33tMatch[] = [] | ||
const subbedPasswords = getCleanPasswords( | ||
password, | ||
zxcvbnOptions.l33tMaxSubstitutions, | ||
zxcvbnOptions.trieNodeRoot, | ||
matchOptions.password, | ||
this.options.l33tMaxSubstitutions, | ||
this.options.trieNodeRoot, | ||
) | ||
let hasFullMatch = false | ||
let isFullSubstitution = true | ||
subbedPasswords.forEach((subbedPassword) => { | ||
@@ -87,13 +85,16 @@ if (hasFullMatch) { | ||
const matchedDictionary = this.defaultMatch({ | ||
...matchOptions, | ||
password: subbedPassword.password, | ||
useLevenshtein: isFullSubstitution, | ||
useLevenshtein: subbedPassword.isFullSubstitution, | ||
}) | ||
// only the first entry has a full substitution | ||
isFullSubstitution = false | ||
matchedDictionary.forEach((match: DictionaryMatch) => { | ||
if (!hasFullMatch) { | ||
hasFullMatch = match.i === 0 && match.j === password.length - 1 | ||
hasFullMatch = | ||
match.i === 0 && match.j === matchOptions.password.length - 1 | ||
} | ||
const extras = getExtras(subbedPassword, match.i, match.j) | ||
const token = password.slice(extras.i, +extras.j + 1 || 9e9) | ||
const token = matchOptions.password.slice( | ||
extras.i, | ||
+extras.j + 1 || 9e9, | ||
) | ||
const newMatch: L33tMatch = { | ||
@@ -100,0 +101,0 @@ ...match, |
import { DictionaryMatch } from '../../../../types' | ||
import { DefaultMatch } from '../../types' | ||
import { DefaultMatch, DictionaryMatchOptions } from '../../types' | ||
import Options from '../../../../Options' | ||
@@ -10,11 +11,11 @@ /* | ||
class MatchReverse { | ||
defaultMatch: DefaultMatch | ||
constructor( | ||
private options: Options, | ||
private defaultMatch: DefaultMatch, | ||
) {} | ||
constructor(defaultMatch: DefaultMatch) { | ||
this.defaultMatch = defaultMatch | ||
} | ||
match({ password }: { password: string }) { | ||
const passwordReversed = password.split('').reverse().join('') | ||
match(matchOptions: DictionaryMatchOptions) { | ||
const passwordReversed = matchOptions.password.split('').reverse().join('') | ||
return this.defaultMatch({ | ||
...matchOptions, | ||
password: passwordReversed, | ||
@@ -26,4 +27,4 @@ }).map((match: DictionaryMatch) => ({ | ||
// map coordinates back to original string | ||
i: password.length - 1 - match.j, | ||
j: password.length - 1 - match.i, | ||
i: matchOptions.password.length - 1 - match.j, | ||
j: matchOptions.password.length - 1 - match.i, | ||
})) | ||
@@ -30,0 +31,0 @@ } |
@@ -19,2 +19,3 @@ import TrieNode from './TrieNode' | ||
changes: IndexedPasswordChanges[] | ||
isFullSubstitution: boolean | ||
} | ||
@@ -79,3 +80,7 @@ | ||
if (onlyFullSub === isFullSub) { | ||
this.finalPasswords.push({ password: this.buffer.join(''), changes }) | ||
this.finalPasswords.push({ | ||
password: this.buffer.join(''), | ||
changes, | ||
isFullSubstitution: onlyFullSub, | ||
}) | ||
} | ||
@@ -92,2 +97,3 @@ return | ||
const cur = nodes[i - index] | ||
const sub = cur.parents.join('') | ||
if (cur.isTerminal()) { | ||
@@ -97,6 +103,3 @@ // Skip if this would be a 4th or more consecutive substitution of the same letter | ||
// So we can ignore the rest to save calculation time | ||
if ( | ||
lastSubLetter === cur.parents.join('') && | ||
consecutiveSubCount >= 3 | ||
) { | ||
if (lastSubLetter === sub && consecutiveSubCount >= 3) { | ||
// eslint-disable-next-line no-continue | ||
@@ -106,10 +109,10 @@ continue | ||
hasSubs = true | ||
const subs = cur.subs! | ||
const letters = cur.subs! | ||
// eslint-disable-next-line no-restricted-syntax | ||
for (const sub of subs) { | ||
this.buffer.push(sub) | ||
for (const letter of letters) { | ||
this.buffer.push(letter) | ||
const newSubs = changes.concat({ | ||
i: subIndex, | ||
letter: sub, | ||
substitution: cur.parents.join(''), | ||
letter, | ||
substitution: sub, | ||
}) | ||
@@ -121,10 +124,8 @@ | ||
isFullSub, | ||
index: i + 1, | ||
subIndex: subIndex + sub.length, | ||
index: index + sub.length, | ||
subIndex: subIndex + letter.length, | ||
changes: newSubs, | ||
lastSubLetter: cur.parents.join(''), | ||
lastSubLetter: sub, | ||
consecutiveSubCount: | ||
lastSubLetter === cur.parents.join('') | ||
? consecutiveSubCount + 1 | ||
: 1, | ||
lastSubLetter === sub ? consecutiveSubCount + 1 : 1, | ||
}) | ||
@@ -131,0 +132,0 @@ // backtrack by ignoring the added postfix |
@@ -1,11 +0,11 @@ | ||
import { zxcvbnOptions } from '../../Options' | ||
import Options from '../../Options' | ||
import { MatchEstimated } from '../../types' | ||
export default (match: MatchEstimated) => { | ||
export default (options: Options, match: MatchEstimated) => { | ||
if (match.regexName === 'recentYear') { | ||
return { | ||
warning: zxcvbnOptions.translations.warnings.recentYears, | ||
warning: options.translations.warnings.recentYears, | ||
suggestions: [ | ||
zxcvbnOptions.translations.suggestions.recentYears, | ||
zxcvbnOptions.translations.suggestions.associatedYears, | ||
options.translations.suggestions.recentYears, | ||
options.translations.suggestions.associatedYears, | ||
], | ||
@@ -12,0 +12,0 @@ } |
import { REGEXEN } from '../../data/const' | ||
import { sorted } from '../../helper' | ||
import { RegexMatch } from '../../types' | ||
import { sorted } from '../../utils/helper' | ||
import { MatchOptions, RegexMatch } from '../../types' | ||
import Options from '../../Options' | ||
interface RegexMatchOptions { | ||
password: string | ||
regexes?: typeof REGEXEN | ||
} | ||
type RegexMatchOptions = Pick<MatchOptions, 'password'> | ||
@@ -17,6 +15,8 @@ type RegexesKeys = keyof typeof REGEXEN | ||
class MatchRegex { | ||
match({ password, regexes = REGEXEN }: RegexMatchOptions) { | ||
constructor(private options: Options) {} | ||
match({ password }: RegexMatchOptions) { | ||
const matches: RegexMatch[] = [] | ||
Object.keys(regexes).forEach((name) => { | ||
const regex = regexes[name as RegexesKeys] | ||
Object.keys(REGEXEN).forEach((name) => { | ||
const regex = REGEXEN[name as RegexesKeys] | ||
regex.lastIndex = 0 // keeps regexMatch stateless | ||
@@ -23,0 +23,0 @@ |
@@ -1,8 +0,8 @@ | ||
import { zxcvbnOptions } from '../../Options' | ||
import Options from '../../Options' | ||
import { MatchEstimated } from '../../types' | ||
export default (match: MatchEstimated) => { | ||
let warning = zxcvbnOptions.translations.warnings.extendedRepeat | ||
export default (options: Options, match: MatchEstimated) => { | ||
let warning = options.translations.warnings.extendedRepeat | ||
if (match.baseToken.length === 1) { | ||
warning = zxcvbnOptions.translations.warnings.simpleRepeat | ||
warning = options.translations.warnings.simpleRepeat | ||
} | ||
@@ -12,4 +12,4 @@ | ||
warning, | ||
suggestions: [zxcvbnOptions.translations.suggestions.repeated], | ||
suggestions: [options.translations.suggestions.repeated], | ||
} | ||
} |
@@ -1,9 +0,6 @@ | ||
import { RepeatMatch } from '../../types' | ||
import scoring from '../../scoring' | ||
import { MatchOptions, RepeatMatch } from '../../types' | ||
import Scoring from '../../scoring' | ||
import Matching from '../../Matching' | ||
import Options from '../../Options' | ||
interface RepeatMatchOptions { | ||
password: string | ||
omniMatch: Matching | ||
} | ||
/* | ||
@@ -15,4 +12,10 @@ *------------------------------------------------------------------------------- | ||
class MatchRepeat { | ||
private scoring: Scoring | ||
constructor(private options: Options) { | ||
this.scoring = new Scoring(options) | ||
} | ||
// eslint-disable-next-line max-statements | ||
match({ password, omniMatch }: RepeatMatchOptions) { | ||
match({ password, omniMatch }: MatchOptions) { | ||
const matches: (RepeatMatch | Promise<RepeatMatch>)[] = [] | ||
@@ -127,3 +130,3 @@ let lastIndex = 0 | ||
return matches.then((resolvedMatches) => { | ||
const baseAnalysis = scoring.mostGuessableMatchSequence( | ||
const baseAnalysis = this.scoring.mostGuessableMatchSequence( | ||
baseToken, | ||
@@ -135,3 +138,6 @@ resolvedMatches, | ||
} | ||
const baseAnalysis = scoring.mostGuessableMatchSequence(baseToken, matches) | ||
const baseAnalysis = this.scoring.mostGuessableMatchSequence( | ||
baseToken, | ||
matches, | ||
) | ||
return baseAnalysis.guesses | ||
@@ -138,0 +144,0 @@ } |
import { SEPERATOR_CHARS } from '../../data/const' | ||
import { SeparatorMatch } from '../../types' | ||
import { MatchOptions, SeparatorMatch } from '../../types' | ||
import Options from '../../Options' | ||
interface SeparatorMatchOptions { | ||
password: string | ||
} | ||
type SeparatorMatchOptions = Pick<MatchOptions, 'password'> | ||
@@ -16,2 +15,4 @@ const separatorRegex = new RegExp(`[${SEPERATOR_CHARS.join('')}]`) | ||
class MatchSeparator { | ||
constructor(private options: Options) {} | ||
static getMostUsedSeparatorChar(password: string): string | undefined { | ||
@@ -18,0 +19,0 @@ const mostUsedSeperators = [ |
@@ -1,8 +0,8 @@ | ||
import { zxcvbnOptions } from '../../Options' | ||
import Options from '../../Options' | ||
export default () => { | ||
export default (options: Options) => { | ||
return { | ||
warning: zxcvbnOptions.translations.warnings.sequences, | ||
suggestions: [zxcvbnOptions.translations.suggestions.sequences], | ||
warning: options.translations.warnings.sequences, | ||
suggestions: [options.translations.suggestions.sequences], | ||
} | ||
} |
import { ALL_UPPER, ALL_LOWER, ALL_DIGIT } from '../../data/const' | ||
import { SequenceMatch } from '../../types' | ||
import { MatchOptions, SequenceMatch } from '../../types' | ||
import Options from '../../Options' | ||
@@ -12,5 +13,3 @@ type UpdateParams = { | ||
interface SequenceMatchOptions { | ||
password: string | ||
} | ||
type SequenceMatchOptions = Pick<MatchOptions, 'password'> | ||
/* | ||
@@ -24,2 +23,4 @@ *------------------------------------------------------------------------------- | ||
constructor(private options: Options) {} | ||
// eslint-disable-next-line max-statements | ||
@@ -26,0 +27,0 @@ match({ password }: SequenceMatchOptions) { |
@@ -1,13 +0,13 @@ | ||
import { zxcvbnOptions } from '../../Options' | ||
import Options from '../../Options' | ||
import { MatchEstimated } from '../../types' | ||
export default (match: MatchEstimated) => { | ||
let warning = zxcvbnOptions.translations.warnings.keyPattern | ||
export default (options: Options, match: MatchEstimated) => { | ||
let warning = options.translations.warnings.keyPattern | ||
if (match.turns === 1) { | ||
warning = zxcvbnOptions.translations.warnings.straightRow | ||
warning = options.translations.warnings.straightRow | ||
} | ||
return { | ||
warning, | ||
suggestions: [zxcvbnOptions.translations.suggestions.longerKeyboardPattern], | ||
suggestions: [options.translations.suggestions.longerKeyboardPattern], | ||
} | ||
} |
@@ -1,8 +0,6 @@ | ||
import { sorted, extend } from '../../helper' | ||
import { zxcvbnOptions } from '../../Options' | ||
import { LooseObject, SpatialMatch } from '../../types' | ||
import { sorted, extend } from '../../utils/helper' | ||
import Options from '../../Options' | ||
import { LooseObject, MatchOptions, SpatialMatch } from '../../types' | ||
interface SpatialMatchOptions { | ||
password: string | ||
} | ||
type SpatialMatchOptions = Pick<MatchOptions, 'password'> | ||
/* | ||
@@ -16,6 +14,8 @@ * ------------------------------------------------------------------------------ | ||
constructor(private options: Options) {} | ||
match({ password }: SpatialMatchOptions) { | ||
const matches: SpatialMatch[] = [] | ||
Object.keys(zxcvbnOptions.graphs).forEach((graphName) => { | ||
const graph = zxcvbnOptions.graphs[graphName] | ||
Object.keys(this.options.graphs).forEach((graphName) => { | ||
const graph = this.options.graphs[graphName] | ||
extend(matches, this.helper(password, graph, graphName)) | ||
@@ -22,0 +22,0 @@ }) |
import utils from '../../scoring/utils' | ||
import { zxcvbnOptions } from '../../Options' | ||
import { LooseObject, MatchEstimated, MatchExtended } from '../../types' | ||
import Options from '../../Options' | ||
import { | ||
LooseObject, | ||
MatchEstimated, | ||
MatchExtended, | ||
OptionsGraphEntry, | ||
} from '../../types' | ||
interface EstimatePossiblePatternsOptions { | ||
token: string | ||
graph: string | ||
turns: number | ||
@@ -21,9 +25,8 @@ } | ||
const estimatePossiblePatterns = ({ | ||
token, | ||
graph, | ||
turns, | ||
}: EstimatePossiblePatternsOptions) => { | ||
const startingPosition = Object.keys(zxcvbnOptions.graphs[graph]).length | ||
const averageDegree = calcAverageDegree(zxcvbnOptions.graphs[graph]) | ||
const estimatePossiblePatterns = ( | ||
graphEntry: OptionsGraphEntry, | ||
{ token, turns }: EstimatePossiblePatternsOptions, | ||
) => { | ||
const startingPosition = Object.keys(graphEntry).length | ||
const averageDegree = calcAverageDegree(graphEntry) | ||
@@ -42,9 +45,10 @@ let guesses = 0 | ||
export default ({ | ||
graph, | ||
token, | ||
shiftedCount, | ||
turns, | ||
}: MatchExtended | MatchEstimated) => { | ||
let guesses = estimatePossiblePatterns({ token, graph, turns }) | ||
export default ( | ||
{ graph, token, shiftedCount, turns }: MatchExtended | MatchEstimated, | ||
options: Options, | ||
) => { | ||
let guesses = estimatePossiblePatterns(options.graphs[graph], { | ||
token, | ||
turns, | ||
}) | ||
@@ -51,0 +55,0 @@ // add extra guesses for shifted keys. (% instead of 5, A instead of a.) |
@@ -1,3 +0,3 @@ | ||
import { extend, sorted } from './helper' | ||
import { MatchExtended, MatchingType } from './types' | ||
import { extend, sorted } from './utils/helper' | ||
import { MatchExtended, MatchingType, UserInputsOptions } from './types' | ||
import dateMatcher from './matcher/date/matching' | ||
@@ -10,3 +10,3 @@ import dictionaryMatcher from './matcher/dictionary/matching' | ||
import separatorMatcher from './matcher/separator/matching' | ||
import { zxcvbnOptions } from './Options' | ||
import Options from './Options' | ||
@@ -24,3 +24,3 @@ /* | ||
class Matching { | ||
readonly matchers: Matchers = { | ||
private readonly matchers: Matchers = { | ||
date: dateMatcher, | ||
@@ -36,34 +36,36 @@ dictionary: dictionaryMatcher, | ||
match(password: string): MatchExtended[] | Promise<MatchExtended[]> { | ||
const matches: MatchExtended[] = [] | ||
constructor(private options: Options) {} | ||
const promises: Promise<MatchExtended[]>[] = [] | ||
const matchers = [ | ||
...Object.keys(this.matchers), | ||
...Object.keys(zxcvbnOptions.matchers), | ||
] | ||
matchers.forEach((key) => { | ||
if (!this.matchers[key] && !zxcvbnOptions.matchers[key]) { | ||
return | ||
} | ||
const Matcher = this.matchers[key] | ||
? this.matchers[key] | ||
: zxcvbnOptions.matchers[key].Matching | ||
const usedMatcher = new Matcher() | ||
const result = usedMatcher.match({ | ||
password, | ||
omniMatch: this, | ||
private matcherFactory(name: string) { | ||
if (!this.matchers[name] && !this.options.matchers[name]) { | ||
return null | ||
} | ||
const Matcher = this.matchers[name] | ||
? this.matchers[name] | ||
: this.options.matchers[name].Matching | ||
return new Matcher(this.options) | ||
} | ||
private processResult( | ||
matches: MatchExtended[], | ||
promises: Promise<MatchExtended[]>[], | ||
result: MatchExtended[] | Promise<MatchExtended[]>, | ||
) { | ||
if (result instanceof Promise) { | ||
result.then((response) => { | ||
extend(matches, response) | ||
}) | ||
promises.push(result) | ||
} else { | ||
extend(matches, result) | ||
} | ||
} | ||
if (result instanceof Promise) { | ||
result.then((response) => { | ||
extend(matches, response) | ||
}) | ||
promises.push(result) | ||
} else { | ||
extend(matches, result) | ||
} | ||
}) | ||
private handlePromises( | ||
matches: MatchExtended[], | ||
promises: Promise<MatchExtended[]>[], | ||
) { | ||
if (promises.length > 0) { | ||
return new Promise((resolve, reject) => { | ||
return new Promise<MatchExtended[]>((resolve, reject) => { | ||
Promise.all(promises) | ||
@@ -80,4 +82,33 @@ .then(() => { | ||
} | ||
match( | ||
password: string, | ||
userInputsOptions?: UserInputsOptions, | ||
): MatchExtended[] | Promise<MatchExtended[]> { | ||
const matches: MatchExtended[] = [] | ||
const promises: Promise<MatchExtended[]>[] = [] | ||
const matchers = [ | ||
...Object.keys(this.matchers), | ||
...Object.keys(this.options.matchers), | ||
] | ||
matchers.forEach((key) => { | ||
const matcher = this.matcherFactory(key) | ||
if (!matcher) { | ||
return | ||
} | ||
const result = matcher.match({ | ||
password, | ||
omniMatch: this, | ||
userInputsOptions, | ||
}) | ||
// extends matches and promises by references | ||
this.processResult(matches, promises, result) | ||
}) | ||
return this.handlePromises(matches, promises) | ||
} | ||
} | ||
export default Matching |
@@ -1,2 +0,2 @@ | ||
import { buildRankedDictionary } from './helper' | ||
import { buildRankedDictionary } from './utils/helper' | ||
import { | ||
@@ -11,2 +11,5 @@ TranslationKeys, | ||
Matcher, | ||
UserInputsOptions, | ||
RankedDictionary, | ||
TimeEstimationValues, | ||
} from './types' | ||
@@ -17,36 +20,55 @@ import l33tTable from './data/l33tTable' | ||
import l33tTableToTrieNode from './matcher/dictionary/variants/matching/unmunger/l33tTableToTrieNode' | ||
import { | ||
checkTimeEstimationValues, | ||
timeEstimationValuesDefaults, | ||
} from './TimeEstimates' | ||
export class Options { | ||
matchers: Matchers = {} | ||
export default class Options { | ||
public matchers: Matchers = {} | ||
l33tTable: OptionsL33tTable = l33tTable | ||
public l33tTable: OptionsL33tTable = l33tTable | ||
trieNodeRoot: TrieNode = l33tTableToTrieNode(l33tTable, new TrieNode()) | ||
public trieNodeRoot: TrieNode = l33tTableToTrieNode(l33tTable, new TrieNode()) | ||
dictionary: OptionsDictionary = { | ||
public dictionary: OptionsDictionary = { | ||
userInputs: [], | ||
} | ||
rankedDictionaries: RankedDictionaries = {} | ||
public rankedDictionaries: RankedDictionaries = {} | ||
rankedDictionariesMaxWordSize: Record<string, number> = {} | ||
public rankedDictionariesMaxWordSize: Record<string, number> = {} | ||
translations: TranslationKeys = translationKeys | ||
public translations: TranslationKeys = translationKeys | ||
graphs: OptionsGraph = {} | ||
public graphs: OptionsGraph = {} | ||
useLevenshteinDistance: boolean = false | ||
public useLevenshteinDistance: boolean = false | ||
levenshteinThreshold: number = 2 | ||
public levenshteinThreshold: number = 2 | ||
l33tMaxSubstitutions: number = 100 | ||
public l33tMaxSubstitutions: number = 100 | ||
maxLength: number = 256 | ||
public maxLength: number = 256 | ||
constructor() { | ||
this.setRankedDictionaries() | ||
timeEstimationValues: TimeEstimationValues = { | ||
scoring: { | ||
...timeEstimationValuesDefaults.scoring, | ||
}, | ||
attackTime: { | ||
...timeEstimationValuesDefaults.attackTime, | ||
}, | ||
} | ||
constructor( | ||
options: OptionsType = {}, | ||
customMatchers: Record<string, Matcher> = {}, | ||
) { | ||
this.setOptions(options) | ||
Object.entries(customMatchers).forEach(([name, matcher]) => { | ||
this.addMatcher(name, matcher) | ||
}) | ||
} | ||
// eslint-disable-next-line max-statements,complexity | ||
setOptions(options: OptionsType = {}) { | ||
private setOptions(options: OptionsType = {}) { | ||
if (options.l33tTable) { | ||
@@ -86,5 +108,17 @@ this.l33tTable = options.l33tTable | ||
} | ||
if (options.timeEstimationValues !== undefined) { | ||
checkTimeEstimationValues(options.timeEstimationValues) | ||
this.timeEstimationValues = { | ||
scoring: { | ||
...options.timeEstimationValues.scoring, | ||
}, | ||
attackTime: { | ||
...options.timeEstimationValues.attackTime, | ||
}, | ||
} | ||
} | ||
} | ||
setTranslations(translations: TranslationKeys) { | ||
private setTranslations(translations: TranslationKeys) { | ||
if (this.checkCustomTranslations(translations)) { | ||
@@ -97,3 +131,3 @@ this.translations = translations | ||
checkCustomTranslations(translations: TranslationKeys) { | ||
private checkCustomTranslations(translations: TranslationKeys) { | ||
let valid = true | ||
@@ -115,3 +149,3 @@ Object.keys(translationKeys).forEach((type) => { | ||
setRankedDictionaries() { | ||
private setRankedDictionaries() { | ||
const rankedDictionaries: RankedDictionaries = {} | ||
@@ -128,3 +162,3 @@ const rankedDictionariesMaxWorkSize: Record<string, number> = {} | ||
getRankedDictionariesMaxWordSize(list: (string | number)[]) { | ||
private getRankedDictionariesMaxWordSize(list: (string | number)[]) { | ||
const data = list.map((el) => { | ||
@@ -144,3 +178,3 @@ if (typeof el !== 'string') { | ||
buildSanitizedRankedDictionary(list: (string | number)[]) { | ||
private buildSanitizedRankedDictionary(list: (string | number)[]) { | ||
const sanitizedInputs: string[] = [] | ||
@@ -162,15 +196,20 @@ | ||
extendUserInputsDictionary(dictionary: (string | number)[]) { | ||
if (!this.dictionary.userInputs) { | ||
this.dictionary.userInputs = [] | ||
public getUserInputsOptions( | ||
dictionary?: (string | number)[], | ||
): UserInputsOptions { | ||
let rankedDictionary: RankedDictionary = {} | ||
let rankedDictionaryMaxWordSize: number = 0 | ||
if (dictionary) { | ||
rankedDictionary = this.buildSanitizedRankedDictionary(dictionary) | ||
rankedDictionaryMaxWordSize = | ||
this.getRankedDictionariesMaxWordSize(dictionary) | ||
} | ||
const newList = [...this.dictionary.userInputs, ...dictionary] | ||
this.rankedDictionaries.userInputs = | ||
this.buildSanitizedRankedDictionary(newList) | ||
this.rankedDictionariesMaxWordSize.userInputs = | ||
this.getRankedDictionariesMaxWordSize(newList) | ||
return { | ||
rankedDictionary, | ||
rankedDictionaryMaxWordSize, | ||
} | ||
} | ||
public addMatcher(name: string, matcher: Matcher) { | ||
private addMatcher(name: string, matcher: Matcher) { | ||
if (this.matchers[name]) { | ||
@@ -183,3 +222,1 @@ console.info(`Matcher ${name} already exists`) | ||
} | ||
export const zxcvbnOptions = new Options() |
@@ -6,3 +6,3 @@ import { | ||
import utils from './utils' | ||
import { zxcvbnOptions } from '../Options' | ||
import Options from '../Options' | ||
import { | ||
@@ -53,11 +53,12 @@ DefaultScoringFunction, | ||
const getScoring = (name: string, match: MatchExtended | MatchEstimated) => { | ||
const getScoring = ( | ||
options: Options, | ||
name: string, | ||
match: MatchExtended | MatchEstimated, | ||
) => { | ||
if (matchers[name]) { | ||
return matchers[name](match) | ||
return matchers[name](match, options) | ||
} | ||
if ( | ||
zxcvbnOptions.matchers[name] && | ||
'scoring' in zxcvbnOptions.matchers[name] | ||
) { | ||
return zxcvbnOptions.matchers[name].scoring(match) | ||
if (options.matchers[name] && 'scoring' in options.matchers[name]) { | ||
return options.matchers[name].scoring(match, options) | ||
} | ||
@@ -71,3 +72,7 @@ return 0 | ||
// eslint-disable-next-line complexity, max-statements | ||
export default (match: MatchExtended | MatchEstimated, password: string) => { | ||
export default ( | ||
options: Options, | ||
match: MatchExtended | MatchEstimated, | ||
password: string, | ||
) => { | ||
const extraData: LooseObject = {} | ||
@@ -81,3 +86,3 @@ // a match's guess estimate doesn't change. cache it. | ||
const estimationResult = getScoring(match.pattern, match) | ||
const estimationResult = getScoring(options, match.pattern, match) | ||
let guesses = 0 | ||
@@ -84,0 +89,0 @@ if (typeof estimationResult === 'number') { |
@@ -9,7 +9,9 @@ import utils from './utils' | ||
LooseObject, | ||
Optimal, | ||
} from '../types' | ||
import Options from '../Options' | ||
const scoringHelper = { | ||
password: '', | ||
optimal: {} as any, | ||
optimal: {} as Optimal, | ||
excludeAdditive: false, | ||
@@ -41,5 +43,5 @@ separatorRegex: undefined as RegExp | null | undefined, | ||
// than previously encountered sequences, updating state if so. | ||
update(match: MatchExtended, sequenceLength: number) { | ||
update(options: Options, match: MatchExtended, sequenceLength: number) { | ||
const k = match.j | ||
const estimatedMatch = estimateGuesses(match, this.password) | ||
const estimatedMatch = estimateGuesses(options, match, this.password) | ||
let pi = estimatedMatch.guesses as number | ||
@@ -80,6 +82,6 @@ if (sequenceLength > 1) { | ||
// helper: evaluate bruteforce matches ending at passwordCharIndex. | ||
bruteforceUpdate(passwordCharIndex: number) { | ||
bruteforceUpdate(options: Options, passwordCharIndex: number) { | ||
// see if a single bruteforce match spanning the passwordCharIndex-prefix is optimal. | ||
let match = this.makeBruteforceMatch(0, passwordCharIndex) | ||
this.update(match, 1) | ||
this.update(options, match, 1) | ||
@@ -101,3 +103,3 @@ for (let i = 1; i <= passwordCharIndex; i += 1) { | ||
// try adding m to this length-sequenceLength sequence. | ||
this.update(match, parseInt(sequenceLength, 10) + 1) | ||
this.update(options, match, parseInt(sequenceLength, 10) + 1) | ||
} | ||
@@ -138,3 +140,5 @@ }) | ||
export default { | ||
export default class Scoring { | ||
constructor(private options: Options) {} | ||
// ------------------------------------------------------------------------------ | ||
@@ -201,2 +205,3 @@ // search --- most guessable match sequence ------------------------------------- | ||
// optimal.m[k][sequenceLength] is undefined. | ||
// @ts-ignore | ||
m: scoringHelper.fillArray(passwordLength, 'object'), | ||
@@ -215,10 +220,14 @@ // same structure as optimal.m -- holds the product term Prod(m.guesses for m in sequence). | ||
(sequenceLength) => { | ||
scoringHelper.update(match, parseInt(sequenceLength, 10) + 1) | ||
scoringHelper.update( | ||
this.options, | ||
match, | ||
parseInt(sequenceLength, 10) + 1, | ||
) | ||
}, | ||
) | ||
} else { | ||
scoringHelper.update(match, 1) | ||
scoringHelper.update(this.options, match, 1) | ||
} | ||
}) | ||
scoringHelper.bruteforceUpdate(k) | ||
scoringHelper.bruteforceUpdate(this.options, k) | ||
} | ||
@@ -234,3 +243,3 @@ const optimalMatchSequence = scoringHelper.unwind(passwordLength) | ||
} | ||
}, | ||
} | ||
@@ -247,3 +256,3 @@ getGuesses(password: string, optimalSequenceLength: number) { | ||
return guesses | ||
}, | ||
} | ||
} |
@@ -1,3 +0,8 @@ | ||
import { zxcvbnOptions } from './Options' | ||
import { CrackTimesDisplay, CrackTimesSeconds, Score } from './types' | ||
import Options from './Options' | ||
import { | ||
CrackTimes, | ||
CrackTimesSeconds, | ||
Score, | ||
TimeEstimationValues, | ||
} from './types' | ||
@@ -22,2 +27,32 @@ const SECOND = 1 | ||
export const timeEstimationValuesDefaults: TimeEstimationValues = { | ||
scoring: { | ||
0: 1e3, | ||
1: 1e6, | ||
2: 1e8, | ||
3: 1e10, | ||
}, | ||
attackTime: { | ||
onlineThrottlingXPerHour: 100, | ||
onlineNoThrottlingXPerSecond: 10, | ||
offlineSlowHashingXPerSecond: 1e4, | ||
offlineFastHashingXPerSecond: 1e10, | ||
}, | ||
} | ||
export const checkTimeEstimationValues = ( | ||
timeEstimationValues: TimeEstimationValues, | ||
) => { | ||
Object.entries(timeEstimationValues).forEach(([key, data]) => { | ||
Object.entries(data).forEach(([subKey, value]) => { | ||
// @ts-ignore | ||
if (value < timeEstimationValuesDefaults[key][subKey]) { | ||
throw new Error( | ||
'Time estimation values are not to be allowed to be less than default', | ||
) | ||
} | ||
}) | ||
}) | ||
} | ||
/* | ||
@@ -28,55 +63,59 @@ * ------------------------------------------------------------------------------- | ||
*/ | ||
class TimeEstimates { | ||
translate(displayStr: string, value: number | undefined) { | ||
let key = displayStr | ||
if (value !== undefined && value !== 1) { | ||
key += 's' | ||
export class TimeEstimates { | ||
constructor(private options: Options) {} | ||
public estimateAttackTimes(guesses: number) { | ||
const crackTimesSeconds = this.calculateCrackTimesSeconds(guesses) | ||
const crackTimes = Object.keys(crackTimesSeconds).reduce( | ||
(previousValue, crackTime) => { | ||
const usedScenario = crackTime as keyof CrackTimesSeconds | ||
const seconds = crackTimesSeconds[usedScenario] | ||
const { base, displayStr } = this.displayTime(seconds) | ||
// eslint-disable-next-line no-param-reassign | ||
previousValue[usedScenario] = { | ||
base, | ||
seconds, | ||
display: this.translate(displayStr, base), | ||
} | ||
return previousValue | ||
}, | ||
{} as CrackTimes, | ||
) | ||
return { | ||
crackTimes, | ||
score: this.guessesToScore(guesses), | ||
} | ||
const { timeEstimation } = zxcvbnOptions.translations | ||
return timeEstimation[key as keyof typeof timeEstimation].replace( | ||
'{base}', | ||
`${value}`, | ||
) | ||
} | ||
estimateAttackTimes(guesses: number) { | ||
const crackTimesSeconds: CrackTimesSeconds = { | ||
onlineThrottling100PerHour: guesses / (100 / 3600), | ||
onlineNoThrottling10PerSecond: guesses / 10, | ||
offlineSlowHashing1e4PerSecond: guesses / 1e4, | ||
offlineFastHashing1e10PerSecond: guesses / 1e10, | ||
} | ||
const crackTimesDisplay: CrackTimesDisplay = { | ||
onlineThrottling100PerHour: '', | ||
onlineNoThrottling10PerSecond: '', | ||
offlineSlowHashing1e4PerSecond: '', | ||
offlineFastHashing1e10PerSecond: '', | ||
} | ||
Object.keys(crackTimesSeconds).forEach((scenario) => { | ||
const seconds = crackTimesSeconds[scenario as keyof CrackTimesSeconds] | ||
crackTimesDisplay[scenario as keyof CrackTimesDisplay] = | ||
this.displayTime(seconds) | ||
}) | ||
private calculateCrackTimesSeconds(guesses: number): CrackTimesSeconds { | ||
const attackTimesOptions = this.options.timeEstimationValues.attackTime | ||
return { | ||
crackTimesSeconds, | ||
crackTimesDisplay, | ||
score: this.guessesToScore(guesses), | ||
onlineThrottlingXPerHour: | ||
guesses / (attackTimesOptions.onlineThrottlingXPerHour / 3600), | ||
onlineNoThrottlingXPerSecond: | ||
guesses / attackTimesOptions.onlineNoThrottlingXPerSecond, | ||
offlineSlowHashingXPerSecond: | ||
guesses / attackTimesOptions.offlineSlowHashingXPerSecond, | ||
offlineFastHashingXPerSecond: | ||
guesses / attackTimesOptions.offlineFastHashingXPerSecond, | ||
} | ||
} | ||
guessesToScore(guesses: number): Score { | ||
private guessesToScore(guesses: number): Score { | ||
const scoringOptions = this.options.timeEstimationValues.scoring | ||
const DELTA = 5 | ||
if (guesses < 1e3 + DELTA) { | ||
if (guesses < scoringOptions[0] + DELTA) { | ||
// risky password: "too guessable" | ||
return 0 | ||
} | ||
if (guesses < 1e6 + DELTA) { | ||
if (guesses < scoringOptions[1] + DELTA) { | ||
// modest protection from throttled online attacks: "very guessable" | ||
return 1 | ||
} | ||
if (guesses < 1e8 + DELTA) { | ||
if (guesses < scoringOptions[2] + DELTA) { | ||
// modest protection from unthrottled online attacks: "somewhat guessable" | ||
return 2 | ||
} | ||
if (guesses < 1e10 + DELTA) { | ||
if (guesses < scoringOptions[3] + DELTA) { | ||
// modest protection from offline attacks: "safely unguessable" | ||
@@ -90,5 +129,5 @@ // assuming a salted, slow hash function like bcrypt, scrypt, PBKDF2, argon, etc | ||
displayTime(seconds: number) { | ||
private displayTime(seconds: number) { | ||
let displayStr = 'centuries' | ||
let base | ||
let base = null | ||
const timeKeys = Object.keys(times) | ||
@@ -106,6 +145,19 @@ const foundIndex = timeKeys.findIndex( | ||
} | ||
return this.translate(displayStr, base) | ||
return { | ||
base, | ||
displayStr, | ||
} | ||
} | ||
private translate(displayStr: string, value: number | null) { | ||
let key = displayStr | ||
if (value !== null && value !== 1) { | ||
key += 's' | ||
} | ||
const { timeEstimation } = this.options.translations | ||
return timeEstimation[key as keyof typeof timeEstimation].replace( | ||
'{base}', | ||
`${value}`, | ||
) | ||
} | ||
} | ||
export default TimeEstimates |
@@ -7,2 +7,3 @@ import translationKeys from './data/translationKeys' | ||
import { PasswordChanges } from './matcher/dictionary/variants/matching/unmunger/getCleanPasswords' | ||
import Options from './Options' | ||
@@ -59,3 +60,3 @@ export type TranslationKeys = typeof translationKeys | ||
rank: number | ||
dictionaryName: DictionaryNames | ||
dictionaryName: DictionaryNames | string | ||
reversed: boolean | ||
@@ -135,21 +136,27 @@ l33t: boolean | ||
export interface Optimal { | ||
m: Match | ||
pi: Match | ||
g: Match | ||
m: Match[] | ||
pi: Record<string, number>[] | ||
g: Record<string, number>[] | ||
} | ||
export interface CrackTimesSeconds { | ||
onlineThrottling100PerHour: number | ||
onlineNoThrottling10PerSecond: number | ||
offlineSlowHashing1e4PerSecond: number | ||
offlineFastHashing1e10PerSecond: number | ||
export interface CrackTime { | ||
base: number | null | ||
seconds: number | ||
display: string | ||
} | ||
export interface CrackTimesDisplay { | ||
onlineThrottling100PerHour: string | ||
onlineNoThrottling10PerSecond: string | ||
offlineSlowHashing1e4PerSecond: string | ||
offlineFastHashing1e10PerSecond: string | ||
export interface CrackTimes { | ||
onlineThrottlingXPerHour: CrackTime | ||
onlineNoThrottlingXPerSecond: CrackTime | ||
offlineSlowHashingXPerSecond: CrackTime | ||
offlineFastHashingXPerSecond: CrackTime | ||
} | ||
export interface CrackTimesSeconds { | ||
onlineThrottlingXPerHour: number | ||
onlineNoThrottlingXPerSecond: number | ||
offlineSlowHashingXPerSecond: number | ||
offlineFastHashingXPerSecond: number | ||
} | ||
export interface FeedbackType { | ||
@@ -178,2 +185,17 @@ warning: string | null | ||
export interface TimeEstimationValues { | ||
scoring: { | ||
0: number | ||
1: number | ||
2: number | ||
3: number | ||
} | ||
attackTime: { | ||
onlineThrottlingXPerHour: number | ||
onlineNoThrottlingXPerSecond: number | ||
offlineSlowHashingXPerSecond: number | ||
offlineFastHashingXPerSecond: number | ||
} | ||
} | ||
export interface OptionsType { | ||
@@ -216,2 +238,6 @@ /** | ||
maxLength?: number | ||
/** | ||
* @description Define the values to calculate the scoring and attack times. DO NOT CHANGE unless you know what you are doing. The default values are just fine as long as you are using a strong, slow hash function. Can be adjusted to account for increasingly powerful attacker hardware. | ||
*/ | ||
timeEstimationValues?: TimeEstimationValues | ||
} | ||
@@ -228,2 +254,3 @@ | ||
export type DefaultFeedbackFunction = ( | ||
options: Options, | ||
match: MatchEstimated, | ||
@@ -235,4 +262,9 @@ isSoleMatch?: boolean, | ||
match: MatchExtended | MatchEstimated, | ||
options: Options, | ||
) => number | DictionaryReturn | ||
export interface UserInputsOptions { | ||
rankedDictionary: RankedDictionary | ||
rankedDictionaryMaxWordSize: number | ||
} | ||
export interface MatchOptions { | ||
@@ -244,8 +276,10 @@ password: string | ||
omniMatch: Matching | ||
userInputsOptions?: UserInputsOptions | ||
} | ||
export type MatchingType = new () => { | ||
export type MatchingType = new (options: Options) => { | ||
match({ | ||
password, | ||
omniMatch, | ||
userInputsOptions, | ||
}: MatchOptions): MatchExtended[] | Promise<MatchExtended[]> | ||
@@ -268,4 +302,3 @@ } | ||
feedback: FeedbackType | ||
crackTimesSeconds: CrackTimesSeconds | ||
crackTimesDisplay: CrackTimesDisplay | ||
crackTimes: CrackTimes | ||
score: Score | ||
@@ -272,0 +305,0 @@ password: string |
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 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
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
287
11783
777694
1