diff --git a/apps/web/package.json b/apps/web/package.json index b42886cdb5..174cf6b82e 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -53,9 +53,9 @@ "@vector-im/compound-design-tokens": "catalog:", "@vector-im/compound-web": "catalog:", "@vector-im/matrix-wysiwyg": "2.40.0", - "@zxcvbn-ts/core": "^3.0.4", - "@zxcvbn-ts/language-common": "^3.0.4", - "@zxcvbn-ts/language-en": "^3.0.2", + "@zxcvbn-ts/core": "^4.0.0", + "@zxcvbn-ts/language-common": "^4.0.0", + "@zxcvbn-ts/language-en": "^4.0.0", "await-lock": "^3.0.0", "bloom-filters": "^3.0.3", "blurhash": "^2.0.3", diff --git a/apps/web/src/utils/PasswordScorer.test.ts b/apps/web/src/utils/PasswordScorer.test.ts new file mode 100644 index 0000000000..d87e01ce7f --- /dev/null +++ b/apps/web/src/utils/PasswordScorer.test.ts @@ -0,0 +1,37 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +import { vi, describe, it, expect } from "vitest"; +import { ZxcvbnFactory } from "@zxcvbn-ts/core"; + +import { scorePassword, baseOptions } from "./PasswordScorer.ts"; + +vi.mock("../languageHandler", () => ({ + getCurrentLanguage: vi.fn().mockReturnValue("en"), + _t: vi.fn((k) => k), +})); +vi.mock("../SdkConfig", () => ({ + default: { + get: vi.fn().mockReturnValue({ + brand: "BRAND", + }), + }, +})); + +describe("scorePassword", () => { + const baseZxcvbn = new ZxcvbnFactory(baseOptions); + + it("should handle inputs with spaces by removing them", () => { + const input = "apple banana cherry"; + const baseResult = baseZxcvbn.check(input); + + const output = scorePassword(null, input); + expect(output).toBeDefined(); + expect(output!.score).toBe(3); + expect(output!.score).toBeLessThan(baseResult.score); + }); +}); diff --git a/apps/web/src/utils/PasswordScorer.ts b/apps/web/src/utils/PasswordScorer.ts index b01e5051cf..ff50c43ee0 100644 --- a/apps/web/src/utils/PasswordScorer.ts +++ b/apps/web/src/utils/PasswordScorer.ts @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import { zxcvbn, zxcvbnOptions, type ZxcvbnResult, type TranslationKeys } from "@zxcvbn-ts/core"; +import { ZxcvbnFactory, type ZxcvbnResult, type TranslationKeys } from "@zxcvbn-ts/core"; import * as zxcvbnCommonPackage from "@zxcvbn-ts/language-common"; import * as zxcvbnEnPackage from "@zxcvbn-ts/language-en"; import { type MatrixClient } from "matrix-js-sdk/src/matrix"; @@ -13,7 +13,11 @@ import { type MatrixClient } from "matrix-js-sdk/src/matrix"; import { _t } from "../languageHandler"; import SdkConfig from "../SdkConfig"; -zxcvbnOptions.setOptions({ +/** + * Base zxcvbn options + * @knipignore - exported for tests + */ +export const baseOptions = { dictionary: { ...zxcvbnCommonPackage.dictionary, ...zxcvbnEnPackage.dictionary, @@ -21,7 +25,7 @@ zxcvbnOptions.setOptions({ }, graphs: zxcvbnCommonPackage.adjacencyGraphs, useLevenshteinDistance: true, -}); +}; function getTranslations(): TranslationKeys { return { @@ -95,12 +99,15 @@ export function scorePassword( } } - zxcvbnOptions.setTranslations(getTranslations()); + const zxcvbn = new ZxcvbnFactory({ + ...baseOptions, + translations: getTranslations(), + }); - let zxcvbnResult = zxcvbn(password, inputs); + let zxcvbnResult = zxcvbn.check(password, inputs); // Work around https://github.com/dropbox/zxcvbn/issues/216 if (password.includes(" ")) { - const resultNoSpaces = zxcvbn(password.replace(/ /g, ""), inputs); + const resultNoSpaces = zxcvbn.check(password.replace(/ /g, ""), inputs); if (resultNoSpaces.score < zxcvbnResult.score) zxcvbnResult = resultNoSpaces; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d524783274..8042c04fd7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -544,14 +544,14 @@ importers: specifier: 2.40.0 version: 2.40.0(patch_hash=7bdf6150f2905bc2f055a6bcaa7b9d78fa7ffde82e800bcc454ac7b0096bd65e)(react@19.2.7) '@zxcvbn-ts/core': - specifier: ^3.0.4 - version: 3.0.4 + specifier: ^4.0.0 + version: 4.1.2 '@zxcvbn-ts/language-common': - specifier: ^3.0.4 - version: 3.0.4 + specifier: ^4.0.0 + version: 4.1.2 '@zxcvbn-ts/language-en': - specifier: ^3.0.2 - version: 3.0.2 + specifier: ^4.0.0 + version: 4.1.1 await-lock: specifier: ^3.0.0 version: 3.0.0(patch_hash=b767a571946a4f8710ac54b1a7bec8a7c1570f9b85d75922392fae20d0578964) @@ -6784,14 +6784,17 @@ packages: resolution: {integrity: sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ==} hasBin: true - '@zxcvbn-ts/core@3.0.4': - resolution: {integrity: sha512-aQeiT0F09FuJaAqNrxynlAwZ2mW/1MdXakKWNmGM1Qp/VaY6CnB/GfnMS2T8gB2231Esp1/maCWd8vTG4OuShw==} + '@zxcvbn-ts/core@4.1.2': + resolution: {integrity: sha512-RQmxWB3AMI+HGQErQdUv6Aq32aQhp6xOxrfgCP0+T9MsLZoP3xtLHuT8O8VojsUxdmQVZfJlYkYb1A0wOwIS+Q==} - '@zxcvbn-ts/language-common@3.0.4': - resolution: {integrity: sha512-viSNNnRYtc7ULXzxrQIVUNwHAPSXRtoIwy/Tq4XQQdIknBzw4vz36lQLF6mvhMlTIlpjoN/Z1GFu/fwiAlUSsw==} + '@zxcvbn-ts/dictionary-compression@3.0.1': + resolution: {integrity: sha512-p3KyPzxGc3vWSap5hHA6SllbUCmh7s+NtpGyC3qEWrxYJT9t9TUAzjPm48Okipo+UUyPQfDlIvTcs9JRShBFiQ==} - '@zxcvbn-ts/language-en@3.0.2': - resolution: {integrity: sha512-Zp+zL+I6Un2Bj0tRXNs6VUBq3Djt+hwTwUz4dkt2qgsQz47U0/XthZ4ULrT/RxjwJRl5LwiaKOOZeOtmixHnjg==} + '@zxcvbn-ts/language-common@4.1.2': + resolution: {integrity: sha512-uJlBzhC9/KjPImqdnc1/lPxmdn4xKbkruN5p1mASWkXA0gli+GZ5LrVL+dqscA8Pcf4OfudE56TtCWeHljJOvA==} + + '@zxcvbn-ts/language-en@4.1.1': + resolution: {integrity: sha512-6UdzuBd3Uex8TKubohcn+uXRVAH34Zjs2eCfT4hQVo9zeTy7AkQRQfdV4OnHR5hQfW/XBrK/AGTZk7VBWh7wwQ==} abbrev@4.0.0: resolution: {integrity: sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==} @@ -19801,13 +19804,19 @@ snapshots: dependencies: argparse: 2.0.1 - '@zxcvbn-ts/core@3.0.4': + '@zxcvbn-ts/core@4.1.2': dependencies: fastest-levenshtein: 1.0.16 - '@zxcvbn-ts/language-common@3.0.4': {} + '@zxcvbn-ts/dictionary-compression@3.0.1': {} - '@zxcvbn-ts/language-en@3.0.2': {} + '@zxcvbn-ts/language-common@4.1.2': + dependencies: + '@zxcvbn-ts/dictionary-compression': 3.0.1 + + '@zxcvbn-ts/language-en@4.1.1': + dependencies: + '@zxcvbn-ts/dictionary-compression': 3.0.1 abbrev@4.0.0: {}