includes/clientside/static/pwstrength.js
changeset 1227 bdac73ed481e
parent 586 234ddd896555
equal deleted inserted replaced
1226:de56132c008d 1227:bdac73ed481e
    10  * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
    10  * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
    11  */
    11  */
    12 
    12 
    13 function password_score_len(password)
    13 function password_score_len(password)
    14 {
    14 {
    15   if ( typeof(password) != "string" )
    15 	if ( typeof(password) != "string" )
    16   {
    16 	{
    17     return -10;
    17 		return -10;
    18   }
    18 	}
    19   var len = password.length;
    19 	var len = password.length;
    20   var score = len - 7;
    20 	var score = len - 7;
    21   return score;
    21 	return score;
    22 }
    22 }
    23 
    23 
    24 function password_score(password)
    24 function password_score(password)
    25 {
    25 {
    26   if ( typeof(password) != "string" )
    26 	if ( typeof(password) != "string" )
    27   {
    27 	{
    28     return -10;
    28 		return -10;
    29   }
    29 	}
    30   var score = 0;
    30 	var score = 0;
    31   var debug = [];
    31 	var debug = [];
    32   // length check
    32 	// length check
    33   var lenscore = password_score_len(password);
    33 	var lenscore = password_score_len(password);
    34   
    34 	
    35   debug.push(''+lenscore+' points for length');
    35 	debug.push(''+lenscore+' points for length');
    36   
    36 	
    37   score += lenscore;
    37 	score += lenscore;
    38     
    38 		
    39   var has_upper_lower = false;
    39 	var has_upper_lower = false;
    40   var has_symbols     = false;
    40 	var has_symbols     = false;
    41   var has_numbers     = false;
    41 	var has_numbers     = false;
    42   
    42 	
    43   // contains uppercase and lowercase
    43 	// contains uppercase and lowercase
    44   if ( password.match(/[A-z]+/) && password.toLowerCase() != password )
    44 	if ( password.match(/[A-z]+/) && password.toLowerCase() != password )
    45   {
    45 	{
    46     score += 1;
    46 		score += 1;
    47     has_upper_lower = true;
    47 		has_upper_lower = true;
    48     debug.push('1 point for having uppercase and lowercase');
    48 		debug.push('1 point for having uppercase and lowercase');
    49   }
    49 	}
    50   
    50 	
    51   // contains symbols
    51 	// contains symbols
    52   if ( password.match(/[^A-z0-9]+/) )
    52 	if ( password.match(/[^A-z0-9]+/) )
    53   {
    53 	{
    54     score += 1;
    54 		score += 1;
    55     has_symbols = true;
    55 		has_symbols = true;
    56     debug.push('1 point for having nonalphanumeric characters (matching /[^A-z0-9]+/)');
    56 		debug.push('1 point for having nonalphanumeric characters (matching /[^A-z0-9]+/)');
    57   }
    57 	}
    58   
    58 	
    59   // contains numbers
    59 	// contains numbers
    60   if ( password.match(/[0-9]+/) )
    60 	if ( password.match(/[0-9]+/) )
    61   {
    61 	{
    62     score += 1;
    62 		score += 1;
    63     has_numbers = true;
    63 		has_numbers = true;
    64     debug.push('1 point for having numbers');
    64 		debug.push('1 point for having numbers');
    65   }
    65 	}
    66   
    66 	
    67   if ( has_upper_lower && has_symbols && has_numbers && password.length >= 9 )
    67 	if ( has_upper_lower && has_symbols && has_numbers && password.length >= 9 )
    68   {
    68 	{
    69     // if it has uppercase and lowercase letters, symbols, and numbers, and is of considerable length, add some serious points
    69 		// if it has uppercase and lowercase letters, symbols, and numbers, and is of considerable length, add some serious points
    70     score += 4;
    70 		score += 4;
    71     debug.push('4 points for having uppercase and lowercase, numbers, and nonalphanumeric and being more than 8 characters');
    71 		debug.push('4 points for having uppercase and lowercase, numbers, and nonalphanumeric and being more than 8 characters');
    72   }
    72 	}
    73   else if ( has_upper_lower && has_symbols && has_numbers && password.length >= 6 )
    73 	else if ( has_upper_lower && has_symbols && has_numbers && password.length >= 6 )
    74   {
    74 	{
    75     // still give some points for passing complexity check
    75 		// still give some points for passing complexity check
    76     score += 2;
    76 		score += 2;
    77     debug.push('2 points for having uppercase and lowercase, numbers, and nonalphanumeric');
    77 		debug.push('2 points for having uppercase and lowercase, numbers, and nonalphanumeric');
    78   }
    78 	}
    79   else if(( ( has_upper_lower && has_symbols ) ||
    79 	else if(( ( has_upper_lower && has_symbols ) ||
    80             ( has_upper_lower && has_numbers ) ||
    80 						( has_upper_lower && has_numbers ) ||
    81             ( has_symbols && has_numbers ) ) && password.length >= 6 )
    81 						( has_symbols && has_numbers ) ) && password.length >= 6 )
    82   {
    82 	{
    83     // if 2 of the three main complexity checks passed, add a point
    83 		// if 2 of the three main complexity checks passed, add a point
    84     score += 1;
    84 		score += 1;
    85     debug.push('1 point for having 2 of 3 complexity checks');
    85 		debug.push('1 point for having 2 of 3 complexity checks');
    86   }
    86 	}
    87   else if ( ( !has_upper_lower && !has_numbers && has_symbols ) ||
    87 	else if ( ( !has_upper_lower && !has_numbers && has_symbols ) ||
    88             ( !has_upper_lower && !has_symbols && has_numbers ) ||
    88 						( !has_upper_lower && !has_symbols && has_numbers ) ||
    89             ( !has_numbers && !has_symbols && has_upper_lower ) )
    89 						( !has_numbers && !has_symbols && has_upper_lower ) )
    90   {
    90 	{
    91     score += -2;
    91 		score += -2;
    92     debug.push('-2 points for only meeting 1 complexity check');
    92 		debug.push('-2 points for only meeting 1 complexity check');
    93   }
    93 	}
    94   else if ( password.match(/^[0-9]*?([a-z]+)[0-9]?$/) )
    94 	else if ( password.match(/^[0-9]*?([a-z]+)[0-9]?$/) )
    95   {
    95 	{
    96     // password is something like magnum1 which will be cracked in seconds
    96 		// password is something like magnum1 which will be cracked in seconds
    97     score += -4;
    97 		score += -4;
    98     debug.push('-4 points for being of the form [number][word][number], which is easily cracked');
    98 		debug.push('-4 points for being of the form [number][word][number], which is easily cracked');
    99   }
    99 	}
   100   else if ( !has_upper_lower && !has_numbers && !has_symbols )
   100 	else if ( !has_upper_lower && !has_numbers && !has_symbols )
   101   {
   101 	{
   102     // this is if somehow the user inputs a password that doesn't match the rule above, but still doesn't contain upper and lowercase, numbers, or symbols
   102 		// this is if somehow the user inputs a password that doesn't match the rule above, but still doesn't contain upper and lowercase, numbers, or symbols
   103     debug.push('-3 points for not meeting any complexity checks');
   103 		debug.push('-3 points for not meeting any complexity checks');
   104     score += -3;
   104 		score += -3;
   105   }
   105 	}
   106   
   106 	
   107   //
   107 	//
   108   // Repetition
   108 	// Repetition
   109   // Example: foobar12345 should be deducted points, where f1o2o3b4a5r should be given points
   109 	// Example: foobar12345 should be deducted points, where f1o2o3b4a5r should be given points
   110   // None of the positive ones kick in unless the length is at least 8
   110 	// None of the positive ones kick in unless the length is at least 8
   111   //
   111 	//
   112   
   112 	
   113   if ( password.match(/([A-Z][A-Z][A-Z][A-Z]|[a-z][a-z][a-z][a-z])/) )
   113 	if ( password.match(/([A-Z][A-Z][A-Z][A-Z]|[a-z][a-z][a-z][a-z])/) )
   114   {
   114 	{
   115     debug.push('-2 points for having more than 4 letters of the same case in a row');
   115 		debug.push('-2 points for having more than 4 letters of the same case in a row');
   116     score += -2;
   116 		score += -2;
   117   }
   117 	}
   118   else if ( password.match(/([A-Z][A-Z][A-Z]|[a-z][a-z][a-z])/) )
   118 	else if ( password.match(/([A-Z][A-Z][A-Z]|[a-z][a-z][a-z])/) )
   119   {
   119 	{
   120     debug.push('-1 points for having more than 3 letters of the same case in a row');
   120 		debug.push('-1 points for having more than 3 letters of the same case in a row');
   121     score += -1;
   121 		score += -1;
   122   }
   122 	}
   123   else if ( password.match(/[A-z]/) && !password.match(/([A-Z][A-Z][A-Z]|[a-z][a-z][a-z])/) && password.length >= 8 )
   123 	else if ( password.match(/[A-z]/) && !password.match(/([A-Z][A-Z][A-Z]|[a-z][a-z][a-z])/) && password.length >= 8 )
   124   {
   124 	{
   125     debug.push('1 point for never having more than 2 letters of the same case in a row');
   125 		debug.push('1 point for never having more than 2 letters of the same case in a row');
   126     score += 1;
   126 		score += 1;
   127   }
   127 	}
   128   
   128 	
   129   if ( password.match(/[0-9][0-9][0-9][0-9]/) )
   129 	if ( password.match(/[0-9][0-9][0-9][0-9]/) )
   130   {
   130 	{
   131     debug.push('-2 points for having 4 or more numbers in a row');
   131 		debug.push('-2 points for having 4 or more numbers in a row');
   132     score += -2;
   132 		score += -2;
   133   }
   133 	}
   134   else if ( password.match(/[0-9][0-9][0-9]/) )
   134 	else if ( password.match(/[0-9][0-9][0-9]/) )
   135   {
   135 	{
   136     debug.push('-1 points for having 3 or more numbers in a row');
   136 		debug.push('-1 points for having 3 or more numbers in a row');
   137     score += -1;
   137 		score += -1;
   138   }
   138 	}
   139   else if ( has_numbers && !password.match(/[0-9][0-9][0-9]/) && password.length >= 8 )
   139 	else if ( has_numbers && !password.match(/[0-9][0-9][0-9]/) && password.length >= 8 )
   140   {
   140 	{
   141     debug.push('1 point for never more than 2 numbers in a row');
   141 		debug.push('1 point for never more than 2 numbers in a row');
   142     score += -1;
   142 		score += -1;
   143   }
   143 	}
   144   
   144 	
   145   // make passwords like fooooooooooooooooooooooooooooooooooooo totally die by subtracting a point for each character repeated at least 3 times in a row
   145 	// make passwords like fooooooooooooooooooooooooooooooooooooo totally die by subtracting a point for each character repeated at least 3 times in a row
   146   var prev_char = '';
   146 	var prev_char = '';
   147   var warn = false;
   147 	var warn = false;
   148   var loss = 0;
   148 	var loss = 0;
   149   for ( var i = 0; i < password.length; i++ )
   149 	for ( var i = 0; i < password.length; i++ )
   150   {
   150 	{
   151     var chr = password.substr(i, 1);
   151 		var chr = password.substr(i, 1);
   152     if ( chr == prev_char && warn )
   152 		if ( chr == prev_char && warn )
   153     {
   153 		{
   154       loss += -1;
   154 			loss += -1;
   155     }
   155 		}
   156     else if ( chr == prev_char && !warn )
   156 		else if ( chr == prev_char && !warn )
   157     {
   157 		{
   158       warn = true;
   158 			warn = true;
   159     }
   159 		}
   160     else if ( chr != prev_char && warn )
   160 		else if ( chr != prev_char && warn )
   161     {
   161 		{
   162       warn = false;
   162 			warn = false;
   163     }
   163 		}
   164     prev_char = chr;
   164 		prev_char = chr;
   165   }
   165 	}
   166   if ( loss < 0 )
   166 	if ( loss < 0 )
   167   {
   167 	{
   168     debug.push(''+loss+' points for immediate character repetition');
   168 		debug.push(''+loss+' points for immediate character repetition');
   169     score += loss;
   169 		score += loss;
   170     // this can bring the score below -10 sometimes
   170 		// this can bring the score below -10 sometimes
   171     if ( score < -10 )
   171 		if ( score < -10 )
   172     {
   172 		{
   173       debug.push('Score set to -10 because it went below that floor');
   173 			debug.push('Score set to -10 because it went below that floor');
   174       score = -10;
   174 			score = -10;
   175     }
   175 		}
   176   }
   176 	}
   177   
   177 	
   178   var debug_txt = "<b>How this score was calculated</b>\nYour score was tallied up based on an extensive algorithm which outputted\nthe following scores based on traits of your password. Above you can see the\ncomposite score; your individual scores based on certain tests are below.\n\nThe scale is open-ended, with a minimum score of -10. 10 is very strong, 4\nis strong, 1 is good and -3 is fair. Below -3 scores \"Weak.\"\n\n";
   178 	var debug_txt = "<b>How this score was calculated</b>\nYour score was tallied up based on an extensive algorithm which outputted\nthe following scores based on traits of your password. Above you can see the\ncomposite score; your individual scores based on certain tests are below.\n\nThe scale is open-ended, with a minimum score of -10. 10 is very strong, 4\nis strong, 1 is good and -3 is fair. Below -3 scores \"Weak.\"\n\n";
   179   for ( var i = 0; i < debug.length; i++ )
   179 	for ( var i = 0; i < debug.length; i++ )
   180   {
   180 	{
   181     debug_txt += debug[i] + "\n";
   181 		debug_txt += debug[i] + "\n";
   182   }
   182 	}
   183   
   183 	
   184   // For users that really want to know why their password sucks.
   184 	// For users that really want to know why their password sucks.
   185   // Not localized because the feature is really only used for debugging the algorithm.
   185 	// Not localized because the feature is really only used for debugging the algorithm.
   186   if ( document.getElementById('passdebug') )
   186 	if ( document.getElementById('passdebug') )
   187     document.getElementById('passdebug').innerHTML = debug_txt;
   187 		document.getElementById('passdebug').innerHTML = debug_txt;
   188   
   188 	
   189   return score;
   189 	return score;
   190 }
   190 }
   191 
   191 
   192 function password_score_draw(score)
   192 function password_score_draw(score)
   193 {
   193 {
   194   if ( !$lang )
   194 	if ( !$lang )
   195   {
   195 	{
   196     // $lang isn't initted yet, this happens sometimes on the usercp/emailpassword form.
   196 		// $lang isn't initted yet, this happens sometimes on the usercp/emailpassword form.
   197     // Try to init it if we have ENANO_LANG_ID and enano_lang; if not, report an error.
   197 		// Try to init it if we have ENANO_LANG_ID and enano_lang; if not, report an error.
   198     load_component('l10n');
   198 		load_component('l10n');
   199     if ( typeof(enano_lang) == 'object' && typeof(ENANO_LANG_ID) == 'number' )
   199 		if ( typeof(enano_lang) == 'object' && typeof(ENANO_LANG_ID) == 'number' )
   200     {
   200 		{
   201       language_onload();
   201 			language_onload();
   202     }
   202 		}
   203     else
   203 		else
   204     {      
   204 		{      
   205       return {
   205 			return {
   206         'color' : '#000000',
   206 				'color' : '#000000',
   207         'fgcolor' : '#666666',
   207 				'fgcolor' : '#666666',
   208         'str' : 'Language init failed'
   208 				'str' : 'Language init failed'
   209       };
   209 			};
   210     }
   210 		}
   211   }
   211 	}
   212   // some colors are from the Gmail sign-up form
   212 	// some colors are from the Gmail sign-up form
   213   if ( score >= 10 )
   213 	if ( score >= 10 )
   214   {
   214 	{
   215     var color = '#010101';
   215 		var color = '#010101';
   216     var fgcolor = '#666666';
   216 		var fgcolor = '#666666';
   217     var str = $lang.get('usercp_pwstrength_score_verystrong', { score: score });
   217 		var str = $lang.get('usercp_pwstrength_score_verystrong', { score: score });
   218   }
   218 	}
   219   else if ( score > 3 )
   219 	else if ( score > 3 )
   220   {
   220 	{
   221     var color = '#008000';
   221 		var color = '#008000';
   222     var fgcolor = '#004000';
   222 		var fgcolor = '#004000';
   223     var str = $lang.get('usercp_pwstrength_score_strong', { score: score });
   223 		var str = $lang.get('usercp_pwstrength_score_strong', { score: score });
   224   }
   224 	}
   225   else if ( score >= 1 )
   225 	else if ( score >= 1 )
   226   {
   226 	{
   227     var color = '#6699cc';
   227 		var color = '#6699cc';
   228     var fgcolor = '#4477aa';
   228 		var fgcolor = '#4477aa';
   229     var str = $lang.get('usercp_pwstrength_score_good', { score: score });
   229 		var str = $lang.get('usercp_pwstrength_score_good', { score: score });
   230   }
   230 	}
   231   else if ( score >= -3 )
   231 	else if ( score >= -3 )
   232   {
   232 	{
   233     var color = '#f5ac00';
   233 		var color = '#f5ac00';
   234     var fgcolor = '#ffcc33';
   234 		var fgcolor = '#ffcc33';
   235     var str = $lang.get('usercp_pwstrength_score_fair', { score: score });
   235 		var str = $lang.get('usercp_pwstrength_score_fair', { score: score });
   236   }
   236 	}
   237   else
   237 	else
   238   {
   238 	{
   239     var color = '#aa0033';
   239 		var color = '#aa0033';
   240     var fgcolor = '#FF6060';
   240 		var fgcolor = '#FF6060';
   241     var str = $lang.get('usercp_pwstrength_score_weak', { score: score });
   241 		var str = $lang.get('usercp_pwstrength_score_weak', { score: score });
   242   }
   242 	}
   243   var ret = {
   243 	var ret = {
   244     color: color,
   244 		color: color,
   245     fgcolor: fgcolor,
   245 		fgcolor: fgcolor,
   246     str: str
   246 		str: str
   247   };
   247 	};
   248   return ret;
   248 	return ret;
   249 }
   249 }
   250 
   250 
   251 function password_score_field(field)
   251 function password_score_field(field)
   252 {
   252 {
   253   var indicator = false;
   253 	var indicator = false;
   254   if ( field.nextSibling )
   254 	if ( field.nextSibling )
   255   {
   255 	{
   256     if ( field.nextSibling.className == 'password-checker' )
   256 		if ( field.nextSibling.className == 'password-checker' )
   257     {
   257 		{
   258       indicator = field.nextSibling;
   258 			indicator = field.nextSibling;
   259     }
   259 		}
   260   }
   260 	}
   261   if ( !indicator )
   261 	if ( !indicator )
   262   {
   262 	{
   263     var indicator = document.createElement('span');
   263 		var indicator = document.createElement('span');
   264     indicator.className = 'password-checker';
   264 		indicator.className = 'password-checker';
   265     if ( field.nextSibling )
   265 		if ( field.nextSibling )
   266     {
   266 		{
   267       field.parentNode.insertBefore(indicator, field.nextSibling);
   267 			field.parentNode.insertBefore(indicator, field.nextSibling);
   268     }
   268 		}
   269     else
   269 		else
   270     {
   270 		{
   271       field.parentNode.appendChild(indicator);
   271 			field.parentNode.appendChild(indicator);
   272     }
   272 		}
   273   }
   273 	}
   274   var score = password_score(field.value);
   274 	var score = password_score(field.value);
   275   var data = password_score_draw(score);
   275 	var data = password_score_draw(score);
   276   indicator.style.color = data.color;
   276 	indicator.style.color = data.color;
   277   indicator.style.fontWeight = 'bold';
   277 	indicator.style.fontWeight = 'bold';
   278   indicator.innerHTML = ' ' + data.str;
   278 	indicator.innerHTML = ' ' + data.str;
   279   
   279 	
   280   if ( document.getElementById('pwmeter') )
   280 	if ( document.getElementById('pwmeter') )
   281   {
   281 	{
   282     var div = document.getElementById('pwmeter');
   282 		var div = document.getElementById('pwmeter');
   283     div.style.width = '250px';
   283 		div.style.width = '250px';
   284     score += 10;
   284 		score += 10;
   285     if ( score > 25 )
   285 		if ( score > 25 )
   286       score = 25;
   286 			score = 25;
   287     div.style.backgroundColor = data.color;
   287 		div.style.backgroundColor = data.color;
   288     var width = Math.round( score * (250 / 25) );
   288 		var width = Math.round( score * (250 / 25) );
   289     div.innerHTML = '<div style="width: '+width+'px; background-color: '+data.fgcolor+'; height: 8px;"></div>';
   289 		div.innerHTML = '<div style="width: '+width+'px; background-color: '+data.fgcolor+'; height: 8px;"></div>';
   290   }
   290 	}
   291 }
   291 }
   292 
   292