zxcvbn-ruby 1.2.2 → 1.2.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7132f3b910a1849c47f1edcb52d2dd2f6f3954472ad912bd20520c942fbc0bda
4
- data.tar.gz: 45c893886c18c81fcb03627b7dd341eecab816a5fab4e7a9efb9ab5ca0ff9375
3
+ metadata.gz: 62e582cfff03a7e2075efea27771a6e8a542e799546b67ab7e342df77fc503e5
4
+ data.tar.gz: 9ac56545038db446e0dac7421f8d5ebc22bdb62abc808ba9801f18fbaf474257
5
5
  SHA512:
6
- metadata.gz: 259633b2081be2ddd35ae1dfbcf17cf44e50f099e2bff7accbe15ffc6f1faa92217517a6ffdb060540276170f1a12d4cb8fab3ef256ee152efc525b6a902d3c2
7
- data.tar.gz: 73e728a75b41c46e87074a95a7a8b1849eb6fcb50899e3b3bee6900d77ca0ecacd85f7d2ed539a180420423f0a6d705dc9da377bce107db5e21cb7f661682230
6
+ metadata.gz: 15a3f510c69b660169c08e7552f04ba5072db4865977aa436e3b673cd2053dadd0af5a5d143ea936a86993c1cbe48653957384bf97aac7056d80d31a99c986a9
7
+ data.tar.gz: 6c69b9142475710c4ce02ccbef7da7faaa4c54426118d29383f55d20c1b9c1d62459bd8de842f37830b22568ba41cb4a5717dc6531d9c4edb7625691cf3d1229
data/CHANGELOG.md CHANGED
@@ -6,14 +6,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
- [Unreleased]: https://github.com/envato/zxcvbn-ruby/compare/v1.2.2...HEAD
9
+ [Unreleased]: https://github.com/envato/zxcvbn-ruby/compare/v1.2.3...HEAD
10
+
11
+ ## [1.2.3] - 2025-12-07
12
+
13
+ ### Changed
14
+ - Address linting issues ([#52])
15
+ - Address style issues ([#53], [#54], [#55])
16
+
17
+ [1.2.3]: https://github.com/envato/zxcvbn-ruby/compare/v1.2.2...v1.2.3
18
+ [#52]: https://github.com/envato/zxcvbn-ruby/pull/52
19
+ [#53]: https://github.com/envato/zxcvbn-ruby/pull/53
20
+ [#54]: https://github.com/envato/zxcvbn-ruby/pull/54
21
+ [#55]: https://github.com/envato/zxcvbn-ruby/pull/55
10
22
 
11
23
  ## [1.2.2] - 2025-12-06
12
24
 
13
25
  ### Changed
14
26
  - Address layout and frozen string literal issues ([#49])
15
27
 
16
- [1.2.2]: https://github.com/envato/zxcvbn-ruby/compare/v1.2.1...v.1.2.2
28
+ [1.2.2]: https://github.com/envato/zxcvbn-ruby/compare/v1.2.1...v1.2.2
17
29
  [#49]: https://github.com/envato/zxcvbn-ruby/pull/49
18
30
 
19
31
  ## [1.2.1] - 2025-12-05
@@ -22,7 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
22
34
  - Removed the dependency on the Ruby `benchmark` module ([#44]).
23
35
  - Tests are no longer included in the gem package ([#45]).
24
36
 
25
- [1.2.1]: https://github.com/envato/zxcvbn-ruby/compare/v1.2.0...v.1.2.1
37
+ [1.2.1]: https://github.com/envato/zxcvbn-ruby/compare/v1.2.0...v1.2.1
26
38
  [#44]: https://github.com/envato/zxcvbn-ruby/pull/44
27
39
  [#45]: https://github.com/envato/zxcvbn-ruby/pull/45
28
40
 
@@ -35,7 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
35
47
  - Use [mini\_racer] for running JavaScript specs (thanks [@RSO] ([#33]))
36
48
  - Moved CI to GitHub Actions ([#34])
37
49
 
38
- [1.2.0]: https://github.com/envato/zxcvbn-ruby/compare/v1.1.0...v.1.2.0
50
+ [1.2.0]: https://github.com/envato/zxcvbn-ruby/compare/v1.1.0...v1.2.0
39
51
  [@rso]: https://github.com/RSO
40
52
  [mini\_racer]: https://rubygems.org/gems/mini_racer/
41
53
  [#32]: https://github.com/envato/zxcvbn-ruby/pull/32
data/README.md CHANGED
@@ -126,11 +126,6 @@ attacker. For this reason we advise you not to store the results of
126
126
  - [GitHub project](https://github.com/envato/zxcvbn-ruby)
127
127
  - Bug reports and feature requests are welcome via [GitHub Issues](https://github.com/envato/zxcvbn-ruby/issues)
128
128
 
129
- ## Maintainers
130
-
131
- - [Pete Johns](https://github.com/johnsyweb)
132
- - [Steve Hodgkiss](https://github.com/stevehodgkiss)
133
-
134
129
  ## Authors
135
130
 
136
131
  - [Steve Hodgkiss](https://github.com/stevehodgkiss)
@@ -162,18 +157,14 @@ For larger new features: Do everything as above, but first also make contact wit
162
157
 
163
158
  ## About [![code with heart by Envato](https://img.shields.io/badge/%3C%2F%3E%20with%20%E2%99%A5%20by-Envato-ff69b4.svg?style=flat-square)](https://github.com/envato/zxcvbn-ruby)
164
159
 
165
- This project is maintained by the [Envato engineering team][webuild] and funded by [Envato][envato].
166
-
167
- [<img src="http://opensource.envato.com/images/envato-oss-readme-logo.png" alt="Envato logo">][envato]
160
+ This project is maintained by the Envato engineering team and funded by [Envato][envato].
168
161
 
169
162
  Encouraging the use and creation of open source software is one of the ways we
170
- serve our community. See [our other projects][oss] or [come work with us][careers]
163
+ serve our community. Perhaps [come work with us][careers]
171
164
  where you'll find an incredibly diverse, intelligent and capable group of people
172
165
  who help make our company succeed and make our workplace fun, friendly and
173
166
  happy.
174
167
 
175
168
  [careers]: https://envato.com/careers/?utm_source=github
176
169
  [envato]: https://envato.com?utm_source=github
177
- [oss]: https://opensource.envato.com/?utm_source=github
178
- [webuild]: https://webuild.envato.com?utm_source=github
179
170
  [zxcvbn.js]: https://github.com/dropbox/zxcvbn
@@ -12,14 +12,13 @@ module Zxcvbn
12
12
  end
13
13
 
14
14
  def crack_time_to_score(seconds)
15
- case
16
- when seconds < 10**2
15
+ if seconds < 10**2
17
16
  0
18
- when seconds < 10**4
17
+ elsif seconds < 10**4
19
18
  1
20
- when seconds < 10**6
19
+ elsif seconds < 10**6
21
20
  2
22
- when seconds < 10**8
21
+ elsif seconds < 10**8
23
22
  3
24
23
  else
25
24
  4
@@ -34,18 +33,17 @@ module Zxcvbn
34
33
  year = month * 12
35
34
  century = year * 100
36
35
 
37
- case
38
- when seconds < minute
36
+ if seconds < minute
39
37
  'instant'
40
- when seconds < hour
38
+ elsif seconds < hour
41
39
  "#{1 + (seconds / minute).ceil} minutes"
42
- when seconds < day
40
+ elsif seconds < day
43
41
  "#{1 + (seconds / hour).ceil} hours"
44
- when seconds < month
42
+ elsif seconds < month
45
43
  "#{1 + (seconds / day).ceil} days"
46
- when seconds < year
44
+ elsif seconds < year
47
45
  "#{1 + (seconds / month).ceil} months"
48
- when seconds < century
46
+ elsif seconds < century
49
47
  "#{1 + (seconds / year).ceil} years"
50
48
  else
51
49
  'centuries'
data/lib/zxcvbn/data.rb CHANGED
@@ -7,11 +7,11 @@ module Zxcvbn
7
7
  class Data
8
8
  def initialize
9
9
  @ranked_dictionaries = DictionaryRanker.rank_dictionaries(
10
- "english" => read_word_list("english.txt"),
11
- "female_names" => read_word_list("female_names.txt"),
12
- "male_names" => read_word_list("male_names.txt"),
13
- "passwords" => read_word_list("passwords.txt"),
14
- "surnames" => read_word_list("surnames.txt")
10
+ 'english' => read_word_list('english.txt'),
11
+ 'female_names' => read_word_list('female_names.txt'),
12
+ 'male_names' => read_word_list('male_names.txt'),
13
+ 'passwords' => read_word_list('passwords.txt'),
14
+ 'surnames' => read_word_list('surnames.txt')
15
15
  )
16
16
  @adjacency_graphs = JSON.load(DATA_PATH.join('adjacency_graphs.json').read)
17
17
  end
@@ -25,7 +25,7 @@ module Zxcvbn
25
25
  private
26
26
 
27
27
  def read_word_list(file)
28
- DATA_PATH.join("frequency_lists", file).read.split
28
+ DATA_PATH.join('frequency_lists', file).read.split
29
29
  end
30
30
  end
31
31
  end
@@ -3,8 +3,8 @@
3
3
  module Zxcvbn
4
4
  class DictionaryRanker
5
5
  def self.rank_dictionaries(lists)
6
- lists.each_with_object({}) do |(dict_name, words), dictionaries|
7
- dictionaries[dict_name] = rank_dictionary(words)
6
+ lists.transform_values do |words|
7
+ rank_dictionary(words)
8
8
  end
9
9
  end
10
10
 
@@ -58,20 +58,19 @@ module Zxcvbn::Entropy
58
58
  NUM_MONTHS = 12
59
59
  NUM_DAYS = 31
60
60
 
61
- def year_entropy(match)
61
+ def year_entropy(_match)
62
62
  lg(NUM_YEARS)
63
63
  end
64
64
 
65
65
  def date_entropy(match)
66
- if match.year < 100
67
- entropy = lg(NUM_DAYS * NUM_MONTHS * 100)
68
- else
69
- entropy = lg(NUM_DAYS * NUM_MONTHS * NUM_YEARS)
70
- end
66
+ entropy =
67
+ if match.year < 100
68
+ lg(NUM_DAYS * NUM_MONTHS * 100)
69
+ else
70
+ lg(NUM_DAYS * NUM_MONTHS * NUM_YEARS)
71
+ end
71
72
 
72
- if match.separator
73
- entropy += 2
74
- end
73
+ entropy += 2 if match.separator
75
74
 
76
75
  entropy
77
76
  end
@@ -84,10 +83,10 @@ module Zxcvbn::Entropy
84
83
  match.base_entropy + match.uppercase_entropy + match.l33t_entropy
85
84
  end
86
85
 
87
- START_UPPER = /^[A-Z][^A-Z]+$/
88
- END_UPPER = /^[^A-Z]+[A-Z]$/
89
- ALL_UPPER = /^[A-Z]+$/
90
- ALL_LOWER = /^[a-z]+$/
86
+ START_UPPER = /^[A-Z][^A-Z]+$/.freeze
87
+ END_UPPER = /^[^A-Z]+[A-Z]$/.freeze
88
+ ALL_UPPER = /^[A-Z]+$/.freeze
89
+ ALL_LOWER = /^[a-z]+$/.freeze
91
90
 
92
91
  def extra_uppercase_entropy(match)
93
92
  word = match.token
@@ -116,11 +115,11 @@ module Zxcvbn::Entropy
116
115
  end
117
116
  end
118
117
  entropy = lg(possibilities)
119
- entropy == 0 ? 1 : entropy
118
+ entropy.zero? ? 1 : entropy
120
119
  end
121
120
 
122
121
  def spatial_entropy(match)
123
- if %w|qwerty dvorak|.include? match.graph
122
+ if %w[qwerty dvorak].include? match.graph
124
123
  starting_positions = starting_positions_for_graph('qwerty')
125
124
  average_degree = average_degree_for_graph('qwerty')
126
125
  else
@@ -18,14 +18,14 @@ module Zxcvbn
18
18
 
19
19
  def self.get_feedback(score, sequence)
20
20
  # starting feedback
21
- return DEFAULT_FEEDBACK if sequence.length.zero?
21
+ return DEFAULT_FEEDBACK if sequence.empty?
22
22
 
23
23
  # no feedback if score is good or great.
24
24
  return EMPTY_FEEDBACK if score > 2
25
25
 
26
26
  # tie feedback to the longest match for longer sequences
27
27
  longest_match = sequence[0]
28
- for match in sequence[1..-1]
28
+ sequence[1..-1].each do |match|
29
29
  longest_match = match if match.token.length > longest_match.token.length
30
30
  end
31
31
 
@@ -47,7 +47,6 @@ module Zxcvbn
47
47
  get_dictionary_match_feedback match, is_sole_match
48
48
 
49
49
  when 'spatial'
50
- layout = match.graph.upcase
51
50
  warning = if match.turns == 1
52
51
  'Straight rows of keys are easy to guess'
53
52
  else
@@ -7,23 +7,23 @@ module Zxcvbn
7
7
  class Date
8
8
  include RegexHelpers
9
9
 
10
- YEAR_SUFFIX = /
10
+ YEAR_SUFFIX = %r{
11
11
  ( \d{1,2} ) # day or month
12
- ( \s | \- | \/ | \\ | \_ | \. ) # separator
12
+ ( \s | - | / | \\ | _ | \. ) # separator
13
13
  ( \d{1,2} ) # month or day
14
14
  \2 # same separator
15
15
  ( 19\d{2} | 200\d | 201\d | \d{2} ) # year
16
- /x
16
+ }x.freeze
17
17
 
18
- YEAR_PREFIX = /
18
+ YEAR_PREFIX = %r{
19
19
  ( 19\d{2} | 200\d | 201\d | \d{2} ) # year
20
- ( \s | - | \/ | \\ | _ | \. ) # separator
20
+ ( \s | - | / | \\ | _ | \. ) # separator
21
21
  ( \d{1,2} ) # day or month
22
22
  \2 # same separator
23
23
  ( \d{1,2} ) # month or day
24
- /x
24
+ }x.freeze
25
25
 
26
- WITHOUT_SEPARATOR = /\d{4,8}/
26
+ WITHOUT_SEPARATOR = /\d{4,8}/.freeze
27
27
 
28
28
  def matches(password)
29
29
  match_with_separator(password) + match_without_separator(password)
@@ -54,9 +54,11 @@ module Zxcvbn
54
54
 
55
55
  def match_without_separator(password)
56
56
  result = []
57
- re_match_all(WITHOUT_SEPARATOR, password) do |match, re_match|
57
+ re_match_all(WITHOUT_SEPARATOR, password) do |match, _re_match|
58
58
  extract_dates(match.token).each do |candidate|
59
- day, month, year = candidate[:day], candidate[:month], candidate[:year]
59
+ day = candidate[:day]
60
+ month = candidate[:month]
61
+ year = candidate[:year]
60
62
 
61
63
  match.pattern = 'date'
62
64
  match.day = day
@@ -73,11 +75,11 @@ module Zxcvbn
73
75
  dates = []
74
76
  date_patterns_for_length(token.length).map do |pattern|
75
77
  candidate = {
76
- :year => +'',
77
- :month => +'',
78
- :day => +''
78
+ year: +'',
79
+ month: +'',
80
+ day: +''
79
81
  }
80
- for i in 0...token.length
82
+ (0...token.length).each do |i|
81
83
  candidate[PATTERN_CHAR_TO_SYM[pattern[i]]] << token[i]
82
84
  end
83
85
  candidate.each do |component, value|
@@ -94,18 +96,18 @@ module Zxcvbn
94
96
  end
95
97
 
96
98
  DATE_PATTERN_FOR_LENGTH = {
97
- 8 => %w[yyyymmdd ddmmyyyy mmddyyyy],
98
- 7 => %w[yyyymdd yyyymmd ddmyyyy dmmyyyy],
99
- 6 => %w[yymmdd ddmmyy mmddyy],
100
- 5 => %w[yymdd yymmd ddmyy dmmyy mmdyy mddyy],
101
- 4 => %w[yymd dmyy mdyy]
102
- }
99
+ 8 => %w[yyyymmdd ddmmyyyy mmddyyyy].freeze,
100
+ 7 => %w[yyyymdd yyyymmd ddmyyyy dmmyyyy].freeze,
101
+ 6 => %w[yymmdd ddmmyy mmddyy].freeze,
102
+ 5 => %w[yymdd yymmd ddmyy dmmyy mmdyy mddyy].freeze,
103
+ 4 => %w[yymd dmyy mdyy].freeze
104
+ }.freeze
103
105
 
104
106
  PATTERN_CHAR_TO_SYM = {
105
107
  'y' => :year,
106
108
  'm' => :month,
107
109
  'd' => :day
108
- }
110
+ }.freeze
109
111
 
110
112
  def date_patterns_for_length(length)
111
113
  DATE_PATTERN_FOR_LENGTH[length] || []
@@ -123,7 +125,7 @@ module Zxcvbn
123
125
  end
124
126
 
125
127
  def expand_year(year)
126
- return year
128
+ year
127
129
  # Block dates with 2 digit years for now to be compatible with the JS version
128
130
  # return year unless year < 100
129
131
  # now = Time.now.year
@@ -20,15 +20,17 @@ module Zxcvbn
20
20
  (0..password_length).each do |i|
21
21
  (i...password_length).each do |j|
22
22
  word = lowercased_password[i..j]
23
- if @ranked_dictionary.has_key?(word)
24
- results << Match.new(:matched_word => word,
25
- :token => password[i..j],
26
- :i => i,
27
- :j => j,
28
- :rank => @ranked_dictionary[word],
29
- :pattern => 'dictionary',
30
- :dictionary_name => @name)
31
- end
23
+ next unless @ranked_dictionary.key?(word)
24
+
25
+ results << Match.new(
26
+ matched_word: word,
27
+ token: password[i..j],
28
+ i: i,
29
+ j: j,
30
+ rank: @ranked_dictionary[word],
31
+ pattern: 'dictionary',
32
+ dictionary_name: @name
33
+ )
32
34
  end
33
35
  end
34
36
  results
@@ -7,7 +7,7 @@ module Zxcvbn
7
7
  class Digits
8
8
  include RegexHelpers
9
9
 
10
- DIGITS_REGEX = /\d{3,}/
10
+ DIGITS_REGEX = /\d{3,}/.freeze
11
11
 
12
12
  def matches(password)
13
13
  result = []
@@ -4,19 +4,19 @@ module Zxcvbn
4
4
  module Matchers
5
5
  class L33t
6
6
  L33T_TABLE = {
7
- 'a' => ['4', '@'],
8
- 'b' => ['8'],
9
- 'c' => ['(', '{', '[', '<'],
10
- 'e' => ['3'],
11
- 'g' => ['6', '9'],
12
- 'i' => ['1', '!', '|'],
13
- 'l' => ['1', '|', '7'],
14
- 'o' => ['0'],
15
- 's' => ['$', '5'],
16
- 't' => ['+', '7'],
17
- 'x' => ['%'],
18
- 'z' => ['2']
19
- }
7
+ 'a' => ['4', '@'].freeze,
8
+ 'b' => ['8'].freeze,
9
+ 'c' => ['(', '{', '[', '<'].freeze,
10
+ 'e' => ['3'].freeze,
11
+ 'g' => ['6', '9'].freeze,
12
+ 'i' => ['1', '!', '|'].freeze,
13
+ 'l' => ['1', '|', '7'].freeze,
14
+ 'o' => ['0'].freeze,
15
+ 's' => ['$', '5'].freeze,
16
+ 't' => ['+', '7'].freeze,
17
+ 'x' => ['%'].freeze,
18
+ 'z' => ['2'].freeze
19
+ }.freeze
20
20
 
21
21
  def initialize(dictionary_matchers)
22
22
  @dictionary_matchers = dictionary_matchers
@@ -117,7 +117,7 @@ module Zxcvbn
117
117
  subs.each do |sub|
118
118
  assoc = sub.dup
119
119
 
120
- assoc.sort! rescue debugger
120
+ assoc.sort!
121
121
  label = assoc.map { |k, v| "#{k},#{v}" }.join('-')
122
122
  unless members.include?(label)
123
123
  members << label
@@ -4,19 +4,19 @@ module Zxcvbn
4
4
  module Matchers
5
5
  class L33t
6
6
  L33T_TABLE = {
7
- 'a' => ['4', '@'],
8
- 'b' => ['8'],
9
- 'c' => ['(', '{', '[', '<'],
10
- 'e' => ['3'],
11
- 'g' => ['6', '9'],
12
- 'i' => ['1', '!', '|'],
13
- 'l' => ['1', '|', '7'],
14
- 'o' => ['0'],
15
- 's' => ['$', '5'],
16
- 't' => ['+', '7'],
17
- 'x' => ['%'],
18
- 'z' => ['2']
19
- }
7
+ 'a' => ['4', '@'].freeze,
8
+ 'b' => ['8'].freeze,
9
+ 'c' => ['(', '{', '[', '<'].freeze,
10
+ 'e' => ['3'].freeze,
11
+ 'g' => ['6', '9'].freeze,
12
+ 'i' => ['1', '!', '|'].freeze,
13
+ 'l' => ['1', '|', '7'].freeze,
14
+ 'o' => ['0'].freeze,
15
+ 's' => ['$', '5'].freeze,
16
+ 't' => ['+', '7'].freeze,
17
+ 'x' => ['%'].freeze,
18
+ 'z' => ['2'].freeze
19
+ }.freeze
20
20
 
21
21
  def initialize(dictionary_matchers)
22
22
  @dictionary_matchers = dictionary_matchers
@@ -26,17 +26,15 @@ module Zxcvbn
26
26
  matches = []
27
27
  lowercased_password = password.downcase
28
28
  combinations_to_try = substitution_combinations(relevant_l33t_substitutions(lowercased_password))
29
- # debugger if password == 'abcdefghijk987654321'
30
- combinations_to_try.each do |substitution|
29
+ combinations_to_try.each do |substitutions|
31
30
  @dictionary_matchers.each do |matcher|
32
- subbed_password = substitute(lowercased_password, substitution)
31
+ subbed_password = substitute(lowercased_password, substitutions)
33
32
  matcher.matches(subbed_password).each do |match|
34
33
  token = lowercased_password[match.i..match.j]
35
34
  next if token == match.matched_word.downcase
36
35
 
37
- # debugger if token == '1'
38
36
  match_substitutions = {}
39
- substitution.each do |letter, substitution|
37
+ substitutions.each do |letter, substitution|
40
38
  match_substitutions[substitution] = letter if token.include?(substitution)
41
39
  end
42
40
  match.l33t = true
@@ -52,9 +50,9 @@ module Zxcvbn
52
50
  matches
53
51
  end
54
52
 
55
- def substitute(password, substitution)
53
+ def substitute(password, substitutions)
56
54
  subbed_password = password.dup
57
- substitution.each do |letter, substitution|
55
+ substitutions.each do |letter, substitution|
58
56
  subbed_password.gsub!(substitution, letter)
59
57
  end
60
58
  subbed_password
@@ -67,9 +65,7 @@ module Zxcvbn
67
65
  end
68
66
  L33T_TABLE.each do |letter, substibutions|
69
67
  password.each_char do |password_char|
70
- if substibutions.include?(password_char)
71
- subs[letter] << password_char
72
- end
68
+ subs[letter] << password_char if substibutions.include?(password_char)
73
69
  end
74
70
  end
75
71
  subs
@@ -97,15 +93,13 @@ module Zxcvbn
97
93
  end
98
94
 
99
95
  # convert back to simple hash per substitution combination
100
- combination_hashes = combinations.map do |combination_set|
96
+ combinations.map do |combination_set|
101
97
  hash = {}
102
98
  combination_set.each do |combination_hash|
103
99
  hash.merge!(combination_hash)
104
100
  end
105
101
  hash
106
102
  end
107
-
108
- combination_hashes
109
103
  end
110
104
 
111
105
  # expand possible combinations if multiple characters can be substituted
@@ -7,15 +7,15 @@ module Zxcvbn
7
7
  module RegexHelpers
8
8
  def re_match_all(regex, password)
9
9
  pos = 0
10
- while re_match = regex.match(password, pos)
10
+ while (re_match = regex.match(password, pos))
11
11
  i, j = re_match.offset(0)
12
12
  pos = j
13
13
  j -= 1
14
14
 
15
15
  match = Match.new(
16
- :i => i,
17
- :j => j,
18
- :token => password[i..j]
16
+ i: i,
17
+ j: j,
18
+ token: password[i..j]
19
19
  )
20
20
  yield match, re_match
21
21
  end
@@ -11,17 +11,15 @@ module Zxcvbn
11
11
  while i < password.length
12
12
  cur_char = password[i]
13
13
  j = i + 1
14
- while cur_char == password[j]
15
- j += 1
16
- end
14
+ j += 1 while cur_char == password[j]
17
15
 
18
16
  if j - i > 2 # don't consider length 1 or 2 chains.
19
17
  result << Match.new(
20
- :pattern => 'repeat',
21
- :i => i,
22
- :j => j - 1,
23
- :token => password[i...j],
24
- :repeated_char => cur_char
18
+ pattern: 'repeat',
19
+ i: i,
20
+ j: j - 1,
21
+ token: password[i...j],
22
+ repeated_char: cur_char
25
23
  )
26
24
  end
27
25
 
@@ -9,7 +9,7 @@ module Zxcvbn
9
9
  'lower' => 'abcdefghijklmnopqrstuvwxyz',
10
10
  'upper' => 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
11
11
  'digits' => '01234567890'
12
- }
12
+ }.freeze
13
13
 
14
14
  def seq_match_length(password, from, direction, seq)
15
15
  index_from = seq.index(password[from])
@@ -27,14 +27,12 @@ module Zxcvbn
27
27
  SEQUENCES.each do |name, sequence|
28
28
  index1 = sequence.index(password[i])
29
29
  index2 = sequence.index(password[i + 1])
30
- if index1 and index2
31
- seq_direction = index2 - index1
32
- if [-1, 1].include?(seq_direction)
33
- return [name, sequence, seq_direction]
34
- else
35
- return nil
36
- end
37
- end
30
+ next unless index1 && index2
31
+
32
+ seq_direction = index2 - index1
33
+ return [name, sequence, seq_direction] if [-1, 1].include?(seq_direction)
34
+
35
+ return nil
38
36
  end
39
37
  end
40
38
 
@@ -48,13 +46,13 @@ module Zxcvbn
48
46
  length = seq_match_length(password, i, seq_direction, seq)
49
47
  if length > 2
50
48
  result << Match.new(
51
- :pattern => 'sequence',
52
- :i => i,
53
- :j => i + length - 1,
54
- :token => password[i, length],
55
- :sequence_name => seq_name,
56
- :sequence_space => seq.length,
57
- :ascending => seq_direction == 1
49
+ pattern: 'sequence',
50
+ i: i,
51
+ j: i + length - 1,
52
+ token: password[i, length],
53
+ sequence_name: seq_name,
54
+ sequence_space: seq.length,
55
+ ascending: seq_direction == 1
58
56
  )
59
57
  end
60
58
  i += length - 1
@@ -36,22 +36,22 @@ module Zxcvbn
36
36
  cur_char = password[j]
37
37
  adjacents.each do |adj|
38
38
  cur_direction += 1
39
- if adj && adj.index(cur_char)
40
- found = true
41
- found_direction = cur_direction
42
- if adj.index(cur_char) == 1
43
- # index 1 in the adjacency means the key is shifted, 0 means unshifted: A vs a, % vs 5, etc.
44
- # for example, 'q' is adjacent to the entry '2@'. @ is shifted w/ index 1, 2 is unshifted.
45
- shifted_count += 1
46
- end
47
- if last_direction != found_direction
48
- # adding a turn is correct even in the initial case when last_direction is null:
49
- # every spatial pattern starts with a turn.
50
- turns += 1
51
- last_direction = found_direction
52
- end
53
- break
39
+ next unless adj&.index(cur_char)
40
+
41
+ found = true
42
+ found_direction = cur_direction
43
+ if adj.index(cur_char) == 1
44
+ # index 1 in the adjacency means the key is shifted, 0 means unshifted: A vs a, % vs 5, etc.
45
+ # for example, 'q' is adjacent to the entry '2@'. @ is shifted w/ index 1, 2 is unshifted.
46
+ shifted_count += 1
47
+ end
48
+ if last_direction != found_direction
49
+ # adding a turn is correct even in the initial case when last_direction is null:
50
+ # every spatial pattern starts with a turn.
51
+ turns += 1
52
+ last_direction = found_direction
54
53
  end
54
+ break
55
55
  end
56
56
  end
57
57
  # if the current pattern continued, extend j and try to grow again
@@ -61,13 +61,13 @@ module Zxcvbn
61
61
  # otherwise push the pattern discovered so far, if any...
62
62
  if j - i > 2 # don't consider length 1 or 2 chains.
63
63
  result << Match.new(
64
- :pattern => 'spatial',
65
- :i => i,
66
- :j => j - 1,
67
- :token => password[i...j],
68
- :graph => graph_name,
69
- :turns => turns,
70
- :shifted_count => shifted_count
64
+ pattern: 'spatial',
65
+ i: i,
66
+ j: j - 1,
67
+ token: password[i...j],
68
+ graph: graph_name,
69
+ turns: turns,
70
+ shifted_count: shifted_count
71
71
  )
72
72
  end
73
73
  # ...and then start a new search for the rest of the password.
@@ -7,7 +7,7 @@ module Zxcvbn
7
7
  class Year
8
8
  include RegexHelpers
9
9
 
10
- YEAR_REGEX = /19\d\d|200\d|201\d/
10
+ YEAR_REGEX = /19\d\d|200\d|201\d/.freeze
11
11
 
12
12
  def matches(password)
13
13
  result = []
data/lib/zxcvbn/math.rb CHANGED
@@ -32,12 +32,12 @@ module Zxcvbn
32
32
 
33
33
  def nCk(n, k)
34
34
  return 0 if k > n
35
- return 1 if k == 0
35
+ return 1 if k.zero?
36
36
 
37
37
  r = 1
38
38
  (1..k).each do |d|
39
- r = r * n
40
- r = r / d
39
+ r *= n
40
+ r /= d
41
41
  n -= 1
42
42
  end
43
43
  r
@@ -13,7 +13,7 @@ module Zxcvbn
13
13
  end
14
14
 
15
15
  def test(password, user_inputs = [])
16
- password = password || ''
16
+ password ||= ''
17
17
  result = nil
18
18
  calc_time = Clock.realtime do
19
19
  matches = @omnimatch.matches(password, user_inputs)
data/lib/zxcvbn/scorer.rb CHANGED
@@ -24,15 +24,16 @@ module Zxcvbn
24
24
  backpointers = []
25
25
  (0...password.length).each do |k|
26
26
  # starting scenario to try and beat: adding a brute-force character to the minimum entropy sequence at k-1.
27
- previous_k_entropy = k > 0 ? up_to_k[k - 1] : 0
27
+ previous_k_entropy = k.positive? ? up_to_k[k - 1] : 0
28
28
  up_to_k[k] = previous_k_entropy + lg(bruteforce_cardinality)
29
29
  backpointers[k] = nil
30
30
  matches.select do |match|
31
31
  match.j == k
32
32
  end.each do |match|
33
- i, j = match.i, match.j
33
+ i = match.i
34
+ j = match.j
34
35
  # see if best entropy up to i-1 + entropy of this match is less than the current minimum at j.
35
- previous_i_entropy = i > 0 ? up_to_k[i - 1] : 0
36
+ previous_i_entropy = i.positive? ? up_to_k[i - 1] : 0
36
37
  candidate_entropy = previous_i_entropy + calc_entropy(match)
37
38
  if up_to_k[j] && candidate_entropy < up_to_k[j]
38
39
  up_to_k[j] = candidate_entropy
@@ -58,18 +59,18 @@ module Zxcvbn
58
59
  score_for(password, match_sequence, up_to_k)
59
60
  end
60
61
 
61
- def score_for password, match_sequence, up_to_k
62
+ def score_for(password, match_sequence, up_to_k)
62
63
  min_entropy = up_to_k[password.length - 1] || 0 # or 0 corner case is for an empty password ''
63
64
  crack_time = entropy_to_crack_time(min_entropy)
64
65
 
65
66
  # final result object
66
67
  Score.new(
67
- :password => password,
68
- :entropy => min_entropy.round(3),
69
- :match_sequence => match_sequence,
70
- :crack_time => crack_time.round(3),
71
- :crack_time_display => display_time(crack_time),
72
- :score => crack_time_to_score(crack_time)
68
+ password: password,
69
+ entropy: min_entropy.round(3),
70
+ match_sequence: match_sequence,
71
+ crack_time: crack_time.round(3),
72
+ crack_time_display: display_time(crack_time),
73
+ score: crack_time_to_score(crack_time)
73
74
  )
74
75
  end
75
76
 
@@ -77,9 +78,7 @@ module Zxcvbn
77
78
  k = 0
78
79
  match_sequence_copy = []
79
80
  match_sequence.each do |match|
80
- if match.i > k
81
- match_sequence_copy << make_bruteforce_match(password, k, match.i - 1, bruteforce_cardinality)
82
- end
81
+ match_sequence_copy << make_bruteforce_match(password, k, match.i - 1, bruteforce_cardinality) if match.i > k
83
82
  k = match.j + 1
84
83
  match_sequence_copy << match
85
84
  end
@@ -94,12 +93,12 @@ module Zxcvbn
94
93
  # match1.j == match2.i - 1 for every adjacent match1, match2.
95
94
  def make_bruteforce_match(password, i, j, bruteforce_cardinality)
96
95
  Match.new(
97
- :pattern => 'bruteforce',
98
- :i => i,
99
- :j => j,
100
- :token => password[i..j],
101
- :entropy => lg(bruteforce_cardinality**(j - i + 1)),
102
- :cardinality => bruteforce_cardinality
96
+ pattern: 'bruteforce',
97
+ i: i,
98
+ j: j,
99
+ token: password[i..j],
100
+ entropy: lg(bruteforce_cardinality**(j - i + 1)),
101
+ cardinality: bruteforce_cardinality
103
102
  )
104
103
  end
105
104
  end
data/lib/zxcvbn/tester.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "zxcvbn/data"
4
- require "zxcvbn/password_strength"
3
+ require 'zxcvbn/data'
4
+ require 'zxcvbn/password_strength'
5
5
 
6
6
  module Zxcvbn
7
7
  # Allows you to test the strength of multiple passwords without reading and
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zxcvbn
4
- VERSION = '1.2.2'
4
+ VERSION = '1.2.3'
5
5
  end
data/lib/zxcvbn.rb CHANGED
@@ -5,9 +5,9 @@ require 'zxcvbn/version'
5
5
  require 'zxcvbn/tester'
6
6
 
7
7
  module Zxcvbn
8
- extend self
8
+ module_function
9
9
 
10
- DATA_PATH = Pathname(File.expand_path('../../data', __FILE__))
10
+ DATA_PATH = Pathname(File.expand_path('../data', __dir__))
11
11
 
12
12
  # Returns a Zxcvbn::Score for the given password
13
13
  #
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zxcvbn-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.2
4
+ version: 1.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steve Hodgkiss