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 |