134
+ − 1
/*
+ − 2
* Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
+ − 3
* Copyright (C) 2006-2007 Dan Fuhry
+ − 4
* pwstrength - Password evaluation and strength testing algorithm
+ − 5
*
+ − 6
* This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
+ − 7
* as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+ − 8
*
+ − 9
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ − 10
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ − 11
*/
+ − 12
+ − 13
function password_score_len(password)
+ − 14
{
+ − 15
if ( typeof(password) != "string" )
+ − 16
{
+ − 17
return -10;
+ − 18
}
+ − 19
var len = password.length;
+ − 20
var score = len - 7;
+ − 21
return score;
+ − 22
}
+ − 23
+ − 24
function password_score(password)
+ − 25
{
+ − 26
if ( typeof(password) != "string" )
+ − 27
{
+ − 28
return -10;
+ − 29
}
+ − 30
var score = 0;
+ − 31
var debug = [];
+ − 32
// length check
+ − 33
var lenscore = password_score_len(password);
+ − 34
+ − 35
debug.push(''+lenscore+' points for length');
+ − 36
+ − 37
score += lenscore;
+ − 38
+ − 39
var has_upper_lower = false;
+ − 40
var has_symbols = false;
+ − 41
var has_numbers = false;
+ − 42
+ − 43
// contains uppercase and lowercase
+ − 44
if ( password.match(/[A-z]+/) && password.toLowerCase() != password )
+ − 45
{
+ − 46
score += 1;
+ − 47
has_upper_lower = true;
+ − 48
debug.push('1 point for having uppercase and lowercase');
+ − 49
}
+ − 50
+ − 51
// contains symbols
+ − 52
if ( password.match(/[^A-z0-9]+/) )
+ − 53
{
+ − 54
score += 1;
+ − 55
has_symbols = true;
+ − 56
debug.push('1 point for having nonalphanumeric characters (matching /[^A-z0-9]+/)');
+ − 57
}
+ − 58
+ − 59
// contains numbers
+ − 60
if ( password.match(/[0-9]+/) )
+ − 61
{
+ − 62
score += 1;
+ − 63
has_numbers = true;
+ − 64
debug.push('1 point for having numbers');
+ − 65
}
+ − 66
+ − 67
if ( has_upper_lower && has_symbols && has_numbers && password.length >= 9 )
+ − 68
{
+ − 69
// if it has uppercase and lowercase letters, symbols, and numbers, and is of considerable length, add some serious points
+ − 70
score += 4;
+ − 71
debug.push('4 points for having uppercase and lowercase, numbers, and nonalphanumeric and being more than 8 characters');
+ − 72
}
+ − 73
else if ( has_upper_lower && has_symbols && has_numbers && password.length >= 6 )
+ − 74
{
+ − 75
// still give some points for passing complexity check
+ − 76
score += 2;
+ − 77
debug.push('2 points for having uppercase and lowercase, numbers, and nonalphanumeric');
+ − 78
}
+ − 79
else if(( ( has_upper_lower && has_symbols ) ||
+ − 80
( has_upper_lower && has_numbers ) ||
+ − 81
( has_symbols && has_numbers ) ) && password.length >= 6 )
+ − 82
{
+ − 83
// if 2 of the three main complexity checks passed, add a point
+ − 84
score += 1;
+ − 85
debug.push('1 point for having 2 of 3 complexity checks');
+ − 86
}
+ − 87
else if ( ( !has_upper_lower && !has_numbers && has_symbols ) ||
+ − 88
( !has_upper_lower && !has_symbols && has_numbers ) ||
+ − 89
( !has_numbers && !has_symbols && has_upper_lower ) )
+ − 90
{
+ − 91
score += -2;
+ − 92
debug.push('-2 points for only meeting 1 complexity check');
+ − 93
}
+ − 94
else if ( password.match(/^[0-9]*?([a-z]+)[0-9]?$/) )
+ − 95
{
+ − 96
// password is something like magnum1 which will be cracked in seconds
+ − 97
score += -4;
+ − 98
debug.push('-4 points for being of the form [number][word][number], which is easily cracked');
+ − 99
}
+ − 100
else if ( !has_upper_lower && !has_numbers && !has_symbols )
+ − 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
+ − 103
debug.push('-3 points for not meeting any complexity checks');
+ − 104
score += -3;
+ − 105
}
+ − 106
+ − 107
//
+ − 108
// Repetition
+ − 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
+ − 111
//
+ − 112
+ − 113
if ( password.match(/([A-Z][A-Z][A-Z][A-Z]|[a-z][a-z][a-z][a-z])/) )
+ − 114
{
+ − 115
debug.push('-2 points for having more than 4 letters of the same case in a row');
+ − 116
score += -2;
+ − 117
}
+ − 118
else if ( password.match(/([A-Z][A-Z][A-Z]|[a-z][a-z][a-z])/) )
+ − 119
{
+ − 120
debug.push('-1 points for having more than 3 letters of the same case in a row');
+ − 121
score += -1;
+ − 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 )
+ − 124
{
+ − 125
debug.push('1 point for never having more than 2 letters of the same case in a row');
+ − 126
score += 1;
+ − 127
}
+ − 128
+ − 129
if ( password.match(/[0-9][0-9][0-9][0-9]/) )
+ − 130
{
+ − 131
debug.push('-2 points for having 4 or more numbers in a row');
+ − 132
score += -2;
+ − 133
}
+ − 134
else if ( password.match(/[0-9][0-9][0-9]/) )
+ − 135
{
+ − 136
debug.push('-1 points for having 3 or more numbers in a row');
+ − 137
score += -1;
+ − 138
}
+ − 139
else if ( has_numbers && !password.match(/[0-9][0-9][0-9]/) && password.length >= 8 )
+ − 140
{
+ − 141
debug.push('1 point for never more than 2 numbers in a row');
+ − 142
score += -1;
+ − 143
}
+ − 144
+ − 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 = '';
+ − 147
var warn = false;
+ − 148
var loss = 0;
+ − 149
for ( var i = 0; i < password.length; i++ )
+ − 150
{
+ − 151
var chr = password.substr(i, 1);
+ − 152
if ( chr == prev_char && warn )
+ − 153
{
+ − 154
loss += -1;
+ − 155
}
+ − 156
else if ( chr == prev_char && !warn )
+ − 157
{
+ − 158
warn = true;
+ − 159
}
+ − 160
else if ( chr != prev_char && warn )
+ − 161
{
+ − 162
warn = false;
+ − 163
}
+ − 164
prev_char = chr;
+ − 165
}
+ − 166
if ( loss < 0 )
+ − 167
{
+ − 168
debug.push(''+loss+' points for immediate character repetition');
+ − 169
score += loss;
+ − 170
// this can bring the score below -10 sometimes
+ − 171
if ( score < -10 )
+ − 172
{
+ − 173
debug.push('Score set to -10 because it went below that floor');
+ − 174
score = -10;
+ − 175
}
+ − 176
}
+ − 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";
+ − 179
for ( var i = 0; i < debug.length; i++ )
+ − 180
{
+ − 181
debug_txt += debug[i] + "\n";
+ − 182
}
+ − 183
362
02d315d1cc58
Started localization on User CP. Localized pagination, password strength, and various other small widgets. Fixed bug in path manager causing return of fullpage from get_page_id_from_url() even when namespace is Special.
Dan
diff
changeset
+ − 184
// For users that really want to know why their password sucks.
02d315d1cc58
Started localization on User CP. Localized pagination, password strength, and various other small widgets. Fixed bug in path manager causing return of fullpage from get_page_id_from_url() even when namespace is Special.
Dan
diff
changeset
+ − 185
// Not localized because the feature is really only used for debugging the algorithm.
02d315d1cc58
Started localization on User CP. Localized pagination, password strength, and various other small widgets. Fixed bug in path manager causing return of fullpage from get_page_id_from_url() even when namespace is Special.
Dan
diff
changeset
+ − 186
if ( document.getElementById('passdebug') )
134
+ − 187
document.getElementById('passdebug').innerHTML = debug_txt;
+ − 188
+ − 189
return score;
+ − 190
}
+ − 191
+ − 192
function password_score_draw(score)
+ − 193
{
362
02d315d1cc58
Started localization on User CP. Localized pagination, password strength, and various other small widgets. Fixed bug in path manager causing return of fullpage from get_page_id_from_url() even when namespace is Special.
Dan
diff
changeset
+ − 194
if ( !$lang )
02d315d1cc58
Started localization on User CP. Localized pagination, password strength, and various other small widgets. Fixed bug in path manager causing return of fullpage from get_page_id_from_url() even when namespace is Special.
Dan
diff
changeset
+ − 195
{
02d315d1cc58
Started localization on User CP. Localized pagination, password strength, and various other small widgets. Fixed bug in path manager causing return of fullpage from get_page_id_from_url() even when namespace is Special.
Dan
diff
changeset
+ − 196
// $lang isn't initted yet, this happens sometimes on the usercp/emailpassword form.
02d315d1cc58
Started localization on User CP. Localized pagination, password strength, and various other small widgets. Fixed bug in path manager causing return of fullpage from get_page_id_from_url() even when namespace is Special.
Dan
diff
changeset
+ − 197
// Try to init it if we have ENANO_LANG_ID and enano_lang; if not, report an error.
02d315d1cc58
Started localization on User CP. Localized pagination, password strength, and various other small widgets. Fixed bug in path manager causing return of fullpage from get_page_id_from_url() even when namespace is Special.
Dan
diff
changeset
+ − 198
if ( typeof(enano_lang) == 'object' && typeof(ENANO_LANG_ID) == 'number' )
02d315d1cc58
Started localization on User CP. Localized pagination, password strength, and various other small widgets. Fixed bug in path manager causing return of fullpage from get_page_id_from_url() even when namespace is Special.
Dan
diff
changeset
+ − 199
{
02d315d1cc58
Started localization on User CP. Localized pagination, password strength, and various other small widgets. Fixed bug in path manager causing return of fullpage from get_page_id_from_url() even when namespace is Special.
Dan
diff
changeset
+ − 200
language_onload();
02d315d1cc58
Started localization on User CP. Localized pagination, password strength, and various other small widgets. Fixed bug in path manager causing return of fullpage from get_page_id_from_url() even when namespace is Special.
Dan
diff
changeset
+ − 201
}
02d315d1cc58
Started localization on User CP. Localized pagination, password strength, and various other small widgets. Fixed bug in path manager causing return of fullpage from get_page_id_from_url() even when namespace is Special.
Dan
diff
changeset
+ − 202
else
460
+ − 203
{
362
02d315d1cc58
Started localization on User CP. Localized pagination, password strength, and various other small widgets. Fixed bug in path manager causing return of fullpage from get_page_id_from_url() even when namespace is Special.
Dan
diff
changeset
+ − 204
return {
460
+ − 205
'color' : '#000000',
+ − 206
'fgcolor' : '#666666',
+ − 207
'str' : 'Language init failed'
362
02d315d1cc58
Started localization on User CP. Localized pagination, password strength, and various other small widgets. Fixed bug in path manager causing return of fullpage from get_page_id_from_url() even when namespace is Special.
Dan
diff
changeset
+ − 208
};
02d315d1cc58
Started localization on User CP. Localized pagination, password strength, and various other small widgets. Fixed bug in path manager causing return of fullpage from get_page_id_from_url() even when namespace is Special.
Dan
diff
changeset
+ − 209
}
02d315d1cc58
Started localization on User CP. Localized pagination, password strength, and various other small widgets. Fixed bug in path manager causing return of fullpage from get_page_id_from_url() even when namespace is Special.
Dan
diff
changeset
+ − 210
}
134
+ − 211
// some colors are from the Gmail sign-up form
+ − 212
if ( score >= 10 )
+ − 213
{
+ − 214
var color = '#000000';
+ − 215
var fgcolor = '#666666';
362
02d315d1cc58
Started localization on User CP. Localized pagination, password strength, and various other small widgets. Fixed bug in path manager causing return of fullpage from get_page_id_from_url() even when namespace is Special.
Dan
diff
changeset
+ − 216
var str = $lang.get('usercp_pwstrength_score_verystrong', { score: score });
134
+ − 217
}
+ − 218
else if ( score > 3 )
+ − 219
{
+ − 220
var color = '#008000';
+ − 221
var fgcolor = '#004000';
362
02d315d1cc58
Started localization on User CP. Localized pagination, password strength, and various other small widgets. Fixed bug in path manager causing return of fullpage from get_page_id_from_url() even when namespace is Special.
Dan
diff
changeset
+ − 222
var str = $lang.get('usercp_pwstrength_score_strong', { score: score });
134
+ − 223
}
+ − 224
else if ( score >= 1 )
+ − 225
{
+ − 226
var color = '#6699cc';
+ − 227
var fgcolor = '#4477aa';
362
02d315d1cc58
Started localization on User CP. Localized pagination, password strength, and various other small widgets. Fixed bug in path manager causing return of fullpage from get_page_id_from_url() even when namespace is Special.
Dan
diff
changeset
+ − 228
var str = $lang.get('usercp_pwstrength_score_good', { score: score });
134
+ − 229
}
+ − 230
else if ( score >= -3 )
+ − 231
{
+ − 232
var color = '#f5ac00';
+ − 233
var fgcolor = '#ffcc33';
362
02d315d1cc58
Started localization on User CP. Localized pagination, password strength, and various other small widgets. Fixed bug in path manager causing return of fullpage from get_page_id_from_url() even when namespace is Special.
Dan
diff
changeset
+ − 234
var str = $lang.get('usercp_pwstrength_score_fair', { score: score });
134
+ − 235
}
+ − 236
else
+ − 237
{
+ − 238
var color = '#aa0033';
+ − 239
var fgcolor = '#FF6060';
362
02d315d1cc58
Started localization on User CP. Localized pagination, password strength, and various other small widgets. Fixed bug in path manager causing return of fullpage from get_page_id_from_url() even when namespace is Special.
Dan
diff
changeset
+ − 240
var str = $lang.get('usercp_pwstrength_score_weak', { score: score });
134
+ − 241
}
+ − 242
return {
+ − 243
color: color,
+ − 244
fgcolor: fgcolor,
+ − 245
str: str
+ − 246
};
+ − 247
}
+ − 248
+ − 249
function password_score_field(field)
+ − 250
{
+ − 251
var indicator = false;
+ − 252
if ( field.nextSibling )
+ − 253
{
+ − 254
if ( field.nextSibling.className == 'password-checker' )
+ − 255
{
+ − 256
indicator = field.nextSibling;
+ − 257
}
+ − 258
}
+ − 259
if ( !indicator )
+ − 260
{
+ − 261
var indicator = document.createElement('span');
+ − 262
indicator.className = 'password-checker';
+ − 263
if ( field.nextSibling )
+ − 264
{
+ − 265
field.parentNode.insertBefore(indicator, field.nextSibling);
+ − 266
}
+ − 267
else
+ − 268
{
+ − 269
field.parentNode.appendChild(indicator);
+ − 270
}
+ − 271
}
+ − 272
var score = password_score(field.value);
+ − 273
var data = password_score_draw(score);
+ − 274
indicator.style.color = data.color;
+ − 275
indicator.style.fontWeight = 'bold';
+ − 276
indicator.innerHTML = ' ' + data.str;
+ − 277
+ − 278
if ( document.getElementById('pwmeter') )
+ − 279
{
+ − 280
var div = document.getElementById('pwmeter');
+ − 281
div.style.width = '250px';
+ − 282
score += 10;
+ − 283
if ( score > 25 )
+ − 284
score = 25;
+ − 285
div.style.backgroundColor = data.color;
+ − 286
var width = Math.round( score * (250 / 25) );
+ − 287
div.innerHTML = '<div style="width: '+width+'px; background-color: '+data.fgcolor+'; height: 8px;"></div>';
+ − 288
}
+ − 289
}
+ − 290