password_strength 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.rdoc +9 -0
- data/README.rdoc +5 -0
- data/lib/jquery.strength.js +11 -1
- data/lib/password_strength/active_record/ar2.rb +2 -1
- data/lib/password_strength/active_record/ar3.rb +2 -1
- data/lib/password_strength/base.rb +61 -26
- data/lib/password_strength/version.rb +1 -1
- data/lib/password_strength.js +52 -31
- data/lib/password_strength.rb +15 -2
- data/test/active_record_test.rb +7 -0
- data/test/jquery_strength_test.js +10 -0
- data/test/password_strength_test.js +17 -3
- data/test/password_strength_test.rb +14 -0
- metadata +3 -3
data/CHANGELOG.rdoc
CHANGED
@@ -20,3 +20,12 @@
|
|
20
20
|
|
21
21
|
* jQuery function can accept string instead of selectors
|
22
22
|
* Added repetition penalization
|
23
|
+
|
24
|
+
== 0.1.5 - March 23 2010
|
25
|
+
|
26
|
+
* Added ActiveSupport as dependency
|
27
|
+
* Added JavaScript implementation for JavaScript
|
28
|
+
|
29
|
+
== 0.1.6 - March 31 2010
|
30
|
+
|
31
|
+
* Added :exclude option
|
data/README.rdoc
CHANGED
@@ -76,6 +76,11 @@ The PasswordStrength also implements the algorithm in the JavaScript.
|
|
76
76
|
|
77
77
|
The API is basically the same!
|
78
78
|
|
79
|
+
You can use the <tt>:exclude</tt> option. Only regular expressions are supported for now.
|
80
|
+
|
81
|
+
var strength = PasswordStrength.test("johndoe", "password with whitespaces", {exclude: /\s/});
|
82
|
+
strength.isInvalid();
|
83
|
+
|
79
84
|
Additionaly, a jQuery plugin is available.
|
80
85
|
|
81
86
|
$.strength("#username", "#password");
|
data/lib/jquery.strength.js
CHANGED
@@ -1,8 +1,18 @@
|
|
1
1
|
(function($){
|
2
|
-
$.strength = function(username, password, callback) {
|
2
|
+
$.strength = function(username, password, options, callback) {
|
3
|
+
if (typeof(options) == "function") {
|
4
|
+
callback = options;
|
5
|
+
options = {};
|
6
|
+
} else if (!options) {
|
7
|
+
options = {};
|
8
|
+
}
|
9
|
+
|
3
10
|
var usernameField = $(username);
|
4
11
|
var passwordField = $(password);
|
5
12
|
var strength = new PasswordStrength();
|
13
|
+
|
14
|
+
strength.exclude = options["exclude"];
|
15
|
+
|
6
16
|
callback = callback || $.strength.callback;
|
7
17
|
|
8
18
|
var handler = function(){
|
@@ -19,10 +19,11 @@ module PasswordStrength
|
|
19
19
|
options.reverse_merge!(:level => :good, :with => :username)
|
20
20
|
|
21
21
|
raise ArgumentError, "The :with option must be supplied" unless options.include?(:with)
|
22
|
+
raise ArgumentError, "The :exclude options must be an array of string or regular expression" if options[:exclude] && !options[:exclude].kind_of?(Array) && !options[:exclude].kind_of?(Regexp)
|
22
23
|
raise ArgumentError, "The :level option must be one of [:weak, :good, :strong]" unless [:weak, :good, :strong].include?(options[:level])
|
23
24
|
|
24
25
|
validates_each(attr_names, options) do |record, attr_name, value|
|
25
|
-
strength = PasswordStrength.test(record.send(options[:with]), value)
|
26
|
+
strength = PasswordStrength.test(record.send(options[:with]), value, :exclude => options[:exclude])
|
26
27
|
record.errors.add(attr_name, :too_weak, :default => options[:message]) unless strength.valid?(options[:level])
|
27
28
|
end
|
28
29
|
end
|
@@ -6,12 +6,13 @@ module ActiveModel # :nodoc:
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def validate_each(record, attribute, value)
|
9
|
-
strength = PasswordStrength.test(record.send(options[:with]), value)
|
9
|
+
strength = PasswordStrength.test(record.send(options[:with]), value, :exclude => options[:exclude])
|
10
10
|
record.errors.add(attribute, :too_weak, :default => options[:message], :value => value) unless strength.valid?(options[:level])
|
11
11
|
end
|
12
12
|
|
13
13
|
def check_validity!
|
14
14
|
raise ArgumentError, "The :with option must be supplied" unless options.include?(:with)
|
15
|
+
raise ArgumentError, "The :exclude options must be an array of string or regular expression" if options[:exclude] && !options[:exclude].kind_of?(Array) && !options[:exclude].kind_of?(Regexp)
|
15
16
|
raise ArgumentError, "The :level option must be one of [:weak, :good, :strong]" unless [:weak, :good, :strong].include?(options[:level])
|
16
17
|
super
|
17
18
|
end
|
@@ -5,34 +5,51 @@ module PasswordStrength
|
|
5
5
|
SYMBOL_RE = /[!@#\$%^&*?_~-]/
|
6
6
|
UPPERCASE_LOWERCASE_RE = /([a-z].*[A-Z])|([A-Z].*[a-z])/
|
7
7
|
|
8
|
-
# Hold the username that will be matched against password
|
8
|
+
# Hold the username that will be matched against password.
|
9
9
|
attr_accessor :username
|
10
10
|
|
11
|
-
# The password that will be tested
|
11
|
+
# The password that will be tested.
|
12
12
|
attr_accessor :password
|
13
13
|
|
14
|
-
# The score for the latest test. Will be nil if the password has not been tested.
|
14
|
+
# The score for the latest test. Will be +nil+ if the password has not been tested.
|
15
15
|
attr_reader :score
|
16
16
|
|
17
|
-
# The current test status. Can be +:weak+, +:good+ or +:
|
17
|
+
# The current test status. Can be +:weak+, +:good+, +:strong+ or +:invalid+.
|
18
18
|
attr_reader :status
|
19
19
|
|
20
|
-
|
20
|
+
# Set what characters cannot be present on password.
|
21
|
+
# Can be a regular expression or array.
|
22
|
+
#
|
23
|
+
# strength = PasswordStrength.test("john", "password with whitespaces", :exclude => [" ", "asdf"])
|
24
|
+
# strength = PasswordStrength.test("john", "password with whitespaces", :exclude => /\s/)
|
25
|
+
#
|
26
|
+
# Then you can check the test result:
|
27
|
+
#
|
28
|
+
# strength.valid?(:weak)
|
29
|
+
# #=> false
|
30
|
+
#
|
31
|
+
# strength.status
|
32
|
+
# #=> :invalid
|
33
|
+
#
|
34
|
+
attr_accessor :exclude
|
35
|
+
|
36
|
+
def initialize(username, password, options = {})
|
21
37
|
@username = username.to_s
|
22
38
|
@password = password.to_s
|
23
39
|
@score = 0
|
40
|
+
@exclude = options[:exclude]
|
24
41
|
end
|
25
42
|
|
26
43
|
# Check if the password has the specified score.
|
27
44
|
# Level can be +:weak+, +:good+ or +:strong+.
|
28
|
-
def valid?(level)
|
45
|
+
def valid?(level = :good)
|
29
46
|
case level
|
30
47
|
when :strong then
|
31
48
|
strong?
|
32
49
|
when :good then
|
33
50
|
good? || strong?
|
34
51
|
else
|
35
|
-
|
52
|
+
!invalid?
|
36
53
|
end
|
37
54
|
end
|
38
55
|
|
@@ -51,6 +68,11 @@ module PasswordStrength
|
|
51
68
|
status == :good
|
52
69
|
end
|
53
70
|
|
71
|
+
# Check if password has invalid characters based on PasswordStrength::Base#exclude.
|
72
|
+
def invalid?
|
73
|
+
status == :invalid
|
74
|
+
end
|
75
|
+
|
54
76
|
# Return the score for the specified rule.
|
55
77
|
# Available rules:
|
56
78
|
#
|
@@ -109,31 +131,44 @@ module PasswordStrength
|
|
109
131
|
score
|
110
132
|
end
|
111
133
|
|
134
|
+
# Run all tests on password and return the final score.
|
112
135
|
def test
|
113
136
|
@score = 0
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
137
|
+
|
138
|
+
if contain_invalid_matches?
|
139
|
+
@status = :invalid
|
140
|
+
else
|
141
|
+
@score += score_for(:password_size)
|
142
|
+
@score += score_for(:numbers)
|
143
|
+
@score += score_for(:symbols)
|
144
|
+
@score += score_for(:uppercase_lowercase)
|
145
|
+
@score += score_for(:numbers_chars)
|
146
|
+
@score += score_for(:numbers_symbols)
|
147
|
+
@score += score_for(:symbols_chars)
|
148
|
+
@score += score_for(:only_chars)
|
149
|
+
@score += score_for(:only_numbers)
|
150
|
+
@score += score_for(:username)
|
151
|
+
@score += score_for(:sequences)
|
152
|
+
@score += score_for(:repetitions)
|
153
|
+
|
154
|
+
@score = 0 if score < 0
|
155
|
+
@score = 100 if score > 100
|
156
|
+
|
157
|
+
@status = :weak if score < 35
|
158
|
+
@status = :good if score >= 35 && score < 70
|
159
|
+
@status = :strong if score >= 70
|
160
|
+
end
|
133
161
|
|
134
162
|
score
|
135
163
|
end
|
136
164
|
|
165
|
+
def contain_invalid_matches? # :nodoc:
|
166
|
+
return false unless exclude
|
167
|
+
regex = exclude
|
168
|
+
regex = /#{exclude.collect {|i| Regexp.escape(i)}.join("|")}/ if exclude.kind_of?(Array)
|
169
|
+
password.to_s =~ regex
|
170
|
+
end
|
171
|
+
|
137
172
|
def repetitions(text, size) # :nodoc:
|
138
173
|
text = text.mb_chars
|
139
174
|
count = 0
|
data/lib/password_strength.js
CHANGED
@@ -11,37 +11,42 @@ var PasswordStrength = function() {
|
|
11
11
|
|
12
12
|
this.test = function() {
|
13
13
|
this.score = 0;
|
14
|
-
this.score += this.scoreFor("password_size");
|
15
|
-
this.score += this.scoreFor("numbers");
|
16
|
-
this.score += this.scoreFor("symbols");
|
17
|
-
this.score += this.scoreFor("uppercase_lowercase");
|
18
|
-
this.score += this.scoreFor("numbers_chars");
|
19
|
-
this.score += this.scoreFor("numbers_symbols");
|
20
|
-
this.score += this.scoreFor("symbols_chars");
|
21
|
-
this.score += this.scoreFor("only_chars");
|
22
|
-
this.score += this.scoreFor("only_numbers");
|
23
|
-
this.score += this.scoreFor("username");
|
24
|
-
this.score += this.scoreFor("sequences");
|
25
|
-
this.score += this.scoreFor("repetitions");
|
26
|
-
|
27
|
-
if (this.score < 0) {
|
28
|
-
this.score = 0;
|
29
|
-
}
|
30
|
-
|
31
|
-
if (this.score > 100) {
|
32
|
-
this.score = 100;
|
33
|
-
}
|
34
|
-
|
35
|
-
if (this.score < 35) {
|
36
|
-
this.status = "weak";
|
37
|
-
}
|
38
|
-
|
39
|
-
if (this.score >= 35 && this.score < 70) {
|
40
|
-
this.status = "good";
|
41
|
-
}
|
42
14
|
|
43
|
-
if (this.
|
44
|
-
|
15
|
+
if (this.containInvalidMatches()) {
|
16
|
+
this.status = "invalid";
|
17
|
+
} else {
|
18
|
+
this.score += this.scoreFor("password_size");
|
19
|
+
this.score += this.scoreFor("numbers");
|
20
|
+
this.score += this.scoreFor("symbols");
|
21
|
+
this.score += this.scoreFor("uppercase_lowercase");
|
22
|
+
this.score += this.scoreFor("numbers_chars");
|
23
|
+
this.score += this.scoreFor("numbers_symbols");
|
24
|
+
this.score += this.scoreFor("symbols_chars");
|
25
|
+
this.score += this.scoreFor("only_chars");
|
26
|
+
this.score += this.scoreFor("only_numbers");
|
27
|
+
this.score += this.scoreFor("username");
|
28
|
+
this.score += this.scoreFor("sequences");
|
29
|
+
this.score += this.scoreFor("repetitions");
|
30
|
+
|
31
|
+
if (this.score < 0) {
|
32
|
+
this.score = 0;
|
33
|
+
}
|
34
|
+
|
35
|
+
if (this.score > 100) {
|
36
|
+
this.score = 100;
|
37
|
+
}
|
38
|
+
|
39
|
+
if (this.score < 35) {
|
40
|
+
this.status = "weak";
|
41
|
+
}
|
42
|
+
|
43
|
+
if (this.score >= 35 && this.score < 70) {
|
44
|
+
this.status = "good";
|
45
|
+
}
|
46
|
+
|
47
|
+
if (this.score >= 70) {
|
48
|
+
this.status = "strong";
|
49
|
+
}
|
45
50
|
}
|
46
51
|
|
47
52
|
return this.score;
|
@@ -142,16 +147,32 @@ var PasswordStrength = function() {
|
|
142
147
|
return this.status == "strong";
|
143
148
|
};
|
144
149
|
|
150
|
+
this.isInvalid = function() {
|
151
|
+
return this.status == "invalid";
|
152
|
+
};
|
153
|
+
|
145
154
|
this.isValid = function(level) {
|
146
155
|
if(level == "strong") {
|
147
156
|
return this.isStrong();
|
148
157
|
} else if (level == "good") {
|
149
158
|
return this.isStrong() || this.isGood();
|
150
159
|
} else {
|
151
|
-
return
|
160
|
+
return !this.containInvalidMatches();
|
152
161
|
}
|
153
162
|
};
|
154
163
|
|
164
|
+
this.containInvalidMatches = function() {
|
165
|
+
if (!this.exclude) {
|
166
|
+
return false;
|
167
|
+
}
|
168
|
+
|
169
|
+
if (!this.exclude.test) {
|
170
|
+
return false;
|
171
|
+
}
|
172
|
+
|
173
|
+
return this.exclude.test(this.password.toString());
|
174
|
+
};
|
175
|
+
|
155
176
|
this.sequences = function(text) {
|
156
177
|
var matches = 0;
|
157
178
|
var sequenceSize = 0;
|
data/lib/password_strength.rb
CHANGED
@@ -10,8 +10,21 @@ module PasswordStrength
|
|
10
10
|
# strength.weak?
|
11
11
|
# #=> true
|
12
12
|
#
|
13
|
-
|
14
|
-
|
13
|
+
# You can provide an options hash.
|
14
|
+
#
|
15
|
+
# strength = PasswordStrength.test("johndoe", "^Str0ng P4ssw0rd$", :exclude => /\s/)
|
16
|
+
# strength.status
|
17
|
+
# #=> :invalid
|
18
|
+
#
|
19
|
+
# strength.invalid?
|
20
|
+
# #=> true
|
21
|
+
#
|
22
|
+
# You can also provide an array.
|
23
|
+
#
|
24
|
+
# strength = PasswordStrength.test("johndoe", "^Str0ng P4ssw0rd$", :exclude => [" ", "asdf", "123"])
|
25
|
+
#
|
26
|
+
def self.test(username, password, options = {})
|
27
|
+
strength = Base.new(username, password, options)
|
15
28
|
strength.test
|
16
29
|
strength
|
17
30
|
end
|
data/test/active_record_test.rb
CHANGED
@@ -66,4 +66,11 @@ class TestActiveRecord < Test::Unit::TestCase
|
|
66
66
|
@user.update_attributes :password => "johndoe"
|
67
67
|
assert @user.errors.full_messages.any?
|
68
68
|
end
|
69
|
+
|
70
|
+
def test_exclude_option
|
71
|
+
User.validates_strength_of :password, :exclude => /\s/
|
72
|
+
|
73
|
+
@user.update_attributes :password => "^password with whitespaces 1234ASDF$"
|
74
|
+
assert @user.errors.full_messages.any?
|
75
|
+
end
|
69
76
|
end
|
@@ -90,6 +90,16 @@ new Test.Unit.Runner({
|
|
90
90
|
assertEqual("mypass", strength.password);
|
91
91
|
});
|
92
92
|
|
93
|
+
$("#username").trigger("keydown");
|
94
|
+
}},
|
95
|
+
|
96
|
+
// Exclude option as regular expression
|
97
|
+
testExcludeOption: function() { with(this) {
|
98
|
+
$.strength("#username", "password with whitespaces", {exclude: /\s/}, function(username, password, strength){
|
99
|
+
assertEqual("invalid", strength.status);
|
100
|
+
assert(strength.isInvalid());
|
101
|
+
});
|
102
|
+
|
93
103
|
$("#username").trigger("keydown");
|
94
104
|
}}
|
95
105
|
});
|
@@ -24,15 +24,17 @@ new Test.Unit.Runner({
|
|
24
24
|
assert(strength.isValid("good"));
|
25
25
|
assertEqual(false, strength.isWeak());
|
26
26
|
assertEqual(false, strength.isStrong());
|
27
|
+
assertEqual(false, strength.isInvalid());
|
27
28
|
}},
|
28
29
|
|
29
30
|
// Weak strength
|
30
31
|
testWeakStrength: function() { with(this) {
|
31
32
|
strength.status = "weak";
|
32
33
|
assert(strength.isWeak());
|
33
|
-
|
34
|
-
|
35
|
-
|
34
|
+
assert(strength.isValid("weak"));
|
35
|
+
// assertEqual(false, strength.isStrong());
|
36
|
+
// assertEqual(false, strength.isGood());
|
37
|
+
// assertEqual(false, strength.isInvalid());
|
36
38
|
}},
|
37
39
|
|
38
40
|
// Strong strength
|
@@ -43,6 +45,7 @@ new Test.Unit.Runner({
|
|
43
45
|
assert(strength.isValid("good"));
|
44
46
|
assertEqual(false, strength.isWeak());
|
45
47
|
assertEqual(false, strength.isGood());
|
48
|
+
assertEqual(false, strength.isInvalid());
|
46
49
|
}},
|
47
50
|
|
48
51
|
// Short password
|
@@ -240,5 +243,16 @@ new Test.Unit.Runner({
|
|
240
243
|
// Four char repetition
|
241
244
|
testFourCharRepetition: function() { with(this) {
|
242
245
|
assertEqual(4, strength.repetitions("abcdabcdabcd", 4));
|
246
|
+
}},
|
247
|
+
|
248
|
+
// Exclude option as regular expression
|
249
|
+
testExcludeOptionAsRegularExpression: function() { with(this) {
|
250
|
+
strength.password = "password with whitespaces";
|
251
|
+
strength.exclude = /\s/;
|
252
|
+
strength.test();
|
253
|
+
|
254
|
+
assertEqual("invalid", strength.status);
|
255
|
+
assert(strength.isInvalid());
|
256
|
+
assertEqual(false, strength.isValid());
|
243
257
|
}}
|
244
258
|
});
|
@@ -223,4 +223,18 @@ class TestPasswordStrength < Test::Unit::TestCase
|
|
223
223
|
# expected: §£€à, £€à§, €à§£, à§£€
|
224
224
|
assert_equal 4, @strength.repetitions("§£€à§£€à§£€à", 4)
|
225
225
|
end
|
226
|
+
|
227
|
+
def test_exclude_option_as_regular_expression
|
228
|
+
@strength = PasswordStrength.test("johndoe", "^Str0ng P4ssw0rd$", :exclude => /\s/)
|
229
|
+
assert_equal :invalid, @strength.status
|
230
|
+
assert @strength.invalid?
|
231
|
+
assert_equal false, @strength.valid?
|
232
|
+
end
|
233
|
+
|
234
|
+
def test_exclude_option_as_array
|
235
|
+
@strength = PasswordStrength.test("johndoe", "asdfasdfasdf", :exclude => ["asdf", "123"])
|
236
|
+
assert_equal :invalid, @strength.status
|
237
|
+
assert @strength.invalid?
|
238
|
+
assert_equal false, @strength.valid?
|
239
|
+
end
|
226
240
|
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 0.1.
|
8
|
+
- 6
|
9
|
+
version: 0.1.6
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Nando Vieira
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-03-
|
17
|
+
date: 2010-03-31 00:00:00 -03:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|