Index: trunk/phase3/skins/common/password.css |
— | — | @@ -0,0 +1,17 @@ |
| 2 | +span.mw-password-bad { |
| 3 | + background: red; |
| 4 | + color: yellow; |
| 5 | + font-weight: bold; |
| 6 | +} |
| 7 | + |
| 8 | +.mw-password-mediocre { |
| 9 | + background: yellow; |
| 10 | +} |
| 11 | + |
| 12 | +.mw-password-acceptable { |
| 13 | + background: silver; |
| 14 | +} |
| 15 | + |
| 16 | +.mw-password-good { |
| 17 | + background: green; |
| 18 | +} |
\ No newline at end of file |
Property changes on: trunk/phase3/skins/common/password.css |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 19 | + native |
Index: trunk/phase3/skins/common/password.js |
— | — | @@ -0,0 +1,136 @@ |
| 2 | +/** |
| 3 | + * Password strength checker |
| 4 | + * @license WTFPL 2.0 |
| 5 | + * All scores are ranged approximately 0 (total disaster) - 100 (_looks_ great) |
| 6 | + * @todo Check for popular passwords and keyboard sequences (QWERTY, etc) |
| 7 | + */ |
| 8 | + |
| 9 | +function bruteForceComplexity( pwd ) { |
| 10 | + var score = 0; |
| 11 | + |
| 12 | + if ( pwd.length < 16 ) { |
| 13 | + score = pwd.length * 5; |
| 14 | + } else { |
| 15 | + score = 80; |
| 16 | + } |
| 17 | + |
| 18 | + var regexes = [ |
| 19 | + /[a-z]/, |
| 20 | + /[A-Z]/, |
| 21 | + /[0-9]/, |
| 22 | + /[-_;:\.,'"`~!@#$%\^&\*\(\)\[\]\{\} ]/ ]; |
| 23 | + |
| 24 | + var charClasses = 0; |
| 25 | + for ( var i in regexes ) { |
| 26 | + if ( pwd.match( regexes[i] ) ) { |
| 27 | + charClasses++; |
| 28 | + } |
| 29 | + } |
| 30 | + |
| 31 | + var matches = pwd.match( /[\x80-\uFFFF]/g ); |
| 32 | + if ( matches ) { |
| 33 | + charClasses++; |
| 34 | + |
| 35 | + // poor man's isUpper() and isLower() |
| 36 | + var i, lower = false, upper = false; |
| 37 | + for ( i in matches ) { |
| 38 | + var ch = matches[i]; |
| 39 | + upper |= ch != ch.toLowerCase(); |
| 40 | + lower |= ch != ch.toUpperCase(); |
| 41 | + if ( upper && lower ) break; |
| 42 | + } |
| 43 | + if ( upper && lower ) { |
| 44 | + charClasses++; |
| 45 | + } |
| 46 | + } |
| 47 | + score += ( charClasses - 1 ) * 10; |
| 48 | + |
| 49 | + return score; |
| 50 | +} |
| 51 | + |
| 52 | +function repetitionScore( pwd ) { |
| 53 | + var unique = ''; |
| 54 | + for ( var i in pwd ) { |
| 55 | + if ( unique.indexOf( pwd[i] ) < 0 ) { |
| 56 | + unique += pwd[i]; |
| 57 | + } |
| 58 | + } |
| 59 | + var ratio = pwd.length / unique.length - 0.4; // allow up to 40% repetition, reward for less, penalize for more |
| 60 | + |
| 61 | + return 100 / ratio; |
| 62 | +} |
| 63 | + |
| 64 | +function sequenceScore( pwd ) { |
| 65 | + pwd = pwd.concat( '\0' ); |
| 66 | + var score = 100, sequence = 1; |
| 67 | + for ( var i = 1; i < pwd.length; i++ ) { |
| 68 | + if ( pwd.charCodeAt( i ) == pwd.charCodeAt(i - 1) + 1 ) { |
| 69 | + sequence++; |
| 70 | + } else { |
| 71 | + if ( sequence > 2 ) { |
| 72 | + score -= Math.sqrt( sequence ) * 15; |
| 73 | + } |
| 74 | + sequence = 1; |
| 75 | + } |
| 76 | + } |
| 77 | + for ( var i = 1; i < pwd.length; i++ ) { |
| 78 | + if ( pwd.charCodeAt( i ) == pwd.charCodeAt(i - 1) - 1 ) { |
| 79 | + sequence++; |
| 80 | + } else { |
| 81 | + if ( sequence > 2 ) { |
| 82 | + score -= Math.sqrt( sequence ) * 15; |
| 83 | + } |
| 84 | + sequence = 1; |
| 85 | + } |
| 86 | + } |
| 87 | + return score; |
| 88 | +} |
| 89 | + |
| 90 | +(function( $ ) { |
| 91 | + function passwordChanged() { |
| 92 | + retypeChanged(); |
| 93 | + var pwd = $( passwordSecurity.password ).val(); |
| 94 | + if ( pwd == '' ) { |
| 95 | + $( '#password-strength' ).html( '' ); |
| 96 | + return; |
| 97 | + } |
| 98 | + if ( pwd.length > 100 ) pwd = pwd.slice( 0, 100 ); |
| 99 | + var score = Math.min( |
| 100 | + bruteForceComplexity( pwd ), |
| 101 | + repetitionScore( pwd ), |
| 102 | + sequenceScore( pwd ) |
| 103 | + ); |
| 104 | + var result = 'good'; |
| 105 | + if ( score < 40 ) { |
| 106 | + result = 'bad'; |
| 107 | + } else if ( score < 60 ) { |
| 108 | + result = 'mediocre'; |
| 109 | + } else if ( score < 85 ) { |
| 110 | + result = 'acceptable'; |
| 111 | + } |
| 112 | + var message = '<span class="mw-password-' + result + '">' + passwordSecurity.messages['password-strength-' + result] |
| 113 | + + '</span>'; |
| 114 | + $( '#password-strength' ).html( |
| 115 | + passwordSecurity.messages['password-strength'].replace( '$1', message ) |
| 116 | + ); |
| 117 | + } |
| 118 | + |
| 119 | + function retypeChanged() { |
| 120 | + var pwd = $( passwordSecurity.password ).val(); |
| 121 | + var retype = $( passwordSecurity.retype ).val(); |
| 122 | + var message; |
| 123 | + if ( pwd == '' || pwd == retype ) { |
| 124 | + message = ''; |
| 125 | + } else if ( retype == '' ) { |
| 126 | + message = passwordSecurity.messages['password-retype']; |
| 127 | + } else { |
| 128 | + message = passwordSecurity.messages['password-retype-mismatch']; |
| 129 | + } |
| 130 | + $( '#password-retype' ).html( message ); |
| 131 | + } |
| 132 | + |
| 133 | + $( document ).ready( function() { |
| 134 | + $( passwordSecurity.password ).bind( 'keyup change', passwordChanged ); |
| 135 | + $( passwordSecurity.retype ).bind( 'keyup change', retypeChanged ); |
| 136 | + }) |
| 137 | +})(jQuery); |
Property changes on: trunk/phase3/skins/common/password.js |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 138 | + native |