Add missing default values of `PasswordStrength` properties
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / js / WoltLabSuite / Core / Ui / User / PasswordStrength.js
1 /**
2 * Adds a password strength meter to a password input and exposes
3 * zxcbn's verdict as sibling input.
4 *
5 * @author Tim Duesterhus
6 * @copyright 2001-2020 WoltLab GmbH
7 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
8 * @module WoltLabSuite/Core/Ui/User/PasswordStrength
9 */
10 define(["require", "exports", "tslib", "../../Language", "../../Dom/Util"], function (require, exports, tslib_1, Language, Util_1) {
11 "use strict";
12 var _a;
13 Language = tslib_1.__importStar(Language);
14 Util_1 = tslib_1.__importDefault(Util_1);
15 const STATIC_DICTIONARY = [];
16 const siteName = (_a = document.querySelector('meta[property="og:site_name"]')) === null || _a === void 0 ? void 0 : _a.getAttribute("content");
17 if (siteName) {
18 STATIC_DICTIONARY.push(siteName);
19 }
20 function flatMap(array, callback) {
21 return array.map(callback).reduce((carry, item) => {
22 return carry.concat(item);
23 }, []);
24 }
25 function splitIntoWords(value) {
26 return [].concat(value, value.split(/\W+/));
27 }
28 function initializeFeedbacker(Feedback) {
29 const localizedPhrases = {};
30 Object.entries(Feedback.default_phrases).forEach(([type, phrases]) => {
31 localizedPhrases[type] = {};
32 Object.entries(phrases).forEach(([identifier, phrase]) => {
33 const languageItem = `wcf.user.password.zxcvbn.${type}.${identifier}`;
34 const localizedValue = Language.get(languageItem);
35 localizedPhrases[type][identifier] = localizedValue !== languageItem ? localizedValue : phrase;
36 });
37 });
38 return new Feedback(localizedPhrases);
39 }
40 class PasswordStrength {
41 constructor(input, options) {
42 this.input = input;
43 this.relatedInputs = [];
44 this.staticDictionary = [];
45 this.score = document.createElement("span");
46 this.verdictResult = document.createElement("input");
47 void new Promise((resolve_1, reject_1) => { require(["zxcvbn"], resolve_1, reject_1); }).then(tslib_1.__importStar).then(({ default: zxcvbn }) => {
48 this.zxcvbn = zxcvbn;
49 if (options.relatedInputs) {
50 this.relatedInputs = options.relatedInputs;
51 }
52 if (options.staticDictionary) {
53 this.staticDictionary = options.staticDictionary;
54 }
55 this.feedbacker = initializeFeedbacker(zxcvbn.Feedback);
56 const wrapper = this.input.closest(".inputAddon");
57 if (wrapper === null) {
58 throw new Error("Expected a parent with `.inputAddon`.");
59 }
60 this.wrapper = wrapper;
61 this.wrapper.classList.add("inputAddonPasswordStrength");
62 const rating = document.createElement("div");
63 rating.className = "passwordStrengthRating";
64 const ratingLabel = document.createElement("small");
65 ratingLabel.textContent = Language.get("wcf.user.password.strength");
66 rating.appendChild(ratingLabel);
67 this.score.className = "passwordStrengthScore";
68 this.score.dataset.score = "-1";
69 rating.appendChild(this.score);
70 this.wrapper.appendChild(rating);
71 this.verdictResult.type = "hidden";
72 this.verdictResult.name = `${this.input.name}_passwordStrengthVerdict`;
73 this.wrapper.parentNode.insertBefore(this.verdictResult, this.wrapper);
74 this.input.addEventListener("input", (ev) => this.evaluate(ev));
75 this.relatedInputs.forEach((input) => input.addEventListener("input", (ev) => this.evaluate(ev)));
76 if (this.input.value.trim() !== "") {
77 this.evaluate();
78 }
79 });
80 }
81 evaluate(event) {
82 const dictionary = flatMap(STATIC_DICTIONARY.concat(this.staticDictionary, this.relatedInputs.map((input) => input.value.trim())), splitIntoWords).filter((value) => value.length > 0);
83 const value = this.input.value.trim();
84 // To bound runtime latency for really long passwords, consider sending zxcvbn() only
85 // the first 100 characters or so of user input.
86 const verdict = this.zxcvbn(value.substr(0, 100), dictionary);
87 verdict.feedback = this.feedbacker.from_result(verdict);
88 this.score.dataset.score = value.length === 0 ? "-1" : verdict.score.toString();
89 if (event !== undefined) {
90 // Do not overwrite the value on page load.
91 Util_1.default.innerError(this.wrapper, verdict.feedback.warning);
92 }
93 this.verdictResult.value = JSON.stringify(verdict);
94 }
95 }
96 return PasswordStrength;
97 });