@fr0st/ui-passwordstrength 3.0.1 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/frost-ui-passwordstrength.esm.js +12 -27
- package/dist/frost-ui-passwordstrength.esm.js.map +1 -1
- package/dist/frost-ui-passwordstrength.esm.min.js +1 -1
- package/dist/frost-ui-passwordstrength.esm.min.js.map +1 -1
- package/dist/frost-ui-passwordstrength.js +11 -26
- package/dist/frost-ui-passwordstrength.js.map +1 -1
- package/dist/frost-ui-passwordstrength.min.js +1 -1
- package/dist/frost-ui-passwordstrength.min.js.map +1 -1
- package/package.json +5 -5
- package/src/password-strength.js +9 -28
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseComponent, generateId,
|
|
1
|
+
import { BaseComponent, generateId, initComponent } from "@fr0st/ui";
|
|
2
2
|
import $ from "@fr0st/query";
|
|
3
3
|
|
|
4
4
|
//#region src/strength.js
|
|
@@ -274,12 +274,11 @@ var PasswordStrength = class extends BaseComponent {
|
|
|
274
274
|
scorer: getStrength,
|
|
275
275
|
striped: false
|
|
276
276
|
};
|
|
277
|
-
#container;
|
|
277
|
+
#container = null;
|
|
278
278
|
#describedBy;
|
|
279
|
-
#form;
|
|
280
|
-
#progress;
|
|
281
|
-
#progressBar;
|
|
282
|
-
#resetHandler;
|
|
279
|
+
#form = null;
|
|
280
|
+
#progress = null;
|
|
281
|
+
#progressBar = null;
|
|
283
282
|
/**
|
|
284
283
|
* Calculates the strength of a password.
|
|
285
284
|
* @param {string} password The password.
|
|
@@ -296,18 +295,10 @@ var PasswordStrength = class extends BaseComponent {
|
|
|
296
295
|
* @throws {TypeError} When the node is not an input element.
|
|
297
296
|
*/
|
|
298
297
|
constructor(node, options) {
|
|
299
|
-
if (!$.is(node, "input")) throw new TypeError("PasswordStrength must be created on an input element.");
|
|
298
|
+
if (!$._isElement(node) || !$.is(node, "input")) throw new TypeError("PasswordStrength must be created on an input element.");
|
|
300
299
|
super(node, options);
|
|
301
300
|
try {
|
|
302
301
|
this.#describedBy = $.getAttribute(this.node, "aria-describedby");
|
|
303
|
-
const overrides = {
|
|
304
|
-
...getDataset(node),
|
|
305
|
-
...options
|
|
306
|
-
};
|
|
307
|
-
for (const key of ["levels", "commonPasswords"]) if (Array.isArray(overrides[key])) {
|
|
308
|
-
this.options[key].length = 0;
|
|
309
|
-
$._extend(this.options[key], overrides[key]);
|
|
310
|
-
}
|
|
311
302
|
this.#form = $.getProperty(this.node, "form");
|
|
312
303
|
if (this.options.container) this.#container = $.findOne(this.options.container);
|
|
313
304
|
else this.#container = $.closest(this.node, ":not(.form-input):not(.input-group)").shift();
|
|
@@ -323,15 +314,12 @@ var PasswordStrength = class extends BaseComponent {
|
|
|
323
314
|
dispose() {
|
|
324
315
|
if (!this.node) return;
|
|
325
316
|
$.remove(this.#progress);
|
|
326
|
-
$.removeEvent(this.node, "input.ui.passwordstrength");
|
|
327
|
-
if (this.#form && this.#resetHandler) $.removeEvent(this.#form, "reset.ui.passwordstrength", this.#resetHandler);
|
|
328
317
|
if (this.#describedBy === null) $.removeAttribute(this.node, "aria-describedby");
|
|
329
318
|
else $.setAttribute(this.node, { "aria-describedby": this.#describedBy });
|
|
330
319
|
this.#container = null;
|
|
331
320
|
this.#form = null;
|
|
332
321
|
this.#progress = null;
|
|
333
322
|
this.#progressBar = null;
|
|
334
|
-
this.#resetHandler = null;
|
|
335
323
|
super.dispose();
|
|
336
324
|
}
|
|
337
325
|
/**
|
|
@@ -347,15 +335,12 @@ var PasswordStrength = class extends BaseComponent {
|
|
|
347
335
|
* Attaches events for the PasswordStrength.
|
|
348
336
|
*/
|
|
349
337
|
#events() {
|
|
350
|
-
if (this.#form) {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
$.addEvent(this.#form, "reset.ui.passwordstrength", this.#resetHandler);
|
|
357
|
-
}
|
|
358
|
-
$.addEvent(this.node, "input.ui.passwordstrength", () => {
|
|
338
|
+
if (this.#form) this.listen(this.#form, "reset.ui.passwordstrength", (event) => {
|
|
339
|
+
setTimeout(() => {
|
|
340
|
+
if (this.node && !event.defaultPrevented) this.#refresh();
|
|
341
|
+
}, 0);
|
|
342
|
+
});
|
|
343
|
+
this.listen(this.node, "input.ui.passwordstrength", () => {
|
|
359
344
|
this.#refresh();
|
|
360
345
|
});
|
|
361
346
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frost-ui-passwordstrength.esm.js","names":[],"sources":["../src/strength.js","../src/password-strength.js","../src/index.js"],"sourcesContent":["import $ from '@fr0st/query';\n\nconst characterTypes = [\n { pattern: /\\p{Ll}/u, type: 'lower' },\n { pattern: /\\p{Lu}/u, type: 'upper' },\n { pattern: /\\p{N}/u, type: 'number' },\n];\n\nconst keyboardSequences = [\n '1234567890',\n 'qwertyuiop',\n 'asdfghjkl',\n 'zxcvbnm',\n].flatMap(\n (row) => [\n row,\n Array.from(row).reverse().join(''),\n ],\n);\n\nconst leetCharacters = {\n '$': 's',\n '0': 'o',\n '3': 'e',\n '4': 'a',\n '5': 's',\n '7': 't',\n '@': 'a',\n};\n\nconst lengthBands = [\n { end: 5, max: 19, min: 1, start: 1 },\n { end: 8, max: 39, min: 20, start: 6 },\n { end: 11, max: 59, min: 40, start: 9 },\n { end: 15, max: 79, min: 60, start: 12 },\n { end: 24, max: 100, min: 80, start: 16 },\n];\n\n/**\n * Gets the type of a password character.\n * @param {string} character The character.\n * @returns {string} The character type.\n */\nconst getCharacterType = (character) =>\n characterTypes.find(\n ({ pattern }) => pattern.test(character),\n )?.type || 'symbol';\n\n/**\n * Gets a length-based password strength.\n * @param {string[]} characters The password characters.\n * @returns {number} The password strength.\n */\nconst getLengthStrength = (characters) => {\n const band = lengthBands.find(\n ({ end }) => characters.length <= end,\n ) || lengthBands.at(-1);\n const score = Math.round(\n $._map(\n Math.min(characters.length, band.end),\n band.start,\n band.end,\n band.min,\n band.max,\n ),\n );\n const diversity = new Set(\n characters.map(getCharacterType),\n ).size;\n\n return Math.min(\n score +\n (diversity - 1) * 2,\n band.max,\n );\n};\n\n/**\n * Gets the smallest repeated pattern.\n * @param {string[]} characters The password characters.\n * @returns {string[]|null} The repeated pattern.\n */\nconst getRepeatedPattern = (characters) => {\n const maxLength = Math.floor(characters.length / 2);\n\n for (let length = 1; length <= maxLength; length++) {\n if (\n characters.length % length === 0 &&\n characters.every(\n (character, index) =>\n character === characters[index % length],\n )\n ) {\n return characters.slice(0, length);\n }\n }\n\n return null;\n};\n\n/**\n * Normalizes common password matching.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizePassword = (password) =>\n String(password)\n .normalize('NFKC')\n .toLowerCase();\n\n/**\n * Normalizes common leet substitutions.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizeLeet = (password) =>\n Array.from(\n password,\n (character) => leetCharacters[character] || character,\n ).join('');\n\n/**\n * Gets normalized variants used to match common passwords.\n * @param {string} password The normalized password.\n * @returns {Set<string>} The password variants.\n */\nconst getPasswordVariants = (password) => {\n // Preserve digits for decoration stripping while resolving symbol substitutions.\n const substituted = password.replace(/[^\\p{L}\\p{N}]/gu, normalizeLeet);\n const variants = [password, substituted].flatMap((value) => {\n const undecorated = value.replace(\n /^[^\\p{L}\\p{N}]+|[^\\p{L}\\p{N}]+$/gu,\n '',\n );\n\n return [\n undecorated,\n undecorated.replace(/^\\d+|\\d+$/g, ''),\n ];\n });\n\n return new Set([\n normalizeLeet(password),\n ...variants,\n ...variants.map(normalizeLeet),\n ]);\n};\n\n/**\n * Gets the score for a common password.\n * @param {string} password The normalized password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number|null} The common-password score.\n */\nconst getCommonPasswordStrength = (password, commonPasswords) => {\n const common = new Set(\n commonPasswords.map(normalizePassword),\n );\n\n if (common.has(password)) {\n return 0;\n }\n\n return common.isDisjointFrom(getPasswordVariants(password)) ? null : 5;\n};\n\n/**\n * Checks whether characters form an ascending or descending sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a sequence.\n */\nconst isSequence = (characters) => {\n const [first, second, third] = characters.map(\n (character) => character.toLowerCase(),\n );\n const type = getCharacterType(first);\n const direction = second.codePointAt(0) - first.codePointAt(0);\n\n return ['lower', 'number'].includes(type) &&\n getCharacterType(second) === type &&\n getCharacterType(third) === type &&\n Math.abs(direction) === 1 &&\n third.codePointAt(0) - second.codePointAt(0) === direction;\n};\n\n/**\n * Checks whether characters form a keyboard sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a keyboard sequence.\n */\nconst isKeyboardSequence = (characters) => {\n const sequence = characters.join('').toLowerCase();\n\n return keyboardSequences.some(\n (keyboardSequence) => keyboardSequence.includes(sequence),\n );\n};\n\n/**\n * Checks whether characters form a predictable pattern.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a predictable pattern.\n */\nconst isPredictable = (characters) =>\n new Set(characters).size === 1 ||\n isSequence(characters) ||\n isKeyboardSequence(characters);\n\n/**\n * Gets the number of characters in predictable patterns.\n * @param {string[]} characters The password characters.\n * @returns {number} The number of predictable characters.\n */\nconst getPredictableCount = (characters) => {\n const indices = new Set();\n\n for (let i = 0; i + 2 < characters.length; i++) {\n if (isPredictable(characters.slice(i, i + 3))) {\n indices.add(i);\n indices.add(i + 1);\n indices.add(i + 2);\n }\n }\n\n return indices.size;\n};\n\n/**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=[]] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\nexport const getStrength = (password, commonPasswords = []) => {\n const normalizedPassword = password.normalize('NFKC');\n const characters = Array.from(normalizedPassword);\n\n if (!characters.length) {\n return 0;\n }\n\n const commonStrength = getCommonPasswordStrength(\n normalizedPassword.toLowerCase(),\n commonPasswords,\n );\n\n if (commonStrength !== null) {\n return commonStrength;\n }\n\n const score = getLengthStrength(characters);\n const repeatedPattern = getRepeatedPattern(characters);\n\n if (repeatedPattern?.length === 1) {\n return 0;\n }\n\n if (repeatedPattern) {\n return Math.min(\n score,\n getLengthStrength(repeatedPattern),\n 15,\n );\n }\n\n const predictableCount = getPredictableCount(characters);\n\n return $._clamp(\n score - predictableCount,\n 0,\n predictableCount === characters.length ? 15 : 100,\n );\n};\n","import $ from '@fr0st/query';\nimport { BaseComponent, generateId, getDataset } from '@fr0st/ui';\nimport { getStrength } from './strength.js';\n\n/**\n * @typedef {object} PasswordStrengthLevel\n * @property {string} class The CSS class applied to the progress bar.\n * @property {number} score The minimum score for the level.\n * @property {string} [text] The label displayed in the progress bar.\n */\n\n/**\n * @callback PasswordStrengthScorer\n * @param {string} password The password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number} The password strength, from 0 to 100.\n */\n\n/**\n * @typedef {object} PasswordStrengthOptions\n * @property {string[]} [commonPasswords] Passwords to score as very weak.\n * @property {string|null} [container=null] The selector for the progress container.\n * @property {PasswordStrengthLevel[]} [levels] The ordered password-strength levels.\n * @property {PasswordStrengthScorer} [scorer] Calculates the password strength.\n * @property {boolean} [striped=false] Whether to display a striped progress bar.\n */\n\n/**\n * Displays the strength of a password input as a progress bar.\n * @augments {BaseComponent<PasswordStrengthOptions>}\n */\nexport default class PasswordStrength extends BaseComponent {\n static classes = {\n progress: 'progress mt-2',\n progressBar: 'progress-bar',\n progressBarStriped: 'progress-bar-striped',\n };\n /** @type {PasswordStrengthOptions} */\n static defaults = {\n commonPasswords: [\n '123456',\n '12345678',\n '123456789',\n '1234567890',\n '000000',\n '111111',\n 'abc123',\n 'admin',\n 'dragon',\n 'iloveyou',\n 'letmein',\n 'monkey',\n 'password',\n 'password1',\n 'password123',\n 'qwerty',\n 'qwerty123',\n 'welcome',\n ],\n levels: [\n {\n score: 0,\n class: 'text-bg-danger',\n text: 'Very Weak',\n },\n {\n score: 20,\n class: 'text-bg-danger',\n text: 'Weak',\n },\n {\n score: 40,\n class: 'text-bg-warning',\n text: 'Normal',\n },\n {\n score: 60,\n class: 'text-bg-success',\n text: 'Strong',\n },\n {\n score: 80,\n class: 'text-bg-success',\n text: 'Very Strong',\n },\n ],\n container: null,\n scorer: getStrength,\n striped: false,\n };\n\n #container;\n #describedBy;\n #form;\n #progress;\n #progressBar;\n #resetHandler;\n\n /**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=this.defaults.commonPasswords] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\n static getStrength(password, commonPasswords = this.defaults.commonPasswords) {\n return getStrength(password, commonPasswords);\n }\n\n /**\n * Creates a PasswordStrength.\n * @param {HTMLInputElement} node The input node.\n * @param {PasswordStrengthOptions} [options] The PasswordStrength options.\n * @throws {TypeError} When the node is not an input element.\n */\n constructor(node, options) {\n if (!$.is(node, 'input')) {\n throw new TypeError('PasswordStrength must be created on an input element.');\n }\n\n super(node, options);\n\n try {\n this.#describedBy = $.getAttribute(this.node, 'aria-describedby');\n\n const overrides = { ...getDataset(node), ...options };\n\n // Replace supplied arrays instead of merging them with default entries.\n for (const key of ['levels', 'commonPasswords']) {\n if (Array.isArray(overrides[key])) {\n this.options[key].length = 0;\n $._extend(this.options[key], overrides[key]);\n }\n }\n\n this.#form = $.getProperty(this.node, 'form');\n\n if (this.options.container) {\n this.#container = $.findOne(this.options.container);\n } else {\n this.#container = $.closest(this.node, ':not(.form-input):not(.input-group)').shift();\n }\n\n this.#render();\n this.#refresh();\n this.#events();\n } catch (error) {\n this.dispose();\n throw error;\n }\n }\n\n /** @inheritdoc */\n dispose() {\n if (!this.node) {\n return;\n }\n\n $.remove(this.#progress);\n $.removeEvent(this.node, 'input.ui.passwordstrength');\n\n if (this.#form && this.#resetHandler) {\n $.removeEvent(this.#form, 'reset.ui.passwordstrength', this.#resetHandler);\n }\n\n if (this.#describedBy === null) {\n $.removeAttribute(this.node, 'aria-describedby');\n } else {\n $.setAttribute(this.node, { 'aria-describedby': this.#describedBy });\n }\n\n this.#container = null;\n this.#form = null;\n this.#progress = null;\n this.#progressBar = null;\n this.#resetHandler = null;\n\n super.dispose();\n }\n\n /**\n * Gets the password strength.\n * @returns {number} The password strength, from 0 to 100.\n */\n getStrength() {\n const value = $.getValue(this.node);\n const strength = Number(this.options.scorer(\n value,\n this.options.commonPasswords,\n ));\n\n return Number.isFinite(strength) ?\n $._clamp(strength, 0, 100) :\n 0;\n }\n\n /**\n * Attaches events for the PasswordStrength.\n */\n #events() {\n if (this.#form) {\n this.#resetHandler = (event) => {\n setTimeout(() => {\n if (this.node && !event.defaultPrevented) {\n this.#refresh();\n }\n }, 0);\n };\n\n $.addEvent(this.#form, 'reset.ui.passwordstrength', this.#resetHandler);\n }\n\n $.addEvent(this.node, 'input.ui.passwordstrength', () => {\n this.#refresh();\n });\n }\n\n /**\n * Refreshes the password strength.\n */\n #refresh() {\n const strength = this.getStrength();\n\n const nextLevel = this.options.levels.findLast(\n (level) => strength >= level.score,\n );\n\n $.setStyle(this.#progressBar, { width: `${strength}%` });\n $.setAttribute(this.#progressBar, {\n 'class': this.constructor.classes.progressBar,\n 'aria-valuenow': strength,\n });\n\n $.addClass(this.#progressBar, nextLevel.class);\n\n if (this.options.striped) {\n $.addClass(this.#progressBar, this.constructor.classes.progressBarStriped);\n }\n\n $.setText(this.#progressBar, nextLevel.text ?? '');\n }\n\n /**\n * Renders the password strength element.\n */\n #render() {\n this.#progress = $.create('div', {\n class: this.constructor.classes.progress,\n });\n\n const id = generateId('password-strength');\n\n this.#progressBar = $.create('div', {\n attributes: {\n id,\n 'role': 'progressbar',\n 'aria-valuemin': 0,\n 'aria-valuemax': 100,\n },\n });\n\n $.append(this.#progress, this.#progressBar);\n $.append(this.#container, this.#progress);\n\n const describedBy = [this.#describedBy, id]\n .filter(Boolean)\n .join(' ');\n\n $.setAttribute(this.node, { 'aria-describedby': describedBy });\n }\n}\n","import { initComponent } from '@fr0st/ui';\nimport PasswordStrength from './password-strength.js';\n\ninitComponent('passwordstrength', PasswordStrength);\n\nexport default PasswordStrength;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"frost-ui-passwordstrength.esm.js","names":[],"sources":["../src/strength.js","../src/password-strength.js","../src/index.js"],"sourcesContent":["import $ from '@fr0st/query';\n\nconst characterTypes = [\n { pattern: /\\p{Ll}/u, type: 'lower' },\n { pattern: /\\p{Lu}/u, type: 'upper' },\n { pattern: /\\p{N}/u, type: 'number' },\n];\n\nconst keyboardSequences = [\n '1234567890',\n 'qwertyuiop',\n 'asdfghjkl',\n 'zxcvbnm',\n].flatMap(\n (row) => [\n row,\n Array.from(row).reverse().join(''),\n ],\n);\n\nconst leetCharacters = {\n '$': 's',\n '0': 'o',\n '3': 'e',\n '4': 'a',\n '5': 's',\n '7': 't',\n '@': 'a',\n};\n\nconst lengthBands = [\n { end: 5, max: 19, min: 1, start: 1 },\n { end: 8, max: 39, min: 20, start: 6 },\n { end: 11, max: 59, min: 40, start: 9 },\n { end: 15, max: 79, min: 60, start: 12 },\n { end: 24, max: 100, min: 80, start: 16 },\n];\n\n/**\n * Gets the type of a password character.\n * @param {string} character The character.\n * @returns {string} The character type.\n */\nconst getCharacterType = (character) =>\n characterTypes.find(\n ({ pattern }) => pattern.test(character),\n )?.type || 'symbol';\n\n/**\n * Gets a length-based password strength.\n * @param {string[]} characters The password characters.\n * @returns {number} The password strength.\n */\nconst getLengthStrength = (characters) => {\n const band = lengthBands.find(\n ({ end }) => characters.length <= end,\n ) || lengthBands.at(-1);\n const score = Math.round(\n $._map(\n Math.min(characters.length, band.end),\n band.start,\n band.end,\n band.min,\n band.max,\n ),\n );\n const diversity = new Set(\n characters.map(getCharacterType),\n ).size;\n\n return Math.min(\n score +\n (diversity - 1) * 2,\n band.max,\n );\n};\n\n/**\n * Gets the smallest repeated pattern.\n * @param {string[]} characters The password characters.\n * @returns {string[]|null} The repeated pattern.\n */\nconst getRepeatedPattern = (characters) => {\n const maxLength = Math.floor(characters.length / 2);\n\n for (let length = 1; length <= maxLength; length++) {\n if (\n characters.length % length === 0 &&\n characters.every(\n (character, index) =>\n character === characters[index % length],\n )\n ) {\n return characters.slice(0, length);\n }\n }\n\n return null;\n};\n\n/**\n * Normalizes common password matching.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizePassword = (password) =>\n String(password)\n .normalize('NFKC')\n .toLowerCase();\n\n/**\n * Normalizes common leet substitutions.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizeLeet = (password) =>\n Array.from(\n password,\n (character) => leetCharacters[character] || character,\n ).join('');\n\n/**\n * Gets normalized variants used to match common passwords.\n * @param {string} password The normalized password.\n * @returns {Set<string>} The password variants.\n */\nconst getPasswordVariants = (password) => {\n // Preserve digits for decoration stripping while resolving symbol substitutions.\n const substituted = password.replace(/[^\\p{L}\\p{N}]/gu, normalizeLeet);\n const variants = [password, substituted].flatMap((value) => {\n const undecorated = value.replace(\n /^[^\\p{L}\\p{N}]+|[^\\p{L}\\p{N}]+$/gu,\n '',\n );\n\n return [\n undecorated,\n undecorated.replace(/^\\d+|\\d+$/g, ''),\n ];\n });\n\n return new Set([\n normalizeLeet(password),\n ...variants,\n ...variants.map(normalizeLeet),\n ]);\n};\n\n/**\n * Gets the score for a common password.\n * @param {string} password The normalized password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number|null} The common-password score.\n */\nconst getCommonPasswordStrength = (password, commonPasswords) => {\n const common = new Set(\n commonPasswords.map(normalizePassword),\n );\n\n if (common.has(password)) {\n return 0;\n }\n\n return common.isDisjointFrom(getPasswordVariants(password)) ? null : 5;\n};\n\n/**\n * Checks whether characters form an ascending or descending sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a sequence.\n */\nconst isSequence = (characters) => {\n const [first, second, third] = characters.map(\n (character) => character.toLowerCase(),\n );\n const type = getCharacterType(first);\n const direction = second.codePointAt(0) - first.codePointAt(0);\n\n return ['lower', 'number'].includes(type) &&\n getCharacterType(second) === type &&\n getCharacterType(third) === type &&\n Math.abs(direction) === 1 &&\n third.codePointAt(0) - second.codePointAt(0) === direction;\n};\n\n/**\n * Checks whether characters form a keyboard sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a keyboard sequence.\n */\nconst isKeyboardSequence = (characters) => {\n const sequence = characters.join('').toLowerCase();\n\n return keyboardSequences.some(\n (keyboardSequence) => keyboardSequence.includes(sequence),\n );\n};\n\n/**\n * Checks whether characters form a predictable pattern.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a predictable pattern.\n */\nconst isPredictable = (characters) =>\n new Set(characters).size === 1 ||\n isSequence(characters) ||\n isKeyboardSequence(characters);\n\n/**\n * Gets the number of characters in predictable patterns.\n * @param {string[]} characters The password characters.\n * @returns {number} The number of predictable characters.\n */\nconst getPredictableCount = (characters) => {\n const indices = new Set();\n\n for (let i = 0; i + 2 < characters.length; i++) {\n if (isPredictable(characters.slice(i, i + 3))) {\n indices.add(i);\n indices.add(i + 1);\n indices.add(i + 2);\n }\n }\n\n return indices.size;\n};\n\n/**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=[]] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\nexport const getStrength = (password, commonPasswords = []) => {\n const normalizedPassword = password.normalize('NFKC');\n const characters = Array.from(normalizedPassword);\n\n if (!characters.length) {\n return 0;\n }\n\n const commonStrength = getCommonPasswordStrength(\n normalizedPassword.toLowerCase(),\n commonPasswords,\n );\n\n if (commonStrength !== null) {\n return commonStrength;\n }\n\n const score = getLengthStrength(characters);\n const repeatedPattern = getRepeatedPattern(characters);\n\n if (repeatedPattern?.length === 1) {\n return 0;\n }\n\n if (repeatedPattern) {\n return Math.min(\n score,\n getLengthStrength(repeatedPattern),\n 15,\n );\n }\n\n const predictableCount = getPredictableCount(characters);\n\n return $._clamp(\n score - predictableCount,\n 0,\n predictableCount === characters.length ? 15 : 100,\n );\n};\n","import $ from '@fr0st/query';\nimport { BaseComponent, generateId } from '@fr0st/ui';\nimport { getStrength } from './strength.js';\n\n/**\n * @typedef {object} PasswordStrengthLevel\n * @property {string} class The CSS class applied to the progress bar.\n * @property {number} score The minimum score for the level.\n * @property {string} [text] The label displayed in the progress bar.\n */\n\n/**\n * @callback PasswordStrengthScorer\n * @param {string} password The password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number} The password strength, from 0 to 100.\n */\n\n/**\n * @typedef {object} PasswordStrengthOptions\n * @property {string[]} [commonPasswords] Passwords to score as very weak.\n * @property {string|null} [container=null] The selector for the progress container.\n * @property {PasswordStrengthLevel[]} [levels] The ordered password-strength levels.\n * @property {PasswordStrengthScorer} [scorer] Calculates the password strength.\n * @property {boolean} [striped=false] Whether to display a striped progress bar.\n */\n\n/**\n * Displays the strength of a password input as a progress bar.\n * @augments {BaseComponent<PasswordStrengthOptions>}\n */\nexport default class PasswordStrength extends BaseComponent {\n static classes = {\n progress: 'progress mt-2',\n progressBar: 'progress-bar',\n progressBarStriped: 'progress-bar-striped',\n };\n /** @type {PasswordStrengthOptions} */\n static defaults = {\n commonPasswords: [\n '123456',\n '12345678',\n '123456789',\n '1234567890',\n '000000',\n '111111',\n 'abc123',\n 'admin',\n 'dragon',\n 'iloveyou',\n 'letmein',\n 'monkey',\n 'password',\n 'password1',\n 'password123',\n 'qwerty',\n 'qwerty123',\n 'welcome',\n ],\n levels: [\n {\n score: 0,\n class: 'text-bg-danger',\n text: 'Very Weak',\n },\n {\n score: 20,\n class: 'text-bg-danger',\n text: 'Weak',\n },\n {\n score: 40,\n class: 'text-bg-warning',\n text: 'Normal',\n },\n {\n score: 60,\n class: 'text-bg-success',\n text: 'Strong',\n },\n {\n score: 80,\n class: 'text-bg-success',\n text: 'Very Strong',\n },\n ],\n container: null,\n scorer: getStrength,\n striped: false,\n };\n\n #container = null;\n #describedBy;\n #form = null;\n #progress = null;\n #progressBar = null;\n\n /**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=this.defaults.commonPasswords] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\n static getStrength(password, commonPasswords = this.defaults.commonPasswords) {\n return getStrength(password, commonPasswords);\n }\n\n /**\n * Creates a PasswordStrength.\n * @param {HTMLInputElement} node The input node.\n * @param {PasswordStrengthOptions} [options] The PasswordStrength options.\n * @throws {TypeError} When the node is not an input element.\n */\n constructor(node, options) {\n if (!$._isElement(node) || !$.is(node, 'input')) {\n throw new TypeError('PasswordStrength must be created on an input element.');\n }\n\n super(node, options);\n\n try {\n this.#describedBy = $.getAttribute(this.node, 'aria-describedby');\n\n this.#form = $.getProperty(this.node, 'form');\n\n if (this.options.container) {\n this.#container = $.findOne(this.options.container);\n } else {\n this.#container = $.closest(this.node, ':not(.form-input):not(.input-group)').shift();\n }\n\n this.#render();\n this.#refresh();\n this.#events();\n } catch (error) {\n this.dispose();\n throw error;\n }\n }\n\n /** @inheritdoc */\n dispose() {\n if (!this.node) {\n return;\n }\n\n $.remove(this.#progress);\n\n if (this.#describedBy === null) {\n $.removeAttribute(this.node, 'aria-describedby');\n } else {\n $.setAttribute(this.node, { 'aria-describedby': this.#describedBy });\n }\n\n this.#container = null;\n this.#form = null;\n this.#progress = null;\n this.#progressBar = null;\n\n super.dispose();\n }\n\n /**\n * Gets the password strength.\n * @returns {number} The password strength, from 0 to 100.\n */\n getStrength() {\n const value = $.getValue(this.node);\n const strength = Number(this.options.scorer(\n value,\n this.options.commonPasswords,\n ));\n\n return Number.isFinite(strength) ?\n $._clamp(strength, 0, 100) :\n 0;\n }\n\n /**\n * Attaches events for the PasswordStrength.\n */\n #events() {\n if (this.#form) {\n this.listen(this.#form, 'reset.ui.passwordstrength', (event) => {\n setTimeout(() => {\n if (this.node && !event.defaultPrevented) {\n this.#refresh();\n }\n }, 0);\n });\n }\n\n this.listen(this.node, 'input.ui.passwordstrength', () => {\n this.#refresh();\n });\n }\n\n /**\n * Refreshes the password strength.\n */\n #refresh() {\n const strength = this.getStrength();\n\n const nextLevel = this.options.levels.findLast(\n (level) => strength >= level.score,\n );\n\n $.setStyle(this.#progressBar, { width: `${strength}%` });\n $.setAttribute(this.#progressBar, {\n 'class': this.constructor.classes.progressBar,\n 'aria-valuenow': strength,\n });\n\n $.addClass(this.#progressBar, nextLevel.class);\n\n if (this.options.striped) {\n $.addClass(this.#progressBar, this.constructor.classes.progressBarStriped);\n }\n\n $.setText(this.#progressBar, nextLevel.text ?? '');\n }\n\n /**\n * Renders the password strength element.\n */\n #render() {\n this.#progress = $.create('div', {\n class: this.constructor.classes.progress,\n });\n\n const id = generateId('password-strength');\n\n this.#progressBar = $.create('div', {\n attributes: {\n id,\n 'role': 'progressbar',\n 'aria-valuemin': 0,\n 'aria-valuemax': 100,\n },\n });\n\n $.append(this.#progress, this.#progressBar);\n $.append(this.#container, this.#progress);\n\n const describedBy = [this.#describedBy, id]\n .filter(Boolean)\n .join(' ');\n\n $.setAttribute(this.node, { 'aria-describedby': describedBy });\n }\n}\n","import { initComponent } from '@fr0st/ui';\nimport PasswordStrength from './password-strength.js';\n\ninitComponent('passwordstrength', PasswordStrength);\n\nexport default PasswordStrength;\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{BaseComponent as e,generateId as t,
|
|
1
|
+
import{BaseComponent as e,generateId as t,initComponent as n}from"@fr0st/ui";import r from"@fr0st/query";var i=[{pattern:/\p{Ll}/u,type:`lower`},{pattern:/\p{Lu}/u,type:`upper`},{pattern:/\p{N}/u,type:`number`}],a=[`1234567890`,`qwertyuiop`,`asdfghjkl`,`zxcvbnm`].flatMap(e=>[e,Array.from(e).reverse().join(``)]),o={$:`s`,0:`o`,3:`e`,4:`a`,5:`s`,7:`t`,"@":`a`},s=[{end:5,max:19,min:1,start:1},{end:8,max:39,min:20,start:6},{end:11,max:59,min:40,start:9},{end:15,max:79,min:60,start:12},{end:24,max:100,min:80,start:16}],c=e=>i.find(({pattern:t})=>t.test(e))?.type||`symbol`,l=e=>{let t=s.find(({end:t})=>e.length<=t)||s.at(-1),n=Math.round(r._map(Math.min(e.length,t.end),t.start,t.end,t.min,t.max)),i=new Set(e.map(c)).size;return Math.min(n+(i-1)*2,t.max)},u=e=>{let t=Math.floor(e.length/2);for(let n=1;n<=t;n++)if(e.length%n===0&&e.every((t,r)=>t===e[r%n]))return e.slice(0,n);return null},d=e=>String(e).normalize(`NFKC`).toLowerCase(),f=e=>Array.from(e,e=>o[e]||e).join(``),p=e=>{let t=[e,e.replace(/[^\p{L}\p{N}]/gu,f)].flatMap(e=>{let t=e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,``);return[t,t.replace(/^\d+|\d+$/g,``)]});return/* @__PURE__ */ new Set([f(e),...t,...t.map(f)])},m=(e,t)=>{let n=new Set(t.map(d));return n.has(e)?0:n.isDisjointFrom(p(e))?null:5},h=e=>{let[t,n,r]=e.map(e=>e.toLowerCase()),i=c(t),a=n.codePointAt(0)-t.codePointAt(0);return[`lower`,`number`].includes(i)&&c(n)===i&&c(r)===i&&Math.abs(a)===1&&r.codePointAt(0)-n.codePointAt(0)===a},g=e=>{let t=e.join(``).toLowerCase();return a.some(e=>e.includes(t))},_=e=>new Set(e).size===1||h(e)||g(e),v=e=>{let t=/* @__PURE__ */ new Set;for(let n=0;n+2<e.length;n++)_(e.slice(n,n+3))&&(t.add(n),t.add(n+1),t.add(n+2));return t.size},y=(e,t=[])=>{let n=e.normalize(`NFKC`),i=Array.from(n);if(!i.length)return 0;let a=m(n.toLowerCase(),t);if(a!==null)return a;let o=l(i),s=u(i);if(s?.length===1)return 0;if(s)return Math.min(o,l(s),15);let c=v(i);return r._clamp(o-c,0,c===i.length?15:100)},b=class extends e{static classes={progress:`progress mt-2`,progressBar:`progress-bar`,progressBarStriped:`progress-bar-striped`};static defaults={commonPasswords:[`123456`,`12345678`,`123456789`,`1234567890`,`000000`,`111111`,`abc123`,`admin`,`dragon`,`iloveyou`,`letmein`,`monkey`,`password`,`password1`,`password123`,`qwerty`,`qwerty123`,`welcome`],levels:[{score:0,class:`text-bg-danger`,text:`Very Weak`},{score:20,class:`text-bg-danger`,text:`Weak`},{score:40,class:`text-bg-warning`,text:`Normal`},{score:60,class:`text-bg-success`,text:`Strong`},{score:80,class:`text-bg-success`,text:`Very Strong`}],container:null,scorer:y,striped:!1};#e=null;#t;#n=null;#r=null;#i=null;static getStrength(e,t=this.defaults.commonPasswords){return y(e,t)}constructor(e,t){if(!r._isElement(e)||!r.is(e,`input`))throw TypeError(`PasswordStrength must be created on an input element.`);super(e,t);try{this.#t=r.getAttribute(this.node,`aria-describedby`),this.#n=r.getProperty(this.node,`form`),this.#e=this.options.container?r.findOne(this.options.container):r.closest(this.node,`:not(.form-input):not(.input-group)`).shift(),this.#s(),this.#o(),this.#a()}catch(e){throw this.dispose(),e}}dispose(){this.node&&(r.remove(this.#r),this.#t===null?r.removeAttribute(this.node,`aria-describedby`):r.setAttribute(this.node,{"aria-describedby":this.#t}),this.#e=null,this.#n=null,this.#r=null,this.#i=null,super.dispose())}getStrength(){let e=r.getValue(this.node),t=Number(this.options.scorer(e,this.options.commonPasswords));return Number.isFinite(t)?r._clamp(t,0,100):0}#a(){this.#n&&this.listen(this.#n,`reset.ui.passwordstrength`,e=>{setTimeout(()=>{this.node&&!e.defaultPrevented&&this.#o()},0)}),this.listen(this.node,`input.ui.passwordstrength`,()=>{this.#o()})}#o(){let e=this.getStrength(),t=this.options.levels.findLast(t=>e>=t.score);r.setStyle(this.#i,{width:`${e}%`}),r.setAttribute(this.#i,{class:this.constructor.classes.progressBar,"aria-valuenow":e}),r.addClass(this.#i,t.class),this.options.striped&&r.addClass(this.#i,this.constructor.classes.progressBarStriped),r.setText(this.#i,t.text??``)}#s(){this.#r=r.create(`div`,{class:this.constructor.classes.progress});let e=t(`password-strength`);this.#i=r.create(`div`,{attributes:{id:e,role:`progressbar`,"aria-valuemin":0,"aria-valuemax":100}}),r.append(this.#r,this.#i),r.append(this.#e,this.#r);let n=[this.#t,e].filter(Boolean).join(` `);r.setAttribute(this.node,{"aria-describedby":n})}};n(`passwordstrength`,b);var x=b;export{x as default};
|
|
2
2
|
//# sourceMappingURL=frost-ui-passwordstrength.esm.min.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frost-ui-passwordstrength.esm.min.js","names":["$","characterTypes","keyboardSequences","row","leetCharacters","\"$\"","lengthBands","getCharacterType","pattern","character","getLengthStrength","band","characters","end","score","diversity","getRepeatedPattern","maxLength","length","index","normalizePassword","password","normalizeLeet","getPasswordVariants","variants","undecorated","value","getCommonPasswordStrength","common","commonPasswords","isSequence","first","second","third","type","direction","isKeyboardSequence","sequence","keyboardSequence","isPredictable","getPredictableCount","indices","i","getStrength","normalizedPassword","commonStrength","repeatedPattern","predictableCount","PasswordStrength","BaseComponent","node","options","#describedBy","overrides","getDataset","key","#form","#container","#render","#refresh","#events","error","#progress","#resetHandler","#progressBar","strength","event","nextLevel","level","\"class\"","id","generateId","\"role\"","describedBy","initComponent","src_default"],"sources":["../src/strength.js","../src/password-strength.js","../src/index.js"],"sourcesContent":["import $ from '@fr0st/query';\n\nconst characterTypes = [\n { pattern: /\\p{Ll}/u, type: 'lower' },\n { pattern: /\\p{Lu}/u, type: 'upper' },\n { pattern: /\\p{N}/u, type: 'number' },\n];\n\nconst keyboardSequences = [\n '1234567890',\n 'qwertyuiop',\n 'asdfghjkl',\n 'zxcvbnm',\n].flatMap(\n (row) => [\n row,\n Array.from(row).reverse().join(''),\n ],\n);\n\nconst leetCharacters = {\n '$': 's',\n '0': 'o',\n '3': 'e',\n '4': 'a',\n '5': 's',\n '7': 't',\n '@': 'a',\n};\n\nconst lengthBands = [\n { end: 5, max: 19, min: 1, start: 1 },\n { end: 8, max: 39, min: 20, start: 6 },\n { end: 11, max: 59, min: 40, start: 9 },\n { end: 15, max: 79, min: 60, start: 12 },\n { end: 24, max: 100, min: 80, start: 16 },\n];\n\n/**\n * Gets the type of a password character.\n * @param {string} character The character.\n * @returns {string} The character type.\n */\nconst getCharacterType = (character) =>\n characterTypes.find(\n ({ pattern }) => pattern.test(character),\n )?.type || 'symbol';\n\n/**\n * Gets a length-based password strength.\n * @param {string[]} characters The password characters.\n * @returns {number} The password strength.\n */\nconst getLengthStrength = (characters) => {\n const band = lengthBands.find(\n ({ end }) => characters.length <= end,\n ) || lengthBands.at(-1);\n const score = Math.round(\n $._map(\n Math.min(characters.length, band.end),\n band.start,\n band.end,\n band.min,\n band.max,\n ),\n );\n const diversity = new Set(\n characters.map(getCharacterType),\n ).size;\n\n return Math.min(\n score +\n (diversity - 1) * 2,\n band.max,\n );\n};\n\n/**\n * Gets the smallest repeated pattern.\n * @param {string[]} characters The password characters.\n * @returns {string[]|null} The repeated pattern.\n */\nconst getRepeatedPattern = (characters) => {\n const maxLength = Math.floor(characters.length / 2);\n\n for (let length = 1; length <= maxLength; length++) {\n if (\n characters.length % length === 0 &&\n characters.every(\n (character, index) =>\n character === characters[index % length],\n )\n ) {\n return characters.slice(0, length);\n }\n }\n\n return null;\n};\n\n/**\n * Normalizes common password matching.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizePassword = (password) =>\n String(password)\n .normalize('NFKC')\n .toLowerCase();\n\n/**\n * Normalizes common leet substitutions.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizeLeet = (password) =>\n Array.from(\n password,\n (character) => leetCharacters[character] || character,\n ).join('');\n\n/**\n * Gets normalized variants used to match common passwords.\n * @param {string} password The normalized password.\n * @returns {Set<string>} The password variants.\n */\nconst getPasswordVariants = (password) => {\n // Preserve digits for decoration stripping while resolving symbol substitutions.\n const substituted = password.replace(/[^\\p{L}\\p{N}]/gu, normalizeLeet);\n const variants = [password, substituted].flatMap((value) => {\n const undecorated = value.replace(\n /^[^\\p{L}\\p{N}]+|[^\\p{L}\\p{N}]+$/gu,\n '',\n );\n\n return [\n undecorated,\n undecorated.replace(/^\\d+|\\d+$/g, ''),\n ];\n });\n\n return new Set([\n normalizeLeet(password),\n ...variants,\n ...variants.map(normalizeLeet),\n ]);\n};\n\n/**\n * Gets the score for a common password.\n * @param {string} password The normalized password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number|null} The common-password score.\n */\nconst getCommonPasswordStrength = (password, commonPasswords) => {\n const common = new Set(\n commonPasswords.map(normalizePassword),\n );\n\n if (common.has(password)) {\n return 0;\n }\n\n return common.isDisjointFrom(getPasswordVariants(password)) ? null : 5;\n};\n\n/**\n * Checks whether characters form an ascending or descending sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a sequence.\n */\nconst isSequence = (characters) => {\n const [first, second, third] = characters.map(\n (character) => character.toLowerCase(),\n );\n const type = getCharacterType(first);\n const direction = second.codePointAt(0) - first.codePointAt(0);\n\n return ['lower', 'number'].includes(type) &&\n getCharacterType(second) === type &&\n getCharacterType(third) === type &&\n Math.abs(direction) === 1 &&\n third.codePointAt(0) - second.codePointAt(0) === direction;\n};\n\n/**\n * Checks whether characters form a keyboard sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a keyboard sequence.\n */\nconst isKeyboardSequence = (characters) => {\n const sequence = characters.join('').toLowerCase();\n\n return keyboardSequences.some(\n (keyboardSequence) => keyboardSequence.includes(sequence),\n );\n};\n\n/**\n * Checks whether characters form a predictable pattern.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a predictable pattern.\n */\nconst isPredictable = (characters) =>\n new Set(characters).size === 1 ||\n isSequence(characters) ||\n isKeyboardSequence(characters);\n\n/**\n * Gets the number of characters in predictable patterns.\n * @param {string[]} characters The password characters.\n * @returns {number} The number of predictable characters.\n */\nconst getPredictableCount = (characters) => {\n const indices = new Set();\n\n for (let i = 0; i + 2 < characters.length; i++) {\n if (isPredictable(characters.slice(i, i + 3))) {\n indices.add(i);\n indices.add(i + 1);\n indices.add(i + 2);\n }\n }\n\n return indices.size;\n};\n\n/**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=[]] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\nexport const getStrength = (password, commonPasswords = []) => {\n const normalizedPassword = password.normalize('NFKC');\n const characters = Array.from(normalizedPassword);\n\n if (!characters.length) {\n return 0;\n }\n\n const commonStrength = getCommonPasswordStrength(\n normalizedPassword.toLowerCase(),\n commonPasswords,\n );\n\n if (commonStrength !== null) {\n return commonStrength;\n }\n\n const score = getLengthStrength(characters);\n const repeatedPattern = getRepeatedPattern(characters);\n\n if (repeatedPattern?.length === 1) {\n return 0;\n }\n\n if (repeatedPattern) {\n return Math.min(\n score,\n getLengthStrength(repeatedPattern),\n 15,\n );\n }\n\n const predictableCount = getPredictableCount(characters);\n\n return $._clamp(\n score - predictableCount,\n 0,\n predictableCount === characters.length ? 15 : 100,\n );\n};\n","import $ from '@fr0st/query';\nimport { BaseComponent, generateId, getDataset } from '@fr0st/ui';\nimport { getStrength } from './strength.js';\n\n/**\n * @typedef {object} PasswordStrengthLevel\n * @property {string} class The CSS class applied to the progress bar.\n * @property {number} score The minimum score for the level.\n * @property {string} [text] The label displayed in the progress bar.\n */\n\n/**\n * @callback PasswordStrengthScorer\n * @param {string} password The password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number} The password strength, from 0 to 100.\n */\n\n/**\n * @typedef {object} PasswordStrengthOptions\n * @property {string[]} [commonPasswords] Passwords to score as very weak.\n * @property {string|null} [container=null] The selector for the progress container.\n * @property {PasswordStrengthLevel[]} [levels] The ordered password-strength levels.\n * @property {PasswordStrengthScorer} [scorer] Calculates the password strength.\n * @property {boolean} [striped=false] Whether to display a striped progress bar.\n */\n\n/**\n * Displays the strength of a password input as a progress bar.\n * @augments {BaseComponent<PasswordStrengthOptions>}\n */\nexport default class PasswordStrength extends BaseComponent {\n static classes = {\n progress: 'progress mt-2',\n progressBar: 'progress-bar',\n progressBarStriped: 'progress-bar-striped',\n };\n /** @type {PasswordStrengthOptions} */\n static defaults = {\n commonPasswords: [\n '123456',\n '12345678',\n '123456789',\n '1234567890',\n '000000',\n '111111',\n 'abc123',\n 'admin',\n 'dragon',\n 'iloveyou',\n 'letmein',\n 'monkey',\n 'password',\n 'password1',\n 'password123',\n 'qwerty',\n 'qwerty123',\n 'welcome',\n ],\n levels: [\n {\n score: 0,\n class: 'text-bg-danger',\n text: 'Very Weak',\n },\n {\n score: 20,\n class: 'text-bg-danger',\n text: 'Weak',\n },\n {\n score: 40,\n class: 'text-bg-warning',\n text: 'Normal',\n },\n {\n score: 60,\n class: 'text-bg-success',\n text: 'Strong',\n },\n {\n score: 80,\n class: 'text-bg-success',\n text: 'Very Strong',\n },\n ],\n container: null,\n scorer: getStrength,\n striped: false,\n };\n\n #container;\n #describedBy;\n #form;\n #progress;\n #progressBar;\n #resetHandler;\n\n /**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=this.defaults.commonPasswords] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\n static getStrength(password, commonPasswords = this.defaults.commonPasswords) {\n return getStrength(password, commonPasswords);\n }\n\n /**\n * Creates a PasswordStrength.\n * @param {HTMLInputElement} node The input node.\n * @param {PasswordStrengthOptions} [options] The PasswordStrength options.\n * @throws {TypeError} When the node is not an input element.\n */\n constructor(node, options) {\n if (!$.is(node, 'input')) {\n throw new TypeError('PasswordStrength must be created on an input element.');\n }\n\n super(node, options);\n\n try {\n this.#describedBy = $.getAttribute(this.node, 'aria-describedby');\n\n const overrides = { ...getDataset(node), ...options };\n\n // Replace supplied arrays instead of merging them with default entries.\n for (const key of ['levels', 'commonPasswords']) {\n if (Array.isArray(overrides[key])) {\n this.options[key].length = 0;\n $._extend(this.options[key], overrides[key]);\n }\n }\n\n this.#form = $.getProperty(this.node, 'form');\n\n if (this.options.container) {\n this.#container = $.findOne(this.options.container);\n } else {\n this.#container = $.closest(this.node, ':not(.form-input):not(.input-group)').shift();\n }\n\n this.#render();\n this.#refresh();\n this.#events();\n } catch (error) {\n this.dispose();\n throw error;\n }\n }\n\n /** @inheritdoc */\n dispose() {\n if (!this.node) {\n return;\n }\n\n $.remove(this.#progress);\n $.removeEvent(this.node, 'input.ui.passwordstrength');\n\n if (this.#form && this.#resetHandler) {\n $.removeEvent(this.#form, 'reset.ui.passwordstrength', this.#resetHandler);\n }\n\n if (this.#describedBy === null) {\n $.removeAttribute(this.node, 'aria-describedby');\n } else {\n $.setAttribute(this.node, { 'aria-describedby': this.#describedBy });\n }\n\n this.#container = null;\n this.#form = null;\n this.#progress = null;\n this.#progressBar = null;\n this.#resetHandler = null;\n\n super.dispose();\n }\n\n /**\n * Gets the password strength.\n * @returns {number} The password strength, from 0 to 100.\n */\n getStrength() {\n const value = $.getValue(this.node);\n const strength = Number(this.options.scorer(\n value,\n this.options.commonPasswords,\n ));\n\n return Number.isFinite(strength) ?\n $._clamp(strength, 0, 100) :\n 0;\n }\n\n /**\n * Attaches events for the PasswordStrength.\n */\n #events() {\n if (this.#form) {\n this.#resetHandler = (event) => {\n setTimeout(() => {\n if (this.node && !event.defaultPrevented) {\n this.#refresh();\n }\n }, 0);\n };\n\n $.addEvent(this.#form, 'reset.ui.passwordstrength', this.#resetHandler);\n }\n\n $.addEvent(this.node, 'input.ui.passwordstrength', () => {\n this.#refresh();\n });\n }\n\n /**\n * Refreshes the password strength.\n */\n #refresh() {\n const strength = this.getStrength();\n\n const nextLevel = this.options.levels.findLast(\n (level) => strength >= level.score,\n );\n\n $.setStyle(this.#progressBar, { width: `${strength}%` });\n $.setAttribute(this.#progressBar, {\n 'class': this.constructor.classes.progressBar,\n 'aria-valuenow': strength,\n });\n\n $.addClass(this.#progressBar, nextLevel.class);\n\n if (this.options.striped) {\n $.addClass(this.#progressBar, this.constructor.classes.progressBarStriped);\n }\n\n $.setText(this.#progressBar, nextLevel.text ?? '');\n }\n\n /**\n * Renders the password strength element.\n */\n #render() {\n this.#progress = $.create('div', {\n class: this.constructor.classes.progress,\n });\n\n const id = generateId('password-strength');\n\n this.#progressBar = $.create('div', {\n attributes: {\n id,\n 'role': 'progressbar',\n 'aria-valuemin': 0,\n 'aria-valuemax': 100,\n },\n });\n\n $.append(this.#progress, this.#progressBar);\n $.append(this.#container, this.#progress);\n\n const describedBy = [this.#describedBy, id]\n .filter(Boolean)\n .join(' ');\n\n $.setAttribute(this.node, { 'aria-describedby': describedBy });\n }\n}\n","import { initComponent } from '@fr0st/ui';\nimport PasswordStrength from './password-strength.js';\n\ninitComponent('passwordstrength', PasswordStrength);\n\nexport default PasswordStrength;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"frost-ui-passwordstrength.esm.min.js","names":["$","characterTypes","keyboardSequences","row","leetCharacters","\"$\"","lengthBands","getCharacterType","pattern","character","getLengthStrength","band","characters","end","score","diversity","getRepeatedPattern","maxLength","length","index","normalizePassword","password","normalizeLeet","getPasswordVariants","variants","undecorated","value","getCommonPasswordStrength","common","commonPasswords","isSequence","first","second","third","type","direction","isKeyboardSequence","sequence","keyboardSequence","isPredictable","getPredictableCount","indices","i","getStrength","normalizedPassword","commonStrength","repeatedPattern","predictableCount","PasswordStrength","BaseComponent","node","options","#describedBy","#form","#container","#render","#refresh","#events","error","#progress","#progressBar","strength","event","nextLevel","level","\"class\"","id","generateId","\"role\"","describedBy","initComponent","src_default"],"sources":["../src/strength.js","../src/password-strength.js","../src/index.js"],"sourcesContent":["import $ from '@fr0st/query';\n\nconst characterTypes = [\n { pattern: /\\p{Ll}/u, type: 'lower' },\n { pattern: /\\p{Lu}/u, type: 'upper' },\n { pattern: /\\p{N}/u, type: 'number' },\n];\n\nconst keyboardSequences = [\n '1234567890',\n 'qwertyuiop',\n 'asdfghjkl',\n 'zxcvbnm',\n].flatMap(\n (row) => [\n row,\n Array.from(row).reverse().join(''),\n ],\n);\n\nconst leetCharacters = {\n '$': 's',\n '0': 'o',\n '3': 'e',\n '4': 'a',\n '5': 's',\n '7': 't',\n '@': 'a',\n};\n\nconst lengthBands = [\n { end: 5, max: 19, min: 1, start: 1 },\n { end: 8, max: 39, min: 20, start: 6 },\n { end: 11, max: 59, min: 40, start: 9 },\n { end: 15, max: 79, min: 60, start: 12 },\n { end: 24, max: 100, min: 80, start: 16 },\n];\n\n/**\n * Gets the type of a password character.\n * @param {string} character The character.\n * @returns {string} The character type.\n */\nconst getCharacterType = (character) =>\n characterTypes.find(\n ({ pattern }) => pattern.test(character),\n )?.type || 'symbol';\n\n/**\n * Gets a length-based password strength.\n * @param {string[]} characters The password characters.\n * @returns {number} The password strength.\n */\nconst getLengthStrength = (characters) => {\n const band = lengthBands.find(\n ({ end }) => characters.length <= end,\n ) || lengthBands.at(-1);\n const score = Math.round(\n $._map(\n Math.min(characters.length, band.end),\n band.start,\n band.end,\n band.min,\n band.max,\n ),\n );\n const diversity = new Set(\n characters.map(getCharacterType),\n ).size;\n\n return Math.min(\n score +\n (diversity - 1) * 2,\n band.max,\n );\n};\n\n/**\n * Gets the smallest repeated pattern.\n * @param {string[]} characters The password characters.\n * @returns {string[]|null} The repeated pattern.\n */\nconst getRepeatedPattern = (characters) => {\n const maxLength = Math.floor(characters.length / 2);\n\n for (let length = 1; length <= maxLength; length++) {\n if (\n characters.length % length === 0 &&\n characters.every(\n (character, index) =>\n character === characters[index % length],\n )\n ) {\n return characters.slice(0, length);\n }\n }\n\n return null;\n};\n\n/**\n * Normalizes common password matching.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizePassword = (password) =>\n String(password)\n .normalize('NFKC')\n .toLowerCase();\n\n/**\n * Normalizes common leet substitutions.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizeLeet = (password) =>\n Array.from(\n password,\n (character) => leetCharacters[character] || character,\n ).join('');\n\n/**\n * Gets normalized variants used to match common passwords.\n * @param {string} password The normalized password.\n * @returns {Set<string>} The password variants.\n */\nconst getPasswordVariants = (password) => {\n // Preserve digits for decoration stripping while resolving symbol substitutions.\n const substituted = password.replace(/[^\\p{L}\\p{N}]/gu, normalizeLeet);\n const variants = [password, substituted].flatMap((value) => {\n const undecorated = value.replace(\n /^[^\\p{L}\\p{N}]+|[^\\p{L}\\p{N}]+$/gu,\n '',\n );\n\n return [\n undecorated,\n undecorated.replace(/^\\d+|\\d+$/g, ''),\n ];\n });\n\n return new Set([\n normalizeLeet(password),\n ...variants,\n ...variants.map(normalizeLeet),\n ]);\n};\n\n/**\n * Gets the score for a common password.\n * @param {string} password The normalized password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number|null} The common-password score.\n */\nconst getCommonPasswordStrength = (password, commonPasswords) => {\n const common = new Set(\n commonPasswords.map(normalizePassword),\n );\n\n if (common.has(password)) {\n return 0;\n }\n\n return common.isDisjointFrom(getPasswordVariants(password)) ? null : 5;\n};\n\n/**\n * Checks whether characters form an ascending or descending sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a sequence.\n */\nconst isSequence = (characters) => {\n const [first, second, third] = characters.map(\n (character) => character.toLowerCase(),\n );\n const type = getCharacterType(first);\n const direction = second.codePointAt(0) - first.codePointAt(0);\n\n return ['lower', 'number'].includes(type) &&\n getCharacterType(second) === type &&\n getCharacterType(third) === type &&\n Math.abs(direction) === 1 &&\n third.codePointAt(0) - second.codePointAt(0) === direction;\n};\n\n/**\n * Checks whether characters form a keyboard sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a keyboard sequence.\n */\nconst isKeyboardSequence = (characters) => {\n const sequence = characters.join('').toLowerCase();\n\n return keyboardSequences.some(\n (keyboardSequence) => keyboardSequence.includes(sequence),\n );\n};\n\n/**\n * Checks whether characters form a predictable pattern.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a predictable pattern.\n */\nconst isPredictable = (characters) =>\n new Set(characters).size === 1 ||\n isSequence(characters) ||\n isKeyboardSequence(characters);\n\n/**\n * Gets the number of characters in predictable patterns.\n * @param {string[]} characters The password characters.\n * @returns {number} The number of predictable characters.\n */\nconst getPredictableCount = (characters) => {\n const indices = new Set();\n\n for (let i = 0; i + 2 < characters.length; i++) {\n if (isPredictable(characters.slice(i, i + 3))) {\n indices.add(i);\n indices.add(i + 1);\n indices.add(i + 2);\n }\n }\n\n return indices.size;\n};\n\n/**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=[]] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\nexport const getStrength = (password, commonPasswords = []) => {\n const normalizedPassword = password.normalize('NFKC');\n const characters = Array.from(normalizedPassword);\n\n if (!characters.length) {\n return 0;\n }\n\n const commonStrength = getCommonPasswordStrength(\n normalizedPassword.toLowerCase(),\n commonPasswords,\n );\n\n if (commonStrength !== null) {\n return commonStrength;\n }\n\n const score = getLengthStrength(characters);\n const repeatedPattern = getRepeatedPattern(characters);\n\n if (repeatedPattern?.length === 1) {\n return 0;\n }\n\n if (repeatedPattern) {\n return Math.min(\n score,\n getLengthStrength(repeatedPattern),\n 15,\n );\n }\n\n const predictableCount = getPredictableCount(characters);\n\n return $._clamp(\n score - predictableCount,\n 0,\n predictableCount === characters.length ? 15 : 100,\n );\n};\n","import $ from '@fr0st/query';\nimport { BaseComponent, generateId } from '@fr0st/ui';\nimport { getStrength } from './strength.js';\n\n/**\n * @typedef {object} PasswordStrengthLevel\n * @property {string} class The CSS class applied to the progress bar.\n * @property {number} score The minimum score for the level.\n * @property {string} [text] The label displayed in the progress bar.\n */\n\n/**\n * @callback PasswordStrengthScorer\n * @param {string} password The password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number} The password strength, from 0 to 100.\n */\n\n/**\n * @typedef {object} PasswordStrengthOptions\n * @property {string[]} [commonPasswords] Passwords to score as very weak.\n * @property {string|null} [container=null] The selector for the progress container.\n * @property {PasswordStrengthLevel[]} [levels] The ordered password-strength levels.\n * @property {PasswordStrengthScorer} [scorer] Calculates the password strength.\n * @property {boolean} [striped=false] Whether to display a striped progress bar.\n */\n\n/**\n * Displays the strength of a password input as a progress bar.\n * @augments {BaseComponent<PasswordStrengthOptions>}\n */\nexport default class PasswordStrength extends BaseComponent {\n static classes = {\n progress: 'progress mt-2',\n progressBar: 'progress-bar',\n progressBarStriped: 'progress-bar-striped',\n };\n /** @type {PasswordStrengthOptions} */\n static defaults = {\n commonPasswords: [\n '123456',\n '12345678',\n '123456789',\n '1234567890',\n '000000',\n '111111',\n 'abc123',\n 'admin',\n 'dragon',\n 'iloveyou',\n 'letmein',\n 'monkey',\n 'password',\n 'password1',\n 'password123',\n 'qwerty',\n 'qwerty123',\n 'welcome',\n ],\n levels: [\n {\n score: 0,\n class: 'text-bg-danger',\n text: 'Very Weak',\n },\n {\n score: 20,\n class: 'text-bg-danger',\n text: 'Weak',\n },\n {\n score: 40,\n class: 'text-bg-warning',\n text: 'Normal',\n },\n {\n score: 60,\n class: 'text-bg-success',\n text: 'Strong',\n },\n {\n score: 80,\n class: 'text-bg-success',\n text: 'Very Strong',\n },\n ],\n container: null,\n scorer: getStrength,\n striped: false,\n };\n\n #container = null;\n #describedBy;\n #form = null;\n #progress = null;\n #progressBar = null;\n\n /**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=this.defaults.commonPasswords] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\n static getStrength(password, commonPasswords = this.defaults.commonPasswords) {\n return getStrength(password, commonPasswords);\n }\n\n /**\n * Creates a PasswordStrength.\n * @param {HTMLInputElement} node The input node.\n * @param {PasswordStrengthOptions} [options] The PasswordStrength options.\n * @throws {TypeError} When the node is not an input element.\n */\n constructor(node, options) {\n if (!$._isElement(node) || !$.is(node, 'input')) {\n throw new TypeError('PasswordStrength must be created on an input element.');\n }\n\n super(node, options);\n\n try {\n this.#describedBy = $.getAttribute(this.node, 'aria-describedby');\n\n this.#form = $.getProperty(this.node, 'form');\n\n if (this.options.container) {\n this.#container = $.findOne(this.options.container);\n } else {\n this.#container = $.closest(this.node, ':not(.form-input):not(.input-group)').shift();\n }\n\n this.#render();\n this.#refresh();\n this.#events();\n } catch (error) {\n this.dispose();\n throw error;\n }\n }\n\n /** @inheritdoc */\n dispose() {\n if (!this.node) {\n return;\n }\n\n $.remove(this.#progress);\n\n if (this.#describedBy === null) {\n $.removeAttribute(this.node, 'aria-describedby');\n } else {\n $.setAttribute(this.node, { 'aria-describedby': this.#describedBy });\n }\n\n this.#container = null;\n this.#form = null;\n this.#progress = null;\n this.#progressBar = null;\n\n super.dispose();\n }\n\n /**\n * Gets the password strength.\n * @returns {number} The password strength, from 0 to 100.\n */\n getStrength() {\n const value = $.getValue(this.node);\n const strength = Number(this.options.scorer(\n value,\n this.options.commonPasswords,\n ));\n\n return Number.isFinite(strength) ?\n $._clamp(strength, 0, 100) :\n 0;\n }\n\n /**\n * Attaches events for the PasswordStrength.\n */\n #events() {\n if (this.#form) {\n this.listen(this.#form, 'reset.ui.passwordstrength', (event) => {\n setTimeout(() => {\n if (this.node && !event.defaultPrevented) {\n this.#refresh();\n }\n }, 0);\n });\n }\n\n this.listen(this.node, 'input.ui.passwordstrength', () => {\n this.#refresh();\n });\n }\n\n /**\n * Refreshes the password strength.\n */\n #refresh() {\n const strength = this.getStrength();\n\n const nextLevel = this.options.levels.findLast(\n (level) => strength >= level.score,\n );\n\n $.setStyle(this.#progressBar, { width: `${strength}%` });\n $.setAttribute(this.#progressBar, {\n 'class': this.constructor.classes.progressBar,\n 'aria-valuenow': strength,\n });\n\n $.addClass(this.#progressBar, nextLevel.class);\n\n if (this.options.striped) {\n $.addClass(this.#progressBar, this.constructor.classes.progressBarStriped);\n }\n\n $.setText(this.#progressBar, nextLevel.text ?? '');\n }\n\n /**\n * Renders the password strength element.\n */\n #render() {\n this.#progress = $.create('div', {\n class: this.constructor.classes.progress,\n });\n\n const id = generateId('password-strength');\n\n this.#progressBar = $.create('div', {\n attributes: {\n id,\n 'role': 'progressbar',\n 'aria-valuemin': 0,\n 'aria-valuemax': 100,\n },\n });\n\n $.append(this.#progress, this.#progressBar);\n $.append(this.#container, this.#progress);\n\n const describedBy = [this.#describedBy, id]\n .filter(Boolean)\n .join(' ');\n\n $.setAttribute(this.node, { 'aria-describedby': describedBy });\n }\n}\n","import { initComponent } from '@fr0st/ui';\nimport PasswordStrength from './password-strength.js';\n\ninitComponent('passwordstrength', PasswordStrength);\n\nexport default PasswordStrength;\n"],"mappings":"yGAEA,IAAMC,EAAiB,CACnB,CAAE,QAAS,UAAW,KAAM,OAAQ,EACpC,CAAE,QAAS,UAAW,KAAM,OAAQ,EACpC,CAAE,QAAS,SAAU,KAAM,QAAS,CACxC,EAEMC,EAAoB,CACtB,aACA,aACA,YACA,SACJ,CAAC,CAAC,QACG,GAAQ,CACLC,EACA,MAAM,KAAKA,CAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,CACrC,CACJ,EAEMC,EAAiB,CACnBC,EAAK,IACL,EAAK,IACL,EAAK,IACL,EAAK,IACL,EAAK,IACL,EAAK,IACL,IAAK,GACT,EAEMC,EAAc,CAChB,CAAE,IAAK,EAAG,IAAK,GAAI,IAAK,EAAG,MAAO,CAAE,EACpC,CAAE,IAAK,EAAG,IAAK,GAAI,IAAK,GAAI,MAAO,CAAE,EACrC,CAAE,IAAK,GAAI,IAAK,GAAI,IAAK,GAAI,MAAO,CAAE,EACtC,CAAE,IAAK,GAAI,IAAK,GAAI,IAAK,GAAI,MAAO,EAAG,EACvC,CAAE,IAAK,GAAI,IAAK,IAAK,IAAK,GAAI,MAAO,EAAG,CAC5C,EAOMC,EAAoB,GACtBN,EAAe,MACV,CAAE,aAAcO,EAAQ,KAAKC,CAAS,CAC3C,CAAC,EAAE,MAAQ,SAOTC,EAAqB,GAAe,CACtC,IAAMC,EAAOL,EAAY,MACpB,CAAE,SAAUM,EAAW,QAAUC,CACtC,GAAKP,EAAY,GAAG,EAAE,EAChBQ,EAAQ,KAAK,MACfd,EAAE,KACE,KAAK,IAAIY,EAAW,OAAQD,EAAK,GAAG,EACpCA,EAAK,MACLA,EAAK,IACLA,EAAK,IACLA,EAAK,GACT,CACJ,EACMI,EAAY,IAAI,IAClBH,EAAW,IAAIL,CAAgB,CACnC,CAAC,CAAC,KAEF,OAAO,KAAK,IACRO,GACCC,EAAY,GAAK,EAClBJ,EAAK,GACT,CACJ,EAOMK,EAAsB,GAAe,CACvC,IAAMC,EAAY,KAAK,MAAML,EAAW,OAAS,CAAC,EAElD,IAAK,IAAIM,EAAS,EAAGA,GAAUD,EAAW,IACtC,GACIL,EAAW,OAASM,IAAW,GAC/BN,EAAW,OACN,EAAW,IACRH,IAAcG,EAAWO,EAAQD,EACzC,EAEA,OAAON,EAAW,MAAM,EAAGM,CAAM,EAIzC,OAAO,IACX,EAOME,EAAqB,GACvB,OAAOC,CAAQ,CAAC,CACX,UAAU,MAAM,CAAC,CACjB,YAAY,EAOfC,EAAiB,GACnB,MAAM,KACFD,EACC,GAAcjB,EAAeK,IAAcA,CAChD,CAAC,CAAC,KAAK,EAAE,EAOPc,EAAuB,GAAa,CAGtC,IAAMC,EAAW,CAACH,EADEA,EAAS,QAAQ,kBAAmBC,CAClB,CAAC,CAAC,CAAC,QAAS,GAAU,CACxD,IAAMG,EAAcC,EAAM,QACtB,oCACA,EACJ,EAEA,MAAO,CACHD,EACAA,EAAY,QAAQ,aAAc,EAAE,CACxC,CACJ,CAAC,EAED,sBAAO,IAAI,IAAI,CACXH,EAAcD,CAAQ,EACtB,GAAGG,EACH,GAAGA,EAAS,IAAIF,CAAa,CACjC,CAAC,CACL,EAQMK,GAA6B,EAAU,IAAoB,CAC7D,IAAMC,EAAS,IAAI,IACfC,EAAgB,IAAIT,CAAiB,CACzC,EAMA,OAJIQ,EAAO,IAAIP,CAAQ,EACZ,EAGJO,EAAO,eAAeL,EAAoBF,CAAQ,CAAC,EAAI,KAAO,CACzE,EAOMS,EAAc,GAAe,CAC/B,GAAM,CAACC,EAAOC,EAAQC,GAASrB,EAAW,IACrC,GAAcH,EAAU,YAAY,CACzC,EACMyB,EAAO3B,EAAiBwB,CAAK,EAC7BI,EAAYH,EAAO,YAAY,CAAC,EAAID,EAAM,YAAY,CAAC,EAE7D,MAAO,CAAC,QAAS,QAAQ,CAAC,CAAC,SAASG,CAAI,GACpC3B,EAAiByB,CAAM,IAAME,GAC7B3B,EAAiB0B,CAAK,IAAMC,GAC5B,KAAK,IAAIC,CAAS,IAAM,GACxBF,EAAM,YAAY,CAAC,EAAID,EAAO,YAAY,CAAC,IAAMG,CACzD,EAOMC,EAAsB,GAAe,CACvC,IAAMC,EAAWzB,EAAW,KAAK,EAAE,CAAC,CAAC,YAAY,EAEjD,OAAOV,EAAkB,KACpB,GAAqBoC,EAAiB,SAASD,CAAQ,CAC5D,CACJ,EAOME,EAAiB,GACnB,IAAI,IAAI3B,CAAU,CAAC,CAAC,OAAS,GAC7BkB,EAAWlB,CAAU,GACrBwB,EAAmBxB,CAAU,EAO3B4B,EAAuB,GAAe,CACxC,IAAMC,kBAAU,IAAI,IAEpB,IAAK,IAAIC,EAAI,EAAGA,EAAI,EAAI9B,EAAW,OAAQ,IACnC2B,EAAc3B,EAAW,MAAM8B,EAAGA,EAAI,CAAC,CAAC,IACxCD,EAAQ,IAAIC,CAAC,EACbD,EAAQ,IAAIC,EAAI,CAAC,EACjBD,EAAQ,IAAIC,EAAI,CAAC,GAIzB,OAAOD,EAAQ,IACnB,EAQaE,GAAe,EAAU,EAAkB,CAAC,IAAM,CAC3D,IAAMC,EAAqBvB,EAAS,UAAU,MAAM,EAC9CT,EAAa,MAAM,KAAKgC,CAAkB,EAEhD,GAAI,CAAChC,EAAW,OACZ,MAAO,GAGX,IAAMiC,EAAiBlB,EACnBiB,EAAmB,YAAY,EAC/Bf,CACJ,EAEA,GAAIgB,IAAmB,KACnB,OAAOA,EAGX,IAAM/B,EAAQJ,EAAkBE,CAAU,EACpCkC,EAAkB9B,EAAmBJ,CAAU,EAErD,GAAIkC,GAAiB,SAAW,EAC5B,MAAO,GAGX,GAAIA,EACA,OAAO,KAAK,IACRhC,EACAJ,EAAkBoC,CAAe,EACjC,EACJ,EAGJ,IAAMC,EAAmBP,EAAoB5B,CAAU,EAEvD,OAAOZ,EAAE,OACLc,EAAQiC,EACR,EACAA,IAAqBnC,EAAW,OAAS,GAAK,GAClD,CACJ,ECjPqBoC,EAArB,cAA8CC,CAAc,CACxD,OAAO,QAAU,CACb,SAAU,gBACV,YAAa,eACb,mBAAoB,sBACxB,EAEA,OAAO,SAAW,CACd,gBAAiB,CACb,SACA,WACA,YACA,aACA,SACA,SACA,SACA,QACA,SACA,WACA,UACA,SACA,WACA,YACA,cACA,SACA,YACA,SACJ,EACA,OAAQ,CACJ,CACI,MAAO,EACP,MAAO,iBACP,KAAM,WACV,EACA,CACI,MAAO,GACP,MAAO,iBACP,KAAM,MACV,EACA,CACI,MAAO,GACP,MAAO,kBACP,KAAM,QACV,EACA,CACI,MAAO,GACP,MAAO,kBACP,KAAM,QACV,EACA,CACI,MAAO,GACP,MAAO,kBACP,KAAM,aACV,CACJ,EACA,UAAW,KACX,OAAQN,EACR,QAAS,EACb,EAEA,GAAa,KACb,GACA,GAAQ,KACR,GAAY,KACZ,GAAe,KAQf,OAAO,YAAY,EAAU,EAAkB,KAAK,SAAS,gBAAiB,CAC1E,OAAOA,EAAYtB,EAAUQ,CAAe,CAChD,CAQA,YAAY,EAAM,EAAS,CACvB,GAAI,CAAC7B,EAAE,WAAWkD,CAAI,GAAK,CAAClD,EAAE,GAAGkD,EAAM,OAAO,EAC1C,MAAU,UAAU,uDAAuD,EAG/E,MAAMA,EAAMC,CAAO,EAEnB,GAAI,CACA,KAAKC,GAAepD,EAAE,aAAa,KAAK,KAAM,kBAAkB,EAEhE,KAAKqD,GAAQrD,EAAE,YAAY,KAAK,KAAM,MAAM,EAE5C,AAGI,KAAKsD,GAHL,KAAK,QAAQ,UACKtD,EAAE,QAAQ,KAAK,QAAQ,SAAS,EAEhCA,EAAE,QAAQ,KAAK,KAAM,qCAAqC,CAAC,CAAC,MAAM,EAGxF,KAAKuD,GAAQ,EACb,KAAKC,GAAS,EACd,KAAKC,GAAQ,CACjB,OAASC,EAAO,CAEZ,MADA,KAAK,QAAQ,EACPA,CACV,CACJ,CAGA,SAAU,CACD,KAAK,OAIV1D,EAAE,OAAO,KAAK2D,EAAS,EAEnB,KAAKP,KAAiB,KACtBpD,EAAE,gBAAgB,KAAK,KAAM,kBAAkB,EAE/CA,EAAE,aAAa,KAAK,KAAM,CAAE,mBAAoB,KAAKoD,EAAa,CAAC,EAGvE,KAAKE,GAAa,KAClB,KAAKD,GAAQ,KACb,KAAKM,GAAY,KACjB,KAAKC,GAAe,KAEpB,MAAM,QAAQ,EAClB,CAMA,aAAc,CACV,IAAMlC,EAAQ1B,EAAE,SAAS,KAAK,IAAI,EAC5B6D,EAAW,OAAO,KAAK,QAAQ,OACjCnC,EACA,KAAK,QAAQ,eACjB,CAAC,EAED,OAAO,OAAO,SAASmC,CAAQ,EAC3B7D,EAAE,OAAO6D,EAAU,EAAG,GAAG,EACzB,CACR,CAKA,IAAU,CACF,KAAKR,IACL,KAAK,OAAO,KAAKA,GAAO,4BAA8B,GAAU,CAC5D,eAAiB,CACT,KAAK,MAAQ,CAACS,EAAM,kBACpB,KAAKN,GAAS,CAEtB,EAAG,CAAC,CACR,CAAC,EAGL,KAAK,OAAO,KAAK,KAAM,gCAAmC,CACtD,KAAKA,GAAS,CAClB,CAAC,CACL,CAKA,IAAW,CACP,IAAMK,EAAW,KAAK,YAAY,EAE5BE,EAAY,KAAK,QAAQ,OAAO,SACjC,GAAUF,GAAYG,EAAM,KACjC,EAEAhE,EAAE,SAAS,KAAK4D,GAAc,CAAE,MAAO,GAAGC,EAAS,EAAG,CAAC,EACvD7D,EAAE,aAAa,KAAK4D,GAAc,CAC9BK,MAAS,KAAK,YAAY,QAAQ,YAClC,gBAAiBJ,CACrB,CAAC,EAED7D,EAAE,SAAS,KAAK4D,GAAcG,EAAU,KAAK,EAEzC,KAAK,QAAQ,SACb/D,EAAE,SAAS,KAAK4D,GAAc,KAAK,YAAY,QAAQ,kBAAkB,EAG7E5D,EAAE,QAAQ,KAAK4D,GAAcG,EAAU,MAAQ,EAAE,CACrD,CAKA,IAAU,CACN,KAAKJ,GAAY3D,EAAE,OAAO,MAAO,CAC7B,MAAO,KAAK,YAAY,QAAQ,QACpC,CAAC,EAED,IAAMkE,EAAKC,EAAW,mBAAmB,EAEzC,KAAKP,GAAe5D,EAAE,OAAO,MAAO,CAChC,WAAY,CACR,KACAoE,KAAQ,cACR,gBAAiB,EACjB,gBAAiB,GACrB,CACJ,CAAC,EAEDpE,EAAE,OAAO,KAAK2D,GAAW,KAAKC,EAAY,EAC1C5D,EAAE,OAAO,KAAKsD,GAAY,KAAKK,EAAS,EAExC,IAAMU,EAAc,CAAC,KAAKjB,GAAcc,CAAE,CAAC,CACtC,OAAO,OAAO,CAAC,CACf,KAAK,GAAG,EAEblE,EAAE,aAAa,KAAK,KAAM,CAAE,mBAAoBqE,CAAY,CAAC,CACjE,CACJ,ECvPAC,EAAc,mBAAoBtB,CAAgB,EAElD,IAAAuB,EAAevB"}
|
|
@@ -306,12 +306,11 @@ _fr0st_query = __toESM(_fr0st_query, 1);
|
|
|
306
306
|
scorer: getStrength,
|
|
307
307
|
striped: false
|
|
308
308
|
};
|
|
309
|
-
#container;
|
|
309
|
+
#container = null;
|
|
310
310
|
#describedBy;
|
|
311
|
-
#form;
|
|
312
|
-
#progress;
|
|
313
|
-
#progressBar;
|
|
314
|
-
#resetHandler;
|
|
311
|
+
#form = null;
|
|
312
|
+
#progress = null;
|
|
313
|
+
#progressBar = null;
|
|
315
314
|
/**
|
|
316
315
|
* Calculates the strength of a password.
|
|
317
316
|
* @param {string} password The password.
|
|
@@ -328,18 +327,10 @@ _fr0st_query = __toESM(_fr0st_query, 1);
|
|
|
328
327
|
* @throws {TypeError} When the node is not an input element.
|
|
329
328
|
*/
|
|
330
329
|
constructor(node, options) {
|
|
331
|
-
if (!_fr0st_query.default.is(node, "input")) throw new TypeError("PasswordStrength must be created on an input element.");
|
|
330
|
+
if (!_fr0st_query.default._isElement(node) || !_fr0st_query.default.is(node, "input")) throw new TypeError("PasswordStrength must be created on an input element.");
|
|
332
331
|
super(node, options);
|
|
333
332
|
try {
|
|
334
333
|
this.#describedBy = _fr0st_query.default.getAttribute(this.node, "aria-describedby");
|
|
335
|
-
const overrides = {
|
|
336
|
-
...(0, _fr0st_ui.getDataset)(node),
|
|
337
|
-
...options
|
|
338
|
-
};
|
|
339
|
-
for (const key of ["levels", "commonPasswords"]) if (Array.isArray(overrides[key])) {
|
|
340
|
-
this.options[key].length = 0;
|
|
341
|
-
_fr0st_query.default._extend(this.options[key], overrides[key]);
|
|
342
|
-
}
|
|
343
334
|
this.#form = _fr0st_query.default.getProperty(this.node, "form");
|
|
344
335
|
if (this.options.container) this.#container = _fr0st_query.default.findOne(this.options.container);
|
|
345
336
|
else this.#container = _fr0st_query.default.closest(this.node, ":not(.form-input):not(.input-group)").shift();
|
|
@@ -355,15 +346,12 @@ _fr0st_query = __toESM(_fr0st_query, 1);
|
|
|
355
346
|
dispose() {
|
|
356
347
|
if (!this.node) return;
|
|
357
348
|
_fr0st_query.default.remove(this.#progress);
|
|
358
|
-
_fr0st_query.default.removeEvent(this.node, "input.ui.passwordstrength");
|
|
359
|
-
if (this.#form && this.#resetHandler) _fr0st_query.default.removeEvent(this.#form, "reset.ui.passwordstrength", this.#resetHandler);
|
|
360
349
|
if (this.#describedBy === null) _fr0st_query.default.removeAttribute(this.node, "aria-describedby");
|
|
361
350
|
else _fr0st_query.default.setAttribute(this.node, { "aria-describedby": this.#describedBy });
|
|
362
351
|
this.#container = null;
|
|
363
352
|
this.#form = null;
|
|
364
353
|
this.#progress = null;
|
|
365
354
|
this.#progressBar = null;
|
|
366
|
-
this.#resetHandler = null;
|
|
367
355
|
super.dispose();
|
|
368
356
|
}
|
|
369
357
|
/**
|
|
@@ -379,15 +367,12 @@ _fr0st_query = __toESM(_fr0st_query, 1);
|
|
|
379
367
|
* Attaches events for the PasswordStrength.
|
|
380
368
|
*/
|
|
381
369
|
#events() {
|
|
382
|
-
if (this.#form) {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
_fr0st_query.default.addEvent(this.#form, "reset.ui.passwordstrength", this.#resetHandler);
|
|
389
|
-
}
|
|
390
|
-
_fr0st_query.default.addEvent(this.node, "input.ui.passwordstrength", () => {
|
|
370
|
+
if (this.#form) this.listen(this.#form, "reset.ui.passwordstrength", (event) => {
|
|
371
|
+
setTimeout(() => {
|
|
372
|
+
if (this.node && !event.defaultPrevented) this.#refresh();
|
|
373
|
+
}, 0);
|
|
374
|
+
});
|
|
375
|
+
this.listen(this.node, "input.ui.passwordstrength", () => {
|
|
391
376
|
this.#refresh();
|
|
392
377
|
});
|
|
393
378
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frost-ui-passwordstrength.js","names":["$","BaseComponent","$","getDataset","generateId"],"sources":["../src/strength.js","../src/password-strength.js","../src/index.js"],"sourcesContent":["import $ from '@fr0st/query';\n\nconst characterTypes = [\n { pattern: /\\p{Ll}/u, type: 'lower' },\n { pattern: /\\p{Lu}/u, type: 'upper' },\n { pattern: /\\p{N}/u, type: 'number' },\n];\n\nconst keyboardSequences = [\n '1234567890',\n 'qwertyuiop',\n 'asdfghjkl',\n 'zxcvbnm',\n].flatMap(\n (row) => [\n row,\n Array.from(row).reverse().join(''),\n ],\n);\n\nconst leetCharacters = {\n '$': 's',\n '0': 'o',\n '3': 'e',\n '4': 'a',\n '5': 's',\n '7': 't',\n '@': 'a',\n};\n\nconst lengthBands = [\n { end: 5, max: 19, min: 1, start: 1 },\n { end: 8, max: 39, min: 20, start: 6 },\n { end: 11, max: 59, min: 40, start: 9 },\n { end: 15, max: 79, min: 60, start: 12 },\n { end: 24, max: 100, min: 80, start: 16 },\n];\n\n/**\n * Gets the type of a password character.\n * @param {string} character The character.\n * @returns {string} The character type.\n */\nconst getCharacterType = (character) =>\n characterTypes.find(\n ({ pattern }) => pattern.test(character),\n )?.type || 'symbol';\n\n/**\n * Gets a length-based password strength.\n * @param {string[]} characters The password characters.\n * @returns {number} The password strength.\n */\nconst getLengthStrength = (characters) => {\n const band = lengthBands.find(\n ({ end }) => characters.length <= end,\n ) || lengthBands.at(-1);\n const score = Math.round(\n $._map(\n Math.min(characters.length, band.end),\n band.start,\n band.end,\n band.min,\n band.max,\n ),\n );\n const diversity = new Set(\n characters.map(getCharacterType),\n ).size;\n\n return Math.min(\n score +\n (diversity - 1) * 2,\n band.max,\n );\n};\n\n/**\n * Gets the smallest repeated pattern.\n * @param {string[]} characters The password characters.\n * @returns {string[]|null} The repeated pattern.\n */\nconst getRepeatedPattern = (characters) => {\n const maxLength = Math.floor(characters.length / 2);\n\n for (let length = 1; length <= maxLength; length++) {\n if (\n characters.length % length === 0 &&\n characters.every(\n (character, index) =>\n character === characters[index % length],\n )\n ) {\n return characters.slice(0, length);\n }\n }\n\n return null;\n};\n\n/**\n * Normalizes common password matching.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizePassword = (password) =>\n String(password)\n .normalize('NFKC')\n .toLowerCase();\n\n/**\n * Normalizes common leet substitutions.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizeLeet = (password) =>\n Array.from(\n password,\n (character) => leetCharacters[character] || character,\n ).join('');\n\n/**\n * Gets normalized variants used to match common passwords.\n * @param {string} password The normalized password.\n * @returns {Set<string>} The password variants.\n */\nconst getPasswordVariants = (password) => {\n // Preserve digits for decoration stripping while resolving symbol substitutions.\n const substituted = password.replace(/[^\\p{L}\\p{N}]/gu, normalizeLeet);\n const variants = [password, substituted].flatMap((value) => {\n const undecorated = value.replace(\n /^[^\\p{L}\\p{N}]+|[^\\p{L}\\p{N}]+$/gu,\n '',\n );\n\n return [\n undecorated,\n undecorated.replace(/^\\d+|\\d+$/g, ''),\n ];\n });\n\n return new Set([\n normalizeLeet(password),\n ...variants,\n ...variants.map(normalizeLeet),\n ]);\n};\n\n/**\n * Gets the score for a common password.\n * @param {string} password The normalized password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number|null} The common-password score.\n */\nconst getCommonPasswordStrength = (password, commonPasswords) => {\n const common = new Set(\n commonPasswords.map(normalizePassword),\n );\n\n if (common.has(password)) {\n return 0;\n }\n\n return common.isDisjointFrom(getPasswordVariants(password)) ? null : 5;\n};\n\n/**\n * Checks whether characters form an ascending or descending sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a sequence.\n */\nconst isSequence = (characters) => {\n const [first, second, third] = characters.map(\n (character) => character.toLowerCase(),\n );\n const type = getCharacterType(first);\n const direction = second.codePointAt(0) - first.codePointAt(0);\n\n return ['lower', 'number'].includes(type) &&\n getCharacterType(second) === type &&\n getCharacterType(third) === type &&\n Math.abs(direction) === 1 &&\n third.codePointAt(0) - second.codePointAt(0) === direction;\n};\n\n/**\n * Checks whether characters form a keyboard sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a keyboard sequence.\n */\nconst isKeyboardSequence = (characters) => {\n const sequence = characters.join('').toLowerCase();\n\n return keyboardSequences.some(\n (keyboardSequence) => keyboardSequence.includes(sequence),\n );\n};\n\n/**\n * Checks whether characters form a predictable pattern.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a predictable pattern.\n */\nconst isPredictable = (characters) =>\n new Set(characters).size === 1 ||\n isSequence(characters) ||\n isKeyboardSequence(characters);\n\n/**\n * Gets the number of characters in predictable patterns.\n * @param {string[]} characters The password characters.\n * @returns {number} The number of predictable characters.\n */\nconst getPredictableCount = (characters) => {\n const indices = new Set();\n\n for (let i = 0; i + 2 < characters.length; i++) {\n if (isPredictable(characters.slice(i, i + 3))) {\n indices.add(i);\n indices.add(i + 1);\n indices.add(i + 2);\n }\n }\n\n return indices.size;\n};\n\n/**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=[]] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\nexport const getStrength = (password, commonPasswords = []) => {\n const normalizedPassword = password.normalize('NFKC');\n const characters = Array.from(normalizedPassword);\n\n if (!characters.length) {\n return 0;\n }\n\n const commonStrength = getCommonPasswordStrength(\n normalizedPassword.toLowerCase(),\n commonPasswords,\n );\n\n if (commonStrength !== null) {\n return commonStrength;\n }\n\n const score = getLengthStrength(characters);\n const repeatedPattern = getRepeatedPattern(characters);\n\n if (repeatedPattern?.length === 1) {\n return 0;\n }\n\n if (repeatedPattern) {\n return Math.min(\n score,\n getLengthStrength(repeatedPattern),\n 15,\n );\n }\n\n const predictableCount = getPredictableCount(characters);\n\n return $._clamp(\n score - predictableCount,\n 0,\n predictableCount === characters.length ? 15 : 100,\n );\n};\n","import $ from '@fr0st/query';\nimport { BaseComponent, generateId, getDataset } from '@fr0st/ui';\nimport { getStrength } from './strength.js';\n\n/**\n * @typedef {object} PasswordStrengthLevel\n * @property {string} class The CSS class applied to the progress bar.\n * @property {number} score The minimum score for the level.\n * @property {string} [text] The label displayed in the progress bar.\n */\n\n/**\n * @callback PasswordStrengthScorer\n * @param {string} password The password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number} The password strength, from 0 to 100.\n */\n\n/**\n * @typedef {object} PasswordStrengthOptions\n * @property {string[]} [commonPasswords] Passwords to score as very weak.\n * @property {string|null} [container=null] The selector for the progress container.\n * @property {PasswordStrengthLevel[]} [levels] The ordered password-strength levels.\n * @property {PasswordStrengthScorer} [scorer] Calculates the password strength.\n * @property {boolean} [striped=false] Whether to display a striped progress bar.\n */\n\n/**\n * Displays the strength of a password input as a progress bar.\n * @augments {BaseComponent<PasswordStrengthOptions>}\n */\nexport default class PasswordStrength extends BaseComponent {\n static classes = {\n progress: 'progress mt-2',\n progressBar: 'progress-bar',\n progressBarStriped: 'progress-bar-striped',\n };\n /** @type {PasswordStrengthOptions} */\n static defaults = {\n commonPasswords: [\n '123456',\n '12345678',\n '123456789',\n '1234567890',\n '000000',\n '111111',\n 'abc123',\n 'admin',\n 'dragon',\n 'iloveyou',\n 'letmein',\n 'monkey',\n 'password',\n 'password1',\n 'password123',\n 'qwerty',\n 'qwerty123',\n 'welcome',\n ],\n levels: [\n {\n score: 0,\n class: 'text-bg-danger',\n text: 'Very Weak',\n },\n {\n score: 20,\n class: 'text-bg-danger',\n text: 'Weak',\n },\n {\n score: 40,\n class: 'text-bg-warning',\n text: 'Normal',\n },\n {\n score: 60,\n class: 'text-bg-success',\n text: 'Strong',\n },\n {\n score: 80,\n class: 'text-bg-success',\n text: 'Very Strong',\n },\n ],\n container: null,\n scorer: getStrength,\n striped: false,\n };\n\n #container;\n #describedBy;\n #form;\n #progress;\n #progressBar;\n #resetHandler;\n\n /**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=this.defaults.commonPasswords] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\n static getStrength(password, commonPasswords = this.defaults.commonPasswords) {\n return getStrength(password, commonPasswords);\n }\n\n /**\n * Creates a PasswordStrength.\n * @param {HTMLInputElement} node The input node.\n * @param {PasswordStrengthOptions} [options] The PasswordStrength options.\n * @throws {TypeError} When the node is not an input element.\n */\n constructor(node, options) {\n if (!$.is(node, 'input')) {\n throw new TypeError('PasswordStrength must be created on an input element.');\n }\n\n super(node, options);\n\n try {\n this.#describedBy = $.getAttribute(this.node, 'aria-describedby');\n\n const overrides = { ...getDataset(node), ...options };\n\n // Replace supplied arrays instead of merging them with default entries.\n for (const key of ['levels', 'commonPasswords']) {\n if (Array.isArray(overrides[key])) {\n this.options[key].length = 0;\n $._extend(this.options[key], overrides[key]);\n }\n }\n\n this.#form = $.getProperty(this.node, 'form');\n\n if (this.options.container) {\n this.#container = $.findOne(this.options.container);\n } else {\n this.#container = $.closest(this.node, ':not(.form-input):not(.input-group)').shift();\n }\n\n this.#render();\n this.#refresh();\n this.#events();\n } catch (error) {\n this.dispose();\n throw error;\n }\n }\n\n /** @inheritdoc */\n dispose() {\n if (!this.node) {\n return;\n }\n\n $.remove(this.#progress);\n $.removeEvent(this.node, 'input.ui.passwordstrength');\n\n if (this.#form && this.#resetHandler) {\n $.removeEvent(this.#form, 'reset.ui.passwordstrength', this.#resetHandler);\n }\n\n if (this.#describedBy === null) {\n $.removeAttribute(this.node, 'aria-describedby');\n } else {\n $.setAttribute(this.node, { 'aria-describedby': this.#describedBy });\n }\n\n this.#container = null;\n this.#form = null;\n this.#progress = null;\n this.#progressBar = null;\n this.#resetHandler = null;\n\n super.dispose();\n }\n\n /**\n * Gets the password strength.\n * @returns {number} The password strength, from 0 to 100.\n */\n getStrength() {\n const value = $.getValue(this.node);\n const strength = Number(this.options.scorer(\n value,\n this.options.commonPasswords,\n ));\n\n return Number.isFinite(strength) ?\n $._clamp(strength, 0, 100) :\n 0;\n }\n\n /**\n * Attaches events for the PasswordStrength.\n */\n #events() {\n if (this.#form) {\n this.#resetHandler = (event) => {\n setTimeout(() => {\n if (this.node && !event.defaultPrevented) {\n this.#refresh();\n }\n }, 0);\n };\n\n $.addEvent(this.#form, 'reset.ui.passwordstrength', this.#resetHandler);\n }\n\n $.addEvent(this.node, 'input.ui.passwordstrength', () => {\n this.#refresh();\n });\n }\n\n /**\n * Refreshes the password strength.\n */\n #refresh() {\n const strength = this.getStrength();\n\n const nextLevel = this.options.levels.findLast(\n (level) => strength >= level.score,\n );\n\n $.setStyle(this.#progressBar, { width: `${strength}%` });\n $.setAttribute(this.#progressBar, {\n 'class': this.constructor.classes.progressBar,\n 'aria-valuenow': strength,\n });\n\n $.addClass(this.#progressBar, nextLevel.class);\n\n if (this.options.striped) {\n $.addClass(this.#progressBar, this.constructor.classes.progressBarStriped);\n }\n\n $.setText(this.#progressBar, nextLevel.text ?? '');\n }\n\n /**\n * Renders the password strength element.\n */\n #render() {\n this.#progress = $.create('div', {\n class: this.constructor.classes.progress,\n });\n\n const id = generateId('password-strength');\n\n this.#progressBar = $.create('div', {\n attributes: {\n id,\n 'role': 'progressbar',\n 'aria-valuemin': 0,\n 'aria-valuemax': 100,\n },\n });\n\n $.append(this.#progress, this.#progressBar);\n $.append(this.#container, this.#progress);\n\n const describedBy = [this.#describedBy, id]\n .filter(Boolean)\n .join(' ');\n\n $.setAttribute(this.node, { 'aria-describedby': describedBy });\n }\n}\n","import { initComponent } from '@fr0st/ui';\nimport PasswordStrength from './password-strength.js';\n\ninitComponent('passwordstrength', PasswordStrength);\n\nexport default PasswordStrength;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"frost-ui-passwordstrength.js","names":["$","BaseComponent","$","generateId"],"sources":["../src/strength.js","../src/password-strength.js","../src/index.js"],"sourcesContent":["import $ from '@fr0st/query';\n\nconst characterTypes = [\n { pattern: /\\p{Ll}/u, type: 'lower' },\n { pattern: /\\p{Lu}/u, type: 'upper' },\n { pattern: /\\p{N}/u, type: 'number' },\n];\n\nconst keyboardSequences = [\n '1234567890',\n 'qwertyuiop',\n 'asdfghjkl',\n 'zxcvbnm',\n].flatMap(\n (row) => [\n row,\n Array.from(row).reverse().join(''),\n ],\n);\n\nconst leetCharacters = {\n '$': 's',\n '0': 'o',\n '3': 'e',\n '4': 'a',\n '5': 's',\n '7': 't',\n '@': 'a',\n};\n\nconst lengthBands = [\n { end: 5, max: 19, min: 1, start: 1 },\n { end: 8, max: 39, min: 20, start: 6 },\n { end: 11, max: 59, min: 40, start: 9 },\n { end: 15, max: 79, min: 60, start: 12 },\n { end: 24, max: 100, min: 80, start: 16 },\n];\n\n/**\n * Gets the type of a password character.\n * @param {string} character The character.\n * @returns {string} The character type.\n */\nconst getCharacterType = (character) =>\n characterTypes.find(\n ({ pattern }) => pattern.test(character),\n )?.type || 'symbol';\n\n/**\n * Gets a length-based password strength.\n * @param {string[]} characters The password characters.\n * @returns {number} The password strength.\n */\nconst getLengthStrength = (characters) => {\n const band = lengthBands.find(\n ({ end }) => characters.length <= end,\n ) || lengthBands.at(-1);\n const score = Math.round(\n $._map(\n Math.min(characters.length, band.end),\n band.start,\n band.end,\n band.min,\n band.max,\n ),\n );\n const diversity = new Set(\n characters.map(getCharacterType),\n ).size;\n\n return Math.min(\n score +\n (diversity - 1) * 2,\n band.max,\n );\n};\n\n/**\n * Gets the smallest repeated pattern.\n * @param {string[]} characters The password characters.\n * @returns {string[]|null} The repeated pattern.\n */\nconst getRepeatedPattern = (characters) => {\n const maxLength = Math.floor(characters.length / 2);\n\n for (let length = 1; length <= maxLength; length++) {\n if (\n characters.length % length === 0 &&\n characters.every(\n (character, index) =>\n character === characters[index % length],\n )\n ) {\n return characters.slice(0, length);\n }\n }\n\n return null;\n};\n\n/**\n * Normalizes common password matching.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizePassword = (password) =>\n String(password)\n .normalize('NFKC')\n .toLowerCase();\n\n/**\n * Normalizes common leet substitutions.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizeLeet = (password) =>\n Array.from(\n password,\n (character) => leetCharacters[character] || character,\n ).join('');\n\n/**\n * Gets normalized variants used to match common passwords.\n * @param {string} password The normalized password.\n * @returns {Set<string>} The password variants.\n */\nconst getPasswordVariants = (password) => {\n // Preserve digits for decoration stripping while resolving symbol substitutions.\n const substituted = password.replace(/[^\\p{L}\\p{N}]/gu, normalizeLeet);\n const variants = [password, substituted].flatMap((value) => {\n const undecorated = value.replace(\n /^[^\\p{L}\\p{N}]+|[^\\p{L}\\p{N}]+$/gu,\n '',\n );\n\n return [\n undecorated,\n undecorated.replace(/^\\d+|\\d+$/g, ''),\n ];\n });\n\n return new Set([\n normalizeLeet(password),\n ...variants,\n ...variants.map(normalizeLeet),\n ]);\n};\n\n/**\n * Gets the score for a common password.\n * @param {string} password The normalized password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number|null} The common-password score.\n */\nconst getCommonPasswordStrength = (password, commonPasswords) => {\n const common = new Set(\n commonPasswords.map(normalizePassword),\n );\n\n if (common.has(password)) {\n return 0;\n }\n\n return common.isDisjointFrom(getPasswordVariants(password)) ? null : 5;\n};\n\n/**\n * Checks whether characters form an ascending or descending sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a sequence.\n */\nconst isSequence = (characters) => {\n const [first, second, third] = characters.map(\n (character) => character.toLowerCase(),\n );\n const type = getCharacterType(first);\n const direction = second.codePointAt(0) - first.codePointAt(0);\n\n return ['lower', 'number'].includes(type) &&\n getCharacterType(second) === type &&\n getCharacterType(third) === type &&\n Math.abs(direction) === 1 &&\n third.codePointAt(0) - second.codePointAt(0) === direction;\n};\n\n/**\n * Checks whether characters form a keyboard sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a keyboard sequence.\n */\nconst isKeyboardSequence = (characters) => {\n const sequence = characters.join('').toLowerCase();\n\n return keyboardSequences.some(\n (keyboardSequence) => keyboardSequence.includes(sequence),\n );\n};\n\n/**\n * Checks whether characters form a predictable pattern.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a predictable pattern.\n */\nconst isPredictable = (characters) =>\n new Set(characters).size === 1 ||\n isSequence(characters) ||\n isKeyboardSequence(characters);\n\n/**\n * Gets the number of characters in predictable patterns.\n * @param {string[]} characters The password characters.\n * @returns {number} The number of predictable characters.\n */\nconst getPredictableCount = (characters) => {\n const indices = new Set();\n\n for (let i = 0; i + 2 < characters.length; i++) {\n if (isPredictable(characters.slice(i, i + 3))) {\n indices.add(i);\n indices.add(i + 1);\n indices.add(i + 2);\n }\n }\n\n return indices.size;\n};\n\n/**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=[]] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\nexport const getStrength = (password, commonPasswords = []) => {\n const normalizedPassword = password.normalize('NFKC');\n const characters = Array.from(normalizedPassword);\n\n if (!characters.length) {\n return 0;\n }\n\n const commonStrength = getCommonPasswordStrength(\n normalizedPassword.toLowerCase(),\n commonPasswords,\n );\n\n if (commonStrength !== null) {\n return commonStrength;\n }\n\n const score = getLengthStrength(characters);\n const repeatedPattern = getRepeatedPattern(characters);\n\n if (repeatedPattern?.length === 1) {\n return 0;\n }\n\n if (repeatedPattern) {\n return Math.min(\n score,\n getLengthStrength(repeatedPattern),\n 15,\n );\n }\n\n const predictableCount = getPredictableCount(characters);\n\n return $._clamp(\n score - predictableCount,\n 0,\n predictableCount === characters.length ? 15 : 100,\n );\n};\n","import $ from '@fr0st/query';\nimport { BaseComponent, generateId } from '@fr0st/ui';\nimport { getStrength } from './strength.js';\n\n/**\n * @typedef {object} PasswordStrengthLevel\n * @property {string} class The CSS class applied to the progress bar.\n * @property {number} score The minimum score for the level.\n * @property {string} [text] The label displayed in the progress bar.\n */\n\n/**\n * @callback PasswordStrengthScorer\n * @param {string} password The password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number} The password strength, from 0 to 100.\n */\n\n/**\n * @typedef {object} PasswordStrengthOptions\n * @property {string[]} [commonPasswords] Passwords to score as very weak.\n * @property {string|null} [container=null] The selector for the progress container.\n * @property {PasswordStrengthLevel[]} [levels] The ordered password-strength levels.\n * @property {PasswordStrengthScorer} [scorer] Calculates the password strength.\n * @property {boolean} [striped=false] Whether to display a striped progress bar.\n */\n\n/**\n * Displays the strength of a password input as a progress bar.\n * @augments {BaseComponent<PasswordStrengthOptions>}\n */\nexport default class PasswordStrength extends BaseComponent {\n static classes = {\n progress: 'progress mt-2',\n progressBar: 'progress-bar',\n progressBarStriped: 'progress-bar-striped',\n };\n /** @type {PasswordStrengthOptions} */\n static defaults = {\n commonPasswords: [\n '123456',\n '12345678',\n '123456789',\n '1234567890',\n '000000',\n '111111',\n 'abc123',\n 'admin',\n 'dragon',\n 'iloveyou',\n 'letmein',\n 'monkey',\n 'password',\n 'password1',\n 'password123',\n 'qwerty',\n 'qwerty123',\n 'welcome',\n ],\n levels: [\n {\n score: 0,\n class: 'text-bg-danger',\n text: 'Very Weak',\n },\n {\n score: 20,\n class: 'text-bg-danger',\n text: 'Weak',\n },\n {\n score: 40,\n class: 'text-bg-warning',\n text: 'Normal',\n },\n {\n score: 60,\n class: 'text-bg-success',\n text: 'Strong',\n },\n {\n score: 80,\n class: 'text-bg-success',\n text: 'Very Strong',\n },\n ],\n container: null,\n scorer: getStrength,\n striped: false,\n };\n\n #container = null;\n #describedBy;\n #form = null;\n #progress = null;\n #progressBar = null;\n\n /**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=this.defaults.commonPasswords] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\n static getStrength(password, commonPasswords = this.defaults.commonPasswords) {\n return getStrength(password, commonPasswords);\n }\n\n /**\n * Creates a PasswordStrength.\n * @param {HTMLInputElement} node The input node.\n * @param {PasswordStrengthOptions} [options] The PasswordStrength options.\n * @throws {TypeError} When the node is not an input element.\n */\n constructor(node, options) {\n if (!$._isElement(node) || !$.is(node, 'input')) {\n throw new TypeError('PasswordStrength must be created on an input element.');\n }\n\n super(node, options);\n\n try {\n this.#describedBy = $.getAttribute(this.node, 'aria-describedby');\n\n this.#form = $.getProperty(this.node, 'form');\n\n if (this.options.container) {\n this.#container = $.findOne(this.options.container);\n } else {\n this.#container = $.closest(this.node, ':not(.form-input):not(.input-group)').shift();\n }\n\n this.#render();\n this.#refresh();\n this.#events();\n } catch (error) {\n this.dispose();\n throw error;\n }\n }\n\n /** @inheritdoc */\n dispose() {\n if (!this.node) {\n return;\n }\n\n $.remove(this.#progress);\n\n if (this.#describedBy === null) {\n $.removeAttribute(this.node, 'aria-describedby');\n } else {\n $.setAttribute(this.node, { 'aria-describedby': this.#describedBy });\n }\n\n this.#container = null;\n this.#form = null;\n this.#progress = null;\n this.#progressBar = null;\n\n super.dispose();\n }\n\n /**\n * Gets the password strength.\n * @returns {number} The password strength, from 0 to 100.\n */\n getStrength() {\n const value = $.getValue(this.node);\n const strength = Number(this.options.scorer(\n value,\n this.options.commonPasswords,\n ));\n\n return Number.isFinite(strength) ?\n $._clamp(strength, 0, 100) :\n 0;\n }\n\n /**\n * Attaches events for the PasswordStrength.\n */\n #events() {\n if (this.#form) {\n this.listen(this.#form, 'reset.ui.passwordstrength', (event) => {\n setTimeout(() => {\n if (this.node && !event.defaultPrevented) {\n this.#refresh();\n }\n }, 0);\n });\n }\n\n this.listen(this.node, 'input.ui.passwordstrength', () => {\n this.#refresh();\n });\n }\n\n /**\n * Refreshes the password strength.\n */\n #refresh() {\n const strength = this.getStrength();\n\n const nextLevel = this.options.levels.findLast(\n (level) => strength >= level.score,\n );\n\n $.setStyle(this.#progressBar, { width: `${strength}%` });\n $.setAttribute(this.#progressBar, {\n 'class': this.constructor.classes.progressBar,\n 'aria-valuenow': strength,\n });\n\n $.addClass(this.#progressBar, nextLevel.class);\n\n if (this.options.striped) {\n $.addClass(this.#progressBar, this.constructor.classes.progressBarStriped);\n }\n\n $.setText(this.#progressBar, nextLevel.text ?? '');\n }\n\n /**\n * Renders the password strength element.\n */\n #render() {\n this.#progress = $.create('div', {\n class: this.constructor.classes.progress,\n });\n\n const id = generateId('password-strength');\n\n this.#progressBar = $.create('div', {\n attributes: {\n id,\n 'role': 'progressbar',\n 'aria-valuemin': 0,\n 'aria-valuemax': 100,\n },\n });\n\n $.append(this.#progress, this.#progressBar);\n $.append(this.#container, this.#progress);\n\n const describedBy = [this.#describedBy, id]\n .filter(Boolean)\n .join(' ');\n\n $.setAttribute(this.node, { 'aria-describedby': describedBy });\n }\n}\n","import { initComponent } from '@fr0st/ui';\nimport PasswordStrength from './password-strength.js';\n\ninitComponent('passwordstrength', PasswordStrength);\n\nexport default PasswordStrength;\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("@fr0st/ui"),require("@fr0st/query")):typeof define==`function`&&define.amd?define([`exports`,`@fr0st/ui`,`@fr0st/query`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.UI=e.UI||{},e.UI,e.fQuery))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r=Object.create,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,s=Object.getPrototypeOf,c=Object.prototype.hasOwnProperty,l=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var s=o(t),l=0,u=s.length,d;l<u;l++)d=s[l],!c.call(e,d)&&d!==n&&i(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(r=a(t,d))||r.enumerable});return e};n=((e,t,n)=>(n=e==null?{}:r(s(e)),l(t||!e||!e.__esModule||!c.call(e,`default`)?i(n,`default`,{value:e,enumerable:!0}):n,e)))(n,1);var u=[{pattern:/\p{Ll}/u,type:`lower`},{pattern:/\p{Lu}/u,type:`upper`},{pattern:/\p{N}/u,type:`number`}],d=[`1234567890`,`qwertyuiop`,`asdfghjkl`,`zxcvbnm`].flatMap(e=>[e,Array.from(e).reverse().join(``)]),f={$:`s`,0:`o`,3:`e`,4:`a`,5:`s`,7:`t`,"@":`a`},p=[{end:5,max:19,min:1,start:1},{end:8,max:39,min:20,start:6},{end:11,max:59,min:40,start:9},{end:15,max:79,min:60,start:12},{end:24,max:100,min:80,start:16}],m=e=>u.find(({pattern:t})=>t.test(e))?.type||`symbol`,h=e=>{let t=p.find(({end:t})=>e.length<=t)||p.at(-1),r=Math.round(n.default._map(Math.min(e.length,t.end),t.start,t.end,t.min,t.max)),i=new Set(e.map(m)).size;return Math.min(r+(i-1)*2,t.max)},g=e=>{let t=Math.floor(e.length/2);for(let n=1;n<=t;n++)if(e.length%n===0&&e.every((t,r)=>t===e[r%n]))return e.slice(0,n);return null},_=e=>String(e).normalize(`NFKC`).toLowerCase(),v=e=>Array.from(e,e=>f[e]||e).join(``),y=e=>{let t=[e,e.replace(/[^\p{L}\p{N}]/gu,v)].flatMap(e=>{let t=e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,``);return[t,t.replace(/^\d+|\d+$/g,``)]});return/* @__PURE__ */ new Set([v(e),...t,...t.map(v)])},b=(e,t)=>{let n=new Set(t.map(_));return n.has(e)?0:n.isDisjointFrom(y(e))?null:5},x=e=>{let[t,n,r]=e.map(e=>e.toLowerCase()),i=m(t),a=n.codePointAt(0)-t.codePointAt(0);return[`lower`,`number`].includes(i)&&m(n)===i&&m(r)===i&&Math.abs(a)===1&&r.codePointAt(0)-n.codePointAt(0)===a},S=e=>{let t=e.join(``).toLowerCase();return d.some(e=>e.includes(t))},C=e=>new Set(e).size===1||x(e)||S(e),w=e=>{let t=/* @__PURE__ */ new Set;for(let n=0;n+2<e.length;n++)C(e.slice(n,n+3))&&(t.add(n),t.add(n+1),t.add(n+2));return t.size},T=(e,t=[])=>{let r=e.normalize(`NFKC`),i=Array.from(r);if(!i.length)return 0;let a=b(r.toLowerCase(),t);if(a!==null)return a;let o=h(i),s=g(i);if(s?.length===1)return 0;if(s)return Math.min(o,h(s),15);let c=w(i);return n.default._clamp(o-c,0,c===i.length?15:100)},E=class extends t.BaseComponent{static classes={progress:`progress mt-2`,progressBar:`progress-bar`,progressBarStriped:`progress-bar-striped`};static defaults={commonPasswords:[`123456`,`12345678`,`123456789`,`1234567890`,`000000`,`111111`,`abc123`,`admin`,`dragon`,`iloveyou`,`letmein`,`monkey`,`password`,`password1`,`password123`,`qwerty`,`qwerty123`,`welcome`],levels:[{score:0,class:`text-bg-danger`,text:`Very Weak`},{score:20,class:`text-bg-danger`,text:`Weak`},{score:40,class:`text-bg-warning`,text:`Normal`},{score:60,class:`text-bg-success`,text:`Strong`},{score:80,class:`text-bg-success`,text:`Very Strong`}],container:null,scorer:T,striped:!1};#e;#t;#n;#r;#i
|
|
1
|
+
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("@fr0st/ui"),require("@fr0st/query")):typeof define==`function`&&define.amd?define([`exports`,`@fr0st/ui`,`@fr0st/query`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.UI=e.UI||{},e.UI,e.fQuery))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r=Object.create,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,s=Object.getPrototypeOf,c=Object.prototype.hasOwnProperty,l=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var s=o(t),l=0,u=s.length,d;l<u;l++)d=s[l],!c.call(e,d)&&d!==n&&i(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(r=a(t,d))||r.enumerable});return e};n=((e,t,n)=>(n=e==null?{}:r(s(e)),l(t||!e||!e.__esModule||!c.call(e,`default`)?i(n,`default`,{value:e,enumerable:!0}):n,e)))(n,1);var u=[{pattern:/\p{Ll}/u,type:`lower`},{pattern:/\p{Lu}/u,type:`upper`},{pattern:/\p{N}/u,type:`number`}],d=[`1234567890`,`qwertyuiop`,`asdfghjkl`,`zxcvbnm`].flatMap(e=>[e,Array.from(e).reverse().join(``)]),f={$:`s`,0:`o`,3:`e`,4:`a`,5:`s`,7:`t`,"@":`a`},p=[{end:5,max:19,min:1,start:1},{end:8,max:39,min:20,start:6},{end:11,max:59,min:40,start:9},{end:15,max:79,min:60,start:12},{end:24,max:100,min:80,start:16}],m=e=>u.find(({pattern:t})=>t.test(e))?.type||`symbol`,h=e=>{let t=p.find(({end:t})=>e.length<=t)||p.at(-1),r=Math.round(n.default._map(Math.min(e.length,t.end),t.start,t.end,t.min,t.max)),i=new Set(e.map(m)).size;return Math.min(r+(i-1)*2,t.max)},g=e=>{let t=Math.floor(e.length/2);for(let n=1;n<=t;n++)if(e.length%n===0&&e.every((t,r)=>t===e[r%n]))return e.slice(0,n);return null},_=e=>String(e).normalize(`NFKC`).toLowerCase(),v=e=>Array.from(e,e=>f[e]||e).join(``),y=e=>{let t=[e,e.replace(/[^\p{L}\p{N}]/gu,v)].flatMap(e=>{let t=e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,``);return[t,t.replace(/^\d+|\d+$/g,``)]});return/* @__PURE__ */ new Set([v(e),...t,...t.map(v)])},b=(e,t)=>{let n=new Set(t.map(_));return n.has(e)?0:n.isDisjointFrom(y(e))?null:5},x=e=>{let[t,n,r]=e.map(e=>e.toLowerCase()),i=m(t),a=n.codePointAt(0)-t.codePointAt(0);return[`lower`,`number`].includes(i)&&m(n)===i&&m(r)===i&&Math.abs(a)===1&&r.codePointAt(0)-n.codePointAt(0)===a},S=e=>{let t=e.join(``).toLowerCase();return d.some(e=>e.includes(t))},C=e=>new Set(e).size===1||x(e)||S(e),w=e=>{let t=/* @__PURE__ */ new Set;for(let n=0;n+2<e.length;n++)C(e.slice(n,n+3))&&(t.add(n),t.add(n+1),t.add(n+2));return t.size},T=(e,t=[])=>{let r=e.normalize(`NFKC`),i=Array.from(r);if(!i.length)return 0;let a=b(r.toLowerCase(),t);if(a!==null)return a;let o=h(i),s=g(i);if(s?.length===1)return 0;if(s)return Math.min(o,h(s),15);let c=w(i);return n.default._clamp(o-c,0,c===i.length?15:100)},E=class extends t.BaseComponent{static classes={progress:`progress mt-2`,progressBar:`progress-bar`,progressBarStriped:`progress-bar-striped`};static defaults={commonPasswords:[`123456`,`12345678`,`123456789`,`1234567890`,`000000`,`111111`,`abc123`,`admin`,`dragon`,`iloveyou`,`letmein`,`monkey`,`password`,`password1`,`password123`,`qwerty`,`qwerty123`,`welcome`],levels:[{score:0,class:`text-bg-danger`,text:`Very Weak`},{score:20,class:`text-bg-danger`,text:`Weak`},{score:40,class:`text-bg-warning`,text:`Normal`},{score:60,class:`text-bg-success`,text:`Strong`},{score:80,class:`text-bg-success`,text:`Very Strong`}],container:null,scorer:T,striped:!1};#e=null;#t;#n=null;#r=null;#i=null;static getStrength(e,t=this.defaults.commonPasswords){return T(e,t)}constructor(e,t){if(!n.default._isElement(e)||!n.default.is(e,`input`))throw TypeError(`PasswordStrength must be created on an input element.`);super(e,t);try{this.#t=n.default.getAttribute(this.node,`aria-describedby`),this.#n=n.default.getProperty(this.node,`form`),this.#e=this.options.container?n.default.findOne(this.options.container):n.default.closest(this.node,`:not(.form-input):not(.input-group)`).shift(),this.#s(),this.#o(),this.#a()}catch(e){throw this.dispose(),e}}dispose(){this.node&&(n.default.remove(this.#r),this.#t===null?n.default.removeAttribute(this.node,`aria-describedby`):n.default.setAttribute(this.node,{"aria-describedby":this.#t}),this.#e=null,this.#n=null,this.#r=null,this.#i=null,super.dispose())}getStrength(){let e=n.default.getValue(this.node),t=Number(this.options.scorer(e,this.options.commonPasswords));return Number.isFinite(t)?n.default._clamp(t,0,100):0}#a(){this.#n&&this.listen(this.#n,`reset.ui.passwordstrength`,e=>{setTimeout(()=>{this.node&&!e.defaultPrevented&&this.#o()},0)}),this.listen(this.node,`input.ui.passwordstrength`,()=>{this.#o()})}#o(){let e=this.getStrength(),t=this.options.levels.findLast(t=>e>=t.score);n.default.setStyle(this.#i,{width:`${e}%`}),n.default.setAttribute(this.#i,{class:this.constructor.classes.progressBar,"aria-valuenow":e}),n.default.addClass(this.#i,t.class),this.options.striped&&n.default.addClass(this.#i,this.constructor.classes.progressBarStriped),n.default.setText(this.#i,t.text??``)}#s(){this.#r=n.default.create(`div`,{class:this.constructor.classes.progress});let e=(0,t.generateId)(`password-strength`);this.#i=n.default.create(`div`,{attributes:{id:e,role:`progressbar`,"aria-valuemin":0,"aria-valuemax":100}}),n.default.append(this.#r,this.#i),n.default.append(this.#e,this.#r);let r=[this.#t,e].filter(Boolean).join(` `);n.default.setAttribute(this.node,{"aria-describedby":r})}};(0,t.initComponent)(`passwordstrength`,E),e.PasswordStrength=E});
|
|
2
2
|
//# sourceMappingURL=frost-ui-passwordstrength.min.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frost-ui-passwordstrength.min.js","names":["$","BaseComponent","$","getDataset","generateId","factory","global","exports","__create","__defProp","__getOwnPropDesc","__getOwnPropNames","__getProtoOf","__hasOwnProp","__copyProps","from","keys","i","n","key","to","except","k","desc","mod","isNodeMode","target","_fr0st_query","characterTypes","keyboardSequences","row","leetCharacters","\"$\"","lengthBands","getCharacterType","pattern","character","getLengthStrength","band","characters","end","score","diversity","getRepeatedPattern","maxLength","length","index","normalizePassword","password","normalizeLeet","getPasswordVariants","variants","undecorated","value","getCommonPasswordStrength","common","commonPasswords","isSequence","first","second","third","type","direction","isKeyboardSequence","sequence","keyboardSequence","isPredictable","getPredictableCount","indices","getStrength","normalizedPassword","commonStrength","repeatedPattern","predictableCount","PasswordStrength","_fr0st_ui","node","options","#describedBy","overrides","#form","#container","#render","#refresh","#events","error","#progress","#resetHandler","#progressBar","strength","event","nextLevel","level","\"class\"","id","\"role\"","describedBy","src_default"],"sources":["../src/strength.js","../src/password-strength.js","../src/index.js"],"sourcesContent":["import $ from '@fr0st/query';\n\nconst characterTypes = [\n { pattern: /\\p{Ll}/u, type: 'lower' },\n { pattern: /\\p{Lu}/u, type: 'upper' },\n { pattern: /\\p{N}/u, type: 'number' },\n];\n\nconst keyboardSequences = [\n '1234567890',\n 'qwertyuiop',\n 'asdfghjkl',\n 'zxcvbnm',\n].flatMap(\n (row) => [\n row,\n Array.from(row).reverse().join(''),\n ],\n);\n\nconst leetCharacters = {\n '$': 's',\n '0': 'o',\n '3': 'e',\n '4': 'a',\n '5': 's',\n '7': 't',\n '@': 'a',\n};\n\nconst lengthBands = [\n { end: 5, max: 19, min: 1, start: 1 },\n { end: 8, max: 39, min: 20, start: 6 },\n { end: 11, max: 59, min: 40, start: 9 },\n { end: 15, max: 79, min: 60, start: 12 },\n { end: 24, max: 100, min: 80, start: 16 },\n];\n\n/**\n * Gets the type of a password character.\n * @param {string} character The character.\n * @returns {string} The character type.\n */\nconst getCharacterType = (character) =>\n characterTypes.find(\n ({ pattern }) => pattern.test(character),\n )?.type || 'symbol';\n\n/**\n * Gets a length-based password strength.\n * @param {string[]} characters The password characters.\n * @returns {number} The password strength.\n */\nconst getLengthStrength = (characters) => {\n const band = lengthBands.find(\n ({ end }) => characters.length <= end,\n ) || lengthBands.at(-1);\n const score = Math.round(\n $._map(\n Math.min(characters.length, band.end),\n band.start,\n band.end,\n band.min,\n band.max,\n ),\n );\n const diversity = new Set(\n characters.map(getCharacterType),\n ).size;\n\n return Math.min(\n score +\n (diversity - 1) * 2,\n band.max,\n );\n};\n\n/**\n * Gets the smallest repeated pattern.\n * @param {string[]} characters The password characters.\n * @returns {string[]|null} The repeated pattern.\n */\nconst getRepeatedPattern = (characters) => {\n const maxLength = Math.floor(characters.length / 2);\n\n for (let length = 1; length <= maxLength; length++) {\n if (\n characters.length % length === 0 &&\n characters.every(\n (character, index) =>\n character === characters[index % length],\n )\n ) {\n return characters.slice(0, length);\n }\n }\n\n return null;\n};\n\n/**\n * Normalizes common password matching.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizePassword = (password) =>\n String(password)\n .normalize('NFKC')\n .toLowerCase();\n\n/**\n * Normalizes common leet substitutions.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizeLeet = (password) =>\n Array.from(\n password,\n (character) => leetCharacters[character] || character,\n ).join('');\n\n/**\n * Gets normalized variants used to match common passwords.\n * @param {string} password The normalized password.\n * @returns {Set<string>} The password variants.\n */\nconst getPasswordVariants = (password) => {\n // Preserve digits for decoration stripping while resolving symbol substitutions.\n const substituted = password.replace(/[^\\p{L}\\p{N}]/gu, normalizeLeet);\n const variants = [password, substituted].flatMap((value) => {\n const undecorated = value.replace(\n /^[^\\p{L}\\p{N}]+|[^\\p{L}\\p{N}]+$/gu,\n '',\n );\n\n return [\n undecorated,\n undecorated.replace(/^\\d+|\\d+$/g, ''),\n ];\n });\n\n return new Set([\n normalizeLeet(password),\n ...variants,\n ...variants.map(normalizeLeet),\n ]);\n};\n\n/**\n * Gets the score for a common password.\n * @param {string} password The normalized password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number|null} The common-password score.\n */\nconst getCommonPasswordStrength = (password, commonPasswords) => {\n const common = new Set(\n commonPasswords.map(normalizePassword),\n );\n\n if (common.has(password)) {\n return 0;\n }\n\n return common.isDisjointFrom(getPasswordVariants(password)) ? null : 5;\n};\n\n/**\n * Checks whether characters form an ascending or descending sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a sequence.\n */\nconst isSequence = (characters) => {\n const [first, second, third] = characters.map(\n (character) => character.toLowerCase(),\n );\n const type = getCharacterType(first);\n const direction = second.codePointAt(0) - first.codePointAt(0);\n\n return ['lower', 'number'].includes(type) &&\n getCharacterType(second) === type &&\n getCharacterType(third) === type &&\n Math.abs(direction) === 1 &&\n third.codePointAt(0) - second.codePointAt(0) === direction;\n};\n\n/**\n * Checks whether characters form a keyboard sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a keyboard sequence.\n */\nconst isKeyboardSequence = (characters) => {\n const sequence = characters.join('').toLowerCase();\n\n return keyboardSequences.some(\n (keyboardSequence) => keyboardSequence.includes(sequence),\n );\n};\n\n/**\n * Checks whether characters form a predictable pattern.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a predictable pattern.\n */\nconst isPredictable = (characters) =>\n new Set(characters).size === 1 ||\n isSequence(characters) ||\n isKeyboardSequence(characters);\n\n/**\n * Gets the number of characters in predictable patterns.\n * @param {string[]} characters The password characters.\n * @returns {number} The number of predictable characters.\n */\nconst getPredictableCount = (characters) => {\n const indices = new Set();\n\n for (let i = 0; i + 2 < characters.length; i++) {\n if (isPredictable(characters.slice(i, i + 3))) {\n indices.add(i);\n indices.add(i + 1);\n indices.add(i + 2);\n }\n }\n\n return indices.size;\n};\n\n/**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=[]] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\nexport const getStrength = (password, commonPasswords = []) => {\n const normalizedPassword = password.normalize('NFKC');\n const characters = Array.from(normalizedPassword);\n\n if (!characters.length) {\n return 0;\n }\n\n const commonStrength = getCommonPasswordStrength(\n normalizedPassword.toLowerCase(),\n commonPasswords,\n );\n\n if (commonStrength !== null) {\n return commonStrength;\n }\n\n const score = getLengthStrength(characters);\n const repeatedPattern = getRepeatedPattern(characters);\n\n if (repeatedPattern?.length === 1) {\n return 0;\n }\n\n if (repeatedPattern) {\n return Math.min(\n score,\n getLengthStrength(repeatedPattern),\n 15,\n );\n }\n\n const predictableCount = getPredictableCount(characters);\n\n return $._clamp(\n score - predictableCount,\n 0,\n predictableCount === characters.length ? 15 : 100,\n );\n};\n","import $ from '@fr0st/query';\nimport { BaseComponent, generateId, getDataset } from '@fr0st/ui';\nimport { getStrength } from './strength.js';\n\n/**\n * @typedef {object} PasswordStrengthLevel\n * @property {string} class The CSS class applied to the progress bar.\n * @property {number} score The minimum score for the level.\n * @property {string} [text] The label displayed in the progress bar.\n */\n\n/**\n * @callback PasswordStrengthScorer\n * @param {string} password The password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number} The password strength, from 0 to 100.\n */\n\n/**\n * @typedef {object} PasswordStrengthOptions\n * @property {string[]} [commonPasswords] Passwords to score as very weak.\n * @property {string|null} [container=null] The selector for the progress container.\n * @property {PasswordStrengthLevel[]} [levels] The ordered password-strength levels.\n * @property {PasswordStrengthScorer} [scorer] Calculates the password strength.\n * @property {boolean} [striped=false] Whether to display a striped progress bar.\n */\n\n/**\n * Displays the strength of a password input as a progress bar.\n * @augments {BaseComponent<PasswordStrengthOptions>}\n */\nexport default class PasswordStrength extends BaseComponent {\n static classes = {\n progress: 'progress mt-2',\n progressBar: 'progress-bar',\n progressBarStriped: 'progress-bar-striped',\n };\n /** @type {PasswordStrengthOptions} */\n static defaults = {\n commonPasswords: [\n '123456',\n '12345678',\n '123456789',\n '1234567890',\n '000000',\n '111111',\n 'abc123',\n 'admin',\n 'dragon',\n 'iloveyou',\n 'letmein',\n 'monkey',\n 'password',\n 'password1',\n 'password123',\n 'qwerty',\n 'qwerty123',\n 'welcome',\n ],\n levels: [\n {\n score: 0,\n class: 'text-bg-danger',\n text: 'Very Weak',\n },\n {\n score: 20,\n class: 'text-bg-danger',\n text: 'Weak',\n },\n {\n score: 40,\n class: 'text-bg-warning',\n text: 'Normal',\n },\n {\n score: 60,\n class: 'text-bg-success',\n text: 'Strong',\n },\n {\n score: 80,\n class: 'text-bg-success',\n text: 'Very Strong',\n },\n ],\n container: null,\n scorer: getStrength,\n striped: false,\n };\n\n #container;\n #describedBy;\n #form;\n #progress;\n #progressBar;\n #resetHandler;\n\n /**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=this.defaults.commonPasswords] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\n static getStrength(password, commonPasswords = this.defaults.commonPasswords) {\n return getStrength(password, commonPasswords);\n }\n\n /**\n * Creates a PasswordStrength.\n * @param {HTMLInputElement} node The input node.\n * @param {PasswordStrengthOptions} [options] The PasswordStrength options.\n * @throws {TypeError} When the node is not an input element.\n */\n constructor(node, options) {\n if (!$.is(node, 'input')) {\n throw new TypeError('PasswordStrength must be created on an input element.');\n }\n\n super(node, options);\n\n try {\n this.#describedBy = $.getAttribute(this.node, 'aria-describedby');\n\n const overrides = { ...getDataset(node), ...options };\n\n // Replace supplied arrays instead of merging them with default entries.\n for (const key of ['levels', 'commonPasswords']) {\n if (Array.isArray(overrides[key])) {\n this.options[key].length = 0;\n $._extend(this.options[key], overrides[key]);\n }\n }\n\n this.#form = $.getProperty(this.node, 'form');\n\n if (this.options.container) {\n this.#container = $.findOne(this.options.container);\n } else {\n this.#container = $.closest(this.node, ':not(.form-input):not(.input-group)').shift();\n }\n\n this.#render();\n this.#refresh();\n this.#events();\n } catch (error) {\n this.dispose();\n throw error;\n }\n }\n\n /** @inheritdoc */\n dispose() {\n if (!this.node) {\n return;\n }\n\n $.remove(this.#progress);\n $.removeEvent(this.node, 'input.ui.passwordstrength');\n\n if (this.#form && this.#resetHandler) {\n $.removeEvent(this.#form, 'reset.ui.passwordstrength', this.#resetHandler);\n }\n\n if (this.#describedBy === null) {\n $.removeAttribute(this.node, 'aria-describedby');\n } else {\n $.setAttribute(this.node, { 'aria-describedby': this.#describedBy });\n }\n\n this.#container = null;\n this.#form = null;\n this.#progress = null;\n this.#progressBar = null;\n this.#resetHandler = null;\n\n super.dispose();\n }\n\n /**\n * Gets the password strength.\n * @returns {number} The password strength, from 0 to 100.\n */\n getStrength() {\n const value = $.getValue(this.node);\n const strength = Number(this.options.scorer(\n value,\n this.options.commonPasswords,\n ));\n\n return Number.isFinite(strength) ?\n $._clamp(strength, 0, 100) :\n 0;\n }\n\n /**\n * Attaches events for the PasswordStrength.\n */\n #events() {\n if (this.#form) {\n this.#resetHandler = (event) => {\n setTimeout(() => {\n if (this.node && !event.defaultPrevented) {\n this.#refresh();\n }\n }, 0);\n };\n\n $.addEvent(this.#form, 'reset.ui.passwordstrength', this.#resetHandler);\n }\n\n $.addEvent(this.node, 'input.ui.passwordstrength', () => {\n this.#refresh();\n });\n }\n\n /**\n * Refreshes the password strength.\n */\n #refresh() {\n const strength = this.getStrength();\n\n const nextLevel = this.options.levels.findLast(\n (level) => strength >= level.score,\n );\n\n $.setStyle(this.#progressBar, { width: `${strength}%` });\n $.setAttribute(this.#progressBar, {\n 'class': this.constructor.classes.progressBar,\n 'aria-valuenow': strength,\n });\n\n $.addClass(this.#progressBar, nextLevel.class);\n\n if (this.options.striped) {\n $.addClass(this.#progressBar, this.constructor.classes.progressBarStriped);\n }\n\n $.setText(this.#progressBar, nextLevel.text ?? '');\n }\n\n /**\n * Renders the password strength element.\n */\n #render() {\n this.#progress = $.create('div', {\n class: this.constructor.classes.progress,\n });\n\n const id = generateId('password-strength');\n\n this.#progressBar = $.create('div', {\n attributes: {\n id,\n 'role': 'progressbar',\n 'aria-valuemin': 0,\n 'aria-valuemax': 100,\n },\n });\n\n $.append(this.#progress, this.#progressBar);\n $.append(this.#container, this.#progress);\n\n const describedBy = [this.#describedBy, id]\n .filter(Boolean)\n .join(' ');\n\n $.setAttribute(this.node, { 'aria-describedby': describedBy });\n }\n}\n","import { initComponent } from '@fr0st/ui';\nimport PasswordStrength from './password-strength.js';\n\ninitComponent('passwordstrength', PasswordStrength);\n\nexport default PasswordStrength;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"frost-ui-passwordstrength.min.js","names":["$","BaseComponent","$","generateId","factory","global","exports","__create","__defProp","__getOwnPropDesc","__getOwnPropNames","__getProtoOf","__hasOwnProp","__copyProps","from","keys","i","n","key","to","except","k","desc","mod","isNodeMode","target","_fr0st_query","characterTypes","keyboardSequences","row","leetCharacters","\"$\"","lengthBands","getCharacterType","pattern","character","getLengthStrength","band","characters","end","score","diversity","getRepeatedPattern","maxLength","length","index","normalizePassword","password","normalizeLeet","getPasswordVariants","variants","undecorated","value","getCommonPasswordStrength","common","commonPasswords","isSequence","first","second","third","type","direction","isKeyboardSequence","sequence","keyboardSequence","isPredictable","getPredictableCount","indices","getStrength","normalizedPassword","commonStrength","repeatedPattern","predictableCount","PasswordStrength","_fr0st_ui","node","options","#describedBy","#form","#container","#render","#refresh","#events","error","#progress","#progressBar","strength","event","nextLevel","level","\"class\"","id","\"role\"","describedBy","src_default"],"sources":["../src/strength.js","../src/password-strength.js","../src/index.js"],"sourcesContent":["import $ from '@fr0st/query';\n\nconst characterTypes = [\n { pattern: /\\p{Ll}/u, type: 'lower' },\n { pattern: /\\p{Lu}/u, type: 'upper' },\n { pattern: /\\p{N}/u, type: 'number' },\n];\n\nconst keyboardSequences = [\n '1234567890',\n 'qwertyuiop',\n 'asdfghjkl',\n 'zxcvbnm',\n].flatMap(\n (row) => [\n row,\n Array.from(row).reverse().join(''),\n ],\n);\n\nconst leetCharacters = {\n '$': 's',\n '0': 'o',\n '3': 'e',\n '4': 'a',\n '5': 's',\n '7': 't',\n '@': 'a',\n};\n\nconst lengthBands = [\n { end: 5, max: 19, min: 1, start: 1 },\n { end: 8, max: 39, min: 20, start: 6 },\n { end: 11, max: 59, min: 40, start: 9 },\n { end: 15, max: 79, min: 60, start: 12 },\n { end: 24, max: 100, min: 80, start: 16 },\n];\n\n/**\n * Gets the type of a password character.\n * @param {string} character The character.\n * @returns {string} The character type.\n */\nconst getCharacterType = (character) =>\n characterTypes.find(\n ({ pattern }) => pattern.test(character),\n )?.type || 'symbol';\n\n/**\n * Gets a length-based password strength.\n * @param {string[]} characters The password characters.\n * @returns {number} The password strength.\n */\nconst getLengthStrength = (characters) => {\n const band = lengthBands.find(\n ({ end }) => characters.length <= end,\n ) || lengthBands.at(-1);\n const score = Math.round(\n $._map(\n Math.min(characters.length, band.end),\n band.start,\n band.end,\n band.min,\n band.max,\n ),\n );\n const diversity = new Set(\n characters.map(getCharacterType),\n ).size;\n\n return Math.min(\n score +\n (diversity - 1) * 2,\n band.max,\n );\n};\n\n/**\n * Gets the smallest repeated pattern.\n * @param {string[]} characters The password characters.\n * @returns {string[]|null} The repeated pattern.\n */\nconst getRepeatedPattern = (characters) => {\n const maxLength = Math.floor(characters.length / 2);\n\n for (let length = 1; length <= maxLength; length++) {\n if (\n characters.length % length === 0 &&\n characters.every(\n (character, index) =>\n character === characters[index % length],\n )\n ) {\n return characters.slice(0, length);\n }\n }\n\n return null;\n};\n\n/**\n * Normalizes common password matching.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizePassword = (password) =>\n String(password)\n .normalize('NFKC')\n .toLowerCase();\n\n/**\n * Normalizes common leet substitutions.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizeLeet = (password) =>\n Array.from(\n password,\n (character) => leetCharacters[character] || character,\n ).join('');\n\n/**\n * Gets normalized variants used to match common passwords.\n * @param {string} password The normalized password.\n * @returns {Set<string>} The password variants.\n */\nconst getPasswordVariants = (password) => {\n // Preserve digits for decoration stripping while resolving symbol substitutions.\n const substituted = password.replace(/[^\\p{L}\\p{N}]/gu, normalizeLeet);\n const variants = [password, substituted].flatMap((value) => {\n const undecorated = value.replace(\n /^[^\\p{L}\\p{N}]+|[^\\p{L}\\p{N}]+$/gu,\n '',\n );\n\n return [\n undecorated,\n undecorated.replace(/^\\d+|\\d+$/g, ''),\n ];\n });\n\n return new Set([\n normalizeLeet(password),\n ...variants,\n ...variants.map(normalizeLeet),\n ]);\n};\n\n/**\n * Gets the score for a common password.\n * @param {string} password The normalized password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number|null} The common-password score.\n */\nconst getCommonPasswordStrength = (password, commonPasswords) => {\n const common = new Set(\n commonPasswords.map(normalizePassword),\n );\n\n if (common.has(password)) {\n return 0;\n }\n\n return common.isDisjointFrom(getPasswordVariants(password)) ? null : 5;\n};\n\n/**\n * Checks whether characters form an ascending or descending sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a sequence.\n */\nconst isSequence = (characters) => {\n const [first, second, third] = characters.map(\n (character) => character.toLowerCase(),\n );\n const type = getCharacterType(first);\n const direction = second.codePointAt(0) - first.codePointAt(0);\n\n return ['lower', 'number'].includes(type) &&\n getCharacterType(second) === type &&\n getCharacterType(third) === type &&\n Math.abs(direction) === 1 &&\n third.codePointAt(0) - second.codePointAt(0) === direction;\n};\n\n/**\n * Checks whether characters form a keyboard sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a keyboard sequence.\n */\nconst isKeyboardSequence = (characters) => {\n const sequence = characters.join('').toLowerCase();\n\n return keyboardSequences.some(\n (keyboardSequence) => keyboardSequence.includes(sequence),\n );\n};\n\n/**\n * Checks whether characters form a predictable pattern.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a predictable pattern.\n */\nconst isPredictable = (characters) =>\n new Set(characters).size === 1 ||\n isSequence(characters) ||\n isKeyboardSequence(characters);\n\n/**\n * Gets the number of characters in predictable patterns.\n * @param {string[]} characters The password characters.\n * @returns {number} The number of predictable characters.\n */\nconst getPredictableCount = (characters) => {\n const indices = new Set();\n\n for (let i = 0; i + 2 < characters.length; i++) {\n if (isPredictable(characters.slice(i, i + 3))) {\n indices.add(i);\n indices.add(i + 1);\n indices.add(i + 2);\n }\n }\n\n return indices.size;\n};\n\n/**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=[]] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\nexport const getStrength = (password, commonPasswords = []) => {\n const normalizedPassword = password.normalize('NFKC');\n const characters = Array.from(normalizedPassword);\n\n if (!characters.length) {\n return 0;\n }\n\n const commonStrength = getCommonPasswordStrength(\n normalizedPassword.toLowerCase(),\n commonPasswords,\n );\n\n if (commonStrength !== null) {\n return commonStrength;\n }\n\n const score = getLengthStrength(characters);\n const repeatedPattern = getRepeatedPattern(characters);\n\n if (repeatedPattern?.length === 1) {\n return 0;\n }\n\n if (repeatedPattern) {\n return Math.min(\n score,\n getLengthStrength(repeatedPattern),\n 15,\n );\n }\n\n const predictableCount = getPredictableCount(characters);\n\n return $._clamp(\n score - predictableCount,\n 0,\n predictableCount === characters.length ? 15 : 100,\n );\n};\n","import $ from '@fr0st/query';\nimport { BaseComponent, generateId } from '@fr0st/ui';\nimport { getStrength } from './strength.js';\n\n/**\n * @typedef {object} PasswordStrengthLevel\n * @property {string} class The CSS class applied to the progress bar.\n * @property {number} score The minimum score for the level.\n * @property {string} [text] The label displayed in the progress bar.\n */\n\n/**\n * @callback PasswordStrengthScorer\n * @param {string} password The password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number} The password strength, from 0 to 100.\n */\n\n/**\n * @typedef {object} PasswordStrengthOptions\n * @property {string[]} [commonPasswords] Passwords to score as very weak.\n * @property {string|null} [container=null] The selector for the progress container.\n * @property {PasswordStrengthLevel[]} [levels] The ordered password-strength levels.\n * @property {PasswordStrengthScorer} [scorer] Calculates the password strength.\n * @property {boolean} [striped=false] Whether to display a striped progress bar.\n */\n\n/**\n * Displays the strength of a password input as a progress bar.\n * @augments {BaseComponent<PasswordStrengthOptions>}\n */\nexport default class PasswordStrength extends BaseComponent {\n static classes = {\n progress: 'progress mt-2',\n progressBar: 'progress-bar',\n progressBarStriped: 'progress-bar-striped',\n };\n /** @type {PasswordStrengthOptions} */\n static defaults = {\n commonPasswords: [\n '123456',\n '12345678',\n '123456789',\n '1234567890',\n '000000',\n '111111',\n 'abc123',\n 'admin',\n 'dragon',\n 'iloveyou',\n 'letmein',\n 'monkey',\n 'password',\n 'password1',\n 'password123',\n 'qwerty',\n 'qwerty123',\n 'welcome',\n ],\n levels: [\n {\n score: 0,\n class: 'text-bg-danger',\n text: 'Very Weak',\n },\n {\n score: 20,\n class: 'text-bg-danger',\n text: 'Weak',\n },\n {\n score: 40,\n class: 'text-bg-warning',\n text: 'Normal',\n },\n {\n score: 60,\n class: 'text-bg-success',\n text: 'Strong',\n },\n {\n score: 80,\n class: 'text-bg-success',\n text: 'Very Strong',\n },\n ],\n container: null,\n scorer: getStrength,\n striped: false,\n };\n\n #container = null;\n #describedBy;\n #form = null;\n #progress = null;\n #progressBar = null;\n\n /**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=this.defaults.commonPasswords] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\n static getStrength(password, commonPasswords = this.defaults.commonPasswords) {\n return getStrength(password, commonPasswords);\n }\n\n /**\n * Creates a PasswordStrength.\n * @param {HTMLInputElement} node The input node.\n * @param {PasswordStrengthOptions} [options] The PasswordStrength options.\n * @throws {TypeError} When the node is not an input element.\n */\n constructor(node, options) {\n if (!$._isElement(node) || !$.is(node, 'input')) {\n throw new TypeError('PasswordStrength must be created on an input element.');\n }\n\n super(node, options);\n\n try {\n this.#describedBy = $.getAttribute(this.node, 'aria-describedby');\n\n this.#form = $.getProperty(this.node, 'form');\n\n if (this.options.container) {\n this.#container = $.findOne(this.options.container);\n } else {\n this.#container = $.closest(this.node, ':not(.form-input):not(.input-group)').shift();\n }\n\n this.#render();\n this.#refresh();\n this.#events();\n } catch (error) {\n this.dispose();\n throw error;\n }\n }\n\n /** @inheritdoc */\n dispose() {\n if (!this.node) {\n return;\n }\n\n $.remove(this.#progress);\n\n if (this.#describedBy === null) {\n $.removeAttribute(this.node, 'aria-describedby');\n } else {\n $.setAttribute(this.node, { 'aria-describedby': this.#describedBy });\n }\n\n this.#container = null;\n this.#form = null;\n this.#progress = null;\n this.#progressBar = null;\n\n super.dispose();\n }\n\n /**\n * Gets the password strength.\n * @returns {number} The password strength, from 0 to 100.\n */\n getStrength() {\n const value = $.getValue(this.node);\n const strength = Number(this.options.scorer(\n value,\n this.options.commonPasswords,\n ));\n\n return Number.isFinite(strength) ?\n $._clamp(strength, 0, 100) :\n 0;\n }\n\n /**\n * Attaches events for the PasswordStrength.\n */\n #events() {\n if (this.#form) {\n this.listen(this.#form, 'reset.ui.passwordstrength', (event) => {\n setTimeout(() => {\n if (this.node && !event.defaultPrevented) {\n this.#refresh();\n }\n }, 0);\n });\n }\n\n this.listen(this.node, 'input.ui.passwordstrength', () => {\n this.#refresh();\n });\n }\n\n /**\n * Refreshes the password strength.\n */\n #refresh() {\n const strength = this.getStrength();\n\n const nextLevel = this.options.levels.findLast(\n (level) => strength >= level.score,\n );\n\n $.setStyle(this.#progressBar, { width: `${strength}%` });\n $.setAttribute(this.#progressBar, {\n 'class': this.constructor.classes.progressBar,\n 'aria-valuenow': strength,\n });\n\n $.addClass(this.#progressBar, nextLevel.class);\n\n if (this.options.striped) {\n $.addClass(this.#progressBar, this.constructor.classes.progressBarStriped);\n }\n\n $.setText(this.#progressBar, nextLevel.text ?? '');\n }\n\n /**\n * Renders the password strength element.\n */\n #render() {\n this.#progress = $.create('div', {\n class: this.constructor.classes.progress,\n });\n\n const id = generateId('password-strength');\n\n this.#progressBar = $.create('div', {\n attributes: {\n id,\n 'role': 'progressbar',\n 'aria-valuemin': 0,\n 'aria-valuemax': 100,\n },\n });\n\n $.append(this.#progress, this.#progressBar);\n $.append(this.#container, this.#progress);\n\n const describedBy = [this.#describedBy, id]\n .filter(Boolean)\n .join(' ');\n\n $.setAttribute(this.node, { 'aria-describedby': describedBy });\n }\n}\n","import { initComponent } from '@fr0st/ui';\nimport PasswordStrength from './password-strength.js';\n\ninitComponent('passwordstrength', PasswordStrength);\n\nexport default PasswordStrength;\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fr0st/ui-passwordstrength",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Password strength indicator component for Frost UI with customizable levels, labels, and striped progress styling.",
|
|
6
6
|
"keywords": [
|
|
@@ -59,11 +59,11 @@
|
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@fr0st/eslint-config": "^6.0.1",
|
|
61
61
|
"@fr0st/query": "^5.0.1",
|
|
62
|
-
"@fr0st/ui": "^4.0.
|
|
63
|
-
"@playwright/test": "^1.
|
|
64
|
-
"eslint": "^10.
|
|
62
|
+
"@fr0st/ui": "^4.0.2",
|
|
63
|
+
"@playwright/test": "^1.64.1",
|
|
64
|
+
"eslint": "^10.13.0",
|
|
65
65
|
"monocart-reporter": "^2.14.0",
|
|
66
|
-
"vite": "^8.3.
|
|
66
|
+
"vite": "^8.3.4"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
69
|
"@fr0st/query": "^5.0.0",
|
package/src/password-strength.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import $ from '@fr0st/query';
|
|
2
|
-
import { BaseComponent, generateId
|
|
2
|
+
import { BaseComponent, generateId } from '@fr0st/ui';
|
|
3
3
|
import { getStrength } from './strength.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -89,12 +89,11 @@ export default class PasswordStrength extends BaseComponent {
|
|
|
89
89
|
striped: false,
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
-
#container;
|
|
92
|
+
#container = null;
|
|
93
93
|
#describedBy;
|
|
94
|
-
#form;
|
|
95
|
-
#progress;
|
|
96
|
-
#progressBar;
|
|
97
|
-
#resetHandler;
|
|
94
|
+
#form = null;
|
|
95
|
+
#progress = null;
|
|
96
|
+
#progressBar = null;
|
|
98
97
|
|
|
99
98
|
/**
|
|
100
99
|
* Calculates the strength of a password.
|
|
@@ -113,7 +112,7 @@ export default class PasswordStrength extends BaseComponent {
|
|
|
113
112
|
* @throws {TypeError} When the node is not an input element.
|
|
114
113
|
*/
|
|
115
114
|
constructor(node, options) {
|
|
116
|
-
if (!$.is(node, 'input')) {
|
|
115
|
+
if (!$._isElement(node) || !$.is(node, 'input')) {
|
|
117
116
|
throw new TypeError('PasswordStrength must be created on an input element.');
|
|
118
117
|
}
|
|
119
118
|
|
|
@@ -122,16 +121,6 @@ export default class PasswordStrength extends BaseComponent {
|
|
|
122
121
|
try {
|
|
123
122
|
this.#describedBy = $.getAttribute(this.node, 'aria-describedby');
|
|
124
123
|
|
|
125
|
-
const overrides = { ...getDataset(node), ...options };
|
|
126
|
-
|
|
127
|
-
// Replace supplied arrays instead of merging them with default entries.
|
|
128
|
-
for (const key of ['levels', 'commonPasswords']) {
|
|
129
|
-
if (Array.isArray(overrides[key])) {
|
|
130
|
-
this.options[key].length = 0;
|
|
131
|
-
$._extend(this.options[key], overrides[key]);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
124
|
this.#form = $.getProperty(this.node, 'form');
|
|
136
125
|
|
|
137
126
|
if (this.options.container) {
|
|
@@ -156,11 +145,6 @@ export default class PasswordStrength extends BaseComponent {
|
|
|
156
145
|
}
|
|
157
146
|
|
|
158
147
|
$.remove(this.#progress);
|
|
159
|
-
$.removeEvent(this.node, 'input.ui.passwordstrength');
|
|
160
|
-
|
|
161
|
-
if (this.#form && this.#resetHandler) {
|
|
162
|
-
$.removeEvent(this.#form, 'reset.ui.passwordstrength', this.#resetHandler);
|
|
163
|
-
}
|
|
164
148
|
|
|
165
149
|
if (this.#describedBy === null) {
|
|
166
150
|
$.removeAttribute(this.node, 'aria-describedby');
|
|
@@ -172,7 +156,6 @@ export default class PasswordStrength extends BaseComponent {
|
|
|
172
156
|
this.#form = null;
|
|
173
157
|
this.#progress = null;
|
|
174
158
|
this.#progressBar = null;
|
|
175
|
-
this.#resetHandler = null;
|
|
176
159
|
|
|
177
160
|
super.dispose();
|
|
178
161
|
}
|
|
@@ -198,18 +181,16 @@ export default class PasswordStrength extends BaseComponent {
|
|
|
198
181
|
*/
|
|
199
182
|
#events() {
|
|
200
183
|
if (this.#form) {
|
|
201
|
-
this.#
|
|
184
|
+
this.listen(this.#form, 'reset.ui.passwordstrength', (event) => {
|
|
202
185
|
setTimeout(() => {
|
|
203
186
|
if (this.node && !event.defaultPrevented) {
|
|
204
187
|
this.#refresh();
|
|
205
188
|
}
|
|
206
189
|
}, 0);
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
$.addEvent(this.#form, 'reset.ui.passwordstrength', this.#resetHandler);
|
|
190
|
+
});
|
|
210
191
|
}
|
|
211
192
|
|
|
212
|
-
|
|
193
|
+
this.listen(this.node, 'input.ui.passwordstrength', () => {
|
|
213
194
|
this.#refresh();
|
|
214
195
|
});
|
|
215
196
|
}
|