You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
345 lines
11 KiB
345 lines
11 KiB
/**
|
|
* @author Òscar Casajuana a.k.a. elboletaire <elboletaire at underave dot net>
|
|
* @link https://github.com/elboletaire/password-strength-meter
|
|
* @license GPL-3.0
|
|
*/
|
|
// eslint-disable-next-line
|
|
;(function ($) {
|
|
'use strict';
|
|
|
|
var Password = function ($object, options) {
|
|
var defaults = {
|
|
enterPass: 'Type your password',
|
|
shortPass: 'The password is too short',
|
|
containsField: 'The password contains your username',
|
|
steps: {
|
|
13: 'Really insecure password',
|
|
33: 'Weak; try combining letters & numbers',
|
|
67: 'Medium; try using special characters',
|
|
94: 'Strong password',
|
|
},
|
|
showPercent: false,
|
|
showText: true,
|
|
animate: true,
|
|
animateSpeed: 'fast',
|
|
field: false,
|
|
fieldPartialMatch: true,
|
|
minimumLength: 4,
|
|
closestSelector: 'div',
|
|
useColorBarImage: false,
|
|
customColorBarRGB: {
|
|
red: [0, 240],
|
|
green: [0, 240],
|
|
blue: 10
|
|
},
|
|
};
|
|
|
|
options = $.extend({}, defaults, options);
|
|
|
|
/**
|
|
* Returns strings based on the score given.
|
|
*
|
|
* @param {int} score Score base.
|
|
* @return {string}
|
|
*/
|
|
function scoreText(score)
|
|
{
|
|
if (score === -1) {
|
|
return options.shortPass;
|
|
}
|
|
if (score === -2) {
|
|
return options.containsField;
|
|
}
|
|
|
|
score = score < 0 ? 0 : score;
|
|
|
|
var text = options.shortPass;
|
|
var sortedStepKeys = Object.keys(options.steps).sort();
|
|
for (var step in sortedStepKeys) {
|
|
var stepVal = sortedStepKeys[step];
|
|
if (stepVal < score) {
|
|
text = options.steps[stepVal];
|
|
}
|
|
}
|
|
|
|
return text;
|
|
}
|
|
|
|
/**
|
|
* Returns a value between -2 and 100 to score
|
|
* the user's password.
|
|
*
|
|
* @param {string} password The password to be checked.
|
|
* @param {string} field The field set (if options.field).
|
|
* @return {int}
|
|
*/
|
|
function calculateScore(password, field)
|
|
{
|
|
var score = 0;
|
|
|
|
// password < options.minimumLength
|
|
if (password.length < options.minimumLength) {
|
|
return -1;
|
|
}
|
|
|
|
if (options.field) {
|
|
// password === field
|
|
if (password.toLowerCase() === field.toLowerCase()) {
|
|
return -2;
|
|
}
|
|
// password contains field (and fieldPartialMatch is set to true)
|
|
if (options.fieldPartialMatch && field.length) {
|
|
var user = new RegExp(field.toLowerCase());
|
|
if (password.toLowerCase().match(user)) {
|
|
return -2;
|
|
}
|
|
}
|
|
}
|
|
|
|
// password length
|
|
score += password.length * 4;
|
|
score += checkRepetition(1, password).length - password.length;
|
|
score += checkRepetition(2, password).length - password.length;
|
|
score += checkRepetition(3, password).length - password.length;
|
|
score += checkRepetition(4, password).length - password.length;
|
|
|
|
// password has 3 numbers
|
|
if (password.match(/(.*[0-9].*[0-9].*[0-9])/)) {
|
|
score += 5;
|
|
}
|
|
|
|
// password has at least 2 symbols
|
|
var symbols = '.*[!,@,#,$,%,^,&,*,?,_,~]';
|
|
symbols = new RegExp('(' + symbols + symbols + ')');
|
|
if (password.match(symbols)) {
|
|
score += 5;
|
|
}
|
|
|
|
// password has Upper and Lower chars
|
|
if (password.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/)) {
|
|
score += 10;
|
|
}
|
|
|
|
// password has number and chars
|
|
if (password.match(/([a-zA-Z])/) && password.match(/([0-9])/)) {
|
|
score += 15;
|
|
}
|
|
|
|
// password has number and symbol
|
|
if (password.match(/([!@#$%^&*?_~])/) && password.match(/([0-9])/)) {
|
|
score += 15;
|
|
}
|
|
|
|
// password has char and symbol
|
|
if (password.match(/([!@#$%^&*?_~])/) && password.match(/([a-zA-Z])/)) {
|
|
score += 15;
|
|
}
|
|
|
|
// password is just numbers or chars
|
|
if (password.match(/^\w+$/) || password.match(/^\d+$/)) {
|
|
score -= 10;
|
|
}
|
|
|
|
if (score > 100) {
|
|
score = 100;
|
|
}
|
|
|
|
if (score < 0) {
|
|
score = 0;
|
|
}
|
|
|
|
return score;
|
|
}
|
|
|
|
/**
|
|
* Checks for repetition of characters in
|
|
* a string
|
|
*
|
|
* @param {int} length Repetition length.
|
|
* @param {string} str The string to be checked.
|
|
* @return {string}
|
|
*/
|
|
function checkRepetition(length, str)
|
|
{
|
|
var res = "", repeated = false;
|
|
for (var i = 0; i < str.length; i++) {
|
|
repeated = true;
|
|
for (var j = 0; j < length && (j + i + length) < str.length; j++) {
|
|
repeated = repeated && (str.charAt(j + i) === str.charAt(j + i + length));
|
|
}
|
|
if (j < length) {
|
|
repeated = false;
|
|
}
|
|
if (repeated) {
|
|
i += length - 1;
|
|
repeated = false;
|
|
} else {
|
|
res += str.charAt(i);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* Calculates background colors from percentage value.
|
|
*
|
|
* @param {int} perc The percentage strength of the password.
|
|
* @return {object} Object with colors as keys
|
|
*/
|
|
function calculateColorFromPercentage(perc)
|
|
{
|
|
var minRed = 0;
|
|
var maxRed = 240;
|
|
var minGreen = 0;
|
|
var maxGreen = 240;
|
|
var blue = 10;
|
|
|
|
if (Object.prototype.hasOwnProperty.call(options.customColorBarRGB, 'red')) {
|
|
minRed = options.customColorBarRGB.red[0];
|
|
maxRed = options.customColorBarRGB.red[1];
|
|
}
|
|
|
|
if (Object.prototype.hasOwnProperty.call(options.customColorBarRGB, 'green')) {
|
|
minGreen = options.customColorBarRGB.green[0];
|
|
maxGreen = options.customColorBarRGB.green[1];
|
|
}
|
|
|
|
if (Object.prototype.hasOwnProperty.call(options.customColorBarRGB, 'blue')) {
|
|
blue = options.customColorBarRGB.blue;
|
|
}
|
|
|
|
var green = (perc * maxGreen / 50);
|
|
var red = (2 * maxRed) - (perc * maxRed / 50);
|
|
|
|
return {
|
|
red: Math.min(Math.max(red, minRed), maxRed),
|
|
green: Math.min(Math.max(green, minGreen), maxGreen),
|
|
blue: blue
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds color styles to colorbar jQuery object.
|
|
*
|
|
* @param {jQuery} $colorbar The colorbar jquery object.
|
|
* @param {int} perc The percentage strength of the password.
|
|
* @return {jQuery}
|
|
*/
|
|
function addColorBarStyle($colorbar, perc)
|
|
{
|
|
if (options.useColorBarImage) {
|
|
$colorbar.css({
|
|
backgroundPosition: "0px -" + perc + "px",
|
|
width: perc + '%'
|
|
});
|
|
} else {
|
|
var colors = calculateColorFromPercentage(perc);
|
|
|
|
$colorbar.css({
|
|
'background-image': 'none',
|
|
'background-color': 'rgb(' + colors.red.toString() + ', ' + colors.green.toString() + ', ' + colors.blue.toString() + ')',
|
|
width: perc + '%'
|
|
});
|
|
}
|
|
|
|
return $colorbar;
|
|
}
|
|
|
|
/**
|
|
* Initializes the plugin creating and binding the
|
|
* required layers and events.
|
|
*
|
|
* @return {Password} Returns the Password instance.
|
|
*/
|
|
function init()
|
|
{
|
|
var shown = true;
|
|
var $text = options.showText;
|
|
var $percentage = options.showPercent;
|
|
var $graybar = $('<div>').addClass('pass-graybar');
|
|
var $colorbar = $('<div>').addClass('pass-colorbar');
|
|
var $insert = $('<div>').addClass('pass-wrapper').append(
|
|
$graybar.append($colorbar)
|
|
);
|
|
|
|
$object.closest(options.closestSelector).addClass('pass-strength-visible');
|
|
if (options.animate) {
|
|
$insert.css('display', 'none');
|
|
shown = false;
|
|
$object.closest(options.closestSelector).removeClass('pass-strength-visible');
|
|
}
|
|
|
|
if (options.showPercent) {
|
|
$percentage = $('<span>').addClass('pass-percent').text('0%');
|
|
$insert.append($percentage);
|
|
}
|
|
|
|
if (options.showText) {
|
|
$text = $('<span>').addClass('pass-text').html(options.enterPass);
|
|
$insert.append($text);
|
|
}
|
|
|
|
$object.closest(options.closestSelector).append($insert);
|
|
|
|
$object.keyup(function () {
|
|
var field = options.field || '';
|
|
if (field) {
|
|
field = $(field).val();
|
|
}
|
|
|
|
var score = calculateScore($object.val(), field);
|
|
$object.trigger('password.score', [score]);
|
|
var perc = score < 0 ? 0 : score;
|
|
|
|
$colorbar = addColorBarStyle($colorbar, perc);
|
|
|
|
if (options.showPercent) {
|
|
$percentage.html(perc + '%');
|
|
}
|
|
|
|
if (options.showText) {
|
|
var text = scoreText(score);
|
|
if (!$object.val().length && score <= 0) {
|
|
text = options.enterPass;
|
|
}
|
|
|
|
if ($text.html() !== $('<div>').html(text).html()) {
|
|
$text.html(text);
|
|
$object.trigger('password.text', [text, score]);
|
|
}
|
|
}
|
|
});
|
|
|
|
if (options.animate) {
|
|
$object.focus(function () {
|
|
if (!shown) {
|
|
$insert.slideDown(options.animateSpeed, function () {
|
|
shown = true;
|
|
$object.closest(options.closestSelector).addClass('pass-strength-visible');
|
|
});
|
|
}
|
|
});
|
|
|
|
$object.blur(function () {
|
|
if (!$object.val().length && shown) {
|
|
$insert.slideUp(options.animateSpeed, function () {
|
|
shown = false;
|
|
$object.closest(options.closestSelector).removeClass('pass-strength-visible')
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
return init.call(this);
|
|
};
|
|
|
|
// Bind to jquery
|
|
$.fn.password = function (options) {
|
|
return this.each(function () {
|
|
new Password($(this), options);
|
|
});
|
|
};
|
|
})(jQuery);
|
|
|