@fr0st/ui-passwordstrength 3.0.2 → 3.0.3
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/README.md +6 -3
- package/dist/frost-ui-passwordstrength.esm.js +5 -7
- 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 +9 -12
- 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 +10 -5
- package/src/password-strength.js +3 -1
package/README.md
CHANGED
|
@@ -124,8 +124,6 @@ Calling `PasswordStrength.init()` again for the same input returns its existing
|
|
|
124
124
|
|
|
125
125
|
The indicator renders the initial value and refreshes on `input` events. After changing the input's value in JavaScript, dispatch an `input` event to refresh the indicator; `getStrength()` only returns the current score.
|
|
126
126
|
|
|
127
|
-
Form resets refresh the indicator after the browser restores the input's default value. This refresh is deferred until after the reset event finishes and is skipped if the reset is canceled. Inputs associated with a form through the `form` attribute are also supported.
|
|
128
|
-
|
|
129
127
|
## Options
|
|
130
128
|
|
|
131
129
|
Options are resolved in this order:
|
|
@@ -309,10 +307,15 @@ Pass an options object to initialize every matched element, or pass a public met
|
|
|
309
307
|
- A unique generated progress ID is appended to the input's existing `aria-describedby` value instead of replacing it.
|
|
310
308
|
- Disposal restores the original `aria-describedby` state exactly, including absent and empty values.
|
|
311
309
|
- Disposal also removes the generated progress markup, namespaced input and form-reset listeners, and registered component data.
|
|
312
|
-
- The original password input remains the interactive and submitted form control.
|
|
313
310
|
|
|
314
311
|
Applications remain responsible for meaningful labels, password requirements, validation feedback, and error messages. Treat the displayed score as guidance rather than proof that a password is safe.
|
|
315
312
|
|
|
313
|
+
## Forms
|
|
314
|
+
|
|
315
|
+
The original password input remains the interactive and submitted form control.
|
|
316
|
+
|
|
317
|
+
Form resets refresh the indicator after the browser restores the input's default value. This refresh is deferred until after the reset event finishes and is skipped if the reset is canceled. Inputs associated with a form through the `form` attribute are also supported.
|
|
318
|
+
|
|
316
319
|
## Themes and RTL
|
|
317
320
|
|
|
318
321
|
Frost UI follows the user's preferred color scheme by default. Set `data-ui-theme="light"` or `data-ui-theme="dark"` on the document or an ancestor to select a theme explicitly.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BaseComponent, generateId, initComponent } from "@fr0st/ui";
|
|
2
2
|
import $ from "@fr0st/query";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// src/strength.js
|
|
5
5
|
var characterTypes = [
|
|
6
6
|
{
|
|
7
7
|
pattern: /\p{Ll}/u,
|
|
@@ -189,8 +189,8 @@ var getStrength = (password, commonPasswords = []) => {
|
|
|
189
189
|
return $._clamp(score - predictableCount, 0, predictableCount === characters.length ? 15 : 100);
|
|
190
190
|
};
|
|
191
191
|
|
|
192
|
-
|
|
193
|
-
|
|
192
|
+
// src/password-strength.js
|
|
193
|
+
var window = $.getWindow();
|
|
194
194
|
/**
|
|
195
195
|
* @typedef {object} PasswordStrengthLevel
|
|
196
196
|
* @property {string} class The CSS class applied to the progress bar.
|
|
@@ -336,7 +336,7 @@ var PasswordStrength = class extends BaseComponent {
|
|
|
336
336
|
*/
|
|
337
337
|
#events() {
|
|
338
338
|
if (this.#form) this.listen(this.#form, "reset.ui.passwordstrength", (event) => {
|
|
339
|
-
setTimeout(() => {
|
|
339
|
+
window.setTimeout(() => {
|
|
340
340
|
if (this.node && !event.defaultPrevented) this.#refresh();
|
|
341
341
|
}, 0);
|
|
342
342
|
});
|
|
@@ -378,11 +378,9 @@ var PasswordStrength = class extends BaseComponent {
|
|
|
378
378
|
}
|
|
379
379
|
};
|
|
380
380
|
|
|
381
|
-
|
|
382
|
-
//#region src/index.js
|
|
381
|
+
// src/index.js
|
|
383
382
|
initComponent("passwordstrength", PasswordStrength);
|
|
384
383
|
var src_default = PasswordStrength;
|
|
385
384
|
|
|
386
|
-
//#endregion
|
|
387
385
|
export { src_default as default };
|
|
388
386
|
//# sourceMappingURL=frost-ui-passwordstrength.esm.js.map
|
|
@@ -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 } 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
|
+
{"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\nconst window = $.getWindow();\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 window.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,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`,
|
|
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=r.getWindow(),x=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=>{b.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`,x);var S=x;export{S 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","#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":"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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","window","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\nconst window = $.getWindow();\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 window.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,10 +1,10 @@
|
|
|
1
1
|
(function(global, factory) {
|
|
2
|
-
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require(
|
|
3
|
-
typeof define === 'function' && define.amd ? define(['exports',
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require("@fr0st/ui"), require("@fr0st/query")) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports', "@fr0st/ui", "@fr0st/query"], factory) :
|
|
4
4
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.UI = global.UI || {}), global.UI,global.fQuery));
|
|
5
5
|
})(this, function(exports, _fr0st_ui, _fr0st_query) {
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
7
|
-
|
|
7
|
+
// \0rolldown/runtime.js
|
|
8
8
|
var __create = Object.create;
|
|
9
9
|
var __defProp = Object.defineProperty;
|
|
10
10
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -30,10 +30,9 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
30
30
|
enumerable: true
|
|
31
31
|
}) : target, mod));
|
|
32
32
|
|
|
33
|
-
//#endregion
|
|
34
33
|
_fr0st_query = __toESM(_fr0st_query, 1);
|
|
35
34
|
|
|
36
|
-
|
|
35
|
+
// src/strength.js
|
|
37
36
|
var characterTypes = [
|
|
38
37
|
{
|
|
39
38
|
pattern: /\p{Ll}/u,
|
|
@@ -221,9 +220,9 @@ _fr0st_query = __toESM(_fr0st_query, 1);
|
|
|
221
220
|
return _fr0st_query.default._clamp(score - predictableCount, 0, predictableCount === characters.length ? 15 : 100);
|
|
222
221
|
};
|
|
223
222
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
/**
|
|
223
|
+
// src/password-strength.js
|
|
224
|
+
var window = _fr0st_query.default.getWindow();
|
|
225
|
+
/**
|
|
227
226
|
* @typedef {object} PasswordStrengthLevel
|
|
228
227
|
* @property {string} class The CSS class applied to the progress bar.
|
|
229
228
|
* @property {number} score The minimum score for the level.
|
|
@@ -368,7 +367,7 @@ _fr0st_query = __toESM(_fr0st_query, 1);
|
|
|
368
367
|
*/
|
|
369
368
|
#events() {
|
|
370
369
|
if (this.#form) this.listen(this.#form, "reset.ui.passwordstrength", (event) => {
|
|
371
|
-
setTimeout(() => {
|
|
370
|
+
window.setTimeout(() => {
|
|
372
371
|
if (this.node && !event.defaultPrevented) this.#refresh();
|
|
373
372
|
}, 0);
|
|
374
373
|
});
|
|
@@ -410,12 +409,10 @@ _fr0st_query = __toESM(_fr0st_query, 1);
|
|
|
410
409
|
}
|
|
411
410
|
};
|
|
412
411
|
|
|
413
|
-
|
|
414
|
-
//#region src/index.js
|
|
412
|
+
// src/index.js
|
|
415
413
|
(0, _fr0st_ui.initComponent)("passwordstrength", PasswordStrength);
|
|
416
414
|
var src_default = PasswordStrength;
|
|
417
415
|
|
|
418
|
-
//#endregion
|
|
419
416
|
exports.PasswordStrength = src_default;
|
|
420
417
|
});
|
|
421
418
|
//# sourceMappingURL=frost-ui-passwordstrength.js.map
|
|
@@ -1 +1 @@
|
|
|
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
|
+
{"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\nconst window = $.getWindow();\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 window.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(
|
|
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(i(n,`default`,{value:e,enumerable:!0}),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=n.default.getWindow(),D=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=>{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`,D),e.PasswordStrength=D});
|
|
2
2
|
//# sourceMappingURL=frost-ui-passwordstrength.min.js.map
|
|
@@ -1 +1 @@
|
|
|
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":"q2BAEA,IAAM2B,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,MACfxC,EAAAA,QAAE,KACE,KAAK,IAAIsC,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,IAAInD,EAAI,EAAGA,EAAI,EAAIsB,EAAW,OAAQ,IACnC2B,EAAc3B,EAAW,MAAMtB,EAAGA,EAAI,CAAC,CAAC,IACxCmD,EAAQ,IAAInD,CAAC,EACbmD,EAAQ,IAAInD,EAAI,CAAC,EACjBmD,EAAQ,IAAInD,EAAI,CAAC,GAIzB,OAAOmD,EAAQ,IACnB,EAQaC,GAAe,EAAU,EAAkB,CAAC,IAAM,CAC3D,IAAMC,EAAqBtB,EAAS,UAAU,MAAM,EAC9CT,EAAa,MAAM,KAAK+B,CAAkB,EAEhD,GAAI,CAAC/B,EAAW,OACZ,MAAO,GAGX,IAAMgC,EAAiBjB,EACnBgB,EAAmB,YAAY,EAC/Bd,CACJ,EAEA,GAAIe,IAAmB,KACnB,OAAOA,EAGX,IAAM9B,EAAQJ,EAAkBE,CAAU,EACpCiC,EAAkB7B,EAAmBJ,CAAU,EAErD,GAAIiC,GAAiB,SAAW,EAC5B,MAAO,GAGX,GAAIA,EACA,OAAO,KAAK,IACR/B,EACAJ,EAAkBmC,CAAe,EACjC,EACJ,EAGJ,IAAMC,EAAmBN,EAAoB5B,CAAU,EAEvD,OAAOtC,EAAAA,QAAE,OACLwC,EAAQgC,EACR,EACAA,IAAqBlC,EAAW,OAAS,GAAK,GAClD,CACJ,ECjPqBmC,EAArB,cAA8CxE,EAAAA,aAAc,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,OAAQmE,EACR,QAAS,EACb,EAEA,GAAa,KACb,GACA,GAAQ,KACR,GAAY,KACZ,GAAe,KAQf,OAAO,YAAY,EAAU,EAAkB,KAAK,SAAS,gBAAiB,CAC1E,OAAOA,EAAYrB,EAAUQ,CAAe,CAChD,CAQA,YAAY,EAAM,EAAS,CACvB,GAAI,CAACrD,EAAAA,QAAE,WAAWyE,CAAI,GAAK,CAACzE,EAAAA,QAAE,GAAGyE,EAAM,OAAO,EAC1C,MAAU,UAAU,uDAAuD,EAG/E,MAAMA,EAAMC,CAAO,EAEnB,GAAI,CACA,KAAKC,GAAe3E,EAAAA,QAAE,aAAa,KAAK,KAAM,kBAAkB,EAEhE,KAAK4E,GAAQ5E,EAAAA,QAAE,YAAY,KAAK,KAAM,MAAM,EAE5C,AAGI,KAAK6E,GAHL,KAAK,QAAQ,UACK7E,EAAAA,QAAE,QAAQ,KAAK,QAAQ,SAAS,EAEhCA,EAAAA,QAAE,QAAQ,KAAK,KAAM,qCAAqC,CAAC,CAAC,MAAM,EAGxF,KAAK8E,GAAQ,EACb,KAAKC,GAAS,EACd,KAAKC,GAAQ,CACjB,OAASC,EAAO,CAEZ,MADA,KAAK,QAAQ,EACPA,CACV,CACJ,CAGA,SAAU,CACD,KAAK,OAIVzD,EAAA,QAAE,OAAO,KAAK0D,EAAS,EAEnB,KAAKP,KAAiB,KACtBnD,EAAA,QAAE,gBAAgB,KAAK,KAAM,kBAAkB,EAE/CA,EAAA,QAAE,aAAa,KAAK,KAAM,CAAE,mBAAoB,KAAKmD,EAAa,CAAC,EAGvE,KAAKE,GAAa,KAClB,KAAKD,GAAQ,KACb,KAAKM,GAAY,KACjB,KAAKC,GAAe,KAEpB,MAAM,QAAQ,EAClB,CAMA,aAAc,CACV,IAAMjC,EAAQlD,EAAAA,QAAE,SAAS,KAAK,IAAI,EAC5BoF,EAAW,OAAO,KAAK,QAAQ,OACjClC,EACA,KAAK,QAAQ,eACjB,CAAC,EAED,OAAO,OAAO,SAASkC,CAAQ,EAC3BpF,EAAAA,QAAE,OAAOoF,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,EAEA/D,EAAA,QAAE,SAAS,KAAK2D,GAAc,CAAE,MAAO,GAAGC,EAAS,EAAG,CAAC,EACvD5D,EAAA,QAAE,aAAa,KAAK2D,GAAc,CAC9BK,MAAS,KAAK,YAAY,QAAQ,YAClC,gBAAiBJ,CACrB,CAAC,EAED5D,EAAA,QAAE,SAAS,KAAK2D,GAAcG,EAAU,KAAK,EAEzC,KAAK,QAAQ,SACb9D,EAAA,QAAE,SAAS,KAAK2D,GAAc,KAAK,YAAY,QAAQ,kBAAkB,EAG7E3D,EAAA,QAAE,QAAQ,KAAK2D,GAAcG,EAAU,MAAQ,EAAE,CACrD,CAKA,IAAU,CACN,KAAKJ,GAAYlF,EAAAA,QAAE,OAAO,MAAO,CAC7B,MAAO,KAAK,YAAY,QAAQ,QACpC,CAAC,EAED,IAAMyF,GAAA,EAAKxF,EAAAA,WAAAA,CAAW,mBAAmB,EAEzC,KAAKkF,GAAenF,EAAAA,QAAE,OAAO,MAAO,CAChC,WAAY,CACR,KACA0F,KAAQ,cACR,gBAAiB,EACjB,gBAAiB,GACrB,CACJ,CAAC,EAEDlE,EAAA,QAAE,OAAO,KAAK0D,GAAW,KAAKC,EAAY,EAC1C3D,EAAA,QAAE,OAAO,KAAKqD,GAAY,KAAKK,EAAS,EAExC,IAAMS,EAAc,CAAC,KAAKhB,GAAcc,CAAE,CAAC,CACtC,OAAO,OAAO,CAAC,CACf,KAAK,GAAG,EAEbjE,EAAA,QAAE,aAAa,KAAK,KAAM,CAAE,mBAAoBmE,CAAY,CAAC,CACjE,CACJ,GCvPA,EAAAnB,EAAA,cAAA,CAAc,mBAAoBD,CAAgB"}
|
|
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","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","window","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\nconst window = $.getWindow();\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 window.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.3",
|
|
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": [
|
|
@@ -58,12 +58,17 @@
|
|
|
58
58
|
],
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@fr0st/eslint-config": "^6.0.1",
|
|
61
|
-
"@fr0st/query": "^5.0.
|
|
62
|
-
"@fr0st/ui": "^4.0.
|
|
61
|
+
"@fr0st/query": "^5.0.2",
|
|
62
|
+
"@fr0st/ui": "^4.0.3",
|
|
63
63
|
"@playwright/test": "^1.64.1",
|
|
64
64
|
"eslint": "^10.13.0",
|
|
65
|
-
"monocart-reporter": "
|
|
66
|
-
"vite": "^8.3.
|
|
65
|
+
"monocart-reporter": "2.14.0",
|
|
66
|
+
"vite": "^8.3.5"
|
|
67
|
+
},
|
|
68
|
+
"overrides": {
|
|
69
|
+
"monocart-reporter": {
|
|
70
|
+
"monocart-coverage-reports": "2.13.0"
|
|
71
|
+
}
|
|
67
72
|
},
|
|
68
73
|
"peerDependencies": {
|
|
69
74
|
"@fr0st/query": "^5.0.0",
|
package/src/password-strength.js
CHANGED
|
@@ -2,6 +2,8 @@ import $ from '@fr0st/query';
|
|
|
2
2
|
import { BaseComponent, generateId } from '@fr0st/ui';
|
|
3
3
|
import { getStrength } from './strength.js';
|
|
4
4
|
|
|
5
|
+
const window = $.getWindow();
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* @typedef {object} PasswordStrengthLevel
|
|
7
9
|
* @property {string} class The CSS class applied to the progress bar.
|
|
@@ -182,7 +184,7 @@ export default class PasswordStrength extends BaseComponent {
|
|
|
182
184
|
#events() {
|
|
183
185
|
if (this.#form) {
|
|
184
186
|
this.listen(this.#form, 'reset.ui.passwordstrength', (event) => {
|
|
185
|
-
setTimeout(() => {
|
|
187
|
+
window.setTimeout(() => {
|
|
186
188
|
if (this.node && !event.defaultPrevented) {
|
|
187
189
|
this.#refresh();
|
|
188
190
|
}
|