| 5048 |
dpurdie |
1 |
/**
|
|
|
2 |
* jQuery Form Validator Module: Security
|
|
|
3 |
* ------------------------------------------
|
|
|
4 |
* Created by Victor Jonsson <http://victorjonsson.se>
|
|
|
5 |
*
|
|
|
6 |
* This module adds validators typically used in registration forms.
|
|
|
7 |
* This module adds the following validators:
|
|
|
8 |
* - spamcheck
|
|
|
9 |
* - confirmation
|
|
|
10 |
* - strength
|
|
|
11 |
* - backend
|
|
|
12 |
* - credit card
|
|
|
13 |
* - cvv
|
|
|
14 |
*
|
|
|
15 |
* @website http://formvalidator.net/#security-validators
|
|
|
16 |
* @version 2.2.beta.69
|
|
|
17 |
*/
|
|
|
18 |
(function($, window) {
|
|
|
19 |
|
|
|
20 |
'use strict';
|
|
|
21 |
|
|
|
22 |
/*
|
|
|
23 |
* Simple spam check
|
|
|
24 |
*/
|
|
|
25 |
$.formUtils.addValidator({
|
|
|
26 |
name : 'spamcheck',
|
|
|
27 |
validatorFunction : function(val, $el, config) {
|
|
|
28 |
var attr = $el.valAttr('captcha');
|
|
|
29 |
return attr === val;
|
|
|
30 |
},
|
|
|
31 |
errorMessage : '',
|
|
|
32 |
errorMessageKey: 'badSecurityAnswer'
|
|
|
33 |
});
|
|
|
34 |
|
|
|
35 |
|
|
|
36 |
/*
|
|
|
37 |
* Validate confirmation
|
|
|
38 |
*/
|
|
|
39 |
$.formUtils.addValidator({
|
|
|
40 |
name : 'confirmation',
|
|
|
41 |
validatorFunction : function(value, $el, config, language, $form) {
|
|
|
42 |
var conf = '',
|
|
|
43 |
confInputName = $el.valAttr('confirm') || ($el.attr('name') + '_confirmation'),
|
|
|
44 |
confInput = $form.find('input[name="' +confInputName+ '"]').eq(0);
|
|
|
45 |
|
|
|
46 |
if (confInput) {
|
|
|
47 |
conf = confInput.val();
|
|
|
48 |
} else {
|
|
|
49 |
console.warn('Could not find an input with name "'+confInputName+'"');
|
|
|
50 |
}
|
|
|
51 |
|
|
|
52 |
return value === conf;
|
|
|
53 |
},
|
|
|
54 |
errorMessage : '',
|
|
|
55 |
errorMessageKey: 'notConfirmed'
|
|
|
56 |
});
|
|
|
57 |
|
|
|
58 |
var creditCards = {
|
|
|
59 |
'amex' : [15,15],
|
|
|
60 |
'diners_club' : [14,14],
|
|
|
61 |
'cjb' : [16,16],
|
|
|
62 |
'laser' : [16,19],
|
|
|
63 |
'visa' : [16,16],
|
|
|
64 |
'mastercard' : [16,16],
|
|
|
65 |
'maestro' : [12,19],
|
|
|
66 |
'discover' : [16,16]
|
|
|
67 |
},
|
|
|
68 |
checkOnlyAmex = false,
|
|
|
69 |
allowsAmex = false;
|
|
|
70 |
|
|
|
71 |
/*
|
|
|
72 |
* Credit card
|
|
|
73 |
*/
|
|
|
74 |
$.formUtils.addValidator({
|
|
|
75 |
name : 'creditcard',
|
|
|
76 |
validatorFunction : function(value, $el, config, language, $form) {
|
|
|
77 |
var allowing = $.split( $el.valAttr('allowing') || '' );
|
|
|
78 |
|
|
|
79 |
// Setup for cvv validation
|
|
|
80 |
allowsAmex = $.inArray('amex', allowing) > -1;
|
|
|
81 |
checkOnlyAmex = allowsAmex && allowing.length == 1;
|
|
|
82 |
|
|
|
83 |
// Correct length
|
|
|
84 |
if( allowing.length > 0 ) {
|
|
|
85 |
var hasValidLength = false;
|
|
|
86 |
$.each(allowing, function(i, cardName) {
|
|
|
87 |
if( cardName in creditCards) {
|
|
|
88 |
if( value.length >= creditCards[cardName][0] && value.length <= creditCards[cardName][1]) {
|
|
|
89 |
hasValidLength = true;
|
|
|
90 |
return false;
|
|
|
91 |
}
|
|
|
92 |
} else {
|
|
|
93 |
console.warn('Use of unknown credit card "'+cardName+'"');
|
|
|
94 |
}
|
|
|
95 |
});
|
|
|
96 |
|
|
|
97 |
if( !hasValidLength )
|
|
|
98 |
return false;
|
|
|
99 |
}
|
|
|
100 |
|
|
|
101 |
// only numbers
|
|
|
102 |
if( value.replace(new RegExp('[0-9]', 'g'), '') !== '' ) {
|
|
|
103 |
return false
|
|
|
104 |
}
|
|
|
105 |
|
|
|
106 |
// http://en.wikipedia.org/wiki/Luhn_algorithm
|
|
|
107 |
// http://www.brainjar.com/js/validation/default2.asp
|
|
|
108 |
var checkSum = 0;
|
|
|
109 |
$.each(value.split('').reverse(), function(i, digit) {
|
|
|
110 |
digit = parseInt(digit, 10);
|
|
|
111 |
if( i%2 === 0 ) {
|
|
|
112 |
checkSum += digit;
|
|
|
113 |
} else {
|
|
|
114 |
digit *= 2;
|
|
|
115 |
if (digit < 10) {
|
|
|
116 |
checkSum += digit;
|
|
|
117 |
} else {
|
|
|
118 |
checkSum += digit - 9;
|
|
|
119 |
}
|
|
|
120 |
}
|
|
|
121 |
});
|
|
|
122 |
return checkSum % 10 === 0;
|
|
|
123 |
},
|
|
|
124 |
errorMessage : '',
|
|
|
125 |
errorMessageKey: 'badCreditCard'
|
|
|
126 |
});
|
|
|
127 |
|
|
|
128 |
|
|
|
129 |
/*
|
|
|
130 |
* Credit card number
|
|
|
131 |
*/
|
|
|
132 |
$.formUtils.addValidator({
|
|
|
133 |
name : 'cvv',
|
|
|
134 |
validatorFunction : function(val) {
|
|
|
135 |
if( val.replace(/[0-9]/g, '') === '' ) {
|
|
|
136 |
val = val + '';
|
|
|
137 |
if( checkOnlyAmex ) {
|
|
|
138 |
return val.length == 4;
|
|
|
139 |
} else if( allowsAmex ) {
|
|
|
140 |
return val.length == 3 || val.length == 4;
|
|
|
141 |
} else {
|
|
|
142 |
return val.length == 3;
|
|
|
143 |
}
|
|
|
144 |
}
|
|
|
145 |
return false;
|
|
|
146 |
},
|
|
|
147 |
errorMessage : '',
|
|
|
148 |
errorMessageKey: 'badCVV'
|
|
|
149 |
});
|
|
|
150 |
|
|
|
151 |
/*
|
|
|
152 |
* Validate password strength
|
|
|
153 |
*/
|
|
|
154 |
$.formUtils.addValidator({
|
|
|
155 |
name : 'strength',
|
|
|
156 |
validatorFunction : function(val, $el, conf) {
|
|
|
157 |
var requiredStrength = $el.valAttr('strength') || 2;
|
|
|
158 |
if(requiredStrength && requiredStrength > 3)
|
|
|
159 |
requiredStrength = 3;
|
|
|
160 |
|
|
|
161 |
return $.formUtils.validators.validate_strength.calculatePasswordStrength(val) >= requiredStrength;
|
|
|
162 |
},
|
|
|
163 |
errorMessage : '',
|
|
|
164 |
errorMessageKey: 'badStrength',
|
|
|
165 |
|
|
|
166 |
/**
|
|
|
167 |
* Code more or less borrowed from jQuery plugin "Password Strength Meter"
|
|
|
168 |
* written by Darren Mason (djmason9@gmail.com), myPocket technologies (www.mypocket-technologies.com)
|
|
|
169 |
* @param {String} password
|
|
|
170 |
* @return {Number}
|
|
|
171 |
*/
|
|
|
172 |
calculatePasswordStrength : function(password) {
|
|
|
173 |
|
|
|
174 |
if (password.length < 4) {
|
|
|
175 |
return 0;
|
|
|
176 |
}
|
|
|
177 |
|
|
|
178 |
var score = 0;
|
|
|
179 |
|
|
|
180 |
var checkRepetition = function (pLen, str) {
|
|
|
181 |
var res = "";
|
|
|
182 |
for (var i = 0; i < str.length; i++) {
|
|
|
183 |
var repeated = true;
|
|
|
184 |
|
|
|
185 |
for (var j = 0; j < pLen && (j + i + pLen) < str.length; j++) {
|
|
|
186 |
repeated = repeated && (str.charAt(j + i) == str.charAt(j + i + pLen));
|
|
|
187 |
}
|
|
|
188 |
if (j < pLen) {
|
|
|
189 |
repeated = false;
|
|
|
190 |
}
|
|
|
191 |
if (repeated) {
|
|
|
192 |
i += pLen - 1;
|
|
|
193 |
repeated = false;
|
|
|
194 |
}
|
|
|
195 |
else {
|
|
|
196 |
res += str.charAt(i);
|
|
|
197 |
}
|
|
|
198 |
}
|
|
|
199 |
return res;
|
|
|
200 |
};
|
|
|
201 |
|
|
|
202 |
//password length
|
|
|
203 |
score += password.length * 4;
|
|
|
204 |
score += ( checkRepetition(1, password).length - password.length ) * 1;
|
|
|
205 |
score += ( checkRepetition(2, password).length - password.length ) * 1;
|
|
|
206 |
score += ( checkRepetition(3, password).length - password.length ) * 1;
|
|
|
207 |
score += ( checkRepetition(4, password).length - password.length ) * 1;
|
|
|
208 |
|
|
|
209 |
//password has 3 numbers
|
|
|
210 |
if (password.match(/(.*[0-9].*[0-9].*[0-9])/)) {
|
|
|
211 |
score += 5;
|
|
|
212 |
}
|
|
|
213 |
|
|
|
214 |
//password has 2 symbols
|
|
|
215 |
if (password.match(/(.*[!,@,#,$,%,^,&,*,?,_,~].*[!,@,#,$,%,^,&,*,?,_,~])/)) {
|
|
|
216 |
score += 5;
|
|
|
217 |
}
|
|
|
218 |
|
|
|
219 |
//password has Upper and Lower chars
|
|
|
220 |
if (password.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/)) {
|
|
|
221 |
score += 10;
|
|
|
222 |
}
|
|
|
223 |
|
|
|
224 |
//password has number and chars
|
|
|
225 |
if (password.match(/([a-zA-Z])/) && password.match(/([0-9])/)) {
|
|
|
226 |
score += 15;
|
|
|
227 |
}
|
|
|
228 |
//
|
|
|
229 |
//password has number and symbol
|
|
|
230 |
if (password.match(/([!,@,#,$,%,^,&,*,?,_,~])/) && password.match(/([0-9])/)) {
|
|
|
231 |
score += 15;
|
|
|
232 |
}
|
|
|
233 |
|
|
|
234 |
//password has char and symbol
|
|
|
235 |
if (password.match(/([!,@,#,$,%,^,&,*,?,_,~])/) && password.match(/([a-zA-Z])/)) {
|
|
|
236 |
score += 15;
|
|
|
237 |
}
|
|
|
238 |
|
|
|
239 |
//password is just a numbers or chars
|
|
|
240 |
if (password.match(/^\w+$/) || password.match(/^\d+$/)) {
|
|
|
241 |
score -= 10;
|
|
|
242 |
}
|
|
|
243 |
|
|
|
244 |
//verifying 0 < score < 100
|
|
|
245 |
if (score < 0) {
|
|
|
246 |
score = 0;
|
|
|
247 |
}
|
|
|
248 |
if (score > 100) {
|
|
|
249 |
score = 100;
|
|
|
250 |
}
|
|
|
251 |
|
|
|
252 |
if (score < 20) {
|
|
|
253 |
return 0;
|
|
|
254 |
}
|
|
|
255 |
else if (score < 40) {
|
|
|
256 |
return 1;
|
|
|
257 |
}
|
|
|
258 |
else if(score <= 60) {
|
|
|
259 |
return 2;
|
|
|
260 |
}
|
|
|
261 |
else {
|
|
|
262 |
return 3;
|
|
|
263 |
}
|
|
|
264 |
},
|
|
|
265 |
|
|
|
266 |
strengthDisplay : function($el, options) {
|
|
|
267 |
var config = {
|
|
|
268 |
fontSize: '12pt',
|
|
|
269 |
padding: '4px',
|
|
|
270 |
bad : 'Very bad',
|
|
|
271 |
weak : 'Weak',
|
|
|
272 |
good : 'Good',
|
|
|
273 |
strong : 'Strong'
|
|
|
274 |
};
|
|
|
275 |
|
|
|
276 |
if (options) {
|
|
|
277 |
$.extend(config, options);
|
|
|
278 |
}
|
|
|
279 |
|
|
|
280 |
$el.bind('keyup', function() {
|
|
|
281 |
var val = $(this).val(),
|
|
|
282 |
$parent = typeof config.parent == 'undefined' ? $(this).parent() : $(config.parent),
|
|
|
283 |
$displayContainer = $parent.find('.strength-meter'),
|
|
|
284 |
strength = $.formUtils.validators.validate_strength.calculatePasswordStrength(val),
|
|
|
285 |
css = {
|
|
|
286 |
background: 'pink',
|
|
|
287 |
color : '#FF0000',
|
|
|
288 |
fontWeight : 'bold',
|
|
|
289 |
border : 'red solid 1px',
|
|
|
290 |
borderWidth : '0px 0px 4px',
|
|
|
291 |
display : 'inline-block',
|
|
|
292 |
fontSize : config.fontSize,
|
|
|
293 |
padding : config.padding
|
|
|
294 |
},
|
|
|
295 |
text = config.bad;
|
|
|
296 |
|
|
|
297 |
if($displayContainer.length == 0) {
|
|
|
298 |
$displayContainer = $('<span></span>');
|
|
|
299 |
$displayContainer
|
|
|
300 |
.addClass('strength-meter')
|
|
|
301 |
.appendTo($parent);
|
|
|
302 |
}
|
|
|
303 |
|
|
|
304 |
if( !val ) {
|
|
|
305 |
$displayContainer.hide();
|
|
|
306 |
} else {
|
|
|
307 |
$displayContainer.show();
|
|
|
308 |
}
|
|
|
309 |
|
|
|
310 |
if(strength == 1) {
|
|
|
311 |
text = config.weak;
|
|
|
312 |
}
|
|
|
313 |
else if(strength == 2) {
|
|
|
314 |
css.background = 'lightyellow';
|
|
|
315 |
css.borderColor = 'yellow';
|
|
|
316 |
css.color = 'goldenrod';
|
|
|
317 |
text = config.good;
|
|
|
318 |
}
|
|
|
319 |
else if(strength >= 3) {
|
|
|
320 |
css.background = 'lightgreen';
|
|
|
321 |
css.borderColor = 'darkgreen';
|
|
|
322 |
css.color = 'darkgreen';
|
|
|
323 |
text = config.strong;
|
|
|
324 |
}
|
|
|
325 |
|
|
|
326 |
$displayContainer
|
|
|
327 |
.css(css)
|
|
|
328 |
.text(text);
|
|
|
329 |
});
|
|
|
330 |
}
|
|
|
331 |
});
|
|
|
332 |
|
|
|
333 |
var requestServer = function(serverURL, $element, val, conf, callback) {
|
|
|
334 |
var reqParams = $element.valAttr('req-params') || {};
|
|
|
335 |
if( !reqParams )
|
|
|
336 |
reqParams = {};
|
|
|
337 |
if( typeof reqParams == 'string' ) {
|
|
|
338 |
reqParams = $.parseJSON(reqParams);
|
|
|
339 |
}
|
|
|
340 |
reqParams[$element.valAttr('param-name') || $element.attr('name')] = val;
|
|
|
341 |
|
|
|
342 |
$.ajax({
|
|
|
343 |
url : serverURL,
|
|
|
344 |
type : 'POST',
|
|
|
345 |
cache : false,
|
|
|
346 |
data : reqParams,
|
|
|
347 |
dataType : 'json',
|
|
|
348 |
error : function(error, err) {
|
|
|
349 |
alert('Server validation failed due to: '+error.statusText);
|
|
|
350 |
if( window.JSON && window.JSON.stringify ) {
|
|
|
351 |
alert(window.JSON.stringify(error));
|
|
|
352 |
}
|
|
|
353 |
},
|
|
|
354 |
success : function(response) {
|
|
|
355 |
if(response.valid) {
|
|
|
356 |
$element.valAttr('backend-valid', 'true');
|
|
|
357 |
}
|
|
|
358 |
else {
|
|
|
359 |
$element.valAttr('backend-invalid', 'true');
|
|
|
360 |
if(response.message)
|
|
|
361 |
$element.attr(conf.validationErrorMsgAttribute, response.message);
|
|
|
362 |
}
|
|
|
363 |
|
|
|
364 |
if( !$element.valAttr('has-keyup-event') ) {
|
|
|
365 |
$element
|
|
|
366 |
.valAttr('has-keyup-event', '1')
|
|
|
367 |
.bind('keyup change', function(evt) {
|
|
|
368 |
if( evt.keyCode != 9 && evt.keyCode != 16 ) {
|
|
|
369 |
$(this)
|
|
|
370 |
.valAttr('backend-valid', false)
|
|
|
371 |
.valAttr('backend-invalid', false);
|
|
|
372 |
}
|
|
|
373 |
});
|
|
|
374 |
}
|
|
|
375 |
|
|
|
376 |
callback();
|
|
|
377 |
}
|
|
|
378 |
});
|
|
|
379 |
},
|
|
|
380 |
disableFormSubmit = function() {
|
|
|
381 |
return false;
|
|
|
382 |
};
|
|
|
383 |
|
|
|
384 |
/*
|
|
|
385 |
* Server validation
|
|
|
386 |
* Flow (form submission):
|
|
|
387 |
* 1) Check if the value already has been validated on the server. If so, display the validation
|
|
|
388 |
* result and continue the validation process, otherwise continue to step 2
|
|
|
389 |
* 2) Return false as if the value is invalid and set $.formUtils.haltValidation to true
|
|
|
390 |
* 3) Disable form submission on the form being validated
|
|
|
391 |
* 4) Request the server with value and input name and add class 'validating-server-side' to the form
|
|
|
392 |
* 5) When the server responds an attribute will be added to the element
|
|
|
393 |
* telling the validator that the input has a valid/invalid value and enable form submission
|
|
|
394 |
* 6) Run form submission again (back to step 1)
|
|
|
395 |
*/
|
|
|
396 |
$.formUtils.addValidator({
|
|
|
397 |
name : 'server',
|
|
|
398 |
validatorFunction : function(val, $el, conf, lang, $form) {
|
|
|
399 |
|
|
|
400 |
var backendValid = $el.valAttr('backend-valid'),
|
|
|
401 |
backendInvalid = $el.valAttr('backend-invalid'),
|
|
|
402 |
serverURL = document.location.href;
|
|
|
403 |
|
|
|
404 |
if($el.valAttr('url')) {
|
|
|
405 |
serverURL = $el.valAttr('url');
|
|
|
406 |
} else if('serverURL' in conf) {
|
|
|
407 |
serverURL = conf.backendUrl;
|
|
|
408 |
}
|
|
|
409 |
|
|
|
410 |
if(backendValid)
|
|
|
411 |
return true;
|
|
|
412 |
else if(backendInvalid)
|
|
|
413 |
return false;
|
|
|
414 |
else if($.formUtils.eventType == 'keyup')
|
|
|
415 |
return null;
|
|
|
416 |
|
|
|
417 |
if($.formUtils.isValidatingEntireForm) {
|
|
|
418 |
|
|
|
419 |
$form
|
|
|
420 |
.bind('submit', disableFormSubmit)
|
|
|
421 |
.addClass('validating-server-side')
|
|
|
422 |
.addClass('on-blur');
|
|
|
423 |
|
|
|
424 |
$el.addClass('validating-server-side');
|
|
|
425 |
$.formUtils.haltValidation = true;
|
|
|
426 |
|
|
|
427 |
requestServer(serverURL, $el, val, conf, function() {
|
|
|
428 |
$form
|
|
|
429 |
.removeClass('validating-server-side')
|
|
|
430 |
.removeClass('on-blur')
|
|
|
431 |
.get(0).onsubmit = function() {};
|
|
|
432 |
|
|
|
433 |
$form.unbind('submit', disableFormSubmit);
|
|
|
434 |
$el.removeClass('validating-server-side');
|
|
|
435 |
|
|
|
436 |
$el.valAttr('value-length', val.length);
|
|
|
437 |
|
|
|
438 |
// fire submission again!
|
|
|
439 |
$.formUtils.haltValidation = false;
|
|
|
440 |
$form.trigger('submit');
|
|
|
441 |
});
|
|
|
442 |
|
|
|
443 |
return null;
|
|
|
444 |
|
|
|
445 |
} else {
|
|
|
446 |
// validaiton on blur
|
|
|
447 |
$form.addClass('validating-server-side');
|
|
|
448 |
$el.addClass('validating-server-side');
|
|
|
449 |
requestServer(serverURL, $el, val, conf, function() {
|
|
|
450 |
$form.removeClass('validating-server-side');
|
|
|
451 |
$el.removeClass('validating-server-side');
|
|
|
452 |
$el.trigger('blur');
|
|
|
453 |
});
|
|
|
454 |
return null;
|
|
|
455 |
}
|
|
|
456 |
},
|
|
|
457 |
errorMessage : '',
|
|
|
458 |
errorMessageKey: 'badBackend',
|
|
|
459 |
validateOnKeyUp : false
|
|
|
460 |
});
|
|
|
461 |
|
|
|
462 |
$.fn.displayPasswordStrength = function(conf) {
|
|
|
463 |
new $.formUtils.validators.validate_strength.strengthDisplay(this, conf);
|
|
|
464 |
return this;
|
|
|
465 |
};
|
|
|
466 |
|
|
|
467 |
})(jQuery, window);
|