zxcvbn-ruby 0.1.0 → 1.2.0
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.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +23 -0
- data/CHANGELOG.md +58 -0
- data/CODE_OF_CONDUCT.md +130 -0
- data/Gemfile +8 -1
- data/Guardfile +26 -0
- data/{LICENSE → LICENSE.txt} +0 -0
- data/README.md +152 -12
- data/Rakefile +5 -1
- data/lib/zxcvbn/entropy.rb +1 -1
- data/lib/zxcvbn/feedback.rb +10 -0
- data/lib/zxcvbn/feedback_giver.rb +133 -0
- data/lib/zxcvbn/matchers/l33t.rb +2 -2
- data/lib/zxcvbn/password_strength.rb +2 -0
- data/lib/zxcvbn/score.rb +1 -1
- data/lib/zxcvbn/tester.rb +13 -5
- data/lib/zxcvbn/version.rb +3 -1
- data/spec/dictionary_ranker_spec.rb +2 -2
- data/spec/feedback_giver_spec.rb +212 -0
- data/spec/matchers/date_spec.rb +8 -8
- data/spec/matchers/dictionary_spec.rb +25 -14
- data/spec/matchers/digits_spec.rb +3 -3
- data/spec/matchers/l33t_spec.rb +14 -12
- data/spec/matchers/repeat_spec.rb +6 -6
- data/spec/matchers/sequences_spec.rb +5 -5
- data/spec/matchers/spatial_spec.rb +7 -7
- data/spec/matchers/year_spec.rb +3 -3
- data/spec/omnimatch_spec.rb +1 -1
- data/spec/scoring/crack_time_spec.rb +13 -13
- data/spec/scoring/entropy_spec.rb +24 -24
- data/spec/scoring/math_spec.rb +18 -18
- data/spec/support/js_helpers.rb +3 -4
- data/spec/support/matcher.rb +1 -1
- data/spec/tester_spec.rb +75 -27
- data/zxcvbn-ruby.gemspec +14 -1
- metadata +34 -33
data/spec/scoring/math_spec.rb
CHANGED
@@ -10,7 +10,7 @@ describe Zxcvbn::Math do
|
|
10
10
|
describe '#bruteforce_cardinality' do
|
11
11
|
context 'when empty password' do
|
12
12
|
it 'should return 0 if empty password' do
|
13
|
-
bruteforce_cardinality('').
|
13
|
+
expect(bruteforce_cardinality('')).to eql 0
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
@@ -18,7 +18,7 @@ describe Zxcvbn::Math do
|
|
18
18
|
context 'and a digit' do
|
19
19
|
it 'should return 10' do
|
20
20
|
(0..9).each do |digit|
|
21
|
-
bruteforce_cardinality(digit.to_s).
|
21
|
+
expect(bruteforce_cardinality(digit.to_s)).to eql 10
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
@@ -26,7 +26,7 @@ describe Zxcvbn::Math do
|
|
26
26
|
context 'and an upper case character' do
|
27
27
|
it 'should return 26' do
|
28
28
|
('A'..'Z').each do |character|
|
29
|
-
bruteforce_cardinality(character).
|
29
|
+
expect(bruteforce_cardinality(character)).to eql 26
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
@@ -34,7 +34,7 @@ describe Zxcvbn::Math do
|
|
34
34
|
context 'and a lower case character' do
|
35
35
|
it 'should return 26' do
|
36
36
|
('a'..'z').each do |character|
|
37
|
-
bruteforce_cardinality(character).
|
37
|
+
expect(bruteforce_cardinality(character)).to eql 26
|
38
38
|
end
|
39
39
|
end
|
40
40
|
end
|
@@ -42,7 +42,7 @@ describe Zxcvbn::Math do
|
|
42
42
|
context 'and a symbol' do
|
43
43
|
it 'should return 33' do
|
44
44
|
%w|/ [ ` {|.each do |symbol|
|
45
|
-
bruteforce_cardinality(symbol).
|
45
|
+
expect(bruteforce_cardinality(symbol)).to eql 33
|
46
46
|
end
|
47
47
|
end
|
48
48
|
end
|
@@ -51,31 +51,31 @@ describe Zxcvbn::Math do
|
|
51
51
|
context 'when password is more than one character long' do
|
52
52
|
context 'and only digits' do
|
53
53
|
it 'should return 10' do
|
54
|
-
bruteforce_cardinality('123456789').
|
54
|
+
expect(bruteforce_cardinality('123456789')).to eql 10
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
58
|
context 'and only lowercase characters' do
|
59
59
|
it 'should return 26' do
|
60
|
-
bruteforce_cardinality('password').
|
60
|
+
expect(bruteforce_cardinality('password')).to eql 26
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
64
64
|
context 'and only uppercase characters' do
|
65
65
|
it 'should return 26' do
|
66
|
-
bruteforce_cardinality('PASSWORD').
|
66
|
+
expect(bruteforce_cardinality('PASSWORD')).to eql 26
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
70
70
|
context 'and only symbols' do
|
71
71
|
it 'should return 33' do
|
72
|
-
bruteforce_cardinality('/ [ ` {').
|
72
|
+
expect(bruteforce_cardinality('/ [ ` {')).to eql 33
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
76
76
|
context 'and a mixed of character types' do
|
77
77
|
it 'should add up every character type cardinality' do
|
78
|
-
bruteforce_cardinality('p1SsWorD!').
|
78
|
+
expect(bruteforce_cardinality('p1SsWorD!')).to eql 95
|
79
79
|
end
|
80
80
|
end
|
81
81
|
end
|
@@ -84,25 +84,25 @@ describe Zxcvbn::Math do
|
|
84
84
|
describe '#average_degree_for_graph' do
|
85
85
|
context 'when keyboard is qwerty' do
|
86
86
|
it 'returns the correct average degree over all keys' do
|
87
|
-
average_degree_for_graph('qwerty').
|
87
|
+
expect(average_degree_for_graph('qwerty')).to eql 4.595744680851064
|
88
88
|
end
|
89
89
|
end
|
90
90
|
|
91
91
|
context 'when keyboard is dvorak' do
|
92
92
|
it 'returns the correct average degree over all keys' do
|
93
|
-
average_degree_for_graph('dvorak').
|
93
|
+
expect(average_degree_for_graph('dvorak')).to eql 4.595744680851064
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
97
97
|
context 'when keyboard is keypad' do
|
98
98
|
it 'returns the correct average degree over all keys' do
|
99
|
-
average_degree_for_graph('keypad').
|
99
|
+
expect(average_degree_for_graph('keypad')).to eql 5.066666666666666
|
100
100
|
end
|
101
101
|
end
|
102
102
|
|
103
103
|
context 'when keyboard is mac keypad' do
|
104
104
|
it 'returns the correct average degree over all keys' do
|
105
|
-
average_degree_for_graph('mac_keypad').
|
105
|
+
expect(average_degree_for_graph('mac_keypad')).to eql 5.25
|
106
106
|
end
|
107
107
|
end
|
108
108
|
end
|
@@ -110,25 +110,25 @@ describe Zxcvbn::Math do
|
|
110
110
|
describe '#starting_positions_for_graph' do
|
111
111
|
context 'when keyboard is qwerty' do
|
112
112
|
it 'returns the correct average degree over all keys' do
|
113
|
-
starting_positions_for_graph('qwerty').
|
113
|
+
expect(starting_positions_for_graph('qwerty')).to eql 94
|
114
114
|
end
|
115
115
|
end
|
116
116
|
|
117
117
|
context 'when keyboard is dvorak' do
|
118
118
|
it 'returns the correct average degree over all keys' do
|
119
|
-
starting_positions_for_graph('dvorak').
|
119
|
+
expect(starting_positions_for_graph('dvorak')).to eql 94
|
120
120
|
end
|
121
121
|
end
|
122
122
|
|
123
123
|
context 'when keyboard is keypad' do
|
124
124
|
it 'returns the correct average degree over all keys' do
|
125
|
-
starting_positions_for_graph('keypad').
|
125
|
+
expect(starting_positions_for_graph('keypad')).to eql 15
|
126
126
|
end
|
127
127
|
end
|
128
128
|
|
129
129
|
context 'when keyboard is mac keypad' do
|
130
130
|
it 'returns the correct average degree over all keys' do
|
131
|
-
starting_positions_for_graph('mac_keypad').
|
131
|
+
expect(starting_positions_for_graph('mac_keypad')).to eql 16
|
132
132
|
end
|
133
133
|
end
|
134
134
|
end
|
data/spec/support/js_helpers.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require 'mini_racer'
|
2
2
|
require 'json'
|
3
3
|
|
4
4
|
module JsHelpers
|
@@ -6,9 +6,8 @@ module JsHelpers
|
|
6
6
|
JS_SOURCE_PATH = Pathname(File.expand_path('../js_source/', __FILE__))
|
7
7
|
|
8
8
|
def initialize
|
9
|
-
@ctx =
|
10
|
-
|
11
|
-
end
|
9
|
+
@ctx = MiniRacer::Context.new
|
10
|
+
@ctx.eval(JS_SOURCE_PATH.join('compiled.js').read)
|
12
11
|
end
|
13
12
|
|
14
13
|
def eval(string)
|
data/spec/support/matcher.rb
CHANGED
@@ -17,7 +17,7 @@ RSpec::Matchers.define :match_js_results do |expected_js_results|
|
|
17
17
|
@missing.empty? && @extra.empty?
|
18
18
|
end
|
19
19
|
|
20
|
-
|
20
|
+
failure_message do |actual|
|
21
21
|
"Matches missing from ruby results:\n#{@missing.inspect}\nMatches unique to ruby results:\n#{@extra.inspect}"
|
22
22
|
end
|
23
23
|
|
data/spec/tester_spec.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
-
|
1
|
+
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "spec_helper"
|
2
5
|
|
3
6
|
describe Zxcvbn::Tester do
|
4
7
|
let(:tester) { Zxcvbn::Tester.new }
|
@@ -8,44 +11,89 @@ describe Zxcvbn::Tester do
|
|
8
11
|
ruby_result = tester.test(password)
|
9
12
|
js_result = js_zxcvbn(password)
|
10
13
|
|
11
|
-
ruby_result.calc_time.
|
12
|
-
ruby_result.password.
|
13
|
-
ruby_result.entropy.
|
14
|
-
ruby_result.crack_time.
|
15
|
-
ruby_result.crack_time_display.
|
16
|
-
ruby_result.score.
|
17
|
-
ruby_result.pattern.
|
18
|
-
ruby_result.match_sequence.count.
|
14
|
+
expect(ruby_result.calc_time).not_to be_nil
|
15
|
+
expect(ruby_result.password).to eq js_result["password"]
|
16
|
+
expect(ruby_result.entropy).to eq js_result["entropy"]
|
17
|
+
expect(ruby_result.crack_time).to eq js_result["crack_time"]
|
18
|
+
expect(ruby_result.crack_time_display).to eq js_result["crack_time_display"]
|
19
|
+
expect(ruby_result.score).to eq js_result["score"]
|
20
|
+
expect(ruby_result.pattern).to eq js_result["pattern"]
|
21
|
+
expect(ruby_result.match_sequence.count).to eq js_result["match_sequence"].count
|
22
|
+
|
23
|
+
# NOTE: feedback didn't exist in the version of the JS library this gem
|
24
|
+
# is based on, so instead we just check that it put `Feedback` in
|
25
|
+
# there. Real tests for its values go in `feedback_giver_spec.rb`.
|
26
|
+
expect(ruby_result.feedback).to be_a Zxcvbn::Feedback
|
19
27
|
end
|
20
28
|
end
|
21
29
|
|
22
|
-
context
|
23
|
-
it
|
24
|
-
result = tester.test(
|
25
|
-
result.entropy.
|
26
|
-
result.score.
|
30
|
+
context "with a custom user dictionary" do
|
31
|
+
it "scores them against the user dictionary" do
|
32
|
+
result = tester.test("themeforest", ["themeforest"])
|
33
|
+
expect(result.entropy).to eq 0
|
34
|
+
expect(result.score).to eq 0
|
27
35
|
end
|
28
36
|
|
29
|
-
it
|
30
|
-
result = tester.test(
|
31
|
-
result.entropy.
|
32
|
-
result.score.
|
37
|
+
it "matches l33t substitutions on this dictionary" do
|
38
|
+
result = tester.test("th3m3for3st", ["themeforest"])
|
39
|
+
expect(result.entropy).to eq 1
|
40
|
+
expect(result.score).to eq 0
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "with Unicode entries in the password" do
|
45
|
+
it "validates the password" do
|
46
|
+
result = tester.test("✅🐴🔋staple", %w[Theme Forest themeforest])
|
47
|
+
expect(result.entropy).to be_positive
|
48
|
+
expect(result.score).to be_positive
|
33
49
|
end
|
34
50
|
end
|
35
51
|
|
36
|
-
context
|
37
|
-
|
52
|
+
context "with Unicode entries in the dictionary" do
|
53
|
+
it "validates the password" do
|
54
|
+
result = tester.test("correct horse battery staple", %w[✅ 🐴 🔋])
|
55
|
+
expect(result.entropy).to be_positive
|
56
|
+
expect(result.score).to be_positive
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "with Unicode entries in the password and the dictionary" do
|
61
|
+
it "validates the password" do
|
62
|
+
result = tester.test("✅🐴🔋staple", %w[✅ 🐴 🔋])
|
63
|
+
expect(result.entropy).to be_positive
|
64
|
+
expect(result.score).to be_zero
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "with invalid entries in the dictionary" do
|
69
|
+
it "ignores those entries" do
|
70
|
+
result = tester.test("themeforest", [nil, 1, "themeforest"])
|
71
|
+
expect(result.entropy).to eq 0
|
72
|
+
expect(result.score).to eq 0
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context "with a custom global dictionary" do
|
77
|
+
before { tester.add_word_lists("envato" => ["envato"]) }
|
78
|
+
|
79
|
+
it "scores them against the dictionary" do
|
80
|
+
result = tester.test("envato")
|
81
|
+
expect(result.entropy).to eq 0
|
82
|
+
expect(result.score).to eq 0
|
83
|
+
end
|
84
|
+
|
85
|
+
context "with invalid entries in a custom dictionary" do
|
86
|
+
before { tester.add_word_lists("themeforest" => [nil, 1, "themeforest"]) }
|
38
87
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
result.score.should eq 0
|
88
|
+
it "ignores those entries" do
|
89
|
+
expect(tester.test("themeforest")).to have_attributes(entropy: 0, score: 0, crack_time: 0)
|
90
|
+
end
|
43
91
|
end
|
44
92
|
end
|
45
93
|
|
46
|
-
context
|
94
|
+
context "nil password" do
|
47
95
|
specify do
|
48
|
-
expect
|
96
|
+
expect(tester.test(nil)).to have_attributes(entropy: 0, score: 0, crack_time: 0)
|
49
97
|
end
|
50
98
|
end
|
51
|
-
end
|
99
|
+
end
|
data/zxcvbn-ruby.gemspec
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
require File.expand_path('../lib/zxcvbn/version', __FILE__)
|
3
3
|
|
4
|
+
GITHUB_URL = 'https://github.com/envato/zxcvbn-ruby'
|
5
|
+
|
4
6
|
Gem::Specification.new do |gem|
|
5
7
|
gem.authors = ["Steve Hodgkiss", "Matthieu Aussaguel"]
|
6
8
|
gem.email = ["steve@hodgkiss.me", "matthieu.aussaguel@gmail.com"]
|
@@ -14,7 +16,18 @@ Gem::Specification.new do |gem|
|
|
14
16
|
gem.name = "zxcvbn-ruby"
|
15
17
|
gem.require_paths = ["lib"]
|
16
18
|
gem.version = Zxcvbn::VERSION
|
19
|
+
gem.license = 'MIT'
|
20
|
+
|
21
|
+
gem.required_ruby_version = '>= 2.5'
|
17
22
|
|
18
|
-
gem.add_development_dependency '
|
23
|
+
gem.add_development_dependency 'mini_racer'
|
19
24
|
gem.add_development_dependency 'rspec'
|
25
|
+
|
26
|
+
gem.metadata = {
|
27
|
+
'bug_tracker_uri' => "#{GITHUB_URL}/issues",
|
28
|
+
'changelog_uri' => "#{GITHUB_URL}/blob/HEAD/CHANGELOG.md",
|
29
|
+
'documentation_uri' => "#{GITHUB_URL}/blob/HEAD/README.md",
|
30
|
+
'homepage_uri' => GITHUB_URL,
|
31
|
+
'source_code_uri' => GITHUB_URL
|
32
|
+
}
|
20
33
|
end
|
metadata
CHANGED
@@ -1,47 +1,42 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zxcvbn-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
5
|
-
prerelease:
|
4
|
+
version: 1.2.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Steve Hodgkiss
|
9
8
|
- Matthieu Aussaguel
|
10
|
-
autorequire:
|
9
|
+
autorequire:
|
11
10
|
bindir: bin
|
12
11
|
cert_chain: []
|
13
|
-
date:
|
12
|
+
date: 2021-01-05 00:00:00.000000000 Z
|
14
13
|
dependencies:
|
15
14
|
- !ruby/object:Gem::Dependency
|
16
|
-
name:
|
15
|
+
name: mini_racer
|
17
16
|
requirement: !ruby/object:Gem::Requirement
|
18
|
-
none: false
|
19
17
|
requirements:
|
20
|
-
- -
|
18
|
+
- - ">="
|
21
19
|
- !ruby/object:Gem::Version
|
22
20
|
version: '0'
|
23
21
|
type: :development
|
24
22
|
prerelease: false
|
25
23
|
version_requirements: !ruby/object:Gem::Requirement
|
26
|
-
none: false
|
27
24
|
requirements:
|
28
|
-
- -
|
25
|
+
- - ">="
|
29
26
|
- !ruby/object:Gem::Version
|
30
27
|
version: '0'
|
31
28
|
- !ruby/object:Gem::Dependency
|
32
29
|
name: rspec
|
33
30
|
requirement: !ruby/object:Gem::Requirement
|
34
|
-
none: false
|
35
31
|
requirements:
|
36
|
-
- -
|
32
|
+
- - ">="
|
37
33
|
- !ruby/object:Gem::Version
|
38
34
|
version: '0'
|
39
35
|
type: :development
|
40
36
|
prerelease: false
|
41
37
|
version_requirements: !ruby/object:Gem::Requirement
|
42
|
-
none: false
|
43
38
|
requirements:
|
44
|
-
- -
|
39
|
+
- - ">="
|
45
40
|
- !ruby/object:Gem::Version
|
46
41
|
version: '0'
|
47
42
|
description: Ruby port of Dropboxs zxcvbn.js
|
@@ -52,10 +47,14 @@ executables: []
|
|
52
47
|
extensions: []
|
53
48
|
extra_rdoc_files: []
|
54
49
|
files:
|
55
|
-
- .
|
56
|
-
- .
|
50
|
+
- ".github/workflows/ci.yml"
|
51
|
+
- ".gitignore"
|
52
|
+
- ".rspec"
|
53
|
+
- CHANGELOG.md
|
54
|
+
- CODE_OF_CONDUCT.md
|
57
55
|
- Gemfile
|
58
|
-
-
|
56
|
+
- Guardfile
|
57
|
+
- LICENSE.txt
|
59
58
|
- README.md
|
60
59
|
- Rakefile
|
61
60
|
- data/adjacency_graphs.json
|
@@ -69,6 +68,8 @@ files:
|
|
69
68
|
- lib/zxcvbn/data.rb
|
70
69
|
- lib/zxcvbn/dictionary_ranker.rb
|
71
70
|
- lib/zxcvbn/entropy.rb
|
71
|
+
- lib/zxcvbn/feedback.rb
|
72
|
+
- lib/zxcvbn/feedback_giver.rb
|
72
73
|
- lib/zxcvbn/match.rb
|
73
74
|
- lib/zxcvbn/matchers/date.rb
|
74
75
|
- lib/zxcvbn/matchers/dictionary.rb
|
@@ -88,6 +89,7 @@ files:
|
|
88
89
|
- lib/zxcvbn/tester.rb
|
89
90
|
- lib/zxcvbn/version.rb
|
90
91
|
- spec/dictionary_ranker_spec.rb
|
92
|
+
- spec/feedback_giver_spec.rb
|
91
93
|
- spec/matchers/date_spec.rb
|
92
94
|
- spec/matchers/dictionary_spec.rb
|
93
95
|
- spec/matchers/digits_spec.rb
|
@@ -117,37 +119,36 @@ files:
|
|
117
119
|
- spec/zxcvbn_spec.rb
|
118
120
|
- zxcvbn-ruby.gemspec
|
119
121
|
homepage: http://github.com/envato/zxcvbn-ruby
|
120
|
-
licenses:
|
121
|
-
|
122
|
+
licenses:
|
123
|
+
- MIT
|
124
|
+
metadata:
|
125
|
+
bug_tracker_uri: https://github.com/envato/zxcvbn-ruby/issues
|
126
|
+
changelog_uri: https://github.com/envato/zxcvbn-ruby/blob/HEAD/CHANGELOG.md
|
127
|
+
documentation_uri: https://github.com/envato/zxcvbn-ruby/blob/HEAD/README.md
|
128
|
+
homepage_uri: https://github.com/envato/zxcvbn-ruby
|
129
|
+
source_code_uri: https://github.com/envato/zxcvbn-ruby
|
130
|
+
post_install_message:
|
122
131
|
rdoc_options: []
|
123
132
|
require_paths:
|
124
133
|
- lib
|
125
134
|
required_ruby_version: !ruby/object:Gem::Requirement
|
126
|
-
none: false
|
127
135
|
requirements:
|
128
|
-
- -
|
136
|
+
- - ">="
|
129
137
|
- !ruby/object:Gem::Version
|
130
|
-
version: '
|
131
|
-
segments:
|
132
|
-
- 0
|
133
|
-
hash: 3120558835973257605
|
138
|
+
version: '2.5'
|
134
139
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
135
|
-
none: false
|
136
140
|
requirements:
|
137
|
-
- -
|
141
|
+
- - ">="
|
138
142
|
- !ruby/object:Gem::Version
|
139
143
|
version: '0'
|
140
|
-
segments:
|
141
|
-
- 0
|
142
|
-
hash: 3120558835973257605
|
143
144
|
requirements: []
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
specification_version: 3
|
145
|
+
rubygems_version: 3.2.3
|
146
|
+
signing_key:
|
147
|
+
specification_version: 4
|
148
148
|
summary: ''
|
149
149
|
test_files:
|
150
150
|
- spec/dictionary_ranker_spec.rb
|
151
|
+
- spec/feedback_giver_spec.rb
|
151
152
|
- spec/matchers/date_spec.rb
|
152
153
|
- spec/matchers/dictionary_spec.rb
|
153
154
|
- spec/matchers/digits_spec.rb
|