@fr0st/ui-passwordstrength 2.0.7 → 3.0.0
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/LICENSE +0 -0
- package/README.md +334 -37
- package/dist/frost-ui-passwordstrength.esm.js +395 -0
- package/dist/frost-ui-passwordstrength.esm.js.map +1 -0
- package/dist/frost-ui-passwordstrength.esm.min.js +2 -0
- package/dist/frost-ui-passwordstrength.esm.min.js.map +1 -0
- package/dist/frost-ui-passwordstrength.js +428 -341
- package/dist/frost-ui-passwordstrength.js.map +1 -1
- package/dist/frost-ui-passwordstrength.min.js +2 -1
- package/dist/frost-ui-passwordstrength.min.js.map +1 -1
- package/package.json +57 -25
- package/src/{wrapper.js → browser.js} +0 -0
- package/src/index.js +0 -55
- package/src/password-strength.js +227 -24
- package/src/strength.js +273 -0
- package/src/prototype/events.js +0 -10
- package/src/prototype/helpers.js +0 -33
- package/src/prototype/render.js +0 -29
- package/src/static/helpers.js +0 -162
package/src/password-strength.js
CHANGED
|
@@ -1,52 +1,255 @@
|
|
|
1
1
|
import $ from '@fr0st/query';
|
|
2
|
-
import { BaseComponent } from '@fr0st/ui';
|
|
2
|
+
import { BaseComponent, generateId, getDataset } from '@fr0st/ui';
|
|
3
|
+
import { getStrength } from './strength.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
-
* @class
|
|
6
|
+
* @typedef {object} PasswordStrengthLevel
|
|
7
|
+
* @property {string} class The CSS class applied to the progress bar.
|
|
8
|
+
* @property {number} score The minimum score for the level.
|
|
9
|
+
* @property {string} [text] The label displayed in the progress bar.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @callback PasswordStrengthScorer
|
|
14
|
+
* @param {string} password The password.
|
|
15
|
+
* @param {string[]} commonPasswords The common passwords.
|
|
16
|
+
* @returns {number} The password strength, from 0 to 100.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {object} PasswordStrengthOptions
|
|
21
|
+
* @property {string[]} [commonPasswords] Passwords to score as very weak.
|
|
22
|
+
* @property {string|null} [container=null] The selector for the progress container.
|
|
23
|
+
* @property {PasswordStrengthLevel[]} [levels] The ordered password-strength levels.
|
|
24
|
+
* @property {PasswordStrengthScorer} [scorer] Calculates the password strength.
|
|
25
|
+
* @property {boolean} [striped=false] Whether to display a striped progress bar.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Displays the strength of a password input as a progress bar.
|
|
30
|
+
* @augments {BaseComponent<PasswordStrengthOptions>}
|
|
7
31
|
*/
|
|
8
32
|
export default class PasswordStrength extends BaseComponent {
|
|
33
|
+
static classes = {
|
|
34
|
+
progress: 'progress mt-2',
|
|
35
|
+
progressBar: 'progress-bar',
|
|
36
|
+
progressBarStriped: 'progress-bar-striped',
|
|
37
|
+
};
|
|
38
|
+
/** @type {PasswordStrengthOptions} */
|
|
39
|
+
static defaults = {
|
|
40
|
+
commonPasswords: [
|
|
41
|
+
'123456',
|
|
42
|
+
'12345678',
|
|
43
|
+
'123456789',
|
|
44
|
+
'1234567890',
|
|
45
|
+
'000000',
|
|
46
|
+
'111111',
|
|
47
|
+
'abc123',
|
|
48
|
+
'admin',
|
|
49
|
+
'dragon',
|
|
50
|
+
'iloveyou',
|
|
51
|
+
'letmein',
|
|
52
|
+
'monkey',
|
|
53
|
+
'password',
|
|
54
|
+
'password1',
|
|
55
|
+
'password123',
|
|
56
|
+
'qwerty',
|
|
57
|
+
'qwerty123',
|
|
58
|
+
'welcome',
|
|
59
|
+
],
|
|
60
|
+
levels: [
|
|
61
|
+
{
|
|
62
|
+
score: 0,
|
|
63
|
+
class: 'text-bg-danger',
|
|
64
|
+
text: 'Very Weak',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
score: 20,
|
|
68
|
+
class: 'text-bg-danger',
|
|
69
|
+
text: 'Weak',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
score: 40,
|
|
73
|
+
class: 'text-bg-warning',
|
|
74
|
+
text: 'Normal',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
score: 60,
|
|
78
|
+
class: 'text-bg-success',
|
|
79
|
+
text: 'Strong',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
score: 80,
|
|
83
|
+
class: 'text-bg-success',
|
|
84
|
+
text: 'Very Strong',
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
container: null,
|
|
88
|
+
scorer: getStrength,
|
|
89
|
+
striped: false,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
#container;
|
|
93
|
+
#describedBy;
|
|
94
|
+
#form;
|
|
95
|
+
#progress;
|
|
96
|
+
#progressBar;
|
|
97
|
+
#resetHandler;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Calculates the strength of a password.
|
|
101
|
+
* @param {string} password The password.
|
|
102
|
+
* @param {string[]} [commonPasswords=this.defaults.commonPasswords] Passwords to score as very weak.
|
|
103
|
+
* @returns {number} The password strength, from 0 to 100.
|
|
104
|
+
*/
|
|
105
|
+
static getStrength(password, commonPasswords = this.defaults.commonPasswords) {
|
|
106
|
+
return getStrength(password, commonPasswords);
|
|
107
|
+
}
|
|
108
|
+
|
|
9
109
|
/**
|
|
10
|
-
*
|
|
110
|
+
* Creates a PasswordStrength.
|
|
11
111
|
* @param {HTMLElement} node The input node.
|
|
12
|
-
* @param {
|
|
112
|
+
* @param {PasswordStrengthOptions} [options] The PasswordStrength options.
|
|
13
113
|
*/
|
|
14
114
|
constructor(node, options) {
|
|
15
115
|
super(node, options);
|
|
16
116
|
|
|
17
|
-
|
|
18
|
-
|
|
117
|
+
const overrides = { ...getDataset(node), ...options };
|
|
118
|
+
|
|
119
|
+
// Replace supplied arrays instead of merging them with default entries.
|
|
120
|
+
for (const key of ['levels', 'commonPasswords']) {
|
|
121
|
+
if (Array.isArray(overrides[key])) {
|
|
122
|
+
this.options[key].length = 0;
|
|
123
|
+
$._extend(this.options[key], overrides[key]);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
this.#form = this.node.form;
|
|
128
|
+
|
|
129
|
+
if (this.options.container) {
|
|
130
|
+
this.#container = $.findOne(this.options.container);
|
|
19
131
|
} else {
|
|
20
|
-
this
|
|
132
|
+
this.#container = $.closest(this.node, ':not(.form-input):not(.input-group)').shift();
|
|
21
133
|
}
|
|
22
134
|
|
|
23
|
-
this
|
|
24
|
-
this
|
|
25
|
-
this
|
|
135
|
+
this.#render();
|
|
136
|
+
this.#refresh();
|
|
137
|
+
this.#events();
|
|
26
138
|
}
|
|
27
139
|
|
|
28
|
-
/**
|
|
29
|
-
* Dispose the PasswordStrength.
|
|
30
|
-
*/
|
|
140
|
+
/** @inheritdoc */
|
|
31
141
|
dispose() {
|
|
32
|
-
$.remove(this
|
|
33
|
-
$.removeEvent(this.
|
|
34
|
-
|
|
142
|
+
$.remove(this.#progress);
|
|
143
|
+
$.removeEvent(this.node, 'input.ui.passwordstrength');
|
|
144
|
+
|
|
145
|
+
if (this.#form) {
|
|
146
|
+
$.removeEvent(this.#form, 'reset.ui.passwordstrength', this.#resetHandler);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (this.#describedBy === null) {
|
|
150
|
+
$.removeAttribute(this.node, 'aria-describedby');
|
|
151
|
+
} else {
|
|
152
|
+
$.setAttribute(this.node, { 'aria-describedby': this.#describedBy });
|
|
153
|
+
}
|
|
35
154
|
|
|
36
|
-
this
|
|
37
|
-
this
|
|
38
|
-
this
|
|
155
|
+
this.#container = null;
|
|
156
|
+
this.#form = null;
|
|
157
|
+
this.#progress = null;
|
|
158
|
+
this.#progressBar = null;
|
|
159
|
+
this.#resetHandler = null;
|
|
39
160
|
|
|
40
161
|
super.dispose();
|
|
41
162
|
}
|
|
42
163
|
|
|
43
164
|
/**
|
|
44
|
-
*
|
|
45
|
-
* @
|
|
165
|
+
* Gets the password strength.
|
|
166
|
+
* @returns {number} The password strength, from 0 to 100.
|
|
46
167
|
*/
|
|
47
168
|
getStrength() {
|
|
48
|
-
const value = $.getValue(this.
|
|
169
|
+
const value = $.getValue(this.node);
|
|
170
|
+
const strength = Number(this.options.scorer(
|
|
171
|
+
value,
|
|
172
|
+
this.options.commonPasswords,
|
|
173
|
+
));
|
|
174
|
+
|
|
175
|
+
return Number.isFinite(strength) ?
|
|
176
|
+
$._clamp(strength, 0, 100) :
|
|
177
|
+
0;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Attaches events for the PasswordStrength.
|
|
182
|
+
*/
|
|
183
|
+
#events() {
|
|
184
|
+
if (this.#form) {
|
|
185
|
+
this.#resetHandler = (event) => {
|
|
186
|
+
setTimeout(() => {
|
|
187
|
+
if (this.node && !event.defaultPrevented) {
|
|
188
|
+
this.#refresh();
|
|
189
|
+
}
|
|
190
|
+
}, 0);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
$.addEvent(this.#form, 'reset.ui.passwordstrength', this.#resetHandler);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
$.addEvent(this.node, 'input.ui.passwordstrength', (_) => {
|
|
197
|
+
this.#refresh();
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Refreshes the password strength.
|
|
203
|
+
*/
|
|
204
|
+
#refresh() {
|
|
205
|
+
const strength = this.getStrength();
|
|
206
|
+
|
|
207
|
+
const nextLevel = this.options.levels.findLast(
|
|
208
|
+
(level) => strength >= level.score,
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
$.setStyle(this.#progressBar, { width: `${strength}%` });
|
|
212
|
+
$.setAttribute(this.#progressBar, {
|
|
213
|
+
'class': this.constructor.classes.progressBar,
|
|
214
|
+
'aria-valuenow': strength,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
$.addClass(this.#progressBar, nextLevel.class);
|
|
218
|
+
|
|
219
|
+
if (this.options.striped) {
|
|
220
|
+
$.addClass(this.#progressBar, this.constructor.classes.progressBarStriped);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
$.setText(this.#progressBar, nextLevel.text ?? '');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Renders the password strength element.
|
|
228
|
+
*/
|
|
229
|
+
#render() {
|
|
230
|
+
this.#describedBy = $.getAttribute(this.node, 'aria-describedby');
|
|
231
|
+
this.#progress = $.create('div', {
|
|
232
|
+
class: this.constructor.classes.progress,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const id = generateId('password-strength');
|
|
236
|
+
|
|
237
|
+
this.#progressBar = $.create('div', {
|
|
238
|
+
attributes: {
|
|
239
|
+
'id': id,
|
|
240
|
+
'role': 'progressbar',
|
|
241
|
+
'aria-valuemin': 0,
|
|
242
|
+
'aria-valuemax': 100,
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
$.append(this.#progress, this.#progressBar);
|
|
247
|
+
$.append(this.#container, this.#progress);
|
|
248
|
+
|
|
249
|
+
const describedBy = [this.#describedBy, id]
|
|
250
|
+
.filter(Boolean)
|
|
251
|
+
.join(' ');
|
|
49
252
|
|
|
50
|
-
|
|
253
|
+
$.setAttribute(this.node, { 'aria-describedby': describedBy });
|
|
51
254
|
}
|
|
52
255
|
}
|
package/src/strength.js
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import $ from '@fr0st/query';
|
|
2
|
+
|
|
3
|
+
const characterTypes = [
|
|
4
|
+
{ pattern: /\p{Ll}/u, type: 'lower' },
|
|
5
|
+
{ pattern: /\p{Lu}/u, type: 'upper' },
|
|
6
|
+
{ pattern: /\p{N}/u, type: 'number' },
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
const keyboardSequences = [
|
|
10
|
+
'1234567890',
|
|
11
|
+
'qwertyuiop',
|
|
12
|
+
'asdfghjkl',
|
|
13
|
+
'zxcvbnm',
|
|
14
|
+
].flatMap(
|
|
15
|
+
(row) => [
|
|
16
|
+
row,
|
|
17
|
+
Array.from(row).reverse().join(''),
|
|
18
|
+
],
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const leetCharacters = {
|
|
22
|
+
'$': 's',
|
|
23
|
+
'0': 'o',
|
|
24
|
+
'3': 'e',
|
|
25
|
+
'4': 'a',
|
|
26
|
+
'5': 's',
|
|
27
|
+
'7': 't',
|
|
28
|
+
'@': 'a',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const lengthBands = [
|
|
32
|
+
{ end: 5, max: 19, min: 1, start: 1 },
|
|
33
|
+
{ end: 8, max: 39, min: 20, start: 6 },
|
|
34
|
+
{ end: 11, max: 59, min: 40, start: 9 },
|
|
35
|
+
{ end: 15, max: 79, min: 60, start: 12 },
|
|
36
|
+
{ end: 24, max: 100, min: 80, start: 16 },
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Gets the type of a password character.
|
|
41
|
+
* @param {string} character The character.
|
|
42
|
+
* @returns {string} The character type.
|
|
43
|
+
*/
|
|
44
|
+
const getCharacterType = (character) =>
|
|
45
|
+
characterTypes.find(
|
|
46
|
+
({ pattern }) => pattern.test(character),
|
|
47
|
+
)?.type || 'symbol';
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Gets a length-based password strength.
|
|
51
|
+
* @param {string[]} characters The password characters.
|
|
52
|
+
* @returns {number} The password strength.
|
|
53
|
+
*/
|
|
54
|
+
const getLengthStrength = (characters) => {
|
|
55
|
+
const band = lengthBands.find(
|
|
56
|
+
({ end }) => characters.length <= end,
|
|
57
|
+
) || lengthBands.at(-1);
|
|
58
|
+
const score = Math.round(
|
|
59
|
+
$._map(
|
|
60
|
+
Math.min(characters.length, band.end),
|
|
61
|
+
band.start,
|
|
62
|
+
band.end,
|
|
63
|
+
band.min,
|
|
64
|
+
band.max,
|
|
65
|
+
),
|
|
66
|
+
);
|
|
67
|
+
const diversity = new Set(
|
|
68
|
+
characters.map(getCharacterType),
|
|
69
|
+
).size;
|
|
70
|
+
|
|
71
|
+
return Math.min(
|
|
72
|
+
score +
|
|
73
|
+
(diversity - 1) * 2,
|
|
74
|
+
band.max,
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Gets the smallest repeated pattern.
|
|
80
|
+
* @param {string[]} characters The password characters.
|
|
81
|
+
* @returns {string[]|null} The repeated pattern.
|
|
82
|
+
*/
|
|
83
|
+
const getRepeatedPattern = (characters) => {
|
|
84
|
+
const maxLength = Math.floor(characters.length / 2);
|
|
85
|
+
|
|
86
|
+
for (let length = 1; length <= maxLength; length++) {
|
|
87
|
+
if (
|
|
88
|
+
characters.length % length === 0 &&
|
|
89
|
+
characters.every(
|
|
90
|
+
(character, index) =>
|
|
91
|
+
character === characters[index % length],
|
|
92
|
+
)
|
|
93
|
+
) {
|
|
94
|
+
return characters.slice(0, length);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return null;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Normalizes common password matching.
|
|
103
|
+
* @param {string} password The password.
|
|
104
|
+
* @returns {string} The normalized password.
|
|
105
|
+
*/
|
|
106
|
+
const normalizePassword = (password) =>
|
|
107
|
+
String(password)
|
|
108
|
+
.normalize('NFKC')
|
|
109
|
+
.toLowerCase();
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Normalizes common leet substitutions.
|
|
113
|
+
* @param {string} password The password.
|
|
114
|
+
* @returns {string} The normalized password.
|
|
115
|
+
*/
|
|
116
|
+
const normalizeLeet = (password) =>
|
|
117
|
+
Array.from(
|
|
118
|
+
password,
|
|
119
|
+
(character) => leetCharacters[character] || character,
|
|
120
|
+
).join('');
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Gets normalized variants used to match common passwords.
|
|
124
|
+
* @param {string} password The normalized password.
|
|
125
|
+
* @returns {Set<string>} The password variants.
|
|
126
|
+
*/
|
|
127
|
+
const getPasswordVariants = (password) => {
|
|
128
|
+
// Preserve digits for decoration stripping while resolving symbol substitutions.
|
|
129
|
+
const substituted = password.replace(/[^\p{L}\p{N}]/gu, normalizeLeet);
|
|
130
|
+
const variants = [password, substituted].flatMap((value) => {
|
|
131
|
+
const undecorated = value.replace(
|
|
132
|
+
/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,
|
|
133
|
+
'',
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
return [
|
|
137
|
+
undecorated,
|
|
138
|
+
undecorated.replace(/^\d+|\d+$/g, ''),
|
|
139
|
+
];
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return new Set([
|
|
143
|
+
normalizeLeet(password),
|
|
144
|
+
...variants,
|
|
145
|
+
...variants.map(normalizeLeet),
|
|
146
|
+
]);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Gets the score for a common password.
|
|
151
|
+
* @param {string} password The normalized password.
|
|
152
|
+
* @param {string[]} commonPasswords The common passwords.
|
|
153
|
+
* @returns {number|null} The common-password score.
|
|
154
|
+
*/
|
|
155
|
+
const getCommonPasswordStrength = (password, commonPasswords) => {
|
|
156
|
+
const common = new Set(
|
|
157
|
+
commonPasswords.map(normalizePassword),
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
if (common.has(password)) {
|
|
161
|
+
return 0;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return common.isDisjointFrom(getPasswordVariants(password)) ? null : 5;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Checks whether characters form an ascending or descending sequence.
|
|
169
|
+
* @param {string[]} characters The password characters.
|
|
170
|
+
* @returns {boolean} Whether the characters form a sequence.
|
|
171
|
+
*/
|
|
172
|
+
const isSequence = (characters) => {
|
|
173
|
+
const [first, second, third] = characters.map(
|
|
174
|
+
(character) => character.toLowerCase(),
|
|
175
|
+
);
|
|
176
|
+
const type = getCharacterType(first);
|
|
177
|
+
const direction = second.codePointAt(0) - first.codePointAt(0);
|
|
178
|
+
|
|
179
|
+
return ['lower', 'number'].includes(type) &&
|
|
180
|
+
getCharacterType(second) === type &&
|
|
181
|
+
getCharacterType(third) === type &&
|
|
182
|
+
Math.abs(direction) === 1 &&
|
|
183
|
+
third.codePointAt(0) - second.codePointAt(0) === direction;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Checks whether characters form a keyboard sequence.
|
|
188
|
+
* @param {string[]} characters The password characters.
|
|
189
|
+
* @returns {boolean} Whether the characters form a keyboard sequence.
|
|
190
|
+
*/
|
|
191
|
+
const isKeyboardSequence = (characters) => {
|
|
192
|
+
const sequence = characters.join('').toLowerCase();
|
|
193
|
+
|
|
194
|
+
return keyboardSequences.some(
|
|
195
|
+
(keyboardSequence) => keyboardSequence.includes(sequence),
|
|
196
|
+
);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Checks whether characters form a predictable pattern.
|
|
201
|
+
* @param {string[]} characters The password characters.
|
|
202
|
+
* @returns {boolean} Whether the characters form a predictable pattern.
|
|
203
|
+
*/
|
|
204
|
+
const isPredictable = (characters) =>
|
|
205
|
+
new Set(characters).size === 1 ||
|
|
206
|
+
isSequence(characters) ||
|
|
207
|
+
isKeyboardSequence(characters);
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Gets the number of characters in predictable patterns.
|
|
211
|
+
* @param {string[]} characters The password characters.
|
|
212
|
+
* @returns {number} The number of predictable characters.
|
|
213
|
+
*/
|
|
214
|
+
const getPredictableCount = (characters) => {
|
|
215
|
+
const indices = new Set();
|
|
216
|
+
|
|
217
|
+
for (let i = 0; i + 2 < characters.length; i++) {
|
|
218
|
+
if (isPredictable(characters.slice(i, i + 3))) {
|
|
219
|
+
indices.add(i);
|
|
220
|
+
indices.add(i + 1);
|
|
221
|
+
indices.add(i + 2);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return indices.size;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Calculates the strength of a password.
|
|
230
|
+
* @param {string} password The password.
|
|
231
|
+
* @param {string[]} [commonPasswords=[]] Passwords to score as very weak.
|
|
232
|
+
* @returns {number} The password strength, from 0 to 100.
|
|
233
|
+
*/
|
|
234
|
+
export const getStrength = (password, commonPasswords = []) => {
|
|
235
|
+
const normalizedPassword = password.normalize('NFKC');
|
|
236
|
+
const characters = Array.from(normalizedPassword);
|
|
237
|
+
|
|
238
|
+
if (!characters.length) {
|
|
239
|
+
return 0;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const commonStrength = getCommonPasswordStrength(
|
|
243
|
+
normalizedPassword.toLowerCase(),
|
|
244
|
+
commonPasswords,
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
if (commonStrength !== null) {
|
|
248
|
+
return commonStrength;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const score = getLengthStrength(characters);
|
|
252
|
+
const repeatedPattern = getRepeatedPattern(characters);
|
|
253
|
+
|
|
254
|
+
if (repeatedPattern?.length === 1) {
|
|
255
|
+
return 0;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (repeatedPattern) {
|
|
259
|
+
return Math.min(
|
|
260
|
+
score,
|
|
261
|
+
getLengthStrength(repeatedPattern),
|
|
262
|
+
15,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const predictableCount = getPredictableCount(characters);
|
|
267
|
+
|
|
268
|
+
return $._clamp(
|
|
269
|
+
score - predictableCount,
|
|
270
|
+
0,
|
|
271
|
+
predictableCount === characters.length ? 15 : 100,
|
|
272
|
+
);
|
|
273
|
+
};
|
package/src/prototype/events.js
DELETED
package/src/prototype/helpers.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import $ from '@fr0st/query';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Refresh the password strength.
|
|
5
|
-
*/
|
|
6
|
-
export function _refresh() {
|
|
7
|
-
const strength = this.getStrength();
|
|
8
|
-
|
|
9
|
-
let nextLevel;
|
|
10
|
-
for (const level of this._options.levels) {
|
|
11
|
-
if (strength < level.score) {
|
|
12
|
-
break;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
nextLevel = level;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
$.setStyle(this._progressBar, { width: `${strength}%` });
|
|
19
|
-
$.setAttribute(this._progressBar, {
|
|
20
|
-
'class': this.constructor.classes.progressBar,
|
|
21
|
-
'aria-valuenow': strength,
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
$.addClass(this._progressBar, nextLevel.class);
|
|
25
|
-
|
|
26
|
-
if (this._options.striped) {
|
|
27
|
-
$.addClass(this._progressBar, this.constructor.classes.progressBarStriped);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (nextLevel.text) {
|
|
31
|
-
$.setText(this._progressBar, nextLevel.text);
|
|
32
|
-
}
|
|
33
|
-
};
|
package/src/prototype/render.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import $ from '@fr0st/query';
|
|
2
|
-
import { generateId } from '@fr0st/ui';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Render the password strength element.
|
|
6
|
-
*/
|
|
7
|
-
export function _render() {
|
|
8
|
-
this._progress = $.create('div', {
|
|
9
|
-
class: this.constructor.classes.progress,
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
const id = generateId('password-strength');
|
|
13
|
-
|
|
14
|
-
this._progressBar = $.create('div', {
|
|
15
|
-
attributes: {
|
|
16
|
-
'id': id,
|
|
17
|
-
'role': 'progressbar',
|
|
18
|
-
'aria-valuemin': 0,
|
|
19
|
-
'aria-valuemax': 100,
|
|
20
|
-
},
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
$.append(this._progress, this._progressBar);
|
|
24
|
-
$.append(this._container, this._progress);
|
|
25
|
-
|
|
26
|
-
$.setAttribute(this._node, {
|
|
27
|
-
'aria-describedby': id,
|
|
28
|
-
});
|
|
29
|
-
};
|