UNPKG

10.7 kBJavaScriptView Raw
1import BaseComponent from 'bootstrap/js/src/base-component'
2import EventHandler from 'bootstrap/js/src/dom/event-handler'
3import SelectorEngine from 'bootstrap/js/src/dom/selector-engine'
4import Manipulator from 'bootstrap/js/src/dom/manipulator'
5
6//import Input from './input'
7import InputLabel from './input-label'
8
9const NAME = 'inputpassword'
10const DATA_KEY = 'bs.inputpassword'
11const EVENT_KEY = `.${DATA_KEY}`
12//const DATA_API_KEY = '.data-api'
13
14const Default = {
15 shortPass: 'Password molto debole',
16 badPass: 'Password debole',
17 goodPass: 'Password sicura',
18 strongPass: 'Password molto sicura',
19 enterPass: 'Inserisci almeno 8 caratteri e una lettera maiuscola',
20 alertCaps: 'CAPS LOCK inserito',
21 showText: true,
22 minimumLength: 4,
23}
24
25const EVENT_CLICK = `click${EVENT_KEY}`
26const EVENT_KEYUP = `keyup${EVENT_KEY}`
27const EVENT_KEYDOWN = `keydown${EVENT_KEY}`
28const EVENT_KEYPRESS = `keypress${EVENT_KEY}`
29const EVENT_SCORE = `score${EVENT_KEY}`
30const EVENT_TEXT = `text${EVENT_KEY}`
31
32const CLASS_NAME_PASSWORD = 'input-password'
33const CLASS_NAME_METER = 'input-password-strength-meter'
34
35const SELECTOR_PASSWORD = 'input[data-bs-input][type="password"]'
36const SELECTOR_BTN_SHOW_PWD = '.password-icon'
37
38class InputPassword extends BaseComponent {
39 constructor(element, config) {
40 super(element)
41
42 this._config = this._getConfig(config)
43 this._isCustom = this._element.classList.contains(CLASS_NAME_PASSWORD)
44 this._hasMeter = this._element.classList.contains(CLASS_NAME_METER)
45 this._isShiftPressed = false
46 this._isCapsOn = false
47
48 this._grayBarElement = null
49 this._colorBarElement = null
50 this._textElement = null
51 this._capsElement = null
52 this._showPwdElement = null
53
54 this._label = new InputLabel(element)
55
56 this._init()
57 this._bindEvents()
58 }
59
60 // Getters
61
62 static get NAME() {
63 return NAME
64 }
65
66 // Public
67
68 // Private
69 _getConfig(config) {
70 config = {
71 ...Default,
72 ...Manipulator.getDataAttributes(this._element),
73 ...(typeof config === 'object' ? config : {}),
74 }
75 return config
76 }
77
78 _init() {
79 if (this._hasMeter) {
80 this._grayBarElement = document.createElement('div')
81 this._grayBarElement.classList.add('password-meter', 'progress', 'rounded-0', 'position-absolute')
82 this._grayBarElement.innerHTML = `<div class="row position-absolute w-100 m-0">
83 <div class="col-3 border-start border-end border-white"></div>
84 <div class="col-3 border-start border-end border-white"></div>
85 <div class="col-3 border-start border-end border-white"></div>
86 <div class="col-3 border-start border-end border-white"></div>
87 </div>`
88
89 this._colorBarElement = document.createElement('div')
90 this._colorBarElement.classList.add('progress-bar')
91 this._colorBarElement.setAttribute('role', 'progressbar')
92 this._colorBarElement.setAttribute('aria-valuenow', '0')
93 this._colorBarElement.setAttribute('aria-valuemin', '0')
94 this._colorBarElement.setAttribute('aria-valuemax', '100')
95
96 const wrapper = document.createElement('div')
97 wrapper.classList.add('password-strength-meter')
98
99 this._grayBarElement.appendChild(this._colorBarElement)
100
101 if (this._config.showText) {
102 this._textElement = document.createElement('small')
103 this._textElement.classList.add('form-text', 'text-muted')
104 this._textElement.innerHTML = this._config.enterPass
105 wrapper.appendChild(this._textElement)
106 }
107
108 wrapper.appendChild(this._grayBarElement)
109
110 this._element.parentNode.insertBefore(wrapper, this._element.nextSibling)
111 }
112 if (this._isCustom) {
113 this._capsElement = document.createElement('small')
114 this._capsElement.style.display = 'none'
115 this._capsElement.classList.add('password-caps', 'form-text', 'text-warning', 'position-absolute', 'bg-white', 'w-100')
116 this._capsElement.innerHTML = this._config.alertCaps
117
118 this._element.parentNode.appendChild(this._capsElement)
119 }
120
121 this._showPwdElement = SelectorEngine.findOne(SELECTOR_BTN_SHOW_PWD, this._element.parentNode)
122 }
123
124 _bindEvents() {
125 if (this._hasMeter) {
126 EventHandler.on(this._element, EVENT_KEYUP, () => this._checkPassword())
127 }
128
129 if (this._isCustom) {
130 EventHandler.on(this._element, EVENT_KEYDOWN, (evt) => {
131 if (evt.key === 'Shift') {
132 this._isShiftPressed = true
133 }
134 })
135 EventHandler.on(this._element, EVENT_KEYUP, (evt) => {
136 if (evt.key === 'Shift') {
137 this._isShiftPressed = false
138 }
139 if (evt.key === 'CapsLock') {
140 this._isCapsOn = !this._isCapsOn
141 if (this._isCapsOn) {
142 this._showCapsMsg()
143 } else {
144 this._hideCapsMsg()
145 }
146 }
147 })
148 EventHandler.on(this._element, EVENT_KEYPRESS, (evt) => {
149 const matches = evt.key.match(/[A-Z]$/) || []
150 if (matches.length > 0 && !this._isShiftPressed) {
151 this._isCapsOn = true
152 this._showCapsMsg()
153 } else if (this._isCapsOn) {
154 this._isCapsOn = false
155 this._hideCapsMsg()
156 }
157 })
158 }
159
160 if (this._showPwdElement) {
161 EventHandler.on(this._showPwdElement, EVENT_CLICK, () => this._toggleShowPassword())
162 }
163 }
164
165 _showCapsMsg() {
166 this._capsElement.style.display = 'block'
167 }
168 _hideCapsMsg() {
169 this._capsElement.style.display = 'none'
170 }
171
172 _toggleShowPassword() {
173 const toShow = this._element.getAttribute('type') === 'password'
174 SelectorEngine.find('[class^="password-icon"]', this._showPwdElement).forEach((icon) => icon.classList.toggle('d-none'))
175 if (toShow) {
176 this._element.setAttribute('type', 'text')
177 } else {
178 this._element.setAttribute('type', 'password')
179 }
180 }
181
182 _checkPassword() {
183 const score = this._calculateScore(this._element.value)
184 const perc = score < 0 ? 0 : score
185
186 this._colorBarElement.classList.forEach((className) => {
187 if (className.match(/(^|\s)bg-\S+/g)) {
188 this._colorBarElement.classList.remove(className)
189 }
190 })
191 this._colorBarElement.classList.add('bg-' + this._scoreColor(score))
192 this._colorBarElement.style.width = perc + '%'
193 this._colorBarElement.setAttribute('aria-valuenow', perc)
194
195 EventHandler.trigger(this._element, EVENT_SCORE)
196
197 if (this._config.showText) {
198 let text = this._scoreText(score)
199 if (this._element.value.length === 0 && score <= 0) {
200 text = this._config.enterPass
201 }
202
203 if (this._textElement.innerHTML.search(text) === -1) {
204 this._textElement.innerHTML = text
205 this._textElement.classList.forEach((className) => {
206 if (className.match(/(^|\s)text-\S+/g)) {
207 this._textElement.classList.remove(className)
208 }
209 })
210 this._textElement.classList.add('text-' + this._scoreColor(score))
211 EventHandler.trigger(this._element, EVENT_TEXT)
212 }
213 }
214 }
215
216 /**
217 * Returns strings based on the score given.
218 *
219 * @param int score Score base.
220 * @return string
221 */
222 _scoreText(score) {
223 if (score === -1) {
224 return this._config.shortPass
225 }
226
227 score = score < 0 ? 0 : score
228
229 if (score < 26) {
230 return this._config.shortPass
231 }
232 if (score < 51) {
233 return this._config.badPass
234 }
235 if (score < 76) {
236 return this._config.goodPass
237 }
238
239 return this._config.strongPass
240 }
241
242 _scoreColor(score) {
243 if (score === -1) {
244 return 'danger'
245 }
246 if (score === -2) {
247 return 'muted'
248 }
249
250 score = score < 0 ? 0 : score
251
252 if (score < 26) {
253 return 'danger'
254 }
255 if (score < 51) {
256 return 'warning'
257 }
258 if (score < 76) {
259 return 'success'
260 }
261
262 return 'success'
263 }
264
265 /**
266 * Returns a value between -1 and 100 to score
267 * the user's password.
268 *
269 * @param string password The password to be checked.
270 * @return int
271 */
272 _calculateScore(password) {
273 var score = 0
274
275 // empty password
276 if (password.trim().length === 0) {
277 return -2
278 }
279
280 // password < this._config.minimumLength
281 if (password.length < this._config.minimumLength) {
282 return -1
283 }
284
285 // password length
286 score += password.length * 4
287 score += this._checkRepetition(1, password).length - password.length
288 score += this._checkRepetition(2, password).length - password.length
289 score += this._checkRepetition(3, password).length - password.length
290 score += this._checkRepetition(4, password).length - password.length
291
292 // password has 3 numbers
293 if (password.match(/(.*[0-9].*[0-9].*[0-9])/)) {
294 score += 5
295 }
296
297 // password has at least 2 sybols
298 var symbols = '.*[!,@,#,$,%,^,&,*,?,_,~]'
299 symbols = new RegExp('(' + symbols + symbols + ')')
300 if (password.match(symbols)) {
301 score += 5
302 }
303
304 // password has Upper and Lower chars
305 if (password.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/)) {
306 score += 10
307 }
308
309 // password has number and chars
310 if (password.match(/([a-zA-Z])/) && password.match(/([0-9])/)) {
311 score += 15
312 }
313
314 // password has number and symbol
315 if (password.match(/([!,@,#,$,%,^,&,*,?,_,~])/) && password.match(/([0-9])/)) {
316 score += 15
317 }
318
319 // password has char and symbol
320 if (password.match(/([!,@,#,$,%,^,&,*,?,_,~])/) && password.match(/([a-zA-Z])/)) {
321 score += 15
322 }
323
324 // password is just numbers or chars
325 if (password.match(/^\w+$/) || password.match(/^\d+$/)) {
326 score -= 10
327 }
328
329 if (score > 100) {
330 score = 100
331 }
332
333 if (score < 0) {
334 score = 0
335 }
336
337 return score
338 }
339
340 /**
341 * Checks for repetition of characters in
342 * a string
343 *
344 * @param int rLen Repetition length.
345 * @param string str The string to be checked.
346 * @return string
347 */
348 _checkRepetition(rLen, str) {
349 var res = '',
350 repeated = false
351 for (var i = 0; i < str.length; i++) {
352 repeated = true
353 for (var j = 0; j < rLen && j + i + rLen < str.length; j++) {
354 repeated = repeated && str.charAt(j + i) === str.charAt(j + i + rLen)
355 }
356 if (j < rLen) {
357 repeated = false
358 }
359 if (repeated) {
360 i += rLen - 1
361 repeated = false
362 } else {
363 res += str.charAt(i)
364 }
365 }
366 return res
367 }
368}
369
370/**
371 * ------------------------------------------------------------------------
372 * Data Api implementation
373 * ------------------------------------------------------------------------
374 */
375
376const inputs = SelectorEngine.find(SELECTOR_PASSWORD)
377inputs.forEach((input) => {
378 InputPassword.getOrCreateInstance(input)
379})
380
381export default InputPassword