/** * Copyright since 2007 PrestaShop SA and Contributors * PrestaShop is an International Registered Trademark & Property of PrestaShop SA * * NOTICE OF LICENSE * * This source file is subject to the Academic Free License 3.0 (AFL-3.0) * that is bundled with this package in the file LICENSE.md. * It is also available through the world-wide-web at this URL: * https://opensource.org/licenses/AFL-3.0 * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@prestashop.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade PrestaShop to newer * versions in the future. If you wish to customize PrestaShop for your * needs please refer to https://devdocs.prestashop.com/ for more information. * * @author PrestaShop SA and Contributors * @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) */ import {sprintf} from 'sprintf-js'; const {passwordPolicy: PasswordPolicyMap} = prestashop.themeSelectors; const PASSWORD_POLICY_ERROR = 'The password policy elements are undefined.'; const getPasswordStrengthFeedback = ( strength, ) => { switch (strength) { case 0: return { color: 'bg-danger', }; case 1: return { color: 'bg-danger', }; case 2: return { color: 'bg-warning', }; case 3: return { color: 'bg-success', }; case 4: return { color: 'bg-success', }; default: throw new Error('Invalid password strength indicator.'); } }; const watchPassword = async ( elementInput, feedbackContainer, hints, ) => { const {prestashop} = window; const passwordValue = elementInput.value; const elementIcon = feedbackContainer.querySelector(PasswordPolicyMap.requirementScoreIcon); const result = await prestashop.checkPasswordScore(passwordValue); const feedback = getPasswordStrengthFeedback(result.score); const passwordLength = passwordValue.length; const popoverContent = []; $(elementInput).popover('dispose'); feedbackContainer.style.display = passwordValue === '' ? 'none' : 'block'; if (result.feedback.warning !== '') { if (result.feedback.warning in hints) { popoverContent.push(hints[result.feedback.warning]); } } result.feedback.suggestions.forEach((suggestion) => { if (suggestion in hints) { popoverContent.push(hints[suggestion]); } }); $(elementInput).popover({ html: true, placement: 'top', content: popoverContent.join('
'), }).popover('show'); const passwordLengthValid = passwordLength >= parseInt(elementInput.dataset.minlength, 10) && passwordLength <= parseInt(elementInput.dataset.maxlength, 10); const passwordScoreValid = parseInt(elementInput.dataset.minscore, 10) <= result.score; feedbackContainer.querySelector(PasswordPolicyMap.requirementLengthIcon).classList.toggle( 'text-success', passwordLengthValid, ); elementIcon.classList.toggle( 'text-success', passwordScoreValid, ); // Change input border color depending on the validity elementInput .classList.remove('border-success', 'border-danger'); elementInput .classList.add(passwordScoreValid && passwordLengthValid ? 'border-success' : 'border-danger'); elementInput .classList.add('form-control', 'border'); const percentage = (result.score * 20) + 20; const progressBar = feedbackContainer.querySelector(PasswordPolicyMap.progressBar); // increase and decrease progress bar if (progressBar) { progressBar.style.width = `${percentage}%`; progressBar.classList.remove('bg-success', 'bg-danger', 'bg-warning'); progressBar.classList.add(feedback.color); } }; // Not testable because it manipulates SVG elements, unsupported by JSDom const usePasswordPolicy = (selector) => { const elements = document.querySelectorAll(selector); elements.forEach((element) => { const inputColumn = element?.querySelector(PasswordPolicyMap.inputColumn); const elementInput = element?.querySelector('input'); const templateElement = document.createElement('div'); const feedbackTemplate = document.querySelector(PasswordPolicyMap.template); let feedbackContainer; if (feedbackTemplate && element && inputColumn && elementInput) { templateElement.innerHTML = feedbackTemplate.innerHTML; inputColumn.append(templateElement); feedbackContainer = element.querySelector(PasswordPolicyMap.container); if (feedbackContainer) { const hintElement = document.querySelector(PasswordPolicyMap.hint); if (hintElement) { const hints = JSON.parse(hintElement.innerHTML); // eslint-disable-next-line max-len const passwordRequirementsLength = feedbackContainer.querySelector(PasswordPolicyMap.requirementLength); // eslint-disable-next-line max-len const passwordRequirementsScore = feedbackContainer.querySelector(PasswordPolicyMap.requirementScore); const passwordLengthText = passwordRequirementsLength?.querySelector('span'); const passwordRequirementsText = passwordRequirementsScore?.querySelector('span'); if (passwordLengthText && passwordRequirementsLength && passwordRequirementsLength.dataset.translation) { passwordLengthText.innerText = sprintf( passwordRequirementsLength.dataset.translation, elementInput.dataset.minlength, elementInput.dataset.maxlength, ); } if (passwordRequirementsText && passwordRequirementsScore && passwordRequirementsScore.dataset.translation) { passwordRequirementsText.innerText = sprintf( passwordRequirementsScore.dataset.translation, hints[elementInput.dataset.minscore], ); } // eslint-disable-next-line max-len elementInput.addEventListener('keyup', () => watchPassword(elementInput, feedbackContainer, hints)); elementInput.addEventListener('blur', () => { $(elementInput).popover('dispose'); }); } } } if (element) { return { element, }; } return { error: new Error(PASSWORD_POLICY_ERROR), }; }); }; export default usePasswordPolicy;