strength.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. /*jslint vars: false, browser: true, nomen: true, regexp: true */
  2. /*global jQuery */
  3. /*
  4. * jQuery Password Strength plugin for Twitter Bootstrap
  5. *
  6. * Copyright (c) 2008-2013 Tane Piper
  7. * Copyright (c) 2013 Alejandro Blanco
  8. * Dual licensed under the MIT and GPL licenses.
  9. *
  10. */
  11. (function ($) {
  12. "use strict";
  13. var options = {
  14. errors: [],
  15. // Options
  16. minChar: 8,
  17. errorMessages: {
  18. password_to_short: "The Password is too short",
  19. same_as_username: "Your password cannot be the same as your username"
  20. },
  21. scores: [17, 26, 40, 50],
  22. verdicts: ["Weak", "Normal", "Medium", "Strong", "Very Strong"],
  23. showVerdicts: true,
  24. raisePower: 1.4,
  25. usernameField: "#username",
  26. onLoad: undefined,
  27. onKeyUp: undefined,
  28. viewports: {
  29. progress: undefined,
  30. verdict: undefined,
  31. errors: undefined
  32. },
  33. // Rules stuff
  34. ruleScores: {
  35. wordNotEmail: -100,
  36. wordLength: -100,
  37. wordSimilarToUsername: -100,
  38. wordLowercase: 1,
  39. wordUppercase: 3,
  40. wordOneNumber: 3,
  41. wordThreeNumbers: 5,
  42. wordOneSpecialChar: 3,
  43. wordTwoSpecialChar: 5,
  44. wordUpperLowerCombo: 2,
  45. wordLetterNumberCombo: 2,
  46. wordLetterNumberCharCombo: 2
  47. },
  48. rules: {
  49. wordNotEmail: true,
  50. wordLength: true,
  51. wordSimilarToUsername: true,
  52. wordLowercase: true,
  53. wordUppercase: true,
  54. wordOneNumber: true,
  55. wordThreeNumbers: true,
  56. wordOneSpecialChar: true,
  57. wordTwoSpecialChar: true,
  58. wordUpperLowerCombo: true,
  59. wordLetterNumberCombo: true,
  60. wordLetterNumberCharCombo: true
  61. },
  62. validationRules: {
  63. wordNotEmail: function (options, word, score) {
  64. return 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) && score;
  65. },
  66. wordLength: function (options, word, score) {
  67. var wordlen = word.length,
  68. lenScore = Math.pow(wordlen, options.raisePower);
  69. if (wordlen < options.minChar) {
  70. lenScore = (lenScore + score);
  71. options.errors.push(options.errorMessages.password_to_short);
  72. }
  73. return lenScore;
  74. },
  75. wordSimilarToUsername: function (options, word, score) {
  76. var username = $(options.usernameField).val();
  77. if (username && word.toLowerCase().match(username.toLowerCase())) {
  78. options.errors.push(options.errorMessages.same_as_username);
  79. return score;
  80. }
  81. return true;
  82. },
  83. wordLowercase: function (options, word, score) {
  84. return word.match(/[a-z]/) && score;
  85. },
  86. wordUppercase: function (options, word, score) {
  87. return word.match(/[A-Z]/) && score;
  88. },
  89. wordOneNumber : function (options, word, score) {
  90. return word.match(/\d+/) && score;
  91. },
  92. wordThreeNumbers : function (options, word, score) {
  93. return word.match(/(.*[0-9].*[0-9].*[0-9])/) && score;
  94. },
  95. wordOneSpecialChar : function (options, word, score) {
  96. return word.match(/.[!,@,#,$,%,\^,&,*,?,_,~]/) && score;
  97. },
  98. wordTwoSpecialChar : function (options, word, score) {
  99. return word.match(/(.*[!,@,#,$,%,\^,&,*,?,_,~].*[!,@,#,$,%,\^,&,*,?,_,~])/) && score;
  100. },
  101. wordUpperLowerCombo : function (options, word, score) {
  102. return word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) && score;
  103. },
  104. wordLetterNumberCombo : function (options, word, score) {
  105. return word.match(/([a-zA-Z])/) && word.match(/([0-9])/) && score;
  106. },
  107. wordLetterNumberCharCombo : function (options, word, score) {
  108. return word.match(/([a-zA-Z0-9].*[!,@,#,$,%,\^,&,*,?,_,~])|([!,@,#,$,%,\^,&,*,?,_,~].*[a-zA-Z0-9])/) && score;
  109. }
  110. }
  111. },
  112. setProgressBar = function ($el, score) {
  113. var options = $el.data("pwstrength"),
  114. progressbar = options.progressbar,
  115. $verdict;
  116. if (options.showVerdicts) {
  117. if (options.viewports.verdict) {
  118. $verdict = $(options.viewports.verdict).find(".password-verdict");
  119. } else {
  120. $verdict = $el.parent().find(".password-verdict");
  121. if ($verdict.length === 0) {
  122. $verdict = $('<span class="password-verdict"></span>');
  123. $verdict.insertAfter($el);
  124. }
  125. }
  126. }
  127. if (score < options.scores[0]) {
  128. progressbar.addClass("progress-danger").removeClass("progress-warning").removeClass("progress-success");
  129. progressbar.find(".bar").css("width", "5%");
  130. if (options.showVerdicts) {
  131. $verdict.text(options.verdicts[0]);
  132. }
  133. } else if (score >= options.scores[0] && score < options.scores[1]) {
  134. progressbar.addClass("progress-danger").removeClass("progress-warning").removeClass("progress-success");
  135. progressbar.find(".bar").css("width", "25%");
  136. if (options.showVerdicts) {
  137. $verdict.text(options.verdicts[1]);
  138. }
  139. } else if (score >= options.scores[1] && score < options.scores[2]) {
  140. progressbar.addClass("progress-warning").removeClass("progress-danger").removeClass("progress-success");
  141. progressbar.find(".bar").css("width", "50%");
  142. if (options.showVerdicts) {
  143. $verdict.text(options.verdicts[2]);
  144. }
  145. } else if (score >= options.scores[2] && score < options.scores[3]) {
  146. progressbar.addClass("progress-warning").removeClass("progress-danger").removeClass("progress-success");
  147. progressbar.find(".bar").css("width", "75%");
  148. if (options.showVerdicts) {
  149. $verdict.text(options.verdicts[3]);
  150. }
  151. } else if (score >= options.scores[3]) {
  152. progressbar.addClass("progress-success").removeClass("progress-warning").removeClass("progress-danger");
  153. progressbar.find(".bar").css("width", "100%");
  154. if (options.showVerdicts) {
  155. $verdict.text(options.verdicts[4]);
  156. }
  157. }
  158. },
  159. calculateScore = function ($el) {
  160. var self = this,
  161. word = $el.val(),
  162. totalScore = 0,
  163. options = $el.data("pwstrength");
  164. $.each(options.rules, function (rule, active) {
  165. if (active === true) {
  166. var score = options.ruleScores[rule],
  167. result = options.validationRules[rule](options, word, score);
  168. if (result) {
  169. totalScore += result;
  170. }
  171. }
  172. });
  173. setProgressBar($el, totalScore);
  174. return totalScore;
  175. },
  176. progressWidget = function () {
  177. return '<div class="progress"><div class="bar"></div></div>';
  178. },
  179. methods = {
  180. init: function (settings) {
  181. var self = this,
  182. allOptions = $.extend(options, settings);
  183. return this.each(function (idx, el) {
  184. var $el = $(el),
  185. progressbar,
  186. verdict;
  187. $el.data("pwstrength", allOptions);
  188. $el.on("keyup", function (event) {
  189. var options = $el.data("pwstrength");
  190. options.errors = [];
  191. calculateScore.call(self, $el);
  192. if ($.isFunction(options.onKeyUp)) {
  193. options.onKeyUp(event);
  194. }
  195. });
  196. progressbar = $(progressWidget());
  197. if (allOptions.viewports.progress) {
  198. $(allOptions.viewports.progress).append(progressbar);
  199. } else {
  200. progressbar.insertAfter($el);
  201. }
  202. progressbar.find(".bar").css("width", "0%");
  203. $el.data("pwstrength").progressbar = progressbar;
  204. if (allOptions.showVerdicts) {
  205. verdict = $('<span class="password-verdict">' + allOptions.verdicts[0] + '</span>');
  206. if (allOptions.viewports.verdict) {
  207. $(allOptions.viewports.verdict).append(verdict);
  208. } else {
  209. verdict.insertAfter($el);
  210. }
  211. }
  212. if ($.isFunction(allOptions.onLoad)) {
  213. allOptions.onLoad();
  214. }
  215. });
  216. },
  217. destroy: function () {
  218. this.each(function (idx, el) {
  219. var $el = $(el);
  220. $el.parent().find("span.password-verdict").remove();
  221. $el.parent().find("div.progress").remove();
  222. $el.parent().find("ul.error-list").remove();
  223. $el.removeData("pwstrength");
  224. });
  225. },
  226. forceUpdate: function () {
  227. var self = this;
  228. this.each(function (idx, el) {
  229. var $el = $(el),
  230. options = $el.data("pwstrength");
  231. options.errors = [];
  232. calculateScore.call(self, $el);
  233. });
  234. },
  235. outputErrorList: function () {
  236. this.each(function (idx, el) {
  237. var output = '<ul class="error-list">',
  238. $el = $(el),
  239. errors = $el.data("pwstrength").errors,
  240. viewports = $el.data("pwstrength").viewports,
  241. verdict;
  242. $el.parent().find("ul.error-list").remove();
  243. if (errors.length > 0) {
  244. $.each(errors, function (i, item) {
  245. output += '<li>' + item + '</li>';
  246. });
  247. output += '</ul>';
  248. if (viewports.errors) {
  249. $(viewports.errors).html(output);
  250. } else {
  251. output = $(output);
  252. verdict = $el.parent().find("span.password-verdict");
  253. if (verdict.length > 0) {
  254. el = verdict;
  255. }
  256. output.insertAfter(el);
  257. }
  258. }
  259. });
  260. },
  261. addRule: function (name, method, score, active) {
  262. this.each(function (idx, el) {
  263. var options = $(el).data("pwstrength");
  264. options.rules[name] = active;
  265. options.ruleScores[name] = score;
  266. options.validationRules[name] = method;
  267. });
  268. },
  269. changeScore: function (rule, score) {
  270. this.each(function (idx, el) {
  271. $(el).data("pwstrength").ruleScores[rule] = score;
  272. });
  273. },
  274. ruleActive: function (rule, active) {
  275. this.each(function (idx, el) {
  276. $(el).data("pwstrength").rules[rule] = active;
  277. });
  278. }
  279. };
  280. $.fn.pwstrength = function (method) {
  281. var result;
  282. if (methods[method]) {
  283. result = methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
  284. } else if (typeof method === "object" || !method) {
  285. result = methods.init.apply(this, arguments);
  286. } else {
  287. $.error("Method " + method + " does not exist on jQuery.pwstrength");
  288. }
  289. return result;
  290. };
  291. }(jQuery));