author | Dan |
Wed, 07 May 2008 14:06:16 -0400 | |
changeset 549 | 6894cfd94dfb |
parent 504 | bc8e0e9ee01d |
child 586 | 234ddd896555 |
permissions | -rw-r--r-- |
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
parents:
134
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
parents:
134
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
parents:
134
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
parents:
134
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
parents:
134
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
parents:
134
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
parents:
134
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
parents:
134
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
parents:
134
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
parents:
134
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
parents:
134
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
parents:
134
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
parents:
134
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
parents:
134
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
parents:
134
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
parents:
134
diff
changeset
|
210 |
} |
134 | 211 |
// some colors are from the Gmail sign-up form |
212 |
if ( score >= 10 ) |
|
213 |
{ |
|
504
bc8e0e9ee01d
Added support for embedding language data into plugins; updated all version numbers on plugin files
Dan
parents:
460
diff
changeset
|
214 |
var color = '#010101'; |
134 | 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
parents:
134
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
parents:
134
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
parents:
134
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
parents:
134
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
parents:
134
diff
changeset
|
240 |
var str = $lang.get('usercp_pwstrength_score_weak', { score: score }); |
134 | 241 |
} |
504
bc8e0e9ee01d
Added support for embedding language data into plugins; updated all version numbers on plugin files
Dan
parents:
460
diff
changeset
|
242 |
var ret = { |
134 | 243 |
color: color, |
244 |
fgcolor: fgcolor, |
|
245 |
str: str |
|
246 |
}; |
|
504
bc8e0e9ee01d
Added support for embedding language data into plugins; updated all version numbers on plugin files
Dan
parents:
460
diff
changeset
|
247 |
return ret; |
134 | 248 |
} |
249 |
||
250 |
function password_score_field(field) |
|
251 |
{ |
|
252 |
var indicator = false; |
|
253 |
if ( field.nextSibling ) |
|
254 |
{ |
|
255 |
if ( field.nextSibling.className == 'password-checker' ) |
|
256 |
{ |
|
257 |
indicator = field.nextSibling; |
|
258 |
} |
|
259 |
} |
|
260 |
if ( !indicator ) |
|
261 |
{ |
|
262 |
var indicator = document.createElement('span'); |
|
263 |
indicator.className = 'password-checker'; |
|
264 |
if ( field.nextSibling ) |
|
265 |
{ |
|
266 |
field.parentNode.insertBefore(indicator, field.nextSibling); |
|
267 |
} |
|
268 |
else |
|
269 |
{ |
|
270 |
field.parentNode.appendChild(indicator); |
|
271 |
} |
|
272 |
} |
|
273 |
var score = password_score(field.value); |
|
274 |
var data = password_score_draw(score); |
|
275 |
indicator.style.color = data.color; |
|
276 |
indicator.style.fontWeight = 'bold'; |
|
277 |
indicator.innerHTML = ' ' + data.str; |
|
278 |
||
279 |
if ( document.getElementById('pwmeter') ) |
|
280 |
{ |
|
281 |
var div = document.getElementById('pwmeter'); |
|
282 |
div.style.width = '250px'; |
|
283 |
score += 10; |
|
284 |
if ( score > 25 ) |
|
285 |
score = 25; |
|
286 |
div.style.backgroundColor = data.color; |
|
287 |
var width = Math.round( score * (250 / 25) ); |
|
288 |
div.innerHTML = '<div style="width: '+width+'px; background-color: '+data.fgcolor+'; height: 8px;"></div>'; |
|
289 |
} |
|
290 |
} |
|
291 |