From a66f27b3cf33227190fba5da5d42f2e26e0cd9f3 Mon Sep 17 00:00:00 2001 From: MrWook Date: Wed, 6 Dec 2023 17:05:53 +0100 Subject: [PATCH 01/11] feat!(zxcvbn): change usage of library BREAKING: we moved to a class based approach where the options are handled in the constructor to get rid of the option singleton --- data-scripts/_generators/PasswordGenerator.ts | 8 +- packages/libraries/main/src/Feedback.ts | 34 +++--- packages/libraries/main/src/Matching.ts | 16 +-- packages/libraries/main/src/Options.ts | 28 +++-- packages/libraries/main/src/TimeEstimates.ts | 14 ++- packages/libraries/main/src/index.ts | 106 ++++++++++-------- .../main/src/matcher/date/feedback.ts | 8 +- .../main/src/matcher/date/matching.ts | 5 +- .../main/src/matcher/dictionary/feedback.ts | 49 ++++---- .../main/src/matcher/dictionary/matching.ts | 22 ++-- .../dictionary/variants/matching/l33t.ts | 15 ++- .../dictionary/variants/matching/reverse.ts | 10 +- .../main/src/matcher/regex/feedback.ts | 10 +- .../main/src/matcher/regex/matching.ts | 5 +- .../main/src/matcher/repeat/feedback.ts | 10 +- .../main/src/matcher/repeat/matching.ts | 16 ++- .../main/src/matcher/separator/matching.ts | 3 + .../main/src/matcher/sequence/feedback.ts | 8 +- .../main/src/matcher/sequence/matching.ts | 3 + .../main/src/matcher/spatial/feedback.ts | 10 +- .../main/src/matcher/spatial/matching.ts | 10 +- .../main/src/matcher/spatial/scoring.ts | 27 ++--- .../libraries/main/src/scoring/estimate.ts | 25 +++-- packages/libraries/main/src/scoring/index.ts | 29 +++-- packages/libraries/main/src/types.ts | 5 +- .../main/src/{ => utils}/debounce.ts | 0 .../libraries/main/src/{ => utils}/helper.ts | 2 +- .../main/src/{ => utils}/levenshtein.ts | 2 +- .../libraries/main/test/asyncMatcher.spec.ts | 30 ++--- .../libraries/main/test/customMatcher.spec.ts | 33 +++--- packages/libraries/main/test/feedback.spec.ts | 7 +- packages/libraries/main/test/main.spec.ts | 35 +++--- .../main/test/matcher/date/matching.spec.ts | 4 +- .../test/matcher/dictionary/matching.spec.ts | 29 ++--- .../matching/dictionaryReverse.spec.ts | 9 +- .../dictionary/variant/matching/l33t.spec.ts | 19 ++-- .../dictionary/variant/scoring/l33t.spec.ts | 2 +- .../main/test/matcher/regex/matching.spec.ts | 4 +- .../main/test/matcher/repeat/matching.spec.ts | 8 +- .../main/test/matcher/repeat/scoring.spec.ts | 10 +- .../test/matcher/separator/matching.spec.ts | 4 +- .../test/matcher/sequence/matching.spec.ts | 4 +- .../test/matcher/spatial/matching.spec.ts | 17 ++- .../main/test/matcher/spatial/scoring.spec.ts | 17 ++- packages/libraries/main/test/matching.spec.ts | 6 +- packages/libraries/main/test/options.spec.ts | 6 +- .../main/test/scoring/guesses/calc.spec.ts | 6 +- .../main/test/scoring/search.spec.ts | 8 +- .../libraries/main/test/timeEstimates.spec.ts | 6 +- .../main/test/{ => utils}/debounce.spec.ts | 2 +- .../main/test/{ => utils}/helper.spec.ts | 4 +- .../main/test/{ => utils}/levenshtein.spec.ts | 36 ++++-- packages/libraries/pwned/src/feedback.ts | 8 +- packages/libraries/pwned/src/index.ts | 5 +- packages/libraries/pwned/src/matching.ts | 2 +- packages/libraries/pwned/test/index.spec.ts | 24 ++-- .../libraries/pwned/test/matching.spec.ts | 8 +- 57 files changed, 476 insertions(+), 357 deletions(-) rename packages/libraries/main/src/{ => utils}/debounce.ts (100%) rename packages/libraries/main/src/{ => utils}/helper.ts (95%) rename packages/libraries/main/src/{ => utils}/levenshtein.ts (97%) rename packages/libraries/main/test/{ => utils}/debounce.spec.ts (91%) rename packages/libraries/main/test/{ => utils}/helper.spec.ts (97%) rename packages/libraries/main/test/{ => utils}/levenshtein.spec.ts (78%) diff --git a/data-scripts/_generators/PasswordGenerator.ts b/data-scripts/_generators/PasswordGenerator.ts index 5e2d40df..8c3e944e 100644 --- a/data-scripts/_generators/PasswordGenerator.ts +++ b/data-scripts/_generators/PasswordGenerator.ts @@ -4,7 +4,7 @@ import sprintfClass from 'sprintf-js' import { MatchExtended } from '@zxcvbn-ts/core/src/types' import Matching from '../../packages/libraries/main/src/Matching' import estimateGuesses from '../../packages/libraries/main/src/scoring/estimate' -import { zxcvbnOptions } from '../../packages/libraries/main/src/Options' +import { Options } from '../../packages/libraries/main/src/Options' const CUTOFF = 10 const BATCH_SIZE = 1000000 @@ -12,8 +12,8 @@ const BATCH_SIZE = 1000000 // eslint-disable-next-line @typescript-eslint/no-unused-vars const { sprintf } = sprintfClass -zxcvbnOptions.setOptions() -const matching = new Matching() +const zxcvbnOptions = new Options() +const matching = new Matching(zxcvbnOptions) interface Counts { [key: string]: number @@ -49,7 +49,7 @@ export default class PasswordGenerator { // eslint-disable-next-line no-restricted-syntax for (const match of matches) { - if (estimateGuesses(match, password).guesses < xatoRank) { + if (estimateGuesses(zxcvbnOptions, match, password).guesses < xatoRank) { return false } } diff --git a/packages/libraries/main/src/Feedback.ts b/packages/libraries/main/src/Feedback.ts index 44ba2119..2e34fbbe 100644 --- a/packages/libraries/main/src/Feedback.ts +++ b/packages/libraries/main/src/Feedback.ts @@ -1,4 +1,4 @@ -import { zxcvbnOptions } from './Options' +import { Options } from './Options' import { DefaultFeedbackFunction, FeedbackType, MatchEstimated } from './types' import bruteforceMatcher from './matcher/bruteforce/feedback' import dateMatcher from './matcher/date/feedback' @@ -23,7 +23,7 @@ type Matchers = { * ------------------------------------------------------------------------------- */ class Feedback { - readonly matchers: Matchers = { + private readonly matchers: Matchers = { bruteforce: bruteforceMatcher, date: dateMatcher, dictionary: dictionaryMatcher, @@ -34,30 +34,30 @@ class Feedback { separator: separatorMatcher, } - defaultFeedback: FeedbackType = { + private defaultFeedback: FeedbackType = { warning: null, 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) { return this.defaultFeedback } if (score > 2) { return defaultFeedback } - const extraFeedback = zxcvbnOptions.translations.suggestions.anotherWord + const extraFeedback = this.options.translations.suggestions.anotherWord const longestMatch = this.getLongestMatch(sequence) let feedback = this.getMatchFeedback(longestMatch, sequence.length === 1) if (feedback !== null && feedback !== undefined) { @@ -71,7 +71,7 @@ class Feedback { return feedback } - getLongestMatch(sequence: MatchEstimated[]) { + private getLongestMatch(sequence: MatchEstimated[]) { let longestMatch = sequence[0] const slicedSequence = sequence.slice(1) slicedSequence.forEach((match: MatchEstimated) => { @@ -82,15 +82,19 @@ class Feedback { return longestMatch } - 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, + ) } return defaultFeedback } diff --git a/packages/libraries/main/src/Matching.ts b/packages/libraries/main/src/Matching.ts index e7d6ee11..b8157353 100644 --- a/packages/libraries/main/src/Matching.ts +++ b/packages/libraries/main/src/Matching.ts @@ -1,4 +1,4 @@ -import { extend, sorted } from './helper' +import { extend, sorted } from './utils/helper' import { MatchExtended, MatchingType } from './types' import dateMatcher from './matcher/date/matching' import dictionaryMatcher from './matcher/dictionary/matching' @@ -7,7 +7,7 @@ import repeatMatcher from './matcher/repeat/matching' import sequenceMatcher from './matcher/sequence/matching' import spatialMatcher from './matcher/spatial/matching' import separatorMatcher from './matcher/separator/matching' -import { zxcvbnOptions } from './Options' +import { Options } from './Options' /* * ------------------------------------------------------------------------------- @@ -20,7 +20,7 @@ type Matchers = { } class Matching { - readonly matchers: Matchers = { + private readonly matchers: Matchers = { date: dateMatcher, dictionary: dictionaryMatcher, regex: regexMatcher, @@ -31,22 +31,24 @@ class Matching { separator: separatorMatcher, } + constructor(private options: Options) {} + match(password: string): MatchExtended[] | Promise { const matches: MatchExtended[] = [] const promises: Promise[] = [] const matchers = [ ...Object.keys(this.matchers), - ...Object.keys(zxcvbnOptions.matchers), + ...Object.keys(this.options.matchers), ] matchers.forEach((key) => { - if (!this.matchers[key] && !zxcvbnOptions.matchers[key]) { + if (!this.matchers[key] && !this.options.matchers[key]) { return } const Matcher = this.matchers[key] ? this.matchers[key] - : zxcvbnOptions.matchers[key].Matching - const usedMatcher = new Matcher() + : this.options.matchers[key].Matching + const usedMatcher = new Matcher(this.options) const result = usedMatcher.match({ password, omniMatch: this, diff --git a/packages/libraries/main/src/Options.ts b/packages/libraries/main/src/Options.ts index 1e72ab5e..b19e4bd5 100644 --- a/packages/libraries/main/src/Options.ts +++ b/packages/libraries/main/src/Options.ts @@ -1,4 +1,4 @@ -import { buildRankedDictionary } from './helper' +import { buildRankedDictionary } from './utils/helper' import { TranslationKeys, OptionsType, @@ -41,12 +41,18 @@ export class Options { maxLength: number = 256 - constructor() { - this.setRankedDictionaries() + constructor( + options: OptionsType = {}, + customMatchers: Record = {}, + ) { + 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) { this.l33tTable = options.l33tTable this.trieNodeRoot = l33tTableToTrieNode(options.l33tTable, new TrieNode()) @@ -83,7 +89,7 @@ export class Options { } } - setTranslations(translations: TranslationKeys) { + private setTranslations(translations: TranslationKeys) { if (this.checkCustomTranslations(translations)) { this.translations = translations } else { @@ -91,7 +97,7 @@ export class Options { } } - checkCustomTranslations(translations: TranslationKeys) { + private checkCustomTranslations(translations: TranslationKeys) { let valid = true Object.keys(translationKeys).forEach((type) => { if (type in translations) { @@ -108,7 +114,7 @@ export class Options { return valid } - setRankedDictionaries() { + private setRankedDictionaries() { const rankedDictionaries: RankedDictionaries = {} const rankedDictionariesMaxWorkSize: Record = {} Object.keys(this.dictionary).forEach((name) => { @@ -120,7 +126,7 @@ export class Options { this.rankedDictionariesMaxWordSize = rankedDictionariesMaxWorkSize } - getRankedDictionariesMaxWordSize(list: (string | number)[]) { + private getRankedDictionariesMaxWordSize(list: (string | number)[]) { const data = list.map((el) => { if (typeof el !== 'string') { return el.toString().length @@ -135,7 +141,7 @@ export class Options { return data.reduce((a, b) => Math.max(a, b), -Infinity) } - buildSanitizedRankedDictionary(list: (string | number)[]) { + private buildSanitizedRankedDictionary(list: (string | number)[]) { const sanitizedInputs: string[] = [] list.forEach((input: string | number | boolean) => { @@ -152,7 +158,7 @@ export class Options { return buildRankedDictionary(sanitizedInputs) } - extendUserInputsDictionary(dictionary: (string | number)[]) { + public extendUserInputsDictionary(dictionary: (string | number)[]) { if (!this.dictionary.userInputs) { this.dictionary.userInputs = [] } @@ -164,7 +170,7 @@ export class Options { this.getRankedDictionariesMaxWordSize(newList) } - public addMatcher(name: string, matcher: Matcher) { + private addMatcher(name: string, matcher: Matcher) { if (this.matchers[name]) { console.info(`Matcher ${name} already exists`) } else { diff --git a/packages/libraries/main/src/TimeEstimates.ts b/packages/libraries/main/src/TimeEstimates.ts index de6a305a..d2438140 100644 --- a/packages/libraries/main/src/TimeEstimates.ts +++ b/packages/libraries/main/src/TimeEstimates.ts @@ -1,4 +1,4 @@ -import { zxcvbnOptions } from './Options' +import { Options } from './Options' import { CrackTimesDisplay, CrackTimesSeconds, Score } from './types' const SECOND = 1 @@ -25,19 +25,21 @@ const times = { * ------------------------------------------------------------------------------- */ class TimeEstimates { - translate(displayStr: string, value: number | undefined) { + constructor(private options: Options) {} + + private translate(displayStr: string, value: number | undefined) { let key = displayStr if (value !== undefined && value !== 1) { key += 's' } - const { timeEstimation } = zxcvbnOptions.translations + const { timeEstimation } = this.options.translations return timeEstimation[key as keyof typeof timeEstimation].replace( '{base}', `${value}`, ) } - estimateAttackTimes(guesses: number) { + public estimateAttackTimes(guesses: number) { const crackTimesSeconds: CrackTimesSeconds = { onlineThrottling100PerHour: guesses / (100 / 3600), onlineNoThrottling10PerSecond: guesses / 10, @@ -62,7 +64,7 @@ class TimeEstimates { } } - guessesToScore(guesses: number): Score { + private guessesToScore(guesses: number): Score { const DELTA = 5 if (guesses < 1e3 + DELTA) { // risky password: "too guessable" @@ -85,7 +87,7 @@ class TimeEstimates { return 4 } - displayTime(seconds: number) { + private displayTime(seconds: number) { let displayStr = 'centuries' let base const timeKeys = Object.keys(times) diff --git a/packages/libraries/main/src/index.ts b/packages/libraries/main/src/index.ts index 04c38405..1cca250d 100644 --- a/packages/libraries/main/src/index.ts +++ b/packages/libraries/main/src/index.ts @@ -1,67 +1,79 @@ import Matching from './Matching' -import scoring from './scoring' 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, 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 = {}, + ) { + 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 createReturnValue( + resolvedMatches: MatchExtended[], + password: string, + start: number, + ): ZxcvbnResult { + const feedback = new Feedback(this.options) + const timeEstimates = new TimeEstimates(this.options) + const matchSequence = this.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 matching = new Matching() + private main(password: string, userInputs?: (string | number)[]) { + if (userInputs) { + this.options.extendUserInputsDictionary(userInputs) + } - return matching.match(password) -} + const matching = new Matching(this.options) -export const zxcvbn = (password: string, userInputs?: (string | number)[]) => { - const start = time() - const matches = main(password, userInputs) + return matching.match(password) + } - if (matches instanceof Promise) { - throw new Error( - 'You are using a Promised matcher, please use `zxcvbnAsync` for it.', - ) + public check(password: string, userInputs?: (string | number)[]) { + const usedPassword = password.substring(0, this.options.maxLength) + const start = time() + const matches = this.main(usedPassword, userInputs) + + if (matches instanceof Promise) { + throw new Error( + 'You are using a Promised matcher, please use `zxcvbnAsync` for it.', + ) + } + return this.createReturnValue(matches, usedPassword, start) } - return createReturnValue(matches, password, start) -} -export const zxcvbnAsync = async ( - password: string, - userInputs?: (string | number)[], -): Promise => { - const usedPassword = password.substring(0, zxcvbnOptions.maxLength) - const start = time() - const matches = await main(usedPassword, userInputs) + public async checkAsync(password: string, userInputs?: (string | number)[]) { + const usedPassword = password.substring(0, this.options.maxLength) + const start = time() + const matches = await this.main(usedPassword, userInputs) - return createReturnValue(matches, usedPassword, start) + return this.createReturnValue(matches, usedPassword, start) + } } export * from './types' -export { zxcvbnOptions, Options, debounce } +export { ZxcvbnFactory, Options, debounce } diff --git a/packages/libraries/main/src/matcher/date/feedback.ts b/packages/libraries/main/src/matcher/date/feedback.ts index 26250b8d..23d92761 100644 --- a/packages/libraries/main/src/matcher/date/feedback.ts +++ b/packages/libraries/main/src/matcher/date/feedback.ts @@ -1,8 +1,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], } } diff --git a/packages/libraries/main/src/matcher/date/matching.ts b/packages/libraries/main/src/matcher/date/matching.ts index d60e40dd..95b2acc6 100644 --- a/packages/libraries/main/src/matcher/date/matching.ts +++ b/packages/libraries/main/src/matcher/date/matching.ts @@ -4,8 +4,9 @@ import { DATE_SPLITS, REFERENCE_YEAR, } from '../../data/const' -import { sorted } from '../../helper' +import { sorted } from '../../utils/helper' import { DateMatch } from '../../types' +import { Options } from '../../Options' interface DateMatchOptions { password: string @@ -17,6 +18,8 @@ interface DateMatchOptions { * ------------------------------------------------------------------------------- */ class MatchDate { + constructor(private options: Options) {} + /* * a "date" is recognized as: * any 3-tuple that starts or ends with a 2- or 4-digit year, diff --git a/packages/libraries/main/src/matcher/dictionary/feedback.ts b/packages/libraries/main/src/matcher/dictionary/feedback.ts index 8c683084..74626155 100644 --- a/packages/libraries/main/src/matcher/dictionary/feedback.ts +++ b/packages/libraries/main/src/matcher/dictionary/feedback.ts @@ -1,79 +1,90 @@ -import { zxcvbnOptions } from '../../Options' +import { Options } from '../../Options' import { MatchEstimated } from '../../types' import { ALL_UPPER_INVERTED, START_UPPER } from '../../data/const' const getDictionaryWarningPassword = ( + options: Options, match: MatchEstimated, isSoleMatch?: boolean, ) => { let warning: string | null = 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 = ( + options: Options, match: MatchEstimated, isSoleMatch?: boolean, ) => { let warning: string | null = null if (isSoleMatch) { - warning = zxcvbnOptions.translations.warnings.wordByItself + warning = options.translations.warnings.wordByItself } return warning } const getDictionaryWarningNames = ( + options: Options, match: MatchEstimated, 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) => { +const getDictionaryWarning = ( + options: Options, + match: MatchEstimated, + isSoleMatch?: boolean, +) => { let warning: string | null = null const dictName = match.dictionaryName const isAName = dictName === 'lastnames' || dictName.toLowerCase().includes('firstnames') if (dictName === 'passwords') { - warning = getDictionaryWarningPassword(match, isSoleMatch) + warning = getDictionaryWarningPassword(options, match, isSoleMatch) } else if (dictName.includes('wikipedia')) { - warning = getDictionaryWarningWikipedia(match, isSoleMatch) + warning = getDictionaryWarningWikipedia(options, match, isSoleMatch) } else if (isAName) { - warning = getDictionaryWarningNames(match, isSoleMatch) + warning = getDictionaryWarningNames(options, match, isSoleMatch) } else if (dictName === 'userInputs') { - warning = zxcvbnOptions.translations.warnings.userInputs + warning = options.translations.warnings.userInputs } return warning } -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[] = [] 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) } return { warning, diff --git a/packages/libraries/main/src/matcher/dictionary/matching.ts b/packages/libraries/main/src/matcher/dictionary/matching.ts index b865447d..338eec28 100644 --- a/packages/libraries/main/src/matcher/dictionary/matching.ts +++ b/packages/libraries/main/src/matcher/dictionary/matching.ts @@ -1,8 +1,8 @@ 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' import Reverse from './variants/matching/reverse' import L33t from './variants/matching/l33t' @@ -13,9 +13,9 @@ class MatchDictionary { reverse: Reverse - 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) { @@ -35,11 +35,11 @@ class MatchDictionary { const passwordLower = password.toLowerCase() // eslint-disable-next-line complexity,max-statements - Object.keys(zxcvbnOptions.rankedDictionaries).forEach((dictionaryName) => { + Object.keys(this.options.rankedDictionaries).forEach((dictionaryName) => { const rankedDict = - zxcvbnOptions.rankedDictionaries[dictionaryName as DictionaryNames] + this.options.rankedDictionaries[dictionaryName as DictionaryNames] const longestDictionaryWordSize = - zxcvbnOptions.rankedDictionariesMaxWordSize[dictionaryName] + this.options.rankedDictionariesMaxWordSize[dictionaryName] const searchWidth = Math.min(longestDictionaryWordSize, passwordLength) for (let i = 0; i < passwordLength; i += 1) { const searchEnd = Math.min(i + searchWidth, passwordLength) @@ -52,7 +52,7 @@ class MatchDictionary { // and because otherwise there would be to many false positives const isFullPassword = i === 0 && j === passwordLength - 1 if ( - zxcvbnOptions.useLevenshteinDistance && + this.options.useLevenshteinDistance && isFullPassword && !isInDictionary && useLevenshtein @@ -60,7 +60,7 @@ class MatchDictionary { foundLevenshteinDistance = findLevenshteinDistance( usedPassword, rankedDict, - zxcvbnOptions.levenshteinThreshold, + this.options.levenshteinThreshold, ) } const isLevenshteinMatch = diff --git a/packages/libraries/main/src/matcher/dictionary/variants/matching/l33t.ts b/packages/libraries/main/src/matcher/dictionary/variants/matching/l33t.ts index aac0a80a..7f3f2d33 100644 --- a/packages/libraries/main/src/matcher/dictionary/variants/matching/l33t.ts +++ b/packages/libraries/main/src/matcher/dictionary/variants/matching/l33t.ts @@ -1,4 +1,4 @@ -import { zxcvbnOptions } from '../../../../Options' +import { Options } from '../../../../Options' import { DictionaryMatch, L33tMatch } from '../../../../types' import { DefaultMatch } from '../../types' import getCleanPasswords, { @@ -54,11 +54,10 @@ const getExtras = ( * ------------------------------------------------------------------------------- */ class MatchL33t { - defaultMatch: DefaultMatch - - constructor(defaultMatch: DefaultMatch) { - this.defaultMatch = defaultMatch - } + constructor( + private options: Options, + private defaultMatch: DefaultMatch, + ) {} isAlreadyIncluded(matches: L33tMatch[], newMatch: L33tMatch) { return matches.some((l33tMatch) => { @@ -72,8 +71,8 @@ class MatchL33t { const matches: L33tMatch[] = [] const subbedPasswords = getCleanPasswords( password, - zxcvbnOptions.l33tMaxSubstitutions, - zxcvbnOptions.trieNodeRoot, + this.options.l33tMaxSubstitutions, + this.options.trieNodeRoot, ) let hasFullMatch = false let isFullSubstitution = true diff --git a/packages/libraries/main/src/matcher/dictionary/variants/matching/reverse.ts b/packages/libraries/main/src/matcher/dictionary/variants/matching/reverse.ts index ecc571ae..dc0bdc70 100644 --- a/packages/libraries/main/src/matcher/dictionary/variants/matching/reverse.ts +++ b/packages/libraries/main/src/matcher/dictionary/variants/matching/reverse.ts @@ -1,5 +1,6 @@ import { DictionaryMatch } from '../../../../types' import { DefaultMatch } from '../../types' +import { Options } from '../../../../Options' /* * ------------------------------------------------------------------------------- @@ -7,11 +8,10 @@ import { DefaultMatch } from '../../types' * ------------------------------------------------------------------------------- */ class MatchReverse { - defaultMatch: DefaultMatch - - constructor(defaultMatch: DefaultMatch) { - this.defaultMatch = defaultMatch - } + constructor( + private options: Options, + private defaultMatch: DefaultMatch, + ) {} match({ password }: { password: string }) { const passwordReversed = password.split('').reverse().join('') diff --git a/packages/libraries/main/src/matcher/regex/feedback.ts b/packages/libraries/main/src/matcher/regex/feedback.ts index db552ccb..ba36d991 100644 --- a/packages/libraries/main/src/matcher/regex/feedback.ts +++ b/packages/libraries/main/src/matcher/regex/feedback.ts @@ -1,13 +1,13 @@ -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, ], } } diff --git a/packages/libraries/main/src/matcher/regex/matching.ts b/packages/libraries/main/src/matcher/regex/matching.ts index 99cda779..dbdaf926 100644 --- a/packages/libraries/main/src/matcher/regex/matching.ts +++ b/packages/libraries/main/src/matcher/regex/matching.ts @@ -1,6 +1,7 @@ import { REGEXEN } from '../../data/const' -import { sorted } from '../../helper' +import { sorted } from '../../utils/helper' import { RegexMatch } from '../../types' +import { Options } from '../../Options' interface RegexMatchOptions { password: string @@ -14,6 +15,8 @@ type RegexesKeys = keyof typeof REGEXEN * ------------------------------------------------------------------------------- */ class MatchRegex { + constructor(private options: Options) {} + match({ password, regexes = REGEXEN }: RegexMatchOptions) { const matches: RegexMatch[] = [] Object.keys(regexes).forEach((name) => { diff --git a/packages/libraries/main/src/matcher/repeat/feedback.ts b/packages/libraries/main/src/matcher/repeat/feedback.ts index 077283a5..1db0e2b8 100644 --- a/packages/libraries/main/src/matcher/repeat/feedback.ts +++ b/packages/libraries/main/src/matcher/repeat/feedback.ts @@ -1,14 +1,14 @@ -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 } return { warning, - suggestions: [zxcvbnOptions.translations.suggestions.repeated], + suggestions: [options.translations.suggestions.repeated], } } diff --git a/packages/libraries/main/src/matcher/repeat/matching.ts b/packages/libraries/main/src/matcher/repeat/matching.ts index 91e47fe0..f0c4cbf8 100644 --- a/packages/libraries/main/src/matcher/repeat/matching.ts +++ b/packages/libraries/main/src/matcher/repeat/matching.ts @@ -1,6 +1,7 @@ import { RepeatMatch } from '../../types' -import scoring from '../../scoring' +import Scoring from '../../scoring' import Matching from '../../Matching' +import { Options } from '../../Options' interface RepeatMatchOptions { password: string @@ -12,6 +13,12 @@ interface RepeatMatchOptions { *------------------------------------------------------------------------------- */ class MatchRepeat { + private scoring: Scoring + + constructor(private options: Options) { + this.scoring = new Scoring(options) + } + // eslint-disable-next-line max-statements match({ password, omniMatch }: RepeatMatchOptions) { const matches: (RepeatMatch | Promise)[] = [] @@ -123,14 +130,17 @@ class MatchRepeat { const matches = omniMatch.match(baseToken) if (matches instanceof Promise) { return matches.then((resolvedMatches) => { - const baseAnalysis = scoring.mostGuessableMatchSequence( + 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 } } diff --git a/packages/libraries/main/src/matcher/separator/matching.ts b/packages/libraries/main/src/matcher/separator/matching.ts index bfdf78b8..6bf459f1 100644 --- a/packages/libraries/main/src/matcher/separator/matching.ts +++ b/packages/libraries/main/src/matcher/separator/matching.ts @@ -1,5 +1,6 @@ import { SEPERATOR_CHARS } from '../../data/const' import { SeparatorMatch } from '../../types' +import { Options } from '../../Options' interface SeparatorMatchOptions { password: string @@ -13,6 +14,8 @@ const separatorRegex = new RegExp(`[${SEPERATOR_CHARS.join('')}]`) *------------------------------------------------------------------------------- */ class MatchSeparator { + constructor(private options: Options) {} + static getMostUsedSeparatorChar(password: string): string | undefined { const mostUsedSeperators = [ ...password diff --git a/packages/libraries/main/src/matcher/sequence/feedback.ts b/packages/libraries/main/src/matcher/sequence/feedback.ts index d4098320..feee74ae 100644 --- a/packages/libraries/main/src/matcher/sequence/feedback.ts +++ b/packages/libraries/main/src/matcher/sequence/feedback.ts @@ -1,8 +1,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], } } diff --git a/packages/libraries/main/src/matcher/sequence/matching.ts b/packages/libraries/main/src/matcher/sequence/matching.ts index eac3e1ae..98d666c2 100644 --- a/packages/libraries/main/src/matcher/sequence/matching.ts +++ b/packages/libraries/main/src/matcher/sequence/matching.ts @@ -1,5 +1,6 @@ import { ALL_UPPER, ALL_LOWER, ALL_DIGIT } from '../../data/const' import { SequenceMatch } from '../../types' +import { Options } from '../../Options' type UpdateParams = { i: number @@ -20,6 +21,8 @@ interface SequenceMatchOptions { class MatchSequence { MAX_DELTA = 5 + constructor(private options: Options) {} + // eslint-disable-next-line max-statements match({ password }: SequenceMatchOptions) { /* diff --git a/packages/libraries/main/src/matcher/spatial/feedback.ts b/packages/libraries/main/src/matcher/spatial/feedback.ts index 1d1181c0..06351c82 100644 --- a/packages/libraries/main/src/matcher/spatial/feedback.ts +++ b/packages/libraries/main/src/matcher/spatial/feedback.ts @@ -1,13 +1,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], } } diff --git a/packages/libraries/main/src/matcher/spatial/matching.ts b/packages/libraries/main/src/matcher/spatial/matching.ts index 596e5f3e..04774edd 100644 --- a/packages/libraries/main/src/matcher/spatial/matching.ts +++ b/packages/libraries/main/src/matcher/spatial/matching.ts @@ -1,5 +1,5 @@ -import { sorted, extend } from '../../helper' -import { zxcvbnOptions } from '../../Options' +import { sorted, extend } from '../../utils/helper' +import { Options } from '../../Options' import { LooseObject, SpatialMatch } from '../../types' interface SpatialMatchOptions { @@ -13,10 +13,12 @@ interface SpatialMatchOptions { class MatchSpatial { SHIFTED_RX = /[~!@#$%^&*()_+QWERTYUIOP{}|ASDFGHJKL:"ZXCVBNM<>?]/ + 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)) }) return sorted(matches) diff --git a/packages/libraries/main/src/matcher/spatial/scoring.ts b/packages/libraries/main/src/matcher/spatial/scoring.ts index c4f78699..c70db33e 100644 --- a/packages/libraries/main/src/matcher/spatial/scoring.ts +++ b/packages/libraries/main/src/matcher/spatial/scoring.ts @@ -1,5 +1,5 @@ import utils from '../../scoring/utils' -import { zxcvbnOptions } from '../../Options' +import { Options } from '../../Options' import { LooseObject, MatchEstimated, MatchExtended } from '../../types' interface EstimatePossiblePatternsOptions { @@ -18,13 +18,12 @@ const calcAverageDegree = (graph: LooseObject) => { return average } -const estimatePossiblePatterns = ({ - token, - graph, - turns, -}: EstimatePossiblePatternsOptions) => { - const startingPosition = Object.keys(zxcvbnOptions.graphs[graph]).length - const averageDegree = calcAverageDegree(zxcvbnOptions.graphs[graph]) +const estimatePossiblePatterns = ( + options: Options, + { token, graph, turns }: EstimatePossiblePatternsOptions, +) => { + const startingPosition = Object.keys(options.graphs[graph]).length + const averageDegree = calcAverageDegree(options.graphs[graph]) let guesses = 0 const tokenLength = token.length @@ -38,13 +37,11 @@ const estimatePossiblePatterns = ({ return guesses } -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, { token, graph, turns }) // add extra guesses for shifted keys. (% instead of 5, A instead of a.) // math is similar to extra guesses of l33t substitutions in dictionary matches. diff --git a/packages/libraries/main/src/scoring/estimate.ts b/packages/libraries/main/src/scoring/estimate.ts index 0ed9dffe..164e8d1b 100644 --- a/packages/libraries/main/src/scoring/estimate.ts +++ b/packages/libraries/main/src/scoring/estimate.ts @@ -3,7 +3,7 @@ import { MIN_SUBMATCH_GUESSES_MULTI_CHAR, } from '../data/const' import utils from './utils' -import { zxcvbnOptions } from '../Options' +import { Options } from '../Options' import { DefaultScoringFunction, LooseObject, @@ -49,15 +49,16 @@ const matchers: Matchers = { separator: separatorMatcher, } -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) } return 0 } @@ -66,7 +67,11 @@ const getScoring = (name: string, match: MatchExtended | MatchEstimated) => { // guess estimation -- one function per match pattern --------------------------- // ------------------------------------------------------------------------------ // 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 = {} // a match's guess estimate doesn't change. cache it. if ('guesses' in match && match.guesses != null) { @@ -75,7 +80,7 @@ export default (match: MatchExtended | MatchEstimated, password: string) => { const minGuesses = getMinGuesses(match, password) - const estimationResult = getScoring(match.pattern, match) + const estimationResult = getScoring(options, match.pattern, match) let guesses = 0 if (typeof estimationResult === 'number') { guesses = estimationResult diff --git a/packages/libraries/main/src/scoring/index.ts b/packages/libraries/main/src/scoring/index.ts index 6c759caf..1c441ce5 100644 --- a/packages/libraries/main/src/scoring/index.ts +++ b/packages/libraries/main/src/scoring/index.ts @@ -7,6 +7,7 @@ import { MatchEstimated, LooseObject, } from '../types' +import { Options } from '../Options' const scoringHelper = { password: '', @@ -37,9 +38,9 @@ const scoringHelper = { // helper: considers whether a length-sequenceLength // sequence ending at match m is better (fewer guesses) // 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 if (sequenceLength > 1) { // we're considering a length-sequenceLength sequence ending with match m: @@ -75,10 +76,10 @@ const scoringHelper = { }, // 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) for (let i = 1; i <= passwordCharIndex; i += 1) { // generate passwordCharIndex bruteforce matches, spanning from (i=1, j=passwordCharIndex) up to (i=passwordCharIndex, j=passwordCharIndex). @@ -95,7 +96,7 @@ const scoringHelper = { // --> safe to skip those cases. if (lastMatch.pattern !== 'bruteforce') { // try adding m to this length-sequenceLength sequence. - this.update(match, parseInt(sequenceLength, 10) + 1) + this.update(options, match, parseInt(sequenceLength, 10) + 1) } }) } @@ -131,7 +132,9 @@ const scoringHelper = { }, } -export default { +export default class Scoring { + constructor(private options: Options) {} + // ------------------------------------------------------------------------------ // search --- most guessable match sequence ------------------------------------- // ------------------------------------------------------------------------------ @@ -206,14 +209,18 @@ export default { if (match.i > 0) { 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) } const optimalMatchSequence = scoringHelper.unwind(passwordLength) const optimalSequenceLength = optimalMatchSequence.length @@ -224,7 +231,7 @@ export default { guessesLog10: utils.log10(guesses), sequence: optimalMatchSequence, } - }, + } getGuesses(password: string, optimalSequenceLength: number) { const passwordLength = password.length @@ -236,5 +243,5 @@ export default { scoringHelper.optimal.g[passwordLength - 1][optimalSequenceLength] } return guesses - }, + } } diff --git a/packages/libraries/main/src/types.ts b/packages/libraries/main/src/types.ts index 045647d2..c513f261 100644 --- a/packages/libraries/main/src/types.ts +++ b/packages/libraries/main/src/types.ts @@ -4,6 +4,7 @@ import { REGEXEN } from './data/const' import { DictionaryReturn } from './matcher/dictionary/scoring' import Matching from './Matching' import { PasswordChanges } from './matcher/dictionary/variants/matching/unmunger/getCleanPasswords' +import { Options } from './Options' export type TranslationKeys = typeof translationKeys export type L33tTableDefault = typeof l33tTableDefault @@ -220,12 +221,14 @@ export interface RankedDictionaries { } export type DefaultFeedbackFunction = ( + options: Options, match: MatchEstimated, isSoleMatch?: boolean, ) => FeedbackType | null export type DefaultScoringFunction = ( match: MatchExtended | MatchEstimated, + options: Options, ) => number | DictionaryReturn export interface MatchOptions { @@ -236,7 +239,7 @@ export interface MatchOptions { omniMatch: Matching } -export type MatchingType = new () => { +export type MatchingType = new (options: Options) => { match({ password, omniMatch, diff --git a/packages/libraries/main/src/debounce.ts b/packages/libraries/main/src/utils/debounce.ts similarity index 100% rename from packages/libraries/main/src/debounce.ts rename to packages/libraries/main/src/utils/debounce.ts diff --git a/packages/libraries/main/src/helper.ts b/packages/libraries/main/src/utils/helper.ts similarity index 95% rename from packages/libraries/main/src/helper.ts rename to packages/libraries/main/src/utils/helper.ts index 3d283d3c..c0d9f1c7 100644 --- a/packages/libraries/main/src/helper.ts +++ b/packages/libraries/main/src/utils/helper.ts @@ -1,4 +1,4 @@ -import { LooseObject, MatchExtended } from './types' +import { LooseObject, MatchExtended } from '../types' export const empty = (obj: LooseObject) => Object.keys(obj).length === 0 diff --git a/packages/libraries/main/src/levenshtein.ts b/packages/libraries/main/src/utils/levenshtein.ts similarity index 97% rename from packages/libraries/main/src/levenshtein.ts rename to packages/libraries/main/src/utils/levenshtein.ts index 2c83a206..a921ce6f 100644 --- a/packages/libraries/main/src/levenshtein.ts +++ b/packages/libraries/main/src/utils/levenshtein.ts @@ -1,5 +1,5 @@ import { distance } from 'fastest-levenshtein' -import { LooseObject } from './types' +import { LooseObject } from '../types' const getUsedThreshold = ( password: string, diff --git a/packages/libraries/main/test/asyncMatcher.spec.ts b/packages/libraries/main/test/asyncMatcher.spec.ts index a27addcf..3868af2d 100644 --- a/packages/libraries/main/test/asyncMatcher.spec.ts +++ b/packages/libraries/main/test/asyncMatcher.spec.ts @@ -1,17 +1,8 @@ import * as zxcvbnCommonPackage from '../../../languages/common/src' import * as zxcvbnEnPackage from '../../../languages/en/src' -import { zxcvbn, zxcvbnAsync, zxcvbnOptions } from '../src' +import { ZxcvbnFactory } from '../src' import { Matcher, MatchExtended } from '../src/types' -zxcvbnOptions.setOptions({ - dictionary: { - ...zxcvbnCommonPackage.dictionary, - ...zxcvbnEnPackage.dictionary, - }, - graphs: zxcvbnCommonPackage.adjacencyGraphs, - translations: zxcvbnEnPackage.translations, -}) - const asyncMatcher: Matcher = { Matching: class MatchAsync { match({ password }: { password: string }): Promise { @@ -41,11 +32,22 @@ const asyncMatcher: Matcher = { }, } -zxcvbnOptions.addMatcher('minLength', asyncMatcher) - describe('asyncMatcher', () => { + const zxcvbn = new ZxcvbnFactory( + { + dictionary: { + ...zxcvbnCommonPackage.dictionary, + ...zxcvbnEnPackage.dictionary, + }, + graphs: zxcvbnCommonPackage.adjacencyGraphs, + translations: zxcvbnEnPackage.translations, + }, + { + minLength: asyncMatcher, + }, + ) it('should use async matcher as a promise', async () => { - const promiseResult = zxcvbnAsync('ep8fkw8ds') + const promiseResult = zxcvbn.checkAsync('ep8fkw8ds') expect(promiseResult instanceof Promise).toBe(true) const result = await promiseResult @@ -54,7 +56,7 @@ describe('asyncMatcher', () => { it('should throw an error for wrong function usage', async () => { expect(() => { - zxcvbn('ep8fkw8ds') + zxcvbn.check('ep8fkw8ds') }).toThrow( 'You are using a Promised matcher, please use `zxcvbnAsync` for it.', ) diff --git a/packages/libraries/main/test/customMatcher.spec.ts b/packages/libraries/main/test/customMatcher.spec.ts index 51b266c7..696f46ed 100644 --- a/packages/libraries/main/test/customMatcher.spec.ts +++ b/packages/libraries/main/test/customMatcher.spec.ts @@ -1,20 +1,13 @@ import * as zxcvbnCommonPackage from '../../../languages/common/src' import * as zxcvbnEnPackage from '../../../languages/en/src' -import { zxcvbn, zxcvbnOptions } from '../src' +import { Options, ZxcvbnFactory } from '../src' import { Match, Matcher } from '../src/types' -import { sorted } from '../src/helper' - -zxcvbnOptions.setOptions({ - dictionary: { - ...zxcvbnCommonPackage.dictionary, - ...zxcvbnEnPackage.dictionary, - }, - graphs: zxcvbnCommonPackage.adjacencyGraphs, - translations: zxcvbnEnPackage.translations, -}) +import { sorted } from '../src/utils/helper' const minLengthMatcher: Matcher = { Matching: class MatchMinLength { + constructor(private options: Options) {} + minLength = 10 match({ password }: { password: string }) { @@ -40,12 +33,22 @@ const minLengthMatcher: Matcher = { return match.token.length * 10 }, } - -zxcvbnOptions.addMatcher('minLength', minLengthMatcher) - describe('customMatcher', () => { + const zxcvbn = new ZxcvbnFactory( + { + dictionary: { + ...zxcvbnCommonPackage.dictionary, + ...zxcvbnEnPackage.dictionary, + }, + graphs: zxcvbnCommonPackage.adjacencyGraphs, + translations: zxcvbnEnPackage.translations, + }, + { + minLength: minLengthMatcher, + }, + ) it('should use minLength custom matcher', () => { - const result = zxcvbn('ep8fkw8ds') + const result = zxcvbn.check('ep8fkw8ds') expect(result.calcTime).toBeDefined() result.calcTime = 0 expect(result).toEqual({ diff --git a/packages/libraries/main/test/feedback.spec.ts b/packages/libraries/main/test/feedback.spec.ts index 4f1e033a..7427d994 100644 --- a/packages/libraries/main/test/feedback.spec.ts +++ b/packages/libraries/main/test/feedback.spec.ts @@ -1,14 +1,13 @@ import translations from '../../../languages/en/src/translations' -import { zxcvbnOptions } from '../src/Options' +import { Options } from '../src/Options' import Feedback from '../src/Feedback' -zxcvbnOptions.setOptions({ +const zxcvbnOptions = new Options({ translations, }) - describe('feedback', () => { describe('with default translations', () => { - const feedbackClass = new Feedback() + const feedbackClass = new Feedback(zxcvbnOptions) it('should return no feedback for a good password', () => { // @ts-ignore diff --git a/packages/libraries/main/test/main.spec.ts b/packages/libraries/main/test/main.spec.ts index 2f5801e1..8a7202b6 100644 --- a/packages/libraries/main/test/main.spec.ts +++ b/packages/libraries/main/test/main.spec.ts @@ -1,11 +1,12 @@ import * as zxcvbnCommonPackage from '../../../languages/common/src' import * as zxcvbnEnPackage from '../../../languages/en/src' -import { zxcvbn, zxcvbnOptions } from '../src' +import { ZxcvbnFactory } from '../src' import passwordTests from './helper/passwordTests' describe('main', () => { + let zxcvbn: ZxcvbnFactory beforeEach(() => { - zxcvbnOptions.setOptions({ + zxcvbn = new ZxcvbnFactory({ dictionary: { ...zxcvbnCommonPackage.dictionary, ...zxcvbnEnPackage.dictionary, @@ -16,7 +17,7 @@ describe('main', () => { }) it('should check without userInputs', () => { - const result = zxcvbn('test') + const result = zxcvbn.check('test') expect(result.calcTime).toBeDefined() result.calcTime = 0 expect(result).toEqual({ @@ -63,11 +64,17 @@ describe('main', () => { }) it('should check with userInputs', () => { - zxcvbnOptions.setOptions({ - // @ts-ignore - dictionary: { userInputs: ['test', 12, true, []] }, + const zxcvbnCustom = new ZxcvbnFactory({ + dictionary: { + ...zxcvbnCommonPackage.dictionary, + ...zxcvbnEnPackage.dictionary, + // @ts-ignore + userInputs: ['test', 12, true, []], + }, + graphs: zxcvbnCommonPackage.adjacencyGraphs, + translations: zxcvbnEnPackage.translations, }) - const result = zxcvbn('test') + const result = zxcvbnCustom.check('test') result.calcTime = 0 expect(result).toEqual({ crackTimesDisplay: { @@ -113,7 +120,7 @@ describe('main', () => { }) it('should check with userInputs on the fly', () => { - const result = zxcvbn('onTheFly', ['onTheFly']) + const result = zxcvbn.check('onTheFly', ['onTheFly']) result.calcTime = 0 expect(result).toEqual({ calcTime: 0, @@ -160,21 +167,21 @@ describe('main', () => { describe('attack vectors', () => { it('should not die while processing and have a appropriate calcTime for l33t attack', () => { - const result = zxcvbn( + const result = zxcvbn.check( '4@8({[ { - const result = zxcvbn( + const result = zxcvbn.check( '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!', ) expect(result.calcTime).toBeLessThan(2000) }) it('should not die while processing and have a appropriate calcTime for regex attacks', () => { - const result = zxcvbn(`\x00\x00${'\x00'.repeat(100)}\n`) + const result = zxcvbn.check(`\x00\x00${'\x00'.repeat(100)}\n`) expect(result.calcTime).toBeLessThan(2000) }) }) @@ -182,13 +189,15 @@ describe('main', () => { describe('password tests', () => { passwordTests.forEach((data) => { it(`should resolve ${data.password}`, () => { - zxcvbnOptions.setOptions({ + const zxcvbnCustom = new ZxcvbnFactory({ dictionary: { ...zxcvbnCommonPackage.dictionary, ...zxcvbnEnPackage.dictionary, }, + graphs: zxcvbnCommonPackage.adjacencyGraphs, + translations: zxcvbnEnPackage.translations, }) - const result = zxcvbn(data.password) + const result = zxcvbnCustom.check(data.password) result.calcTime = 0 expect(JSON.stringify(result)).toEqual(JSON.stringify(data)) }) diff --git a/packages/libraries/main/test/matcher/date/matching.spec.ts b/packages/libraries/main/test/matcher/date/matching.spec.ts index f8e8330c..48c77d98 100644 --- a/packages/libraries/main/test/matcher/date/matching.spec.ts +++ b/packages/libraries/main/test/matcher/date/matching.spec.ts @@ -1,9 +1,11 @@ import MatchDate from '../../../src/matcher/date/matching' import checkMatches from '../../helper/checkMatches' import genpws from '../../helper/genpws' +import { Options } from '../../../src' describe('date matching', () => { - const matchDate = new MatchDate() + const zxcvbnOptions = new Options() + const matchDate = new MatchDate(zxcvbnOptions) let password let matches let msg diff --git a/packages/libraries/main/test/matcher/dictionary/matching.spec.ts b/packages/libraries/main/test/matcher/dictionary/matching.spec.ts index 116eb81d..fefd405a 100644 --- a/packages/libraries/main/test/matcher/dictionary/matching.spec.ts +++ b/packages/libraries/main/test/matcher/dictionary/matching.spec.ts @@ -3,19 +3,18 @@ import * as zxcvbnEnPackage from '../../../../../languages/en/src' import MatchDictionary from '../../../src/matcher/dictionary/matching' import checkMatches from '../../helper/checkMatches' import genpws from '../../helper/genpws' -import { zxcvbnOptions } from '../../../src/Options' - -zxcvbnOptions.setOptions({ - dictionary: { - ...zxcvbnCommonPackage.dictionary, - ...zxcvbnEnPackage.dictionary, - }, - translations: zxcvbnEnPackage.translations, -}) +import { Options } from '../../../src/Options' describe('dictionary matching', () => { describe('Default dictionary', () => { - const matchDictionary = new MatchDictionary() + const zxcvbnOptions = new Options({ + dictionary: { + ...zxcvbnCommonPackage.dictionary, + ...zxcvbnEnPackage.dictionary, + }, + translations: zxcvbnEnPackage.translations, + }) + const matchDictionary = new MatchDictionary(zxcvbnOptions) const matches = matchDictionary .match({ password: 'we' }) // @ts-ignore @@ -41,10 +40,11 @@ describe('dictionary matching', () => { d1: ['motherboard', 'mother', 'board', 'abcd', 'cdef'], d2: ['z', '8', '99', '$', 'asdf1234&*'], } - zxcvbnOptions.setOptions({ + const zxcvbnOptions = new Options({ dictionary: testDicts, + translations: zxcvbnEnPackage.translations, }) - const matchDictionary = new MatchDictionary() + const matchDictionary = new MatchDictionary(zxcvbnOptions) const dm = (pw: string) => matchDictionary .match({ password: pw }) @@ -155,12 +155,13 @@ describe('dictionary matching', () => { }) describe('with user input', () => { - zxcvbnOptions.setOptions({ + const zxcvbnOptions = new Options({ dictionary: { userInputs: ['foo', 'bar'], }, + translations: zxcvbnEnPackage.translations, }) - const matchDictionary = new MatchDictionary() + const matchDictionary = new MatchDictionary(zxcvbnOptions) const matches = matchDictionary .match({ password: 'foobar' }) // @ts-ignore diff --git a/packages/libraries/main/test/matcher/dictionary/variant/matching/dictionaryReverse.spec.ts b/packages/libraries/main/test/matcher/dictionary/variant/matching/dictionaryReverse.spec.ts index 240178e0..75caede3 100644 --- a/packages/libraries/main/test/matcher/dictionary/variant/matching/dictionaryReverse.spec.ts +++ b/packages/libraries/main/test/matcher/dictionary/variant/matching/dictionaryReverse.spec.ts @@ -1,19 +1,18 @@ import MatchDictionaryReverse from '../../../../../src/matcher/dictionary/variants/matching/reverse' import MatchDictionary from '../../../../../src/matcher/dictionary/matching' import checkMatches from '../../../../helper/checkMatches' -import { zxcvbnOptions } from '../../../../../src/Options' - -zxcvbnOptions.setOptions() -const dictionaryMatcher = new MatchDictionary() +import { Options } from '../../../../../src/Options' describe('dictionary reverse matching', () => { const testDicts = { d1: [123, 321, 456, 654], } - zxcvbnOptions.setOptions({ + const zxcvbnOptions = new Options({ dictionary: testDicts, }) + const dictionaryMatcher = new MatchDictionary(zxcvbnOptions) const matchDictionaryReverse = new MatchDictionaryReverse( + zxcvbnOptions, dictionaryMatcher.defaultMatch, ) const password = '0123456789' diff --git a/packages/libraries/main/test/matcher/dictionary/variant/matching/l33t.spec.ts b/packages/libraries/main/test/matcher/dictionary/variant/matching/l33t.spec.ts index 15e5576f..6512ceb3 100644 --- a/packages/libraries/main/test/matcher/dictionary/variant/matching/l33t.spec.ts +++ b/packages/libraries/main/test/matcher/dictionary/variant/matching/l33t.spec.ts @@ -1,11 +1,8 @@ import MatchL33t from '../../../../../src/matcher/dictionary/variants/matching/l33t' import MatchDictionary from '../../../../../src/matcher/dictionary/matching' import checkMatches from '../../../../helper/checkMatches' -import { zxcvbnOptions } from '../../../../../src/Options' -import { sorted } from '../../../../../src/helper' - -zxcvbnOptions.setOptions() -const dictionaryMatcher = new MatchDictionary() +import { Options } from '../../../../../src/Options' +import { sorted } from '../../../../../src/utils/helper' describe('l33t matching', () => { let msg @@ -24,18 +21,24 @@ describe('l33t matching', () => { } describe('default const', () => { - const matchL33t = new MatchL33t(dictionaryMatcher.defaultMatch) + const zxcvbnOptions = new Options() + const dictionaryMatcher = new MatchDictionary(zxcvbnOptions) + const matchL33t = new MatchL33t( + zxcvbnOptions, + dictionaryMatcher.defaultMatch, + ) it("doesn't match single-character l33ted words", () => { expect(matchL33t.match({ password: '4 1 @' })).toEqual([]) }) }) - zxcvbnOptions.setOptions({ + const zxcvbnOptions = new Options({ dictionary: dicts, l33tTable: testTable, l33tMaxSubstitutions: 15, }) - const matchL33t = new MatchL33t(dictionaryMatcher.defaultMatch) + const dictionaryMatcher = new MatchDictionary(zxcvbnOptions) + const matchL33t = new MatchL33t(zxcvbnOptions, dictionaryMatcher.defaultMatch) describe('main match', () => { it("doesn't match ''", () => { diff --git a/packages/libraries/main/test/matcher/dictionary/variant/scoring/l33t.spec.ts b/packages/libraries/main/test/matcher/dictionary/variant/scoring/l33t.spec.ts index d3c449ca..23069e4f 100644 --- a/packages/libraries/main/test/matcher/dictionary/variant/scoring/l33t.spec.ts +++ b/packages/libraries/main/test/matcher/dictionary/variant/scoring/l33t.spec.ts @@ -1,6 +1,6 @@ import l33t from '../../../../../src/matcher/dictionary/variants/scoring/l33t' import utils from '../../../../../src/scoring/utils' -import { empty } from '../../../../../src/helper' +import { empty } from '../../../../../src/utils/helper' import { LooseObject } from '../../../../../src/types' const { nCk } = utils diff --git a/packages/libraries/main/test/matcher/regex/matching.spec.ts b/packages/libraries/main/test/matcher/regex/matching.spec.ts index 6e9fb5a3..b14eef63 100644 --- a/packages/libraries/main/test/matcher/regex/matching.spec.ts +++ b/packages/libraries/main/test/matcher/regex/matching.spec.ts @@ -1,5 +1,6 @@ import MatchRegex from '../../../src/matcher/regex/matching' import checkMatches from '../../helper/checkMatches' +import { Options } from '../../../src' describe('regex matching', () => { const data = [ @@ -15,7 +16,8 @@ describe('regex matching', () => { }, ] - const matchRegex = new MatchRegex() + const zxcvbnOptions = new Options() + const matchRegex = new MatchRegex(zxcvbnOptions) data.forEach(({ pattern, regexNames, ijs }) => { const matches = matchRegex.match({ password: pattern }) const msg = `matches ${pattern} as a ${regexNames[0]} pattern` diff --git a/packages/libraries/main/test/matcher/repeat/matching.spec.ts b/packages/libraries/main/test/matcher/repeat/matching.spec.ts index 09acb44e..7fdc4f0c 100644 --- a/packages/libraries/main/test/matcher/repeat/matching.spec.ts +++ b/packages/libraries/main/test/matcher/repeat/matching.spec.ts @@ -2,13 +2,13 @@ import MatchRepeat from '../../../src/matcher/repeat/matching' import checkMatches from '../../helper/checkMatches' import genpws from '../../helper/genpws' import MatchOmni from '../../../src/Matching' -import { zxcvbnOptions } from '../../../src/Options' +import { Options } from '../../../src/Options' import { RepeatMatch } from '../../../src/types' -zxcvbnOptions.setOptions() -const omniMatch = new MatchOmni() +const zxcvbnOptions = new Options() +const omniMatch = new MatchOmni(zxcvbnOptions) describe('repeat matching', () => { - const matchRepeat = new MatchRepeat() + const matchRepeat = new MatchRepeat(zxcvbnOptions) it("doesn't match length repeat patterns", () => { const data = ['', '#'] diff --git a/packages/libraries/main/test/matcher/repeat/scoring.spec.ts b/packages/libraries/main/test/matcher/repeat/scoring.spec.ts index fc2e9287..aafc8beb 100644 --- a/packages/libraries/main/test/matcher/repeat/scoring.spec.ts +++ b/packages/libraries/main/test/matcher/repeat/scoring.spec.ts @@ -1,13 +1,13 @@ import repeatGuesses from '../../../src/matcher/repeat/scoring' -import scoring from '../../../src/scoring' +import Scoring from '../../../src/scoring' import MatchOmni from '../../../src/Matching' -import { zxcvbnOptions } from '../../../src/Options' +import { Options } from '../../../src/Options' import { MatchExtended } from '../../../src/types' -zxcvbnOptions.setOptions() - -const omniMatch = new MatchOmni() +const zxcvbnOptions = new Options() +const omniMatch = new MatchOmni(zxcvbnOptions) describe('scoring guesses repeated', () => { + const scoring = new Scoring(zxcvbnOptions) const data: [string, string, number][] = [ ['aa', 'a', 2], ['999', '9', 3], diff --git a/packages/libraries/main/test/matcher/separator/matching.spec.ts b/packages/libraries/main/test/matcher/separator/matching.spec.ts index a8c7deea..8169fac4 100644 --- a/packages/libraries/main/test/matcher/separator/matching.spec.ts +++ b/packages/libraries/main/test/matcher/separator/matching.spec.ts @@ -1,8 +1,10 @@ import MatchSeparator from '../../../src/matcher/separator/matching' import checkMatches from '../../helper/checkMatches' +import { Options } from '../../../src' describe('separator matching', () => { - const matchSeparator = new MatchSeparator() + const zxcvbnOptions = new Options() + const matchSeparator = new MatchSeparator(zxcvbnOptions) it("doesn't match length separators", () => { const data = [''] diff --git a/packages/libraries/main/test/matcher/sequence/matching.spec.ts b/packages/libraries/main/test/matcher/sequence/matching.spec.ts index 070eb3f4..47574d9e 100644 --- a/packages/libraries/main/test/matcher/sequence/matching.spec.ts +++ b/packages/libraries/main/test/matcher/sequence/matching.spec.ts @@ -1,9 +1,11 @@ import MatchSequence from '../../../src/matcher/sequence/matching' import checkMatches from '../../helper/checkMatches' import genpws from '../../helper/genpws' +import { Options } from '../../../src' describe('sequence matching', () => { - const matchSequence = new MatchSequence() + const zxcvbnOptions = new Options() + const matchSequence = new MatchSequence(zxcvbnOptions) it("doesn't match length sequences", () => { const data = ['', 'a', '1'] diff --git a/packages/libraries/main/test/matcher/spatial/matching.spec.ts b/packages/libraries/main/test/matcher/spatial/matching.spec.ts index 0c3ac58d..10186bfd 100644 --- a/packages/libraries/main/test/matcher/spatial/matching.spec.ts +++ b/packages/libraries/main/test/matcher/spatial/matching.spec.ts @@ -1,14 +1,15 @@ import MatchSpatial from '../../../src/matcher/spatial/matching' import checkMatches from '../../helper/checkMatches' import * as zxcvbnCommonPackage from '../../../../../languages/common/src' -import { zxcvbnOptions } from '../../../src/Options' +import { Options } from '../../../src/Options' import { LooseObject } from '../../../src/types' const { adjacencyGraphs } = zxcvbnCommonPackage describe('spatial matching', () => { it("doesn't match 1- and 2-character spatial patterns", () => { - const matchSpatial = new MatchSpatial() + const zxcvbnOptions = new Options() + const matchSpatial = new MatchSpatial(zxcvbnOptions) const data = ['', '/', 'qw', '*/'] data.forEach((password) => { expect(matchSpatial.match({ password })).toEqual([]) @@ -17,10 +18,8 @@ describe('spatial matching', () => { const graphs: LooseObject = { qwerty: adjacencyGraphs.qwerty, } - zxcvbnOptions.setOptions({ - graphs, - }) - const matchSpatial = new MatchSpatial() + const zxcvbnOptions = new Options({ graphs }) + const matchSpatial = new MatchSpatial(zxcvbnOptions) const pattern = '6tfGHJ' const matches = matchSpatial.match({ password: `rz!${pattern}%z` }) const msg = @@ -60,10 +59,8 @@ describe('spatial matching specific patterns vs keyboards', () => { data.forEach(([pattern, keyboard, turns, shifts]) => { const graphs: any = {} graphs[keyboard] = adjacencyGraphs[keyboard as keyof typeof adjacencyGraphs] - zxcvbnOptions.setOptions({ - graphs, - }) - const matchSpatial = new MatchSpatial() + const zxcvbnOptions = new Options({ graphs }) + const matchSpatial = new MatchSpatial(zxcvbnOptions) const matches = matchSpatial.match({ password: pattern }) const msg = `matches '${pattern}' as a ${keyboard} pattern` diff --git a/packages/libraries/main/test/matcher/spatial/scoring.spec.ts b/packages/libraries/main/test/matcher/spatial/scoring.spec.ts index a999fca7..b80e5cc3 100644 --- a/packages/libraries/main/test/matcher/spatial/scoring.spec.ts +++ b/packages/libraries/main/test/matcher/spatial/scoring.spec.ts @@ -1,12 +1,11 @@ import * as zxcvbnCommonPackage from '../../../../../languages/common/src' import spatialGuesses from '../../../src/matcher/spatial/scoring' -import { zxcvbnOptions } from '../../../src/Options' - -zxcvbnOptions.setOptions({ - graphs: zxcvbnCommonPackage.adjacencyGraphs, -}) +import { Options } from '../../../src/Options' describe('scoring: guesses spatial', () => { + const zxcvbnOptions = new Options({ + graphs: zxcvbnCommonPackage.adjacencyGraphs, + }) it('with no turns or shifts, guesses is starts * degree * (len-1)', () => { const match = { token: 'zxcvbn', @@ -16,7 +15,7 @@ describe('scoring: guesses spatial', () => { } // @ts-ignore - expect(spatialGuesses(match)).toEqual(2160) + expect(spatialGuesses(match, zxcvbnOptions)).toEqual(2160) }) it('guesses is added for shifted keys, similar to capitals in dictionary matching', () => { @@ -29,7 +28,7 @@ describe('scoring: guesses spatial', () => { } // @ts-ignore - expect(spatialGuesses(match)).toEqual(45360) + expect(spatialGuesses(match, zxcvbnOptions)).toEqual(45360) }) it('when everything is shifted, guesses are doubled', () => { @@ -41,7 +40,7 @@ describe('scoring: guesses spatial', () => { guesses: null, } // @ts-ignore - expect(spatialGuesses(match)).toEqual(4320) + expect(spatialGuesses(match, zxcvbnOptions)).toEqual(4320) }) it('spatial guesses accounts for turn positions, directions and starting keys', () => { @@ -53,6 +52,6 @@ describe('scoring: guesses spatial', () => { } // @ts-ignore - expect(spatialGuesses(match)).toEqual(558461) + expect(spatialGuesses(match, zxcvbnOptions)).toEqual(558461) }) }) diff --git a/packages/libraries/main/test/matching.spec.ts b/packages/libraries/main/test/matching.spec.ts index d72205a1..4b2c3938 100644 --- a/packages/libraries/main/test/matching.spec.ts +++ b/packages/libraries/main/test/matching.spec.ts @@ -1,10 +1,10 @@ import * as zxcvbnCommonPackage from '../../../languages/common/src' import * as zxcvbnEnPackage from '../../../languages/en/src' import MatchOmni from '../src/Matching' -import { zxcvbnOptions } from '../src/Options' +import { Options } from '../src/Options' import { MatchExtended } from '../src/types' -zxcvbnOptions.setOptions({ +const zxcvbnOptions = new Options({ dictionary: { ...zxcvbnCommonPackage.dictionary, ...zxcvbnEnPackage.dictionary, @@ -13,7 +13,7 @@ zxcvbnOptions.setOptions({ }) describe('omnimatch matching', () => { - const omniMatch = new MatchOmni() + const omniMatch = new MatchOmni(zxcvbnOptions) it("doesn't match ''", () => { expect(omniMatch.match('')).toEqual([]) diff --git a/packages/libraries/main/test/options.spec.ts b/packages/libraries/main/test/options.spec.ts index ff93bc74..6cdd4d38 100644 --- a/packages/libraries/main/test/options.spec.ts +++ b/packages/libraries/main/test/options.spec.ts @@ -1,10 +1,10 @@ -import { zxcvbnOptions } from '../src/Options' +import { Options } from '../src/Options' import translationKeys from '../src/data/translationKeys' describe('Options', () => { describe('translations', () => { it('should return default feedback for no sequence on custom translations', () => { - zxcvbnOptions.setOptions({ translations: translationKeys }) + const zxcvbnOptions = new Options({ translations: translationKeys }) expect(zxcvbnOptions.translations).toEqual(translationKeys) }) const customTranslations = { @@ -17,7 +17,7 @@ describe('Options', () => { it('should return error for wrong custom translations', () => { expect(() => { // @ts-ignore - zxcvbnOptions.setOptions({ translations: customTranslations }) + new Options({ translations: customTranslations }) }).toThrow('Invalid translations object fallback to keys') }) }) diff --git a/packages/libraries/main/test/scoring/guesses/calc.spec.ts b/packages/libraries/main/test/scoring/guesses/calc.spec.ts index 00d30499..b125218d 100644 --- a/packages/libraries/main/test/scoring/guesses/calc.spec.ts +++ b/packages/libraries/main/test/scoring/guesses/calc.spec.ts @@ -1,13 +1,15 @@ import estimate from '../../../src/scoring/estimate' import dateGuesses from '../../../src/matcher/date/scoring' +import { Options } from '../../../src' describe('scoring', () => { + const zxcvbnOptions = new Options() it('estimate_guesses returns cached guesses when available', () => { const match = { guesses: 1, } // @ts-ignore - expect(estimate(match, '')).toEqual({ + expect(estimate(zxcvbnOptions, match, '')).toEqual({ guesses: 1, }) }) @@ -21,7 +23,7 @@ describe('scoring', () => { day: 14, } // @ts-ignore - expect(estimate(match, '1977')).toEqual({ + expect(estimate(zxcvbnOptions, match, '1977')).toEqual({ pattern: 'date', token: '1977', year: 1977, diff --git a/packages/libraries/main/test/scoring/search.spec.ts b/packages/libraries/main/test/scoring/search.spec.ts index fc2509f0..135af1df 100644 --- a/packages/libraries/main/test/scoring/search.spec.ts +++ b/packages/libraries/main/test/scoring/search.spec.ts @@ -1,9 +1,9 @@ -import scoring from '../../src/scoring' -import { zxcvbnOptions } from '../../src/Options' - -zxcvbnOptions.setOptions() +import Scoring from '../../src/scoring' +import { Options } from '../../src/Options' describe('scoring search', () => { + const zxcvbnOptions = new Options() + const scoring = new Scoring(zxcvbnOptions) const getMatch = (i: number, j: number, guesses: number) => ({ i, j, diff --git a/packages/libraries/main/test/timeEstimates.spec.ts b/packages/libraries/main/test/timeEstimates.spec.ts index 623c705a..436fbb2f 100644 --- a/packages/libraries/main/test/timeEstimates.spec.ts +++ b/packages/libraries/main/test/timeEstimates.spec.ts @@ -1,14 +1,14 @@ import translations from '../../../languages/en/src/translations' import TimeEstimates from '../src/TimeEstimates' -import { zxcvbnOptions } from '../src/Options' +import { Options } from '../src/Options' -zxcvbnOptions.setOptions({ +const zxcvbnOptions = new Options({ translations, }) // TODO add tests describe('timeEstimates', () => { - const timeEstimates = new TimeEstimates() + const timeEstimates = new TimeEstimates(zxcvbnOptions) it('should be very weak', () => { const attackTimes = timeEstimates.estimateAttackTimes(10) diff --git a/packages/libraries/main/test/debounce.spec.ts b/packages/libraries/main/test/utils/debounce.spec.ts similarity index 91% rename from packages/libraries/main/test/debounce.spec.ts rename to packages/libraries/main/test/utils/debounce.spec.ts index f82b16ad..0123a7e5 100644 --- a/packages/libraries/main/test/debounce.spec.ts +++ b/packages/libraries/main/test/utils/debounce.spec.ts @@ -1,4 +1,4 @@ -import debounce from '../src/debounce' +import debounce from '../../src/utils/debounce' describe('debounce', () => { it('should call handler immediately', async () => { diff --git a/packages/libraries/main/test/helper.spec.ts b/packages/libraries/main/test/utils/helper.spec.ts similarity index 97% rename from packages/libraries/main/test/helper.spec.ts rename to packages/libraries/main/test/utils/helper.spec.ts index 52906f9c..9228d383 100644 --- a/packages/libraries/main/test/helper.spec.ts +++ b/packages/libraries/main/test/utils/helper.spec.ts @@ -5,8 +5,8 @@ import { translate, mod, buildRankedDictionary, -} from '../src/helper' -import { LooseObject } from '../src/types' +} from '../../src/utils/helper' +import { LooseObject } from '../../src/types' describe('utils matching', () => { describe('empty', () => { diff --git a/packages/libraries/main/test/levenshtein.spec.ts b/packages/libraries/main/test/utils/levenshtein.spec.ts similarity index 78% rename from packages/libraries/main/test/levenshtein.spec.ts rename to packages/libraries/main/test/utils/levenshtein.spec.ts index ac5b2e80..550c2bb1 100644 --- a/packages/libraries/main/test/levenshtein.spec.ts +++ b/packages/libraries/main/test/utils/levenshtein.spec.ts @@ -1,9 +1,9 @@ -import * as zxcvbnCommonPackage from '../../../languages/common/src' -import * as zxcvbnEnPackage from '../../../languages/en/src' -import { zxcvbn, zxcvbnOptions } from '../src' +import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common/src' +import * as zxcvbnEnPackage from '@zxcvbn-ts/language-en/src' +import { ZxcvbnFactory } from '../../src' describe('levenshtein', () => { - zxcvbnOptions.setOptions({ + const zxcvbn = new ZxcvbnFactory({ dictionary: { ...zxcvbnCommonPackage.dictionary, ...zxcvbnEnPackage.dictionary, @@ -14,7 +14,9 @@ describe('levenshtein', () => { }) it('should find levensteindistance', () => { - const result = zxcvbn('ishduehlduod83h4mfs8', ['ishduehgldueod83h4mfis8']) + const result = zxcvbn.check('ishduehlduod83h4mfs8', [ + 'ishduehgldueod83h4mfis8', + ]) expect(result.calcTime).toBeDefined() result.calcTime = 0 expect(result).toEqual({ @@ -63,7 +65,7 @@ describe('levenshtein', () => { }) it('should recognize a mistyped common English word', () => { - const result = zxcvbn('alaphant') + const result = zxcvbn.check('alaphant') expect(result.calcTime).toBeDefined() result.calcTime = 0 expect( @@ -117,10 +119,17 @@ describe('levenshtein', () => { }) it('should respect threshold which is lower than the default 2', () => { - zxcvbnOptions.setOptions({ + const customZxcvbn = new ZxcvbnFactory({ + dictionary: { + ...zxcvbnCommonPackage.dictionary, + ...zxcvbnEnPackage.dictionary, + }, + graphs: zxcvbnCommonPackage.adjacencyGraphs, + translations: zxcvbnEnPackage.translations, + useLevenshteinDistance: true, levenshteinThreshold: 1, }) - const result = zxcvbn('eeleephaant') + const result = customZxcvbn.check('eeleephaant') expect( result.sequence.find( (sequenceItem) => sequenceItem.levenshteinDistance !== undefined, @@ -129,10 +138,17 @@ describe('levenshtein', () => { }) it('should respect threshold which is higher than the default 2', () => { - zxcvbnOptions.setOptions({ + const customZxcvbn = new ZxcvbnFactory({ + dictionary: { + ...zxcvbnCommonPackage.dictionary, + ...zxcvbnEnPackage.dictionary, + }, + graphs: zxcvbnCommonPackage.adjacencyGraphs, + translations: zxcvbnEnPackage.translations, + useLevenshteinDistance: true, levenshteinThreshold: 3, }) - const result = zxcvbn('eeleephaant') + const result = customZxcvbn.check('eeleephaant') expect(result.sequence.length).toStrictEqual(1) expect(result.sequence[0].levenshteinDistance).toBeDefined() }) diff --git a/packages/libraries/pwned/src/feedback.ts b/packages/libraries/pwned/src/feedback.ts index 513341d5..d59763b7 100644 --- a/packages/libraries/pwned/src/feedback.ts +++ b/packages/libraries/pwned/src/feedback.ts @@ -2,10 +2,8 @@ import { Options } from '@zxcvbn-ts/core' export default (options: Options) => { - return () => { - return { - warning: options.translations.warnings.pwned, - suggestions: [options.translations.suggestions.pwned], - } + return { + warning: options.translations.warnings.pwned, + suggestions: [options.translations.suggestions.pwned], } } diff --git a/packages/libraries/pwned/src/index.ts b/packages/libraries/pwned/src/index.ts index c94fc238..767deb80 100644 --- a/packages/libraries/pwned/src/index.ts +++ b/packages/libraries/pwned/src/index.ts @@ -1,5 +1,5 @@ // @ts-ignore -import { Matcher, Options } from '@zxcvbn-ts/core' +import { Matcher } from '@zxcvbn-ts/core' import MatchPwned from './matching' import scoring from './scoring' import FeedbackFactory from './feedback' @@ -8,12 +8,11 @@ import { FetchApi, MatcherPwnedFactoryConfig } from './types' export const matcherPwnedFactory = ( universalFetch: FetchApi, - options: Options, config: MatcherPwnedFactoryConfig = {}, ): Matcher => { return { Matching: MatchPwned(universalFetch, config), - feedback: FeedbackFactory(options), + feedback: FeedbackFactory, scoring, } } diff --git a/packages/libraries/pwned/src/matching.ts b/packages/libraries/pwned/src/matching.ts index ec314aad..23fc269c 100644 --- a/packages/libraries/pwned/src/matching.ts +++ b/packages/libraries/pwned/src/matching.ts @@ -1,5 +1,5 @@ // @ts-ignore -import { MatchExtended, MatchOptions } from '@zxcvbn-ts/core' +import { MatchExtended, MatchOptions, Options } from '@zxcvbn-ts/core' import haveIBeenPwned from './haveIBeenPwned' import { FetchApi, MatcherPwnedFactoryConfig } from './types' diff --git a/packages/libraries/pwned/test/index.spec.ts b/packages/libraries/pwned/test/index.spec.ts index 72bc0294..cb77c330 100644 --- a/packages/libraries/pwned/test/index.spec.ts +++ b/packages/libraries/pwned/test/index.spec.ts @@ -1,4 +1,4 @@ -import { zxcvbnAsync, zxcvbnOptions } from '../../main/src' +import { ZxcvbnFactory } from '../../main/src' import { matcherPwnedFactory } from '../src' describe('main', () => { @@ -8,9 +8,14 @@ describe('main', () => { return `008A205652858375D71117A63004CC75167:5\r\n3EA386688A0147AB736AABCEDE496610382:244` }, })) - // @ts-ignore - zxcvbnOptions.matchers.pwned = matcherPwnedFactory(fetch, zxcvbnOptions) - const result = await zxcvbnAsync('P4$$w0rd') + const zxcvbn = new ZxcvbnFactory( + {}, + { + // @ts-ignore + pwned: matcherPwnedFactory(fetch), + }, + ) + const result = await zxcvbn.checkAsync('P4$$w0rd') expect(result.calcTime).toBeDefined() result.calcTime = 0 @@ -54,9 +59,14 @@ describe('main', () => { const fetch = jest.fn(async () => { throw new Error('Some Network error') }) - zxcvbnOptions.matchers.pwned = matcherPwnedFactory(fetch, zxcvbnOptions) - const result = await zxcvbnAsync('P4$$w0rd') - + const zxcvbn = new ZxcvbnFactory( + {}, + { + // @ts-ignore + pwned: matcherPwnedFactory(fetch), + }, + ) + const result = await zxcvbn.checkAsync('P4$$w0rd') expect(result.calcTime).toBeDefined() result.calcTime = 0 expect(result).toEqual({ diff --git a/packages/libraries/pwned/test/matching.spec.ts b/packages/libraries/pwned/test/matching.spec.ts index a46b0cc5..a85962af 100644 --- a/packages/libraries/pwned/test/matching.spec.ts +++ b/packages/libraries/pwned/test/matching.spec.ts @@ -1,5 +1,5 @@ -import { zxcvbnOptions } from '@zxcvbn-ts/core/src' import { matcherPwnedFactory } from '../src' +import { Options } from '../../main/src' const fetch = jest.fn(async () => ({ text() { @@ -8,10 +8,12 @@ const fetch = jest.fn(async () => ({ })) describe('pwned matching', () => { + const options = new Options() // @ts-ignore - const matcherPwned = matcherPwnedFactory(fetch, zxcvbnOptions) + const matcherPwned = matcherPwnedFactory(fetch) it('should return a match', async () => { - const matchPwned = new matcherPwned.Matching() + // @ts-ignore + const matchPwned = new matcherPwned.Matching(options) // @ts-ignore const match = await matchPwned.match({ password: 'P4$$w0rd' }) expect(match).toEqual([ From 8c2e00994dda7933970202695aa84075f70bc454 Mon Sep 17 00:00:00 2001 From: MrWook Date: Fri, 8 Dec 2023 08:57:01 +0100 Subject: [PATCH 02/11] refactor!(zxcvbn): improve the internal code structure --- packages/libraries/main/src/Matching.ts | 20 +++++++++++----- packages/libraries/main/src/Options.ts | 24 ++++++++++---------- packages/libraries/main/src/TimeEstimates.ts | 24 ++++++++++---------- packages/libraries/main/src/index.ts | 12 +++++----- 4 files changed, 44 insertions(+), 36 deletions(-) diff --git a/packages/libraries/main/src/Matching.ts b/packages/libraries/main/src/Matching.ts index b8157353..3af271b1 100644 --- a/packages/libraries/main/src/Matching.ts +++ b/packages/libraries/main/src/Matching.ts @@ -33,6 +33,17 @@ class Matching { constructor(private options: Options) {} + 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) + } + match(password: string): MatchExtended[] | Promise { const matches: MatchExtended[] = [] @@ -42,14 +53,11 @@ class Matching { ...Object.keys(this.options.matchers), ] matchers.forEach((key) => { - if (!this.matchers[key] && !this.options.matchers[key]) { + const matcher = this.matcherFactory(key) + if (!matcher) { return } - const Matcher = this.matchers[key] - ? this.matchers[key] - : this.options.matchers[key].Matching - const usedMatcher = new Matcher(this.options) - const result = usedMatcher.match({ + const result = matcher.match({ password, omniMatch: this, }) diff --git a/packages/libraries/main/src/Options.ts b/packages/libraries/main/src/Options.ts index b19e4bd5..286324c6 100644 --- a/packages/libraries/main/src/Options.ts +++ b/packages/libraries/main/src/Options.ts @@ -15,31 +15,31 @@ import TrieNode from './matcher/dictionary/variants/matching/unmunger/TrieNode' import l33tTableToTrieNode from './matcher/dictionary/variants/matching/unmunger/l33tTableToTrieNode' export class Options { - matchers: Matchers = {} + 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 = {} + public rankedDictionariesMaxWordSize: Record = {} - 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( options: OptionsType = {}, diff --git a/packages/libraries/main/src/TimeEstimates.ts b/packages/libraries/main/src/TimeEstimates.ts index d2438140..70ac4d64 100644 --- a/packages/libraries/main/src/TimeEstimates.ts +++ b/packages/libraries/main/src/TimeEstimates.ts @@ -27,18 +27,6 @@ const times = { class TimeEstimates { constructor(private options: Options) {} - private translate(displayStr: string, value: number | undefined) { - let key = displayStr - if (value !== undefined && value !== 1) { - key += 's' - } - const { timeEstimation } = this.options.translations - return timeEstimation[key as keyof typeof timeEstimation].replace( - '{base}', - `${value}`, - ) - } - public estimateAttackTimes(guesses: number) { const crackTimesSeconds: CrackTimesSeconds = { onlineThrottling100PerHour: guesses / (100 / 3600), @@ -104,6 +92,18 @@ class TimeEstimates { } return this.translate(displayStr, base) } + + private translate(displayStr: string, value: number | undefined) { + let key = displayStr + if (value !== undefined && value !== 1) { + key += 's' + } + const { timeEstimation } = this.options.translations + return timeEstimation[key as keyof typeof timeEstimation].replace( + '{base}', + `${value}`, + ) + } } export default TimeEstimates diff --git a/packages/libraries/main/src/index.ts b/packages/libraries/main/src/index.ts index 1cca250d..b3b1f9d5 100644 --- a/packages/libraries/main/src/index.ts +++ b/packages/libraries/main/src/index.ts @@ -54,24 +54,24 @@ class ZxcvbnFactory { } public check(password: string, userInputs?: (string | number)[]) { - const usedPassword = password.substring(0, this.options.maxLength) + const reducedPassword = password.substring(0, this.options.maxLength) const start = time() - const matches = this.main(usedPassword, userInputs) + 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, usedPassword, start) + return this.createReturnValue(matches, reducedPassword, start) } public async checkAsync(password: string, userInputs?: (string | number)[]) { - const usedPassword = password.substring(0, this.options.maxLength) + const reducedPassword = password.substring(0, this.options.maxLength) const start = time() - const matches = await this.main(usedPassword, userInputs) + const matches = await this.main(reducedPassword, userInputs) - return this.createReturnValue(matches, usedPassword, start) + return this.createReturnValue(matches, reducedPassword, start) } } From a88c8c1bcb3424cfb57cf4da0f5da374cb26fc4d Mon Sep 17 00:00:00 2001 From: MrWook Date: Fri, 8 Dec 2023 08:59:15 +0100 Subject: [PATCH 03/11] feat!(zxcvbn): change usage of library BREAKING: we moved to a class based approach where the options are handled in the constructor to get rid of the option singleton --- packages/libraries/main/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/libraries/main/README.md b/packages/libraries/main/README.md index 88e88996..8b085aef 100644 --- a/packages/libraries/main/README.md +++ b/packages/libraries/main/README.md @@ -18,11 +18,10 @@ and recognizes common patterns like dates, repetitions (e.g. 'aaa'), sequences ( ## Setup ```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 = { dictionary: { ...zxcvbnCommonPackage.dictionary, @@ -31,7 +30,8 @@ const options = { graphs: zxcvbnCommonPackage.adjacencyGraphs, translations: zxcvbnEnPackage.translations, } -zxcvbnOptions.setOptions(options) +const zxcvbn = new ZxcvbnFactory(options) +const password = 'somePassword' zxcvbn(password) ``` From f1c2af7d032a73e4829340c98a65035d3a53b120 Mon Sep 17 00:00:00 2001 From: MrWook Date: Wed, 3 Jan 2024 12:04:15 +0100 Subject: [PATCH 04/11] fix(dictionary): do not add userInputs on the fly to the options dictionary --- packages/libraries/main/src/Matching.ts | 8 +++- packages/libraries/main/src/Options.ts | 23 ++++++---- packages/libraries/main/src/index.ts | 6 +-- .../main/src/matcher/date/matching.ts | 6 +-- .../main/src/matcher/dictionary/matching.ts | 30 ++++++++----- .../main/src/matcher/dictionary/types.ts | 10 +++-- .../dictionary/variants/matching/l33t.ts | 15 ++++--- .../dictionary/variants/matching/reverse.ts | 13 +++--- .../main/src/matcher/regex/matching.ts | 13 +++--- .../main/src/matcher/repeat/matching.ts | 8 +--- .../main/src/matcher/separator/matching.ts | 6 +-- .../main/src/matcher/sequence/matching.ts | 6 +-- .../main/src/matcher/spatial/matching.ts | 6 +-- packages/libraries/main/src/types.ts | 6 +++ .../src/utils/mergeUserInputDictionary.ts | 44 +++++++++++++++++++ packages/libraries/main/test/options.spec.ts | 1 + .../main/test/scoring/guesses/calc.spec.ts | 2 +- packages/libraries/pwned/src/matching.ts | 2 +- 18 files changed, 134 insertions(+), 71 deletions(-) create mode 100644 packages/libraries/main/src/utils/mergeUserInputDictionary.ts diff --git a/packages/libraries/main/src/Matching.ts b/packages/libraries/main/src/Matching.ts index 3af271b1..60e98727 100644 --- a/packages/libraries/main/src/Matching.ts +++ b/packages/libraries/main/src/Matching.ts @@ -1,5 +1,5 @@ import { extend, sorted } from './utils/helper' -import { MatchExtended, MatchingType } from './types' +import { MatchExtended, MatchingType, UserInputsOptions } from './types' import dateMatcher from './matcher/date/matching' import dictionaryMatcher from './matcher/dictionary/matching' import regexMatcher from './matcher/regex/matching' @@ -44,7 +44,10 @@ class Matching { return new Matcher(this.options) } - match(password: string): MatchExtended[] | Promise { + match( + password: string, + userInputsOptions?: UserInputsOptions, + ): MatchExtended[] | Promise { const matches: MatchExtended[] = [] const promises: Promise[] = [] @@ -60,6 +63,7 @@ class Matching { const result = matcher.match({ password, omniMatch: this, + userInputsOptions, }) if (result instanceof Promise) { diff --git a/packages/libraries/main/src/Options.ts b/packages/libraries/main/src/Options.ts index 286324c6..6aec1bf5 100644 --- a/packages/libraries/main/src/Options.ts +++ b/packages/libraries/main/src/Options.ts @@ -8,6 +8,8 @@ import { RankedDictionaries, Matchers, Matcher, + UserInputsOptions, + RankedDictionary, } from './types' import l33tTable from './data/l33tTable' import translationKeys from './data/translationKeys' @@ -158,16 +160,21 @@ export class Options { return buildRankedDictionary(sanitizedInputs) } - public 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, + } } private addMatcher(name: string, matcher: Matcher) { diff --git a/packages/libraries/main/src/index.ts b/packages/libraries/main/src/index.ts index b3b1f9d5..63ff345c 100644 --- a/packages/libraries/main/src/index.ts +++ b/packages/libraries/main/src/index.ts @@ -44,13 +44,11 @@ class ZxcvbnFactory { } private main(password: string, userInputs?: (string | number)[]) { - if (userInputs) { - this.options.extendUserInputsDictionary(userInputs) - } + const userInputsOptions = this.options.getUserInputsOptions(userInputs) const matching = new Matching(this.options) - return matching.match(password) + return matching.match(password, userInputsOptions) } public check(password: string, userInputs?: (string | number)[]) { diff --git a/packages/libraries/main/src/matcher/date/matching.ts b/packages/libraries/main/src/matcher/date/matching.ts index 95b2acc6..cb08281a 100644 --- a/packages/libraries/main/src/matcher/date/matching.ts +++ b/packages/libraries/main/src/matcher/date/matching.ts @@ -5,12 +5,10 @@ import { REFERENCE_YEAR, } from '../../data/const' import { sorted } from '../../utils/helper' -import { DateMatch } from '../../types' +import { DateMatch, MatchOptions } from '../../types' import { Options } from '../../Options' -interface DateMatchOptions { - password: string -} +type DateMatchOptions = Pick /* * ------------------------------------------------------------------------------- diff --git a/packages/libraries/main/src/matcher/dictionary/matching.ts b/packages/libraries/main/src/matcher/dictionary/matching.ts index 338eec28..839c433a 100644 --- a/packages/libraries/main/src/matcher/dictionary/matching.ts +++ b/packages/libraries/main/src/matcher/dictionary/matching.ts @@ -7,6 +7,7 @@ import { DictionaryNames, DictionaryMatch, L33tMatch } from '../../types' import Reverse from './variants/matching/reverse' import L33t from './variants/matching/l33t' import { DictionaryMatchOptions } from './types' +import mergeUserInputDictionary from '../../utils/mergeUserInputDictionary' class MatchDictionary { l33t: L33t @@ -18,28 +19,35 @@ class MatchDictionary { 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[]), ] return sorted(matches) } - defaultMatch({ password, useLevenshtein = true }: DictionaryMatchOptions) { + defaultMatch({ + password, + userInputsOptions, + useLevenshtein = true, + }: DictionaryMatchOptions) { const matches: DictionaryMatch[] = [] const passwordLength = password.length const passwordLower = password.toLowerCase() + const { rankedDictionaries, rankedDictionariesMaxWordSize } = + mergeUserInputDictionary( + this.options.rankedDictionaries, + this.options.rankedDictionariesMaxWordSize, + userInputsOptions, + ) // eslint-disable-next-line complexity,max-statements - Object.keys(this.options.rankedDictionaries).forEach((dictionaryName) => { - const rankedDict = - this.options.rankedDictionaries[dictionaryName as DictionaryNames] + Object.keys(rankedDictionaries).forEach((dictionaryName) => { + const rankedDict = rankedDictionaries[dictionaryName as DictionaryNames] const longestDictionaryWordSize = - this.options.rankedDictionariesMaxWordSize[dictionaryName] + rankedDictionariesMaxWordSize[dictionaryName] const searchWidth = Math.min(longestDictionaryWordSize, passwordLength) for (let i = 0; i < passwordLength; i += 1) { const searchEnd = Math.min(i + searchWidth, passwordLength) diff --git a/packages/libraries/main/src/matcher/dictionary/types.ts b/packages/libraries/main/src/matcher/dictionary/types.ts index a5058a71..34b8d961 100644 --- a/packages/libraries/main/src/matcher/dictionary/types.ts +++ b/packages/libraries/main/src/matcher/dictionary/types.ts @@ -1,10 +1,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[] diff --git a/packages/libraries/main/src/matcher/dictionary/variants/matching/l33t.ts b/packages/libraries/main/src/matcher/dictionary/variants/matching/l33t.ts index 7f3f2d33..8d71a1af 100644 --- a/packages/libraries/main/src/matcher/dictionary/variants/matching/l33t.ts +++ b/packages/libraries/main/src/matcher/dictionary/variants/matching/l33t.ts @@ -1,6 +1,6 @@ import { Options } from '../../../../Options' import { DictionaryMatch, L33tMatch } from '../../../../types' -import { DefaultMatch } from '../../types' +import { DefaultMatch, DictionaryMatchOptions } from '../../types' import getCleanPasswords, { PasswordChanges, PasswordWithSubs, @@ -67,10 +67,10 @@ class MatchL33t { }) } - match({ password }: { password: string }) { + match(matchOptions: DictionaryMatchOptions) { const matches: L33tMatch[] = [] const subbedPasswords = getCleanPasswords( - password, + matchOptions.password, this.options.l33tMaxSubstitutions, this.options.trieNodeRoot, ) @@ -81,6 +81,7 @@ class MatchL33t { return } const matchedDictionary = this.defaultMatch({ + ...matchOptions, password: subbedPassword.password, useLevenshtein: isFullSubstitution, }) @@ -88,10 +89,14 @@ class MatchL33t { 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 = { ...match, l33t: true, diff --git a/packages/libraries/main/src/matcher/dictionary/variants/matching/reverse.ts b/packages/libraries/main/src/matcher/dictionary/variants/matching/reverse.ts index dc0bdc70..eaa4cc5a 100644 --- a/packages/libraries/main/src/matcher/dictionary/variants/matching/reverse.ts +++ b/packages/libraries/main/src/matcher/dictionary/variants/matching/reverse.ts @@ -1,5 +1,5 @@ -import { DictionaryMatch } from '../../../../types' -import { DefaultMatch } from '../../types' +import { DictionaryMatch, MatchOptions } from '../../../../types' +import { DefaultMatch, DictionaryMatchOptions } from '../../types' import { Options } from '../../../../Options' /* @@ -13,17 +13,18 @@ class MatchReverse { private 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, }).map((match: DictionaryMatch) => ({ ...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, })) } } diff --git a/packages/libraries/main/src/matcher/regex/matching.ts b/packages/libraries/main/src/matcher/regex/matching.ts index dbdaf926..39da6cb6 100644 --- a/packages/libraries/main/src/matcher/regex/matching.ts +++ b/packages/libraries/main/src/matcher/regex/matching.ts @@ -1,12 +1,9 @@ import { REGEXEN } from '../../data/const' import { sorted } from '../../utils/helper' -import { RegexMatch } from '../../types' +import { MatchOptions, RegexMatch } from '../../types' import { Options } from '../../Options' -interface RegexMatchOptions { - password: string - regexes?: typeof REGEXEN -} +type RegexMatchOptions = Pick type RegexesKeys = keyof typeof REGEXEN /* @@ -17,10 +14,10 @@ type RegexesKeys = keyof typeof REGEXEN class MatchRegex { constructor(private options: Options) {} - match({ password, regexes = REGEXEN }: RegexMatchOptions) { + 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 let regexMatch: RegExpExecArray | null diff --git a/packages/libraries/main/src/matcher/repeat/matching.ts b/packages/libraries/main/src/matcher/repeat/matching.ts index f0c4cbf8..2e1feaae 100644 --- a/packages/libraries/main/src/matcher/repeat/matching.ts +++ b/packages/libraries/main/src/matcher/repeat/matching.ts @@ -1,12 +1,8 @@ -import { RepeatMatch } from '../../types' +import { MatchOptions, RepeatMatch } from '../../types' import Scoring from '../../scoring' import Matching from '../../Matching' import { Options } from '../../Options' -interface RepeatMatchOptions { - password: string - omniMatch: Matching -} /* *------------------------------------------------------------------------------- * repeats (aaa, abcabcabc) ------------------------------ @@ -20,7 +16,7 @@ class MatchRepeat { } // eslint-disable-next-line max-statements - match({ password, omniMatch }: RepeatMatchOptions) { + match({ password, omniMatch }: MatchOptions) { const matches: (RepeatMatch | Promise)[] = [] let lastIndex = 0 while (lastIndex < password.length) { diff --git a/packages/libraries/main/src/matcher/separator/matching.ts b/packages/libraries/main/src/matcher/separator/matching.ts index 6bf459f1..ca1a4f8b 100644 --- a/packages/libraries/main/src/matcher/separator/matching.ts +++ b/packages/libraries/main/src/matcher/separator/matching.ts @@ -1,10 +1,8 @@ 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 const separatorRegex = new RegExp(`[${SEPERATOR_CHARS.join('')}]`) diff --git a/packages/libraries/main/src/matcher/sequence/matching.ts b/packages/libraries/main/src/matcher/sequence/matching.ts index 98d666c2..ba9aa503 100644 --- a/packages/libraries/main/src/matcher/sequence/matching.ts +++ b/packages/libraries/main/src/matcher/sequence/matching.ts @@ -1,5 +1,5 @@ import { ALL_UPPER, ALL_LOWER, ALL_DIGIT } from '../../data/const' -import { SequenceMatch } from '../../types' +import { MatchOptions, SequenceMatch } from '../../types' import { Options } from '../../Options' type UpdateParams = { @@ -10,9 +10,7 @@ type UpdateParams = { result: any[] } -interface SequenceMatchOptions { - password: string -} +type SequenceMatchOptions = Pick /* *------------------------------------------------------------------------------- * sequences (abcdef) ------------------------------ diff --git a/packages/libraries/main/src/matcher/spatial/matching.ts b/packages/libraries/main/src/matcher/spatial/matching.ts index 04774edd..e6cd2bab 100644 --- a/packages/libraries/main/src/matcher/spatial/matching.ts +++ b/packages/libraries/main/src/matcher/spatial/matching.ts @@ -1,10 +1,8 @@ import { sorted, extend } from '../../utils/helper' import { Options } from '../../Options' -import { LooseObject, SpatialMatch } from '../../types' +import { LooseObject, MatchOptions, SpatialMatch } from '../../types' -interface SpatialMatchOptions { - password: string -} +type SpatialMatchOptions = Pick /* * ------------------------------------------------------------------------------ * spatial match (qwerty/dvorak/keypad and so on) ----------------------------------------- diff --git a/packages/libraries/main/src/types.ts b/packages/libraries/main/src/types.ts index c513f261..f1b14cad 100644 --- a/packages/libraries/main/src/types.ts +++ b/packages/libraries/main/src/types.ts @@ -231,18 +231,24 @@ export type DefaultScoringFunction = ( options: Options, ) => number | DictionaryReturn +export interface UserInputsOptions { + rankedDictionary: RankedDictionary + rankedDictionaryMaxWordSize: number +} export interface MatchOptions { password: string /** * @description This is the original Matcher so that one can use other matchers to define a baseGuess. An usage example is the repeat matcher */ omniMatch: Matching + userInputsOptions?: UserInputsOptions } export type MatchingType = new (options: Options) => { match({ password, omniMatch, + userInputsOptions, }: MatchOptions): MatchExtended[] | Promise } diff --git a/packages/libraries/main/src/utils/mergeUserInputDictionary.ts b/packages/libraries/main/src/utils/mergeUserInputDictionary.ts new file mode 100644 index 00000000..1ee274cd --- /dev/null +++ b/packages/libraries/main/src/utils/mergeUserInputDictionary.ts @@ -0,0 +1,44 @@ +import { RankedDictionaries, UserInputsOptions } from '../types' + +export default ( + optionsRankedDictionaries: RankedDictionaries, + optionsRankedDictionariesMaxWordSize: Record, + userInputsOptions?: UserInputsOptions, +) => { + const rankedDictionaries = { + ...optionsRankedDictionaries, + } + const rankedDictionariesMaxWordSize = { + ...optionsRankedDictionariesMaxWordSize, + } + if (!userInputsOptions) { + return { + rankedDictionaries, + rankedDictionariesMaxWordSize, + } + } + + if (rankedDictionaries.userInputs) { + const isWordSizeBigger = + rankedDictionariesMaxWordSize.userInputs < + userInputsOptions.rankedDictionaryMaxWordSize + + rankedDictionaries.userInputs = { + ...rankedDictionaries.userInputs, + ...userInputsOptions.rankedDictionary, + } + + rankedDictionariesMaxWordSize.userInputs = isWordSizeBigger + ? userInputsOptions.rankedDictionaryMaxWordSize + : rankedDictionariesMaxWordSize.userInputs + } else { + rankedDictionaries.userInputs = userInputsOptions.rankedDictionary + rankedDictionariesMaxWordSize.userInputs = + userInputsOptions.rankedDictionaryMaxWordSize + } + + return { + rankedDictionaries, + rankedDictionariesMaxWordSize, + } +} diff --git a/packages/libraries/main/test/options.spec.ts b/packages/libraries/main/test/options.spec.ts index 6cdd4d38..af9d2b76 100644 --- a/packages/libraries/main/test/options.spec.ts +++ b/packages/libraries/main/test/options.spec.ts @@ -17,6 +17,7 @@ describe('Options', () => { it('should return error for wrong custom translations', () => { expect(() => { // @ts-ignore + // eslint-disable-next-line no-new new Options({ translations: customTranslations }) }).toThrow('Invalid translations object fallback to keys') }) diff --git a/packages/libraries/main/test/scoring/guesses/calc.spec.ts b/packages/libraries/main/test/scoring/guesses/calc.spec.ts index b125218d..290f1e01 100644 --- a/packages/libraries/main/test/scoring/guesses/calc.spec.ts +++ b/packages/libraries/main/test/scoring/guesses/calc.spec.ts @@ -31,7 +31,7 @@ describe('scoring', () => { day: 14, // @ts-ignore guesses: dateGuesses(match), - guessesLog10: 4.225050696138048, + guessesLog10: 4.234390722392192, }) }) }) diff --git a/packages/libraries/pwned/src/matching.ts b/packages/libraries/pwned/src/matching.ts index 23fc269c..ec314aad 100644 --- a/packages/libraries/pwned/src/matching.ts +++ b/packages/libraries/pwned/src/matching.ts @@ -1,5 +1,5 @@ // @ts-ignore -import { MatchExtended, MatchOptions, Options } from '@zxcvbn-ts/core' +import { MatchExtended, MatchOptions } from '@zxcvbn-ts/core' import haveIBeenPwned from './haveIBeenPwned' import { FetchApi, MatcherPwnedFactoryConfig } from './types' From 3886f20efb6eaadb1d7c61723ba598dbaf6fa30a Mon Sep 17 00:00:00 2001 From: MrWook Date: Wed, 3 Jan 2024 12:04:36 +0100 Subject: [PATCH 05/11] docs(zxcvbn): update documentation for 4.x.x usage --- docs/.vuepress/components/Comparison.vue | 11 ++- .../components/ZxcvbnInteractive.vue | 27 +++--- docs/guide/framework-examples/README.md | 84 +++++++------------ docs/guide/getting-started/README.md | 13 +-- docs/guide/languages/README.md | 21 ++--- docs/guide/lazy-loading/README.md | 7 +- docs/guide/matcher/README.md | 6 +- docs/guide/migration/README.md | 53 ++++++++++++ docs/guide/user-input/README.md | 10 +-- packages/languages/ar/README.md | 7 +- packages/languages/common/README.md | 7 +- packages/languages/cs/README.md | 8 +- packages/languages/de/README.md | 7 +- packages/languages/en/README.md | 7 +- packages/languages/es-es/README.md | 7 +- packages/languages/fi/README.md | 7 +- packages/languages/fr/README.md | 5 +- packages/languages/id/README.md | 7 +- packages/languages/it/README.md | 7 +- packages/languages/ja/README.md | 7 +- packages/languages/nl-be/README.md | 7 +- packages/languages/pl/README.md | 7 +- packages/languages/pt-br/README.md | 5 +- packages/libraries/pwned/README.md | 13 +-- scripts/cli-password-tester.ts | 8 +- 25 files changed, 184 insertions(+), 164 deletions(-) diff --git a/docs/.vuepress/components/Comparison.vue b/docs/.vuepress/components/Comparison.vue index cf92c350..b372b8f7 100755 --- a/docs/.vuepress/components/Comparison.vue +++ b/docs/.vuepress/components/Comparison.vue @@ -33,10 +33,7 @@ diff --git a/docs/guide/languages/README.md b/docs/guide/languages/README.md index af90198b..97df1f76 100644 --- a/docs/guide/languages/README.md +++ b/docs/guide/languages/README.md @@ -23,7 +23,7 @@ If you don't have an own translation system or want to use predefined translatio Each language pack has its own translation file that you can use like this: ```js -import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core' +import { ZxcvbnFactory } from '@zxcvbn-ts/core' import { translations } from '@zxcvbn-ts/language-en' const password = 'somePassword' @@ -31,9 +31,8 @@ const options = { translations, } -zxcvbnOptions.setOptions(options) - -zxcvbn(password) +const zxcvbn = new ZxcvbnFactory(options) +zxcvbn.check(password) ``` ## Dictionary @@ -43,7 +42,7 @@ This makes the library tiny but inefficient compared to the original library. It is recommended to use at least the common and english language package. ```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' @@ -55,9 +54,8 @@ const options = { }, } -zxcvbnOptions.setOptions(options) - -zxcvbn(password) +const zxcvbn = new ZxcvbnFactory(options) +zxcvbn.check(password) ``` ## Keyboard patterns @@ -66,7 +64,7 @@ By default, `zxcvbn-ts` don't use any keyboard patterns to let the developer dec It is recommended to use at least the common keyboard patterns. ```js -import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core' +import { ZxcvbnFactory } from '@zxcvbn-ts/core' import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common' const password = 'somePassword' @@ -74,9 +72,8 @@ const options = { graphs: zxcvbnCommonPackage.adjacencyGraphs, } -zxcvbnOptions.setOptions(options) - -zxcvbn(password) +const zxcvbn = new ZxcvbnFactory(options) +zxcvbn.check(password) ``` ## Add a new language package diff --git a/docs/guide/lazy-loading/README.md b/docs/guide/lazy-loading/README.md index dc8ee7fe..1f63f5ad 100644 --- a/docs/guide/lazy-loading/README.md +++ b/docs/guide/lazy-loading/README.md @@ -9,7 +9,7 @@ Webpack supports lazy-loading with some configuration; check out the [documentat Here's how you import it: ```js -import { zxcvbn } from '@zxcvbn-ts/core' +import { ZxcvbnFactory } from '@zxcvbn-ts/core' ``` This is how you lazy load dictionaries: @@ -43,8 +43,9 @@ Somewhere in your application you can call the "loadOptions" function, then the const run = async () => { const password = 'asdnlja978o' const options = await loadOptions() - zxcvbnOptions.setOptions(options) - const results = zxcvbn(password) + + const zxcvbn = new ZxcvbnFactory(options) + const results = zxcvbn.check(password) console.log(results) } ``` diff --git a/docs/guide/matcher/README.md b/docs/guide/matcher/README.md index 4318b83a..30e7768f 100644 --- a/docs/guide/matcher/README.md +++ b/docs/guide/matcher/README.md @@ -50,7 +50,7 @@ Custom matchers can be created if needed, including asynchronous matchers. If cr Here is an example of how to create a custom matcher to check for minimum password length. Please note that we do not recommend using a minimum length matcher. ```ts -import { zxcvbnOptions } from '@zxcvbn-ts/core' +import { ZxcvbnFactory } from '@zxcvbn-ts/core' import { MatchEstimated, ExtendedMatch, @@ -87,7 +87,9 @@ const minLengthMatcher: Matcher = { }, } -zxcvbnOptions.addMatcher('minLength', minLengthMatcher) +new ZxcvbnFactory(options, { + 'minLength': minLengthMatcher +}) ``` The Matching function needs to return an array of matched tokens. The four default properties (pattern, token, i, and j) are mandatory but the object can be extended as needed. diff --git a/docs/guide/migration/README.md b/docs/guide/migration/README.md index 321bcae7..a37ea3d9 100644 --- a/docs/guide/migration/README.md +++ b/docs/guide/migration/README.md @@ -1,5 +1,58 @@ # Migration +## `zxcvbn-ts 3.x.x` to `zxcvbn-ts 4.x.x` + +### Move from singleton options to class based approach + +Old: +``` +zxcvbnOptions.setOptions(options) + +zxcvbn(password) +``` + +New: + +``` +const zxcvbn = new ZxcvbnFactory(options, customMatcher) + +zxcvbn.check(password) +``` + + +### Custom matcher setup changed + +This is an example for the pwned custom matcher changes. Generally the options doesn't need to be transferred anymore. + +Old: +``` +zxcvbnOptions.setOptions(options) + +const pwnedOptions = { + url: string, + networkErrorHandler: Function +} +const matcherPwned = matcherPwnedFactory(crossFetch, zxcvbnOptions, pwnedOptions) +zxcvbnOptions.addMatcher('pwned', matcherPwned) + +zxcvbn(password) +``` + +New: + +``` +const pwnedOptions = { + url: string, + networkErrorHandler: Function +} +const customMatcher = { + pwned: matcherPwnedFactory(fetch, pwnedOptions), +} + +const zxcvbn = new ZxcvbnFactory(options, customMatcher) +zxcvbn.check(password) +``` + ## `zxcvbn-ts 2.x.x` to `zxcvbn-ts 3.x.x` ### language packages no longer have a default export diff --git a/docs/guide/user-input/README.md b/docs/guide/user-input/README.md index eac82b82..b47a0339 100644 --- a/docs/guide/user-input/README.md +++ b/docs/guide/user-input/README.md @@ -4,7 +4,7 @@ Often you want to check if the password matches some user content like their use For this purpose, add a `userInputs` dictionary with its own sanitizer. ```js -import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core' +import { ZxcvbnFactory } from '@zxcvbn-ts/core' const password = 'somePassword' const options = { @@ -12,16 +12,16 @@ const options = { userInputs: ['someEmail@email.de', 'someUsername'], }, } -zxcvbnOptions.setOptions(options) - -zxcvbn(password) +const zxcvbn = new ZxcvbnFactory(options) +zxcvbn.check(password) ``` If you need to add the userInputs more dynamically your can add them as the second argument of the normal zxcvbn function like this ```js -import { zxcvbn } from '@zxcvbn-ts/core' +import { ZxcvbnFactory } from '@zxcvbn-ts/core' const password = 'somePassword' +const zxcvbn = new ZxcvbnFactory() zxcvbn(password, ['someEmail@email.de', 'someUsername']) ``` \ No newline at end of file diff --git a/packages/languages/ar/README.md b/packages/languages/ar/README.md index e48c525a..f0b81f55 100644 --- a/packages/languages/ar/README.md +++ b/packages/languages/ar/README.md @@ -15,7 +15,7 @@ The Arabic dictionary and language package for **zxcvbn-ts** ## Setup ```js -import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core' +import { ZxcvbnFactory } from '@zxcvbn-ts/core' import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common' import * as zxcvbnArPackage from '@zxcvbn-ts/language-ar' @@ -28,7 +28,6 @@ const options = { ...zxcvbnArPackage.dictionary, }, } -zxcvbnOptions.setOptions(options) - -zxcvbn(password) +const zxcvbn = new ZxcvbnFactory(options) +zxcvbn.check(password) ``` diff --git a/packages/languages/common/README.md b/packages/languages/common/README.md index 8615fba5..74e0a05f 100644 --- a/packages/languages/common/README.md +++ b/packages/languages/common/README.md @@ -15,7 +15,7 @@ The common dictionary and language package for zxcvbn-ts ## Setup ```js -import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core' +import { ZxcvbnFactory } from '@zxcvbn-ts/core' import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common' const password = 'somePassword' @@ -23,7 +23,6 @@ const options = { ...zxcvbnCommonPackage, } -zxcvbnOptions.setOptions(options) - -zxcvbn(password) +const zxcvbn = new ZxcvbnFactory(options) +zxcvbn.check(password) ``` diff --git a/packages/languages/cs/README.md b/packages/languages/cs/README.md index b06d9afe..faa5ad5e 100644 --- a/packages/languages/cs/README.md +++ b/packages/languages/cs/README.md @@ -16,7 +16,7 @@ The Czech dictionary and language package for zxcvbn-ts ## Setup ```js -import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core' +import { ZxcvbnFactory } from '@zxcvbn-ts/core' import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common' import * as zxcvbnCsPackage from '@zxcvbn-ts/language-cs' @@ -29,8 +29,6 @@ const options = { ...zxcvbnCsPackage.dictionary, }, } - -zxcvbnOptions.setOptions(options) - -zxcvbn(password) +const zxcvbn = new ZxcvbnFactory(options) +zxcvbn.check(password) ``` diff --git a/packages/languages/de/README.md b/packages/languages/de/README.md index c40b8611..45fada14 100644 --- a/packages/languages/de/README.md +++ b/packages/languages/de/README.md @@ -15,7 +15,7 @@ The German dictionary and language package for **zxcvbn-ts** ## Setup ```js -import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core' +import { ZxcvbnFactory } from '@zxcvbn-ts/core' import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common' import * as zxcvbnDePackage from '@zxcvbn-ts/language-de' @@ -28,7 +28,6 @@ const options = { ...zxcvbnDePackage.dictionary, }, } -zxcvbnOptions.setOptions(options) - -zxcvbn(password) +const zxcvbn = new ZxcvbnFactory(options) +zxcvbn.check(password) ``` diff --git a/packages/languages/en/README.md b/packages/languages/en/README.md index 3b76ccef..b14106ac 100644 --- a/packages/languages/en/README.md +++ b/packages/languages/en/README.md @@ -15,7 +15,7 @@ The English dictionary and language package for zxcvbn-ts ## Setup ```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' @@ -29,7 +29,6 @@ const options = { }, } -zxcvbnOptions.setOptions(options) - -zxcvbn(password) +const zxcvbn = new ZxcvbnFactory(options) +zxcvbn.check(password) ``` diff --git a/packages/languages/es-es/README.md b/packages/languages/es-es/README.md index d6044f68..1fe56ead 100644 --- a/packages/languages/es-es/README.md +++ b/packages/languages/es-es/README.md @@ -15,7 +15,7 @@ The European Spanish dictionary and language package for zxcvbn-ts ## Setup ```js -import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core' +import { ZxcvbnFactory } from '@zxcvbn-ts/core' import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common' import * as zxcvbnEsEsPackage from '@zxcvbn-ts/language-es-es' @@ -29,7 +29,6 @@ const options = { }, } -zxcvbnOptions.setOptions(options) - -zxcvbn(password) +const zxcvbn = new ZxcvbnFactory(options) +zxcvbn.check(password) ``` diff --git a/packages/languages/fi/README.md b/packages/languages/fi/README.md index 7dda0408..42ea9171 100644 --- a/packages/languages/fi/README.md +++ b/packages/languages/fi/README.md @@ -19,7 +19,7 @@ Data sources for first and last names: ## Setup ```js -import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core' +import { ZxcvbnFactory } from '@zxcvbn-ts/core' import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common' import * as zxcvbnFiPackage from '@zxcvbn-ts/language-fi' @@ -33,9 +33,8 @@ const options = { }, } -zxcvbnOptions.setOptions(options) - -zxcvbn(password) +const zxcvbn = new ZxcvbnFactory(options) +zxcvbn.check(password) ``` ## source: diff --git a/packages/languages/fr/README.md b/packages/languages/fr/README.md index d1b7cea6..de41a5f7 100644 --- a/packages/languages/fr/README.md +++ b/packages/languages/fr/README.md @@ -15,7 +15,7 @@ The French dictionary and language package for zxcvbn-ts ## Setup ```js -import zxcvbn from '@zxcvbn-ts/core' +import { ZxcvbnFactory } from '@zxcvbn-ts/core' import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common' import * as zxcvbnFrPackage from '@zxcvbn-ts/language-fr' @@ -28,5 +28,6 @@ const options = { }, } -zxcvbn(password, options) +const zxcvbn = new ZxcvbnFactory(options) +zxcvbn.check(password) ``` diff --git a/packages/languages/id/README.md b/packages/languages/id/README.md index d3223db3..2cf0e72b 100644 --- a/packages/languages/id/README.md +++ b/packages/languages/id/README.md @@ -21,7 +21,7 @@ The Indonesia dictionary and language package for zxcvbn-ts ## Setup ```js -import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core' +import { ZxcvbnFactory } from '@zxcvbn-ts/core' import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common' import * as zxcvbnIdPackage from '@zxcvbn-ts/language-id' @@ -35,7 +35,6 @@ const options = { }, } -zxcvbnOptions.setOptions(options) - -zxcvbn(password) +const zxcvbn = new ZxcvbnFactory(options) +zxcvbn.check(password) ``` diff --git a/packages/languages/it/README.md b/packages/languages/it/README.md index 5fccd566..ddabfbaa 100644 --- a/packages/languages/it/README.md +++ b/packages/languages/it/README.md @@ -15,7 +15,7 @@ The Italian dictionary and language package for zxcvbn-ts ## Setup ```js -import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core' +import { ZxcvbnFactory } from '@zxcvbn-ts/core' import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common' import * as zxcvbnItPackage from '@zxcvbn-ts/language-it' @@ -29,7 +29,6 @@ const options = { }, } -zxcvbnOptions.setOptions(options) - -zxcvbn(password) +const zxcvbn = new ZxcvbnFactory(options) +zxcvbn.check(password) ``` diff --git a/packages/languages/ja/README.md b/packages/languages/ja/README.md index f0d4a7ee..36311526 100644 --- a/packages/languages/ja/README.md +++ b/packages/languages/ja/README.md @@ -15,7 +15,7 @@ The Japanese dictionary and language package for zxcvbn-ts ## Setup ```js -import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core' +import { ZxcvbnFactory } from '@zxcvbn-ts/core' import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common' import * as zxcvbnJaPackage from '@zxcvbn-ts/language-ja' @@ -29,7 +29,6 @@ const options = { }, } -zxcvbnOptions.setOptions(options) - -zxcvbn(password) +const zxcvbn = new ZxcvbnFactory(options) +zxcvbn.check(password) ``` diff --git a/packages/languages/nl-be/README.md b/packages/languages/nl-be/README.md index 29184f0e..eaf8c94d 100644 --- a/packages/languages/nl-be/README.md +++ b/packages/languages/nl-be/README.md @@ -15,7 +15,7 @@ Contains Dutch words specific to Belgium and common Dutch words ## Setup ```js -import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core' +import { ZxcvbnFactory } from '@zxcvbn-ts/core' import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common' import * as zxcvbnNlBePackage from '@zxcvbn-ts/language-nl-be' @@ -28,9 +28,8 @@ const options = { ...zxcvbnNlBePackage.dictionary, }, } -zxcvbnOptions.setOptions(options) - -zxcvbn(password) +const zxcvbn = new ZxcvbnFactory(options) +zxcvbn.check(password) ``` ## Sources diff --git a/packages/languages/pl/README.md b/packages/languages/pl/README.md index c9b7ec67..e146b033 100644 --- a/packages/languages/pl/README.md +++ b/packages/languages/pl/README.md @@ -15,7 +15,7 @@ The Polish dictionary and language package for zxcvbn-ts ## Setup ```js -import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core' +import { ZxcvbnFactory } from '@zxcvbn-ts/core' import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common' import * as zxcvbnPlPackage from '@zxcvbn-ts/language-pl' @@ -29,9 +29,8 @@ const options = { }, } -zxcvbnOptions.setOptions(options) - -zxcvbn(password) +const zxcvbn = new ZxcvbnFactory(options) +zxcvbn.check(password) ``` ## Sources diff --git a/packages/languages/pt-br/README.md b/packages/languages/pt-br/README.md index 548da830..c6f211d2 100644 --- a/packages/languages/pt-br/README.md +++ b/packages/languages/pt-br/README.md @@ -15,7 +15,7 @@ The Brazilian portuguese dictionary and language package for zxcvbn-ts ## Setup ```js -import zxcvbn from '@zxcvbn-ts/core' +import { ZxcvbnFactory } from '@zxcvbn-ts/core' import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common' import * as zxcvbnPtBrPackage from '@zxcvbn-ts/language-pt-br' @@ -28,5 +28,6 @@ const options = { }, } -zxcvbn(password, options) +const zxcvbn = new ZxcvbnFactory(options) +zxcvbn.check(password) ``` diff --git a/packages/libraries/pwned/README.md b/packages/libraries/pwned/README.md index b9b721f8..f80e54b7 100644 --- a/packages/libraries/pwned/README.md +++ b/packages/libraries/pwned/README.md @@ -16,16 +16,19 @@ The pwned matcher is an async matcher that will make a k-anonymity password requ ## Setup ```js -import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core' +import { ZxcvbnFactory } from '@zxcvbn-ts/core' import { matcherPwnedFactory } from '@zxcvbn-ts/matcher-pwned' -const password = 'somePassword' +const matcherPwned = matcherPwnedFactory(fetch) +const customMatcher = { + pwned: matcherPwned +} -const matcherPwned = matcherPwnedFactory(fetch, zxcvbnOptions) -zxcvbnOptions.addMatcher('pwned', matcherPwned) +const zxcvbn = new ZxcvbnFactory(options, customMatcher) +const password = 'somePassword' // @zxcvbn-ts/matcher-pwned is async so zxcvbn will return a promise -zxcvbn(password).then((result) => { +zxcvbn.checkAsync(password).then((result) => { }) ``` diff --git a/scripts/cli-password-tester.ts b/scripts/cli-password-tester.ts index a2c9858a..6955bc9c 100644 --- a/scripts/cli-password-tester.ts +++ b/scripts/cli-password-tester.ts @@ -1,10 +1,10 @@ // eslint-disable-next-line import/no-relative-packages import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common/src/index' import * as zxcvbnEnPackage from '@zxcvbn-ts/language-en/src/index' -import { zxcvbnAsync, zxcvbnOptions } from '@zxcvbn-ts/core/src/index' +import { ZxcvbnFactory } from '@zxcvbn-ts/core/src/index' // eslint-disable-next-line prettier/prettier -(async () => { +;(async () => { const options = { dictionary: { ...zxcvbnCommonPackage.dictionary, @@ -14,8 +14,8 @@ import { zxcvbnAsync, zxcvbnOptions } from '@zxcvbn-ts/core/src/index' graphs: zxcvbnCommonPackage.adjacencyGraphs, useLevenshteinDistance: true, } - zxcvbnOptions.setOptions(options) - return zxcvbnAsync(process.argv[2], process.argv[3]?.split(';')) + const zxcvbn = new ZxcvbnFactory(options) + return zxcvbn.checkAsync(process.argv[2], process.argv[3]?.split(';')) })() .then((match) => { // eslint-disable-next-line no-console From 7a06c801446476cb05ced7d91e1f8338b494ebcc Mon Sep 17 00:00:00 2001 From: MrWook Date: Wed, 3 Jan 2024 12:06:02 +0100 Subject: [PATCH 06/11] chore(zxcvbn): resolve linter --- .../main/src/matcher/dictionary/variants/matching/reverse.ts | 2 +- scripts/cli-password-tester.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/libraries/main/src/matcher/dictionary/variants/matching/reverse.ts b/packages/libraries/main/src/matcher/dictionary/variants/matching/reverse.ts index eaa4cc5a..91319482 100644 --- a/packages/libraries/main/src/matcher/dictionary/variants/matching/reverse.ts +++ b/packages/libraries/main/src/matcher/dictionary/variants/matching/reverse.ts @@ -1,4 +1,4 @@ -import { DictionaryMatch, MatchOptions } from '../../../../types' +import { DictionaryMatch } from '../../../../types' import { DefaultMatch, DictionaryMatchOptions } from '../../types' import { Options } from '../../../../Options' diff --git a/scripts/cli-password-tester.ts b/scripts/cli-password-tester.ts index 6955bc9c..881a5d0c 100644 --- a/scripts/cli-password-tester.ts +++ b/scripts/cli-password-tester.ts @@ -3,7 +3,7 @@ import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common/src/index' import * as zxcvbnEnPackage from '@zxcvbn-ts/language-en/src/index' import { ZxcvbnFactory } from '@zxcvbn-ts/core/src/index' -// eslint-disable-next-line prettier/prettier +// eslint-disable-next-line ;(async () => { const options = { dictionary: { From e924c79ebd9f79c683b7c9d7d6ba42fd4502df2c Mon Sep 17 00:00:00 2001 From: MrWook Date: Wed, 3 Jan 2024 12:06:29 +0100 Subject: [PATCH 07/11] chore(zxcvbn): resolve linter --- scripts/cli-password-tester.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/cli-password-tester.ts b/scripts/cli-password-tester.ts index 881a5d0c..f1580e08 100644 --- a/scripts/cli-password-tester.ts +++ b/scripts/cli-password-tester.ts @@ -4,7 +4,7 @@ import * as zxcvbnEnPackage from '@zxcvbn-ts/language-en/src/index' import { ZxcvbnFactory } from '@zxcvbn-ts/core/src/index' // eslint-disable-next-line -;(async () => { +(async () => { const options = { dictionary: { ...zxcvbnCommonPackage.dictionary, From 398666fa10f598a1a51a7380a550f68731a41f01 Mon Sep 17 00:00:00 2001 From: MrWook Date: Wed, 17 Jan 2024 16:10:33 +0100 Subject: [PATCH 08/11] refactor(zxcvbn): cleanup code --- packages/libraries/main/src/Matching.ts | 57 ++++++++++++------- packages/libraries/main/src/index.ts | 24 ++++++-- .../main/src/matcher/spatial/scoring.ts | 21 ++++--- .../src/utils/mergeUserInputDictionary.ts | 25 +++----- packages/libraries/pwned/src/feedback.ts | 6 +- 5 files changed, 82 insertions(+), 51 deletions(-) diff --git a/packages/libraries/main/src/Matching.ts b/packages/libraries/main/src/Matching.ts index 60e98727..6c598c8d 100644 --- a/packages/libraries/main/src/Matching.ts +++ b/packages/libraries/main/src/Matching.ts @@ -44,6 +44,39 @@ class Matching { return new Matcher(this.options) } + private processResult( + matches: MatchExtended[], + promises: Promise[], + result: MatchExtended[] | Promise, + ) { + if (result instanceof Promise) { + result.then((response) => { + extend(matches, response) + }) + promises.push(result) + } else { + extend(matches, result) + } + } + + private handlePromises( + matches: MatchExtended[], + promises: Promise[], + ) { + if (promises.length > 0) { + return new Promise((resolve, reject) => { + Promise.all(promises) + .then(() => { + resolve(sorted(matches)) + }) + .catch((error) => { + reject(error) + }) + }) + } + return sorted(matches) + } + match( password: string, userInputsOptions?: UserInputsOptions, @@ -66,27 +99,11 @@ class Matching { userInputsOptions, }) - if (result instanceof Promise) { - result.then((response) => { - extend(matches, response) - }) - promises.push(result) - } else { - extend(matches, result) - } + // extends matches and promises by references + this.processResult(matches, promises, result) }) - if (promises.length > 0) { - return new Promise((resolve, reject) => { - Promise.all(promises) - .then(() => { - resolve(sorted(matches)) - }) - .catch((error) => { - reject(error) - }) - }) - } - return sorted(matches) + + return this.handlePromises(matches, promises) } } diff --git a/packages/libraries/main/src/index.ts b/packages/libraries/main/src/index.ts index 63ff345c..e2174b6c 100644 --- a/packages/libraries/main/src/index.ts +++ b/packages/libraries/main/src/index.ts @@ -3,7 +3,13 @@ import TimeEstimates from './TimeEstimates' import Feedback from './Feedback' import { Options } from './Options' import debounce from './utils/debounce' -import { Matcher, MatchExtended, OptionsType, ZxcvbnResult } from './types' +import { + Matcher, + MatchEstimated, + MatchExtended, + OptionsType, + ZxcvbnResult, +} from './types' import Scoring from './scoring' const time = () => new Date().getTime() @@ -21,25 +27,33 @@ class ZxcvbnFactory { this.scoring = new Scoring(this.options) } + private estimateAttackTimes(guesses: number) { + const timeEstimates = new TimeEstimates(this.options) + return timeEstimates.estimateAttackTimes(guesses) + } + + private getFeedback(score: number, sequence: MatchEstimated[]) { + const feedback = new Feedback(this.options) + return feedback.getFeedback(score, sequence) + } + private createReturnValue( resolvedMatches: MatchExtended[], password: string, start: number, ): ZxcvbnResult { - const feedback = new Feedback(this.options) - const timeEstimates = new TimeEstimates(this.options) const matchSequence = this.scoring.mostGuessableMatchSequence( password, resolvedMatches, ) const calcTime = time() - start - const attackTimes = timeEstimates.estimateAttackTimes(matchSequence.guesses) + const attackTimes = this.estimateAttackTimes(matchSequence.guesses) return { calcTime, ...matchSequence, ...attackTimes, - feedback: feedback.getFeedback(attackTimes.score, matchSequence.sequence), + feedback: this.getFeedback(attackTimes.score, matchSequence.sequence), } } diff --git a/packages/libraries/main/src/matcher/spatial/scoring.ts b/packages/libraries/main/src/matcher/spatial/scoring.ts index c70db33e..b1957ca3 100644 --- a/packages/libraries/main/src/matcher/spatial/scoring.ts +++ b/packages/libraries/main/src/matcher/spatial/scoring.ts @@ -1,10 +1,14 @@ import utils from '../../scoring/utils' import { Options } from '../../Options' -import { LooseObject, MatchEstimated, MatchExtended } from '../../types' +import { + LooseObject, + MatchEstimated, + MatchExtended, + OptionsGraphEntry, +} from '../../types' interface EstimatePossiblePatternsOptions { token: string - graph: string turns: number } @@ -19,11 +23,11 @@ const calcAverageDegree = (graph: LooseObject) => { } const estimatePossiblePatterns = ( - options: Options, - { token, graph, turns }: EstimatePossiblePatternsOptions, + graphEntry: OptionsGraphEntry, + { token, turns }: EstimatePossiblePatternsOptions, ) => { - const startingPosition = Object.keys(options.graphs[graph]).length - const averageDegree = calcAverageDegree(options.graphs[graph]) + const startingPosition = Object.keys(graphEntry).length + const averageDegree = calcAverageDegree(graphEntry) let guesses = 0 const tokenLength = token.length @@ -41,7 +45,10 @@ export default ( { graph, token, shiftedCount, turns }: MatchExtended | MatchEstimated, options: Options, ) => { - let guesses = estimatePossiblePatterns(options, { token, graph, turns }) + let guesses = estimatePossiblePatterns(options.graphs[graph], { + token, + turns, + }) // add extra guesses for shifted keys. (% instead of 5, A instead of a.) // math is similar to extra guesses of l33t substitutions in dictionary matches. diff --git a/packages/libraries/main/src/utils/mergeUserInputDictionary.ts b/packages/libraries/main/src/utils/mergeUserInputDictionary.ts index 1ee274cd..7cff38e6 100644 --- a/packages/libraries/main/src/utils/mergeUserInputDictionary.ts +++ b/packages/libraries/main/src/utils/mergeUserInputDictionary.ts @@ -18,25 +18,16 @@ export default ( } } - if (rankedDictionaries.userInputs) { - const isWordSizeBigger = - rankedDictionariesMaxWordSize.userInputs < - userInputsOptions.rankedDictionaryMaxWordSize - - rankedDictionaries.userInputs = { - ...rankedDictionaries.userInputs, - ...userInputsOptions.rankedDictionary, - } - - rankedDictionariesMaxWordSize.userInputs = isWordSizeBigger - ? userInputsOptions.rankedDictionaryMaxWordSize - : rankedDictionariesMaxWordSize.userInputs - } else { - rankedDictionaries.userInputs = userInputsOptions.rankedDictionary - rankedDictionariesMaxWordSize.userInputs = - userInputsOptions.rankedDictionaryMaxWordSize + rankedDictionaries.userInputs = { + ...(rankedDictionaries.userInputs || {}), + ...userInputsOptions.rankedDictionary, } + rankedDictionariesMaxWordSize.userInputs = Math.max( + userInputsOptions.rankedDictionaryMaxWordSize, + rankedDictionariesMaxWordSize.userInputs || 0, + ) + return { rankedDictionaries, rankedDictionariesMaxWordSize, diff --git a/packages/libraries/pwned/src/feedback.ts b/packages/libraries/pwned/src/feedback.ts index d59763b7..62b3fcce 100644 --- a/packages/libraries/pwned/src/feedback.ts +++ b/packages/libraries/pwned/src/feedback.ts @@ -1,9 +1,11 @@ // @ts-ignore -import { Options } from '@zxcvbn-ts/core' +import { DefaultFeedbackFunction } from '@zxcvbn-ts/core' -export default (options: Options) => { +const pwnedFeedback: DefaultFeedbackFunction = (options) => { return { warning: options.translations.warnings.pwned, suggestions: [options.translations.suggestions.pwned], } } + +export default pwnedFeedback From a174f420b158d0b3f4f724cc02d382996ffe8409 Mon Sep 17 00:00:00 2001 From: MrWook Date: Wed, 17 Jan 2024 16:28:57 +0100 Subject: [PATCH 09/11] chore(tests): revert guess change as it was fixed in another branch --- packages/libraries/main/test/scoring/guesses/calc.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/libraries/main/test/scoring/guesses/calc.spec.ts b/packages/libraries/main/test/scoring/guesses/calc.spec.ts index 4f2d962b..bca93250 100644 --- a/packages/libraries/main/test/scoring/guesses/calc.spec.ts +++ b/packages/libraries/main/test/scoring/guesses/calc.spec.ts @@ -32,7 +32,7 @@ describe('scoring', () => { day: 14, // @ts-ignore guesses: dateGuesses(match), - guessesLog10: 4.234390722392192, + guessesLog10: 4.225050696138048, }) }) }) From 04ec72bf5f5ea0a610ece37146df5444c4332d52 Mon Sep 17 00:00:00 2001 From: MrWook Date: Wed, 17 Jan 2024 16:38:41 +0100 Subject: [PATCH 10/11] docs(docs): document changes --- docs/guide/framework-examples/README.md | 5 ++--- packages/libraries/main/src/Options.ts | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/guide/framework-examples/README.md b/docs/guide/framework-examples/README.md index 17b94708..3772810e 100644 --- a/docs/guide/framework-examples/README.md +++ b/docs/guide/framework-examples/README.md @@ -36,10 +36,9 @@ const myPlugin = { // optional translations: zxcvbnEnPackage.translations, } - zxcvbnOptions.setOptions(options) + + Vue.prototype.$zxcvbn = new ZxcvbnFactory(options, customMatcher) }, - - Vue.prototype.$zxcvbn = new ZxcvbnFactory(options, customMatcher) } ``` diff --git a/packages/libraries/main/src/Options.ts b/packages/libraries/main/src/Options.ts index 6aec1bf5..23b71d97 100644 --- a/packages/libraries/main/src/Options.ts +++ b/packages/libraries/main/src/Options.ts @@ -185,5 +185,3 @@ export class Options { } } } - -export const zxcvbnOptions = new Options() From 02824b78898399fc96c5cb4f0f1976a16573e67e Mon Sep 17 00:00:00 2001 From: MrWook Date: Wed, 17 Jan 2024 16:53:52 +0100 Subject: [PATCH 11/11] chore(lint): resolve Options class to default export --- data-scripts/_generators/PasswordGenerator.ts | 2 +- packages/libraries/main/src/Feedback.ts | 2 +- packages/libraries/main/src/Matching.ts | 2 +- packages/libraries/main/src/Options.ts | 2 +- packages/libraries/main/src/TimeEstimates.ts | 2 +- packages/libraries/main/src/index.ts | 4 ++-- .../libraries/main/src/matcher/date/feedback.ts | 2 +- .../libraries/main/src/matcher/date/matching.ts | 2 +- .../main/src/matcher/dictionary/feedback.ts | 2 +- .../main/src/matcher/dictionary/matching.ts | 2 +- .../matcher/dictionary/variants/matching/l33t.ts | 2 +- .../dictionary/variants/matching/reverse.ts | 2 +- .../matching/unmunger/getCleanPasswords.ts | 15 +++++++-------- .../libraries/main/src/matcher/regex/feedback.ts | 2 +- .../libraries/main/src/matcher/regex/matching.ts | 2 +- .../libraries/main/src/matcher/repeat/feedback.ts | 2 +- .../libraries/main/src/matcher/repeat/matching.ts | 2 +- .../main/src/matcher/separator/matching.ts | 2 +- .../main/src/matcher/sequence/feedback.ts | 2 +- .../main/src/matcher/sequence/matching.ts | 2 +- .../main/src/matcher/spatial/feedback.ts | 2 +- .../main/src/matcher/spatial/matching.ts | 2 +- .../libraries/main/src/matcher/spatial/scoring.ts | 2 +- packages/libraries/main/src/scoring/estimate.ts | 2 +- packages/libraries/main/src/scoring/index.ts | 2 +- packages/libraries/main/src/types.ts | 2 +- .../libraries/main/test/customMatcher.spec.ts | 3 ++- packages/libraries/main/test/feedback.spec.ts | 2 +- .../main/test/matcher/date/matching.spec.ts | 2 +- .../main/test/matcher/dictionary/matching.spec.ts | 2 +- .../variant/matching/dictionaryReverse.spec.ts | 2 +- .../dictionary/variant/matching/l33t.spec.ts | 2 +- .../main/test/matcher/regex/matching.spec.ts | 2 +- .../main/test/matcher/repeat/matching.spec.ts | 2 +- .../main/test/matcher/repeat/scoring.spec.ts | 2 +- .../main/test/matcher/separator/matching.spec.ts | 2 +- .../main/test/matcher/sequence/matching.spec.ts | 2 +- .../main/test/matcher/spatial/matching.spec.ts | 2 +- .../main/test/matcher/spatial/scoring.spec.ts | 2 +- packages/libraries/main/test/matching.spec.ts | 2 +- packages/libraries/main/test/options.spec.ts | 2 +- .../main/test/scoring/guesses/calc.spec.ts | 2 +- .../libraries/main/test/scoring/search.spec.ts | 2 +- .../libraries/main/test/timeEstimates.spec.ts | 2 +- packages/libraries/pwned/test/matching.spec.ts | 2 +- 45 files changed, 53 insertions(+), 53 deletions(-) diff --git a/data-scripts/_generators/PasswordGenerator.ts b/data-scripts/_generators/PasswordGenerator.ts index 8c3e944e..b4d2f52d 100644 --- a/data-scripts/_generators/PasswordGenerator.ts +++ b/data-scripts/_generators/PasswordGenerator.ts @@ -4,7 +4,7 @@ import sprintfClass from 'sprintf-js' import { MatchExtended } from '@zxcvbn-ts/core/src/types' import Matching from '../../packages/libraries/main/src/Matching' import estimateGuesses from '../../packages/libraries/main/src/scoring/estimate' -import { Options } from '../../packages/libraries/main/src/Options' +import Options from '../../packages/libraries/main/src/Options' const CUTOFF = 10 const BATCH_SIZE = 1000000 diff --git a/packages/libraries/main/src/Feedback.ts b/packages/libraries/main/src/Feedback.ts index 2e34fbbe..53f16440 100644 --- a/packages/libraries/main/src/Feedback.ts +++ b/packages/libraries/main/src/Feedback.ts @@ -1,4 +1,4 @@ -import { Options } from './Options' +import Options from './Options' import { DefaultFeedbackFunction, FeedbackType, MatchEstimated } from './types' import bruteforceMatcher from './matcher/bruteforce/feedback' import dateMatcher from './matcher/date/feedback' diff --git a/packages/libraries/main/src/Matching.ts b/packages/libraries/main/src/Matching.ts index 6c598c8d..2a08d4e2 100644 --- a/packages/libraries/main/src/Matching.ts +++ b/packages/libraries/main/src/Matching.ts @@ -7,7 +7,7 @@ import repeatMatcher from './matcher/repeat/matching' import sequenceMatcher from './matcher/sequence/matching' import spatialMatcher from './matcher/spatial/matching' import separatorMatcher from './matcher/separator/matching' -import { Options } from './Options' +import Options from './Options' /* * ------------------------------------------------------------------------------- diff --git a/packages/libraries/main/src/Options.ts b/packages/libraries/main/src/Options.ts index 23b71d97..3a1dc833 100644 --- a/packages/libraries/main/src/Options.ts +++ b/packages/libraries/main/src/Options.ts @@ -16,7 +16,7 @@ import translationKeys from './data/translationKeys' import TrieNode from './matcher/dictionary/variants/matching/unmunger/TrieNode' import l33tTableToTrieNode from './matcher/dictionary/variants/matching/unmunger/l33tTableToTrieNode' -export class Options { +export default class Options { public matchers: Matchers = {} public l33tTable: OptionsL33tTable = l33tTable diff --git a/packages/libraries/main/src/TimeEstimates.ts b/packages/libraries/main/src/TimeEstimates.ts index 70ac4d64..271a0c61 100644 --- a/packages/libraries/main/src/TimeEstimates.ts +++ b/packages/libraries/main/src/TimeEstimates.ts @@ -1,4 +1,4 @@ -import { Options } from './Options' +import Options from './Options' import { CrackTimesDisplay, CrackTimesSeconds, Score } from './types' const SECOND = 1 diff --git a/packages/libraries/main/src/index.ts b/packages/libraries/main/src/index.ts index e2174b6c..2a9a9484 100644 --- a/packages/libraries/main/src/index.ts +++ b/packages/libraries/main/src/index.ts @@ -1,7 +1,7 @@ import Matching from './Matching' import TimeEstimates from './TimeEstimates' import Feedback from './Feedback' -import { Options } from './Options' +import Options from './Options' import debounce from './utils/debounce' import { Matcher, @@ -88,4 +88,4 @@ class ZxcvbnFactory { } export * from './types' -export { ZxcvbnFactory, Options, debounce } +export { ZxcvbnFactory, debounce } diff --git a/packages/libraries/main/src/matcher/date/feedback.ts b/packages/libraries/main/src/matcher/date/feedback.ts index 23d92761..d6dc28a7 100644 --- a/packages/libraries/main/src/matcher/date/feedback.ts +++ b/packages/libraries/main/src/matcher/date/feedback.ts @@ -1,4 +1,4 @@ -import { Options } from '../../Options' +import Options from '../../Options' export default (options: Options) => { return { diff --git a/packages/libraries/main/src/matcher/date/matching.ts b/packages/libraries/main/src/matcher/date/matching.ts index cb08281a..9ea993b2 100644 --- a/packages/libraries/main/src/matcher/date/matching.ts +++ b/packages/libraries/main/src/matcher/date/matching.ts @@ -6,7 +6,7 @@ import { } from '../../data/const' import { sorted } from '../../utils/helper' import { DateMatch, MatchOptions } from '../../types' -import { Options } from '../../Options' +import Options from '../../Options' type DateMatchOptions = Pick diff --git a/packages/libraries/main/src/matcher/dictionary/feedback.ts b/packages/libraries/main/src/matcher/dictionary/feedback.ts index 74626155..a4f815ed 100644 --- a/packages/libraries/main/src/matcher/dictionary/feedback.ts +++ b/packages/libraries/main/src/matcher/dictionary/feedback.ts @@ -1,4 +1,4 @@ -import { Options } from '../../Options' +import Options from '../../Options' import { MatchEstimated } from '../../types' import { ALL_UPPER_INVERTED, START_UPPER } from '../../data/const' diff --git a/packages/libraries/main/src/matcher/dictionary/matching.ts b/packages/libraries/main/src/matcher/dictionary/matching.ts index 839c433a..ac253620 100644 --- a/packages/libraries/main/src/matcher/dictionary/matching.ts +++ b/packages/libraries/main/src/matcher/dictionary/matching.ts @@ -2,7 +2,7 @@ import findLevenshteinDistance, { FindLevenshteinDistanceResult, } from '../../utils/levenshtein' import { sorted } from '../../utils/helper' -import { Options } from '../../Options' +import Options from '../../Options' import { DictionaryNames, DictionaryMatch, L33tMatch } from '../../types' import Reverse from './variants/matching/reverse' import L33t from './variants/matching/l33t' diff --git a/packages/libraries/main/src/matcher/dictionary/variants/matching/l33t.ts b/packages/libraries/main/src/matcher/dictionary/variants/matching/l33t.ts index 826443ef..8bac2e59 100644 --- a/packages/libraries/main/src/matcher/dictionary/variants/matching/l33t.ts +++ b/packages/libraries/main/src/matcher/dictionary/variants/matching/l33t.ts @@ -1,4 +1,4 @@ -import { Options } from '../../../../Options' +import Options from '../../../../Options' import { DictionaryMatch, L33tMatch } from '../../../../types' import { DefaultMatch, DictionaryMatchOptions } from '../../types' import getCleanPasswords, { diff --git a/packages/libraries/main/src/matcher/dictionary/variants/matching/reverse.ts b/packages/libraries/main/src/matcher/dictionary/variants/matching/reverse.ts index 91319482..6fe81340 100644 --- a/packages/libraries/main/src/matcher/dictionary/variants/matching/reverse.ts +++ b/packages/libraries/main/src/matcher/dictionary/variants/matching/reverse.ts @@ -1,6 +1,6 @@ import { DictionaryMatch } from '../../../../types' import { DefaultMatch, DictionaryMatchOptions } from '../../types' -import { Options } from '../../../../Options' +import Options from '../../../../Options' /* * ------------------------------------------------------------------------------- diff --git a/packages/libraries/main/src/matcher/dictionary/variants/matching/unmunger/getCleanPasswords.ts b/packages/libraries/main/src/matcher/dictionary/variants/matching/unmunger/getCleanPasswords.ts index 45bc77c7..cf743d73 100644 --- a/packages/libraries/main/src/matcher/dictionary/variants/matching/unmunger/getCleanPasswords.ts +++ b/packages/libraries/main/src/matcher/dictionary/variants/matching/unmunger/getCleanPasswords.ts @@ -76,7 +76,11 @@ class CleanPasswords { if (index === this.substr.length) { if (onlyFullSub === isFullSub) { - this.finalPasswords.push({ password: this.buffer.join(''), changes, isFullSubstitution: onlyFullSub }) + this.finalPasswords.push({ + password: this.buffer.join(''), + changes, + isFullSubstitution: onlyFullSub, + }) } return } @@ -93,10 +97,7 @@ class CleanPasswords { // Skip if this would be a 4th or more consecutive substitution of the same letter // this should work in all language as there shouldn't be the same letter more than four times in a row // So we can ignore the rest to save calculation time - if ( - lastSubLetter === sub && - consecutiveSubCount >= 3 - ) { + if (lastSubLetter === sub && consecutiveSubCount >= 3) { // eslint-disable-next-line no-continue continue } @@ -120,9 +121,7 @@ class CleanPasswords { changes: newSubs, lastSubLetter: sub, consecutiveSubCount: - lastSubLetter === sub - ? consecutiveSubCount + 1 - : 1, + lastSubLetter === sub ? consecutiveSubCount + 1 : 1, }) // backtrack by ignoring the added postfix this.buffer.pop() diff --git a/packages/libraries/main/src/matcher/regex/feedback.ts b/packages/libraries/main/src/matcher/regex/feedback.ts index ba36d991..56b74936 100644 --- a/packages/libraries/main/src/matcher/regex/feedback.ts +++ b/packages/libraries/main/src/matcher/regex/feedback.ts @@ -1,4 +1,4 @@ -import { Options } from '../../Options' +import Options from '../../Options' import { MatchEstimated } from '../../types' export default (options: Options, match: MatchEstimated) => { diff --git a/packages/libraries/main/src/matcher/regex/matching.ts b/packages/libraries/main/src/matcher/regex/matching.ts index 39da6cb6..fb5b0463 100644 --- a/packages/libraries/main/src/matcher/regex/matching.ts +++ b/packages/libraries/main/src/matcher/regex/matching.ts @@ -1,7 +1,7 @@ import { REGEXEN } from '../../data/const' import { sorted } from '../../utils/helper' import { MatchOptions, RegexMatch } from '../../types' -import { Options } from '../../Options' +import Options from '../../Options' type RegexMatchOptions = Pick diff --git a/packages/libraries/main/src/matcher/repeat/feedback.ts b/packages/libraries/main/src/matcher/repeat/feedback.ts index 1db0e2b8..a6499d21 100644 --- a/packages/libraries/main/src/matcher/repeat/feedback.ts +++ b/packages/libraries/main/src/matcher/repeat/feedback.ts @@ -1,4 +1,4 @@ -import { Options } from '../../Options' +import Options from '../../Options' import { MatchEstimated } from '../../types' export default (options: Options, match: MatchEstimated) => { diff --git a/packages/libraries/main/src/matcher/repeat/matching.ts b/packages/libraries/main/src/matcher/repeat/matching.ts index 2e1feaae..9cf39b3c 100644 --- a/packages/libraries/main/src/matcher/repeat/matching.ts +++ b/packages/libraries/main/src/matcher/repeat/matching.ts @@ -1,7 +1,7 @@ import { MatchOptions, RepeatMatch } from '../../types' import Scoring from '../../scoring' import Matching from '../../Matching' -import { Options } from '../../Options' +import Options from '../../Options' /* *------------------------------------------------------------------------------- diff --git a/packages/libraries/main/src/matcher/separator/matching.ts b/packages/libraries/main/src/matcher/separator/matching.ts index ca1a4f8b..7d75cbfc 100644 --- a/packages/libraries/main/src/matcher/separator/matching.ts +++ b/packages/libraries/main/src/matcher/separator/matching.ts @@ -1,6 +1,6 @@ import { SEPERATOR_CHARS } from '../../data/const' import { MatchOptions, SeparatorMatch } from '../../types' -import { Options } from '../../Options' +import Options from '../../Options' type SeparatorMatchOptions = Pick diff --git a/packages/libraries/main/src/matcher/sequence/feedback.ts b/packages/libraries/main/src/matcher/sequence/feedback.ts index feee74ae..e437b60c 100644 --- a/packages/libraries/main/src/matcher/sequence/feedback.ts +++ b/packages/libraries/main/src/matcher/sequence/feedback.ts @@ -1,4 +1,4 @@ -import { Options } from '../../Options' +import Options from '../../Options' export default (options: Options) => { return { diff --git a/packages/libraries/main/src/matcher/sequence/matching.ts b/packages/libraries/main/src/matcher/sequence/matching.ts index ba9aa503..f5fa7c0b 100644 --- a/packages/libraries/main/src/matcher/sequence/matching.ts +++ b/packages/libraries/main/src/matcher/sequence/matching.ts @@ -1,6 +1,6 @@ import { ALL_UPPER, ALL_LOWER, ALL_DIGIT } from '../../data/const' import { MatchOptions, SequenceMatch } from '../../types' -import { Options } from '../../Options' +import Options from '../../Options' type UpdateParams = { i: number diff --git a/packages/libraries/main/src/matcher/spatial/feedback.ts b/packages/libraries/main/src/matcher/spatial/feedback.ts index 06351c82..f5ef7388 100644 --- a/packages/libraries/main/src/matcher/spatial/feedback.ts +++ b/packages/libraries/main/src/matcher/spatial/feedback.ts @@ -1,4 +1,4 @@ -import { Options } from '../../Options' +import Options from '../../Options' import { MatchEstimated } from '../../types' export default (options: Options, match: MatchEstimated) => { diff --git a/packages/libraries/main/src/matcher/spatial/matching.ts b/packages/libraries/main/src/matcher/spatial/matching.ts index e6cd2bab..a1c0afa1 100644 --- a/packages/libraries/main/src/matcher/spatial/matching.ts +++ b/packages/libraries/main/src/matcher/spatial/matching.ts @@ -1,5 +1,5 @@ import { sorted, extend } from '../../utils/helper' -import { Options } from '../../Options' +import Options from '../../Options' import { LooseObject, MatchOptions, SpatialMatch } from '../../types' type SpatialMatchOptions = Pick diff --git a/packages/libraries/main/src/matcher/spatial/scoring.ts b/packages/libraries/main/src/matcher/spatial/scoring.ts index b1957ca3..fe5849e0 100644 --- a/packages/libraries/main/src/matcher/spatial/scoring.ts +++ b/packages/libraries/main/src/matcher/spatial/scoring.ts @@ -1,5 +1,5 @@ import utils from '../../scoring/utils' -import { Options } from '../../Options' +import Options from '../../Options' import { LooseObject, MatchEstimated, diff --git a/packages/libraries/main/src/scoring/estimate.ts b/packages/libraries/main/src/scoring/estimate.ts index 164e8d1b..28c84f0f 100644 --- a/packages/libraries/main/src/scoring/estimate.ts +++ b/packages/libraries/main/src/scoring/estimate.ts @@ -3,7 +3,7 @@ import { MIN_SUBMATCH_GUESSES_MULTI_CHAR, } from '../data/const' import utils from './utils' -import { Options } from '../Options' +import Options from '../Options' import { DefaultScoringFunction, LooseObject, diff --git a/packages/libraries/main/src/scoring/index.ts b/packages/libraries/main/src/scoring/index.ts index 1c441ce5..3185d8a0 100644 --- a/packages/libraries/main/src/scoring/index.ts +++ b/packages/libraries/main/src/scoring/index.ts @@ -7,7 +7,7 @@ import { MatchEstimated, LooseObject, } from '../types' -import { Options } from '../Options' +import Options from '../Options' const scoringHelper = { password: '', diff --git a/packages/libraries/main/src/types.ts b/packages/libraries/main/src/types.ts index f1b14cad..c7ece43f 100644 --- a/packages/libraries/main/src/types.ts +++ b/packages/libraries/main/src/types.ts @@ -4,7 +4,7 @@ import { REGEXEN } from './data/const' import { DictionaryReturn } from './matcher/dictionary/scoring' import Matching from './Matching' import { PasswordChanges } from './matcher/dictionary/variants/matching/unmunger/getCleanPasswords' -import { Options } from './Options' +import Options from './Options' export type TranslationKeys = typeof translationKeys export type L33tTableDefault = typeof l33tTableDefault diff --git a/packages/libraries/main/test/customMatcher.spec.ts b/packages/libraries/main/test/customMatcher.spec.ts index 696f46ed..183f9d68 100644 --- a/packages/libraries/main/test/customMatcher.spec.ts +++ b/packages/libraries/main/test/customMatcher.spec.ts @@ -1,8 +1,9 @@ import * as zxcvbnCommonPackage from '../../../languages/common/src' import * as zxcvbnEnPackage from '../../../languages/en/src' -import { Options, ZxcvbnFactory } from '../src' +import { ZxcvbnFactory } from '../src' import { Match, Matcher } from '../src/types' import { sorted } from '../src/utils/helper' +import Options from '../src/Options' const minLengthMatcher: Matcher = { Matching: class MatchMinLength { diff --git a/packages/libraries/main/test/feedback.spec.ts b/packages/libraries/main/test/feedback.spec.ts index 7427d994..00067e6f 100644 --- a/packages/libraries/main/test/feedback.spec.ts +++ b/packages/libraries/main/test/feedback.spec.ts @@ -1,5 +1,5 @@ import translations from '../../../languages/en/src/translations' -import { Options } from '../src/Options' +import Options from '../src/Options' import Feedback from '../src/Feedback' const zxcvbnOptions = new Options({ diff --git a/packages/libraries/main/test/matcher/date/matching.spec.ts b/packages/libraries/main/test/matcher/date/matching.spec.ts index 48c77d98..82061250 100644 --- a/packages/libraries/main/test/matcher/date/matching.spec.ts +++ b/packages/libraries/main/test/matcher/date/matching.spec.ts @@ -1,7 +1,7 @@ import MatchDate from '../../../src/matcher/date/matching' import checkMatches from '../../helper/checkMatches' import genpws from '../../helper/genpws' -import { Options } from '../../../src' +import Options from '../../../src/Options' describe('date matching', () => { const zxcvbnOptions = new Options() diff --git a/packages/libraries/main/test/matcher/dictionary/matching.spec.ts b/packages/libraries/main/test/matcher/dictionary/matching.spec.ts index fefd405a..8cab1772 100644 --- a/packages/libraries/main/test/matcher/dictionary/matching.spec.ts +++ b/packages/libraries/main/test/matcher/dictionary/matching.spec.ts @@ -3,7 +3,7 @@ import * as zxcvbnEnPackage from '../../../../../languages/en/src' import MatchDictionary from '../../../src/matcher/dictionary/matching' import checkMatches from '../../helper/checkMatches' import genpws from '../../helper/genpws' -import { Options } from '../../../src/Options' +import Options from '../../../src/Options' describe('dictionary matching', () => { describe('Default dictionary', () => { diff --git a/packages/libraries/main/test/matcher/dictionary/variant/matching/dictionaryReverse.spec.ts b/packages/libraries/main/test/matcher/dictionary/variant/matching/dictionaryReverse.spec.ts index 75caede3..6af8d6dc 100644 --- a/packages/libraries/main/test/matcher/dictionary/variant/matching/dictionaryReverse.spec.ts +++ b/packages/libraries/main/test/matcher/dictionary/variant/matching/dictionaryReverse.spec.ts @@ -1,7 +1,7 @@ import MatchDictionaryReverse from '../../../../../src/matcher/dictionary/variants/matching/reverse' import MatchDictionary from '../../../../../src/matcher/dictionary/matching' import checkMatches from '../../../../helper/checkMatches' -import { Options } from '../../../../../src/Options' +import Options from '../../../../../src/Options' describe('dictionary reverse matching', () => { const testDicts = { diff --git a/packages/libraries/main/test/matcher/dictionary/variant/matching/l33t.spec.ts b/packages/libraries/main/test/matcher/dictionary/variant/matching/l33t.spec.ts index 6512ceb3..56166fe4 100644 --- a/packages/libraries/main/test/matcher/dictionary/variant/matching/l33t.spec.ts +++ b/packages/libraries/main/test/matcher/dictionary/variant/matching/l33t.spec.ts @@ -1,7 +1,7 @@ import MatchL33t from '../../../../../src/matcher/dictionary/variants/matching/l33t' import MatchDictionary from '../../../../../src/matcher/dictionary/matching' import checkMatches from '../../../../helper/checkMatches' -import { Options } from '../../../../../src/Options' +import Options from '../../../../../src/Options' import { sorted } from '../../../../../src/utils/helper' describe('l33t matching', () => { diff --git a/packages/libraries/main/test/matcher/regex/matching.spec.ts b/packages/libraries/main/test/matcher/regex/matching.spec.ts index b14eef63..897d3a67 100644 --- a/packages/libraries/main/test/matcher/regex/matching.spec.ts +++ b/packages/libraries/main/test/matcher/regex/matching.spec.ts @@ -1,6 +1,6 @@ import MatchRegex from '../../../src/matcher/regex/matching' import checkMatches from '../../helper/checkMatches' -import { Options } from '../../../src' +import Options from '../../../src/Options' describe('regex matching', () => { const data = [ diff --git a/packages/libraries/main/test/matcher/repeat/matching.spec.ts b/packages/libraries/main/test/matcher/repeat/matching.spec.ts index 7fdc4f0c..0b63e2ac 100644 --- a/packages/libraries/main/test/matcher/repeat/matching.spec.ts +++ b/packages/libraries/main/test/matcher/repeat/matching.spec.ts @@ -2,7 +2,7 @@ import MatchRepeat from '../../../src/matcher/repeat/matching' import checkMatches from '../../helper/checkMatches' import genpws from '../../helper/genpws' import MatchOmni from '../../../src/Matching' -import { Options } from '../../../src/Options' +import Options from '../../../src/Options' import { RepeatMatch } from '../../../src/types' const zxcvbnOptions = new Options() diff --git a/packages/libraries/main/test/matcher/repeat/scoring.spec.ts b/packages/libraries/main/test/matcher/repeat/scoring.spec.ts index aafc8beb..a14aee4f 100644 --- a/packages/libraries/main/test/matcher/repeat/scoring.spec.ts +++ b/packages/libraries/main/test/matcher/repeat/scoring.spec.ts @@ -1,7 +1,7 @@ import repeatGuesses from '../../../src/matcher/repeat/scoring' import Scoring from '../../../src/scoring' import MatchOmni from '../../../src/Matching' -import { Options } from '../../../src/Options' +import Options from '../../../src/Options' import { MatchExtended } from '../../../src/types' const zxcvbnOptions = new Options() diff --git a/packages/libraries/main/test/matcher/separator/matching.spec.ts b/packages/libraries/main/test/matcher/separator/matching.spec.ts index 8169fac4..e364a151 100644 --- a/packages/libraries/main/test/matcher/separator/matching.spec.ts +++ b/packages/libraries/main/test/matcher/separator/matching.spec.ts @@ -1,6 +1,6 @@ import MatchSeparator from '../../../src/matcher/separator/matching' import checkMatches from '../../helper/checkMatches' -import { Options } from '../../../src' +import Options from '../../../src/Options' describe('separator matching', () => { const zxcvbnOptions = new Options() diff --git a/packages/libraries/main/test/matcher/sequence/matching.spec.ts b/packages/libraries/main/test/matcher/sequence/matching.spec.ts index 47574d9e..6d12544e 100644 --- a/packages/libraries/main/test/matcher/sequence/matching.spec.ts +++ b/packages/libraries/main/test/matcher/sequence/matching.spec.ts @@ -1,7 +1,7 @@ import MatchSequence from '../../../src/matcher/sequence/matching' import checkMatches from '../../helper/checkMatches' import genpws from '../../helper/genpws' -import { Options } from '../../../src' +import Options from '../../../src/Options' describe('sequence matching', () => { const zxcvbnOptions = new Options() diff --git a/packages/libraries/main/test/matcher/spatial/matching.spec.ts b/packages/libraries/main/test/matcher/spatial/matching.spec.ts index 10186bfd..bf60172f 100644 --- a/packages/libraries/main/test/matcher/spatial/matching.spec.ts +++ b/packages/libraries/main/test/matcher/spatial/matching.spec.ts @@ -1,7 +1,7 @@ import MatchSpatial from '../../../src/matcher/spatial/matching' import checkMatches from '../../helper/checkMatches' import * as zxcvbnCommonPackage from '../../../../../languages/common/src' -import { Options } from '../../../src/Options' +import Options from '../../../src/Options' import { LooseObject } from '../../../src/types' const { adjacencyGraphs } = zxcvbnCommonPackage diff --git a/packages/libraries/main/test/matcher/spatial/scoring.spec.ts b/packages/libraries/main/test/matcher/spatial/scoring.spec.ts index b80e5cc3..df8f061f 100644 --- a/packages/libraries/main/test/matcher/spatial/scoring.spec.ts +++ b/packages/libraries/main/test/matcher/spatial/scoring.spec.ts @@ -1,6 +1,6 @@ import * as zxcvbnCommonPackage from '../../../../../languages/common/src' import spatialGuesses from '../../../src/matcher/spatial/scoring' -import { Options } from '../../../src/Options' +import Options from '../../../src/Options' describe('scoring: guesses spatial', () => { const zxcvbnOptions = new Options({ diff --git a/packages/libraries/main/test/matching.spec.ts b/packages/libraries/main/test/matching.spec.ts index 4b2c3938..a24c4523 100644 --- a/packages/libraries/main/test/matching.spec.ts +++ b/packages/libraries/main/test/matching.spec.ts @@ -1,7 +1,7 @@ import * as zxcvbnCommonPackage from '../../../languages/common/src' import * as zxcvbnEnPackage from '../../../languages/en/src' import MatchOmni from '../src/Matching' -import { Options } from '../src/Options' +import Options from '../src/Options' import { MatchExtended } from '../src/types' const zxcvbnOptions = new Options({ diff --git a/packages/libraries/main/test/options.spec.ts b/packages/libraries/main/test/options.spec.ts index af9d2b76..22c193b0 100644 --- a/packages/libraries/main/test/options.spec.ts +++ b/packages/libraries/main/test/options.spec.ts @@ -1,4 +1,4 @@ -import { Options } from '../src/Options' +import Options from '../src/Options' import translationKeys from '../src/data/translationKeys' describe('Options', () => { diff --git a/packages/libraries/main/test/scoring/guesses/calc.spec.ts b/packages/libraries/main/test/scoring/guesses/calc.spec.ts index bca93250..f82c7578 100644 --- a/packages/libraries/main/test/scoring/guesses/calc.spec.ts +++ b/packages/libraries/main/test/scoring/guesses/calc.spec.ts @@ -1,6 +1,6 @@ import estimate from '../../../src/scoring/estimate' import dateGuesses from '../../../src/matcher/date/scoring' -import { Options } from '../../../src' +import Options from '../../../src/Options' describe('scoring', () => { const zxcvbnOptions = new Options() diff --git a/packages/libraries/main/test/scoring/search.spec.ts b/packages/libraries/main/test/scoring/search.spec.ts index 135af1df..adcfbbc8 100644 --- a/packages/libraries/main/test/scoring/search.spec.ts +++ b/packages/libraries/main/test/scoring/search.spec.ts @@ -1,5 +1,5 @@ import Scoring from '../../src/scoring' -import { Options } from '../../src/Options' +import Options from '../../src/Options' describe('scoring search', () => { const zxcvbnOptions = new Options() diff --git a/packages/libraries/main/test/timeEstimates.spec.ts b/packages/libraries/main/test/timeEstimates.spec.ts index 436fbb2f..6605102e 100644 --- a/packages/libraries/main/test/timeEstimates.spec.ts +++ b/packages/libraries/main/test/timeEstimates.spec.ts @@ -1,6 +1,6 @@ import translations from '../../../languages/en/src/translations' import TimeEstimates from '../src/TimeEstimates' -import { Options } from '../src/Options' +import Options from '../src/Options' const zxcvbnOptions = new Options({ translations, diff --git a/packages/libraries/pwned/test/matching.spec.ts b/packages/libraries/pwned/test/matching.spec.ts index a85962af..b07e1801 100644 --- a/packages/libraries/pwned/test/matching.spec.ts +++ b/packages/libraries/pwned/test/matching.spec.ts @@ -1,5 +1,5 @@ import { matcherPwnedFactory } from '../src' -import { Options } from '../../main/src' +import Options from '../../main/src/Options' const fetch = jest.fn(async () => ({ text() {