pwstrength-bootstrap.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. /*!
  2. * jQuery Password Strength plugin for Twitter Bootstrap
  3. *
  4. * Copyright (c) 2008-2013 Tane Piper
  5. * Copyright (c) 2013 Alejandro Blanco
  6. * Dual licensed under the MIT and GPL licenses.
  7. */
  8. (function (jQuery) {
  9. // Source: src/rules.js
  10. var rulesEngine = {};
  11. try {
  12. if (!jQuery && module && module.exports) {
  13. var jQuery = require("jquery"),
  14. jsdom = require("jsdom").jsdom;
  15. jQuery = jQuery(jsdom().parentWindow);
  16. }
  17. } catch (ignore) {}
  18. (function ($, rulesEngine) {
  19. "use strict";
  20. var validation = {};
  21. rulesEngine.forbiddenSequences = [
  22. "0123456789", "abcdefghijklmnopqrstuvwxyz", "qwertyuiop", "asdfghjkl",
  23. "zxcvbnm", "!@#$%^&*()_+"
  24. ];
  25. validation.wordNotEmail = function (options, word, score) {
  26. if (word.match(/^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$/i)) {
  27. return score;
  28. }
  29. return 0;
  30. };
  31. validation.wordLength = function (options, word, score) {
  32. var wordlen = word.length,
  33. lenScore = Math.pow(wordlen, options.rules.raisePower);
  34. if (wordlen < options.common.minChar) {
  35. lenScore = (lenScore + score);
  36. }
  37. return lenScore;
  38. };
  39. validation.wordSimilarToUsername = function (options, word, score) {
  40. var username = $(options.common.usernameField).val();
  41. if (username && word.toLowerCase().match(username.toLowerCase())) {
  42. return score;
  43. }
  44. return 0;
  45. };
  46. validation.wordTwoCharacterClasses = function (options, word, score) {
  47. if (word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) ||
  48. (word.match(/([a-zA-Z])/) && word.match(/([0-9])/)) ||
  49. (word.match(/(.[!,@,#,$,%,\^,&,*,?,_,~])/) && word.match(/[a-zA-Z0-9_]/))) {
  50. return score;
  51. }
  52. return 0;
  53. };
  54. validation.wordRepetitions = function (options, word, score) {
  55. if (word.match(/(.)\1\1/)) { return score; }
  56. return 0;
  57. };
  58. validation.wordSequences = function (options, word, score) {
  59. var found = false,
  60. j;
  61. if (word.length > 2) {
  62. $.each(rulesEngine.forbiddenSequences, function (idx, seq) {
  63. var sequences = [seq, seq.split('').reverse().join('')];
  64. $.each(sequences, function (idx, sequence) {
  65. for (j = 0; j < (word.length - 2); j += 1) { // iterate the word trough a sliding window of size 3:
  66. if (sequence.indexOf(word.toLowerCase().substring(j, j + 3)) > -1) {
  67. found = true;
  68. }
  69. }
  70. });
  71. });
  72. if (found) { return score; }
  73. }
  74. return 0;
  75. };
  76. validation.wordLowercase = function (options, word, score) {
  77. return word.match(/[a-z]/) && score;
  78. };
  79. validation.wordUppercase = function (options, word, score) {
  80. return word.match(/[A-Z]/) && score;
  81. };
  82. validation.wordOneNumber = function (options, word, score) {
  83. return word.match(/\d+/) && score;
  84. };
  85. validation.wordThreeNumbers = function (options, word, score) {
  86. return word.match(/(.*[0-9].*[0-9].*[0-9])/) && score;
  87. };
  88. validation.wordOneSpecialChar = function (options, word, score) {
  89. return word.match(/.[!,@,#,$,%,\^,&,*,?,_,~]/) && score;
  90. };
  91. validation.wordTwoSpecialChar = function (options, word, score) {
  92. return word.match(/(.*[!,@,#,$,%,\^,&,*,?,_,~].*[!,@,#,$,%,\^,&,*,?,_,~])/) && score;
  93. };
  94. validation.wordUpperLowerCombo = function (options, word, score) {
  95. return word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) && score;
  96. };
  97. validation.wordLetterNumberCombo = function (options, word, score) {
  98. return word.match(/([a-zA-Z])/) && word.match(/([0-9])/) && score;
  99. };
  100. validation.wordLetterNumberCharCombo = function (options, word, score) {
  101. return word.match(/([a-zA-Z0-9].*[!,@,#,$,%,\^,&,*,?,_,~])|([!,@,#,$,%,\^,&,*,?,_,~].*[a-zA-Z0-9])/) && score;
  102. };
  103. rulesEngine.validation = validation;
  104. rulesEngine.executeRules = function (options, word) {
  105. var totalScore = 0;
  106. $.each(options.rules.activated, function (rule, active) {
  107. if (active) {
  108. var score = options.rules.scores[rule],
  109. funct = rulesEngine.validation[rule],
  110. result,
  111. errorMessage;
  112. if (!$.isFunction(funct)) {
  113. funct = options.rules.extra[rule];
  114. }
  115. if ($.isFunction(funct)) {
  116. result = funct(options, word, score);
  117. if (result) {
  118. totalScore += result;
  119. }
  120. if (result < 0 || (!$.isNumeric(result) && !result)) {
  121. errorMessage = options.ui.spanError(options, rule);
  122. if (errorMessage.length > 0) {
  123. options.instances.errors.push(errorMessage);
  124. }
  125. }
  126. }
  127. }
  128. });
  129. return totalScore;
  130. };
  131. }(jQuery, rulesEngine));
  132. try {
  133. if (module && module.exports) {
  134. module.exports = rulesEngine;
  135. }
  136. } catch (ignore) {}
  137. // Source: src/options.js
  138. var defaultOptions = {};
  139. defaultOptions.common = {};
  140. defaultOptions.common.minChar = 6;
  141. defaultOptions.common.usernameField = "#username";
  142. defaultOptions.common.userInputs = [
  143. // Selectors for input fields with user input
  144. ];
  145. defaultOptions.common.onLoad = undefined;
  146. defaultOptions.common.onKeyUp = undefined;
  147. defaultOptions.common.zxcvbn = false;
  148. defaultOptions.common.debug = false;
  149. defaultOptions.rules = {};
  150. defaultOptions.rules.extra = {};
  151. defaultOptions.rules.scores = {
  152. wordNotEmail: -100,
  153. wordLength: -50,
  154. wordSimilarToUsername: -100,
  155. wordSequences: -50,
  156. wordTwoCharacterClasses: 2,
  157. wordRepetitions: -25,
  158. wordLowercase: 1,
  159. wordUppercase: 3,
  160. wordOneNumber: 3,
  161. wordThreeNumbers: 5,
  162. wordOneSpecialChar: 3,
  163. wordTwoSpecialChar: 5,
  164. wordUpperLowerCombo: 2,
  165. wordLetterNumberCombo: 2,
  166. wordLetterNumberCharCombo: 2
  167. };
  168. defaultOptions.rules.activated = {
  169. wordNotEmail: true,
  170. wordLength: true,
  171. wordSimilarToUsername: true,
  172. wordSequences: true,
  173. wordTwoCharacterClasses: false,
  174. wordRepetitions: false,
  175. wordLowercase: true,
  176. wordUppercase: true,
  177. wordOneNumber: true,
  178. wordThreeNumbers: true,
  179. wordOneSpecialChar: true,
  180. wordTwoSpecialChar: true,
  181. wordUpperLowerCombo: true,
  182. wordLetterNumberCombo: true,
  183. wordLetterNumberCharCombo: true
  184. };
  185. defaultOptions.rules.raisePower = 1.4;
  186. defaultOptions.ui = {};
  187. defaultOptions.ui.bootstrap2 = false;
  188. defaultOptions.ui.showProgressBar = true;
  189. defaultOptions.ui.showPopover = false;
  190. defaultOptions.ui.showStatus = false;
  191. defaultOptions.ui.spanError = function (options, key) {
  192. "use strict";
  193. var text = options.ui.errorMessages[key];
  194. if (!text) { return ''; }
  195. return '<span style="color: #d52929">' + text + '</span>';
  196. };
  197. defaultOptions.ui.errorMessages = {
  198. wordLength: "Your password is too short",
  199. wordNotEmail: "Do not use your email as your password",
  200. wordSimilarToUsername: "Your password cannot contain your username",
  201. wordTwoCharacterClasses: "Use different character classes",
  202. wordRepetitions: "Too many repetitions",
  203. wordSequences: "Your password contains sequences"
  204. };
  205. defaultOptions.ui.verdicts = ["Weak", "Normal", "Medium", "Strong", "Very Strong"];
  206. defaultOptions.ui.showVerdicts = true;
  207. defaultOptions.ui.showVerdictsInsideProgressBar = false;
  208. defaultOptions.ui.showErrors = false;
  209. defaultOptions.ui.container = undefined;
  210. defaultOptions.ui.viewports = {
  211. progress: undefined,
  212. verdict: undefined,
  213. errors: undefined
  214. };
  215. defaultOptions.ui.scores = [14, 26, 38, 50];
  216. // Source: src/ui.js
  217. var ui = {};
  218. (function ($, ui) {
  219. "use strict";
  220. var barClasses = ["danger", "warning", "success"],
  221. statusClasses = ["error", "warning", "success"];
  222. ui.getContainer = function (options, $el) {
  223. var $container;
  224. $container = $(options.ui.container);
  225. if (!($container && $container.length === 1)) {
  226. $container = $el.parent();
  227. }
  228. return $container;
  229. };
  230. ui.findElement = function ($container, viewport, cssSelector) {
  231. if (viewport) {
  232. return $container.find(viewport).find(cssSelector);
  233. }
  234. return $container.find(cssSelector);
  235. };
  236. ui.getUIElements = function (options, $el) {
  237. var $container, result;
  238. if (options.instances.viewports) {
  239. return options.instances.viewports;
  240. }
  241. $container = ui.getContainer(options, $el);
  242. result = {};
  243. result.$progressbar = ui.findElement($container, options.ui.viewports.progress, "div.progress");
  244. if (options.ui.showVerdictsInsideProgressBar) {
  245. result.$verdict = result.$progressbar.find("span.password-verdict");
  246. }
  247. if (!options.ui.showPopover) {
  248. if (!options.ui.showVerdictsInsideProgressBar) {
  249. result.$verdict = ui.findElement($container, options.ui.viewports.verdict, "span.password-verdict");
  250. }
  251. result.$errors = ui.findElement($container, options.ui.viewports.errors, "ul.error-list");
  252. }
  253. options.instances.viewports = result;
  254. return result;
  255. };
  256. ui.initProgressBar = function (options, $el) {
  257. var $container = ui.getContainer(options, $el),
  258. progressbar = "<div class='progress'><div class='";
  259. if (!options.ui.bootstrap2) {
  260. progressbar += "progress-";
  261. }
  262. progressbar += "bar'>";
  263. if (options.ui.showVerdictsInsideProgressBar) {
  264. progressbar += "<span class='password-verdict'></span>";
  265. }
  266. progressbar += "</div></div>";
  267. if (options.ui.viewports.progress) {
  268. $container.find(options.ui.viewports.progress).append(progressbar);
  269. } else {
  270. $(progressbar).insertAfter($el);
  271. }
  272. };
  273. ui.initHelper = function (options, $el, html, viewport) {
  274. var $container = ui.getContainer(options, $el);
  275. if (viewport) {
  276. $container.find(viewport).append(html);
  277. } else {
  278. $(html).insertAfter($el);
  279. }
  280. };
  281. ui.initVerdict = function (options, $el) {
  282. ui.initHelper(options, $el, "<span class='password-verdict'></span>",
  283. options.ui.viewports.verdict);
  284. };
  285. ui.initErrorList = function (options, $el) {
  286. ui.initHelper(options, $el, "<ul class='error-list'></ul>",
  287. options.ui.viewports.errors);
  288. };
  289. ui.initPopover = function (options, $el) {
  290. $el.popover("destroy");
  291. $el.popover({
  292. html: true,
  293. placement: "bottom",
  294. trigger: "manual",
  295. content: " "
  296. });
  297. };
  298. ui.initUI = function (options, $el) {
  299. if (options.ui.showPopover) {
  300. ui.initPopover(options, $el);
  301. } else {
  302. if (options.ui.showErrors) { ui.initErrorList(options, $el); }
  303. if (options.ui.showVerdicts && !options.ui.showVerdictsInsideProgressBar) {
  304. ui.initVerdict(options, $el);
  305. }
  306. }
  307. if (options.ui.showProgressBar) {
  308. ui.initProgressBar(options, $el);
  309. }
  310. };
  311. ui.possibleProgressBarClasses = ["danger", "warning", "success"];
  312. ui.updateProgressBar = function (options, $el, cssClass, percentage) {
  313. var $progressbar = ui.getUIElements(options, $el).$progressbar,
  314. $bar = $progressbar.find(".progress-bar"),
  315. cssPrefix = "progress-";
  316. if (options.ui.bootstrap2) {
  317. $bar = $progressbar.find(".bar");
  318. cssPrefix = "";
  319. }
  320. $.each(ui.possibleProgressBarClasses, function (idx, value) {
  321. $bar.removeClass(cssPrefix + "bar-" + value);
  322. });
  323. $bar.addClass(cssPrefix + "bar-" + barClasses[cssClass]);
  324. $bar.css("width", percentage + '%');
  325. };
  326. ui.updateVerdict = function (options, $el, text) {
  327. var $verdict = ui.getUIElements(options, $el).$verdict;
  328. $verdict.text(text);
  329. };
  330. ui.updateErrors = function (options, $el) {
  331. var $errors = ui.getUIElements(options, $el).$errors,
  332. html = "";
  333. $.each(options.instances.errors, function (idx, err) {
  334. html += "<li>" + err + "</li>";
  335. });
  336. $errors.html(html);
  337. };
  338. ui.updatePopover = function (options, $el, verdictText) {
  339. var popover = $el.data("bs.popover"),
  340. html = "",
  341. hide = true;
  342. if (options.ui.showVerdicts &&
  343. !options.ui.showVerdictsInsideProgressBar &&
  344. verdictText.length > 0) {
  345. html = "<h5><span class='password-verdict'>" + verdictText +
  346. "</span></h5>";
  347. hide = false;
  348. }
  349. if (options.ui.showErrors) {
  350. html += "<div>Errors:<ul class='error-list' style='margin-bottom: 0;'>";
  351. $.each(options.instances.errors, function (idx, err) {
  352. html += "<li>" + err + "</li>";
  353. hide = false;
  354. });
  355. html += "</ul></div>";
  356. }
  357. if (hide) {
  358. $el.popover("hide");
  359. return;
  360. }
  361. if (options.ui.bootstrap2) { popover = $el.data("popover"); }
  362. if (popover.$arrow && popover.$arrow.parents("body").length > 0) {
  363. $el.find("+ .popover .popover-content").html(html);
  364. } else {
  365. // It's hidden
  366. popover.options.content = html;
  367. $el.popover("show");
  368. }
  369. };
  370. ui.updateFieldStatus = function (options, $el, cssClass) {
  371. var targetClass = options.ui.bootstrap2 ? ".control-group" : ".form-group",
  372. $container = $el.parents(targetClass).first();
  373. $.each(statusClasses, function (idx, css) {
  374. if (!options.ui.bootstrap2) { css = "has-" + css; }
  375. $container.removeClass(css);
  376. });
  377. cssClass = statusClasses[cssClass];
  378. if (!options.ui.bootstrap2) { cssClass = "has-" + cssClass; }
  379. $container.addClass(cssClass);
  380. };
  381. ui.percentage = function (score, maximun) {
  382. var result = Math.floor(100 * score / maximun);
  383. result = result < 0 ? 0 : result;
  384. result = result > 100 ? 100 : result;
  385. return result;
  386. };
  387. ui.getVerdictAndCssClass = function (options, score) {
  388. var cssClass, verdictText, level;
  389. if (score <= 0) {
  390. cssClass = 0;
  391. level = -1;
  392. verdictText = options.ui.verdicts[0];
  393. } else if (score < options.ui.scores[0]) {
  394. cssClass = 0;
  395. level = 0;
  396. verdictText = options.ui.verdicts[0];
  397. } else if (score < options.ui.scores[1]) {
  398. cssClass = 0;
  399. level = 1;
  400. verdictText = options.ui.verdicts[1];
  401. } else if (score < options.ui.scores[2]) {
  402. cssClass = 1;
  403. level = 2;
  404. verdictText = options.ui.verdicts[2];
  405. } else if (score < options.ui.scores[3]) {
  406. cssClass = 1;
  407. level = 3;
  408. verdictText = options.ui.verdicts[3];
  409. } else {
  410. cssClass = 2;
  411. level = 4;
  412. verdictText = options.ui.verdicts[4];
  413. }
  414. return [verdictText, cssClass, level];
  415. };
  416. ui.updateUI = function (options, $el, score) {
  417. var cssClass, barPercentage, verdictText;
  418. cssClass = ui.getVerdictAndCssClass(options, score);
  419. verdictText = cssClass[0];
  420. cssClass = cssClass[1];
  421. if (options.ui.showProgressBar) {
  422. barPercentage = ui.percentage(score, options.ui.scores[3]);
  423. ui.updateProgressBar(options, $el, cssClass, barPercentage);
  424. if (options.ui.showVerdictsInsideProgressBar) {
  425. ui.updateVerdict(options, $el, verdictText);
  426. }
  427. }
  428. if (options.ui.showStatus) {
  429. ui.updateFieldStatus(options, $el, cssClass);
  430. }
  431. if (options.ui.showPopover) {
  432. ui.updatePopover(options, $el, verdictText);
  433. } else {
  434. if (options.ui.showVerdicts && !options.ui.showVerdictsInsideProgressBar) {
  435. ui.updateVerdict(options, $el, verdictText);
  436. }
  437. if (options.ui.showErrors) {
  438. ui.updateErrors(options, $el);
  439. }
  440. }
  441. };
  442. }(jQuery, ui));
  443. // Source: src/methods.js
  444. var methods = {};
  445. (function ($, methods) {
  446. "use strict";
  447. var onKeyUp, applyToAll;
  448. onKeyUp = function (event) {
  449. var $el = $(event.target),
  450. options = $el.data("pwstrength-bootstrap"),
  451. word = $el.val(),
  452. userInputs,
  453. verdictText,
  454. verdictLevel,
  455. score;
  456. if (options === undefined) { return; }
  457. options.instances.errors = [];
  458. if (options.common.zxcvbn) {
  459. userInputs = [];
  460. $.each(options.common.userInputs, function (idx, selector) {
  461. userInputs.push($(selector).val());
  462. });
  463. userInputs.push($(options.common.usernameField).val());
  464. score = zxcvbn(word, userInputs).entropy;
  465. } else {
  466. score = rulesEngine.executeRules(options, word);
  467. }
  468. ui.updateUI(options, $el, score);
  469. verdictText = ui.getVerdictAndCssClass(options, score);
  470. verdictLevel = verdictText[2];
  471. verdictText = verdictText[0];
  472. if (options.common.debug) { console.log(score + ' - ' + verdictText); }
  473. if ($.isFunction(options.common.onKeyUp)) {
  474. options.common.onKeyUp(event, {
  475. score: score,
  476. verdictText: verdictText,
  477. verdictLevel: verdictLevel
  478. });
  479. }
  480. };
  481. methods.init = function (settings) {
  482. this.each(function (idx, el) {
  483. // Make it deep extend (first param) so it extends too the
  484. // rules and other inside objects
  485. var clonedDefaults = $.extend(true, {}, defaultOptions),
  486. localOptions = $.extend(true, clonedDefaults, settings),
  487. $el = $(el);
  488. localOptions.instances = {};
  489. $el.data("pwstrength-bootstrap", localOptions);
  490. $el.on("keyup", onKeyUp);
  491. $el.on("change", onKeyUp);
  492. $el.on("onpaste", onKeyUp);
  493. ui.initUI(localOptions, $el);
  494. if ($.trim($el.val())) { // Not empty, calculate the strength
  495. $el.trigger("keyup");
  496. }
  497. if ($.isFunction(localOptions.common.onLoad)) {
  498. localOptions.common.onLoad();
  499. }
  500. });
  501. return this;
  502. };
  503. methods.destroy = function () {
  504. this.each(function (idx, el) {
  505. var $el = $(el),
  506. options = $el.data("pwstrength-bootstrap"),
  507. elements = ui.getUIElements(options, $el);
  508. elements.$progressbar.remove();
  509. elements.$verdict.remove();
  510. elements.$errors.remove();
  511. $el.removeData("pwstrength-bootstrap");
  512. });
  513. };
  514. methods.forceUpdate = function () {
  515. this.each(function (idx, el) {
  516. var event = { target: el };
  517. onKeyUp(event);
  518. });
  519. };
  520. methods.addRule = function (name, method, score, active) {
  521. this.each(function (idx, el) {
  522. var options = $(el).data("pwstrength-bootstrap");
  523. options.rules.activated[name] = active;
  524. options.rules.scores[name] = score;
  525. options.rules.extra[name] = method;
  526. });
  527. };
  528. applyToAll = function (rule, prop, value) {
  529. this.each(function (idx, el) {
  530. $(el).data("pwstrength-bootstrap").rules[prop][rule] = value;
  531. });
  532. };
  533. methods.changeScore = function (rule, score) {
  534. applyToAll.call(this, rule, "scores", score);
  535. };
  536. methods.ruleActive = function (rule, active) {
  537. applyToAll.call(this, rule, "activated", active);
  538. };
  539. $.fn.pwstrength = function (method) {
  540. var result;
  541. if (methods[method]) {
  542. result = methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
  543. } else if (typeof method === "object" || !method) {
  544. result = methods.init.apply(this, arguments);
  545. } else {
  546. $.error("Method " + method + " does not exist on jQuery.pwstrength-bootstrap");
  547. }
  548. return result;
  549. };
  550. }(jQuery, methods));
  551. }(jQuery));