Subversion Repositories DevTools

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
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);