zxcvbn-ruby 1.2.4 → 1.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 52ddbaaabcb2929e59d34d91104bce81b86f223428001218872294363d79f2ac
4
- data.tar.gz: 5c488ab8d0dfbd6c46b2b5b05f3b878355e09babe09304c3653c9e45dcf64d02
3
+ metadata.gz: 43a759e1040848c8da4bea8d1e871eb7d811d5cab0ebf962255990963b796f75
4
+ data.tar.gz: c78d0453bb8c92d1f3b21c37f1493f305a6667f24e6ae85a621841a9396fb584
5
5
  SHA512:
6
- metadata.gz: 6ac904b4f3e4219981def358d1f7aa69870e9e33605c3696123e95f936d4880b39d2b5e1d5f323fd89ebc7b2ff43eaa93f22c598ac40ea20bdd768072a082162
7
- data.tar.gz: f32d688a3ee53867c1f47f0e52527d3da4397e26c93854305ee5cbcf224e9dad104f087ab80174848b3765dd41a086a6fd33a4ab72a1b72d8ea05209aa88b289
6
+ metadata.gz: 552d40a11c071613eefb14819b4dabbd3abc6a1f2d336e6bf9bfbc873a34640a229b3bedcc0bd77aca6ffe5e43e31fefec74fe94d74f3cdaebd4e7169610ec02
7
+ data.tar.gz: 84c006df22e0224da4a891f8fa923c81b69229f2b99e5d60ba29104473e566c83a36fbcc404defe980d80cfb176964864f00dbdfa02d655e05eaac7753e64360
data/CHANGELOG.md CHANGED
@@ -6,7 +6,39 @@ 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.4...HEAD
9
+ [Unreleased]: https://github.com/envato/zxcvbn-ruby/compare/v1.4.0...HEAD
10
+
11
+ ## [1.4.0] - 2026-01-15
12
+
13
+ ### Added
14
+ - RBS type signatures for improved type checking and IDE support ([#68])
15
+
16
+ ### Changed
17
+ - Minor fixups in gem metadata ([#67]).
18
+
19
+ [1.4.0]: https://github.com/envato/zxcvbn-ruby/compare/v1.3.0...v1.4.0
20
+ [#67]: https://github.com/envato/zxcvbn-ruby/pull/67
21
+ [#68]: https://github.com/envato/zxcvbn-ruby/pull/68
22
+
23
+ ## [1.3.0] - 2026-01-02
24
+
25
+ ### Changed
26
+ - Replace OpenStruct with regular class in `Zxcvbn::Match` for 2x performance improvement ([#61])
27
+ - Implement Trie data structure for dictionary matching with 1.4x additional performance improvement ([#62])
28
+ - Replace range operators with `String#slice` for string slicing operations ([#63])
29
+ - Optimise L33t matcher with early bailout and improved deduplication ([#64])
30
+ - Pre-compute spatial graph statistics during data initialisation ([#65])
31
+ - Optimise nCk calculation using symmetry property ([#66])
32
+
33
+ Overall performance improvement: 4.1x faster than v1.2.4 (0.722ms → 0.176ms per password)
34
+
35
+ [1.3.0]: https://github.com/envato/zxcvbn-ruby/compare/v1.2.4...v1.3.0
36
+ [#61]: https://github.com/envato/zxcvbn-ruby/pull/61
37
+ [#62]: https://github.com/envato/zxcvbn-ruby/pull/62
38
+ [#63]: https://github.com/envato/zxcvbn-ruby/pull/63
39
+ [#64]: https://github.com/envato/zxcvbn-ruby/pull/64
40
+ [#65]: https://github.com/envato/zxcvbn-ruby/pull/65
41
+ [#66]: https://github.com/envato/zxcvbn-ruby/pull/66
10
42
 
11
43
  ## [1.2.4] - 2025-12-07
12
44
 
data/lib/zxcvbn/data.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'json'
4
4
  require 'zxcvbn/dictionary_ranker'
5
+ require 'zxcvbn/trie'
5
6
 
6
7
  module Zxcvbn
7
8
  class Data
@@ -14,12 +15,16 @@ module Zxcvbn
14
15
  'surnames' => read_word_list('surnames.txt')
15
16
  )
16
17
  @adjacency_graphs = JSON.parse(DATA_PATH.join('adjacency_graphs.json').read)
18
+ @dictionary_tries = build_tries
19
+ @graph_stats = compute_graph_stats
17
20
  end
18
21
 
19
- attr_reader :ranked_dictionaries, :adjacency_graphs
22
+ attr_reader :ranked_dictionaries, :adjacency_graphs, :dictionary_tries, :graph_stats
20
23
 
21
24
  def add_word_list(name, list)
22
- @ranked_dictionaries[name] = DictionaryRanker.rank_dictionary(list)
25
+ ranked_dict = DictionaryRanker.rank_dictionary(list)
26
+ @ranked_dictionaries[name] = ranked_dict
27
+ @dictionary_tries[name] = build_trie(ranked_dict)
23
28
  end
24
29
 
25
30
  private
@@ -27,5 +32,31 @@ module Zxcvbn
27
32
  def read_word_list(file)
28
33
  DATA_PATH.join('frequency_lists', file).read.split
29
34
  end
35
+
36
+ def build_tries
37
+ @ranked_dictionaries.transform_values { |dict| build_trie(dict) }
38
+ end
39
+
40
+ def build_trie(ranked_dictionary)
41
+ trie = Trie.new
42
+ ranked_dictionary.each { |word, rank| trie.insert(word, rank) }
43
+ trie
44
+ end
45
+
46
+ def compute_graph_stats
47
+ stats = {}
48
+ @adjacency_graphs.each do |graph_name, graph|
49
+ degrees = graph.map { |_, neighbors| neighbors.compact.size }
50
+ sum = degrees.inject(0, :+)
51
+ average_degree = sum.to_f / graph.size
52
+ starting_positions = graph.length
53
+
54
+ stats[graph_name] = {
55
+ average_degree: average_degree,
56
+ starting_positions: starting_positions
57
+ }
58
+ end
59
+ stats
60
+ end
30
61
  end
31
62
  end
@@ -9,10 +9,9 @@ module Zxcvbn
9
9
  end
10
10
 
11
11
  def self.rank_dictionary(words)
12
- words.each_with_index
13
- .with_object({}) do |(word, i), dictionary|
14
- dictionary[word.downcase] = i + 1
15
- end
12
+ words
13
+ .each_with_index
14
+ .with_object({}) { |(word, i), dictionary| dictionary[word.downcase] = i + 1 }
16
15
  end
17
16
  end
18
17
  end
data/lib/zxcvbn/match.rb CHANGED
@@ -1,12 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ostruct'
4
-
5
3
  module Zxcvbn
6
- class Match < OpenStruct
4
+ class Match
5
+ attr_accessor :pattern, :i, :j, :token, :matched_word, :rank,
6
+ :dictionary_name, :reversed, :l33t, :sub, :sub_display,
7
+ :l, :entropy, :base_entropy, :uppercase_entropy, :l33t_entropy,
8
+ :repeated_char, :sequence_name, :sequence_space, :ascending,
9
+ :graph, :turns, :shifted_count, :shiffted_count,
10
+ :year, :month, :day, :separator, :cardinality, :offset
11
+
12
+ def initialize(**attributes)
13
+ attributes.each do |key, value|
14
+ instance_variable_set("@#{key}", value)
15
+ end
16
+ end
17
+
7
18
  def to_hash
8
- @table.keys.sort.each_with_object({}) do |key, hash|
9
- hash[key.to_s] = @table[key]
19
+ instance_variables.sort.each_with_object({}) do |var, hash|
20
+ key = var.to_s.delete_prefix('@')
21
+ hash[key] = instance_variable_get(var)
10
22
  end
11
23
  end
12
24
  end
@@ -8,33 +8,64 @@ module Zxcvbn
8
8
  # the lowercased password in the dictionary
9
9
 
10
10
  class Dictionary
11
- def initialize(name, ranked_dictionary)
11
+ def initialize(name, ranked_dictionary, trie = nil)
12
12
  @name = name
13
13
  @ranked_dictionary = ranked_dictionary
14
+ @trie = trie
14
15
  end
15
16
 
16
17
  def matches(password)
18
+ lowercased_password = password.downcase
19
+
20
+ if @trie
21
+ trie_matches(password, lowercased_password)
22
+ else
23
+ hash_matches(password, lowercased_password)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def trie_matches(password, lowercased_password)
30
+ results = []
31
+
32
+ (0...password.length).each do |i|
33
+ @trie.search_prefixes(lowercased_password, i).each do |word, rank, start, ending|
34
+ results << build_match(word, password.slice(start, ending - start + 1), start, ending, rank)
35
+ end
36
+ end
37
+
38
+ results
39
+ end
40
+
41
+ def hash_matches(password, lowercased_password)
17
42
  results = []
18
43
  password_length = password.length
19
- lowercased_password = password.downcase
44
+
20
45
  (0..password_length).each do |i|
21
46
  (i...password_length).each do |j|
22
- word = lowercased_password[i..j]
47
+ length = j - i + 1
48
+ word = lowercased_password.slice(i, length)
23
49
  next unless @ranked_dictionary.key?(word)
24
50
 
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
- )
51
+ results << build_match(word, password.slice(i, length), i, j, @ranked_dictionary[word])
34
52
  end
35
53
  end
54
+
36
55
  results
37
56
  end
57
+
58
+ def build_match(matched_word, token, start_pos, end_pos, rank)
59
+ Match.new(
60
+ matched_word: matched_word,
61
+ token: token,
62
+ i: start_pos,
63
+ j: end_pos,
64
+ rank: rank,
65
+ pattern: 'dictionary',
66
+ dictionary_name: @name
67
+ )
68
+ end
38
69
  end
39
70
  end
40
71
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'set'
4
+
3
5
  module Zxcvbn
4
6
  module Matchers
5
7
  class L33t
@@ -25,25 +27,17 @@ module Zxcvbn
25
27
  def matches(password)
26
28
  matches = []
27
29
  lowercased_password = password.downcase
28
- combinations_to_try = l33t_subs(relevent_l33t_subtable(lowercased_password))
30
+ relevent_subtable = relevent_l33t_subtable(lowercased_password)
31
+
32
+ # Early bailout: if no l33t characters present, return empty matches
33
+ return matches if relevent_subtable.empty?
34
+
35
+ combinations_to_try = l33t_subs(relevent_subtable)
29
36
  combinations_to_try.each do |substitution|
30
37
  @dictionary_matchers.each do |matcher|
31
38
  subbed_password = translate(lowercased_password, substitution)
32
39
  matcher.matches(subbed_password).each do |match|
33
- token = password[match.i..match.j]
34
- next if token.downcase == match.matched_word.downcase
35
-
36
- match_substitutions = {}
37
- substitution.each do |s, letter|
38
- match_substitutions[s] = letter if token.include?(s)
39
- end
40
- match.l33t = true
41
- match.token = password[match.i..match.j]
42
- match.sub = match_substitutions
43
- match.sub_display = match_substitutions.map do |k, v|
44
- "#{k} -> #{v}"
45
- end.join(', ')
46
- matches << match
40
+ process_match(match, password, substitution, matches)
47
41
  end
48
42
  end
49
43
  end
@@ -51,9 +45,11 @@ module Zxcvbn
51
45
  end
52
46
 
53
47
  def translate(password, sub)
54
- password.split('').map do |chr|
55
- sub[chr] || chr
56
- end.join
48
+ result = String.new
49
+ password.each_char do |chr|
50
+ result << (sub[chr] || chr)
51
+ end
52
+ result
57
53
  end
58
54
 
59
55
  def relevent_l33t_subtable(password)
@@ -80,6 +76,26 @@ module Zxcvbn
80
76
  new_subs
81
77
  end
82
78
 
79
+ private
80
+
81
+ def process_match(match, password, substitution, matches)
82
+ length = match.j - match.i + 1
83
+ token = password.slice(match.i, length)
84
+ return if token.downcase == match.matched_word.downcase
85
+
86
+ match_substitutions = {}
87
+ substitution.each do |s, letter|
88
+ match_substitutions[s] = letter if token.include?(s)
89
+ end
90
+ match.l33t = true
91
+ match.token = token
92
+ match.sub = match_substitutions
93
+ match.sub_display = match_substitutions.map do |k, v|
94
+ "#{k} -> #{v}"
95
+ end.join(', ')
96
+ matches << match
97
+ end
98
+
83
99
  def find_substitutions(subs, table, keys)
84
100
  return subs if keys.empty?
85
101
 
@@ -113,14 +129,12 @@ module Zxcvbn
113
129
 
114
130
  def dedup(subs)
115
131
  deduped = []
116
- members = []
132
+ seen = Set.new
117
133
  subs.each do |sub|
118
- assoc = sub.dup
119
-
120
- assoc.sort!
121
- label = assoc.map { |k, v| "#{k},#{v}" }.join('-')
122
- unless members.include?(label)
123
- members << label
134
+ # Sort and convert to hash for consistent comparison
135
+ sorted_sub = sub.sort.to_h
136
+ unless seen.include?(sorted_sub)
137
+ seen.add(sorted_sub)
124
138
  deduped << sub
125
139
  end
126
140
  end
@@ -30,7 +30,8 @@ module Zxcvbn
30
30
  @dictionary_matchers.each do |matcher|
31
31
  subbed_password = substitute(lowercased_password, substitutions)
32
32
  matcher.matches(subbed_password).each do |match|
33
- token = lowercased_password[match.i..match.j]
33
+ length = match.j - match.i + 1
34
+ token = lowercased_password.slice(match.i, length)
34
35
  next if token == match.matched_word.downcase
35
36
 
36
37
  match_substitutions = {}
@@ -38,7 +39,7 @@ module Zxcvbn
38
39
  match_substitutions[substitution] = letter if token.include?(substitution)
39
40
  end
40
41
  match.l33t = true
41
- match.token = password[match.i..match.j]
42
+ match.token = password.slice(match.i, length)
42
43
  match.sub = match_substitutions
43
44
  match.sub_display = match_substitutions.map do |k, v|
44
45
  "#{k} -> #{v}"
@@ -15,7 +15,7 @@ module Zxcvbn
15
15
  match = Match.new(
16
16
  i: i,
17
17
  j: j,
18
- token: password[i..j]
18
+ token: password.slice(i, j - i + 1)
19
19
  )
20
20
  yield match, re_match
21
21
  end
@@ -18,7 +18,7 @@ module Zxcvbn
18
18
  pattern: 'repeat',
19
19
  i: i,
20
20
  j: j - 1,
21
- token: password[i...j],
21
+ token: password.slice(i, j - i),
22
22
  repeated_char: cur_char
23
23
  )
24
24
  end
@@ -64,7 +64,7 @@ module Zxcvbn
64
64
  pattern: 'spatial',
65
65
  i: i,
66
66
  j: j - 1,
67
- token: password[i...j],
67
+ token: password.slice(i, j - i),
68
68
  graph: graph_name,
69
69
  turns: turns,
70
70
  shifted_count: shifted_count
data/lib/zxcvbn/math.rb CHANGED
@@ -34,6 +34,10 @@ module Zxcvbn
34
34
  return 0 if k > n
35
35
  return 1 if k.zero?
36
36
 
37
+ # Use symmetry property: C(n,k) = C(n, n-k)
38
+ # Choose smaller k to minimize iterations
39
+ k = n - k if k > n - k
40
+
37
41
  r = 1
38
42
  (1..k).each do |d|
39
43
  r *= n
@@ -44,14 +48,11 @@ module Zxcvbn
44
48
  end
45
49
 
46
50
  def average_degree_for_graph(graph_name)
47
- graph = data.adjacency_graphs[graph_name]
48
- degrees = graph.map { |_, neighbors| neighbors.compact.size }
49
- sum = degrees.inject(0, :+)
50
- sum.to_f / graph.size
51
+ data.graph_stats[graph_name][:average_degree]
51
52
  end
52
53
 
53
54
  def starting_positions_for_graph(graph_name)
54
- data.adjacency_graphs[graph_name].length
55
+ data.graph_stats[graph_name][:starting_positions]
55
56
  end
56
57
  end
57
58
  end
@@ -38,7 +38,8 @@ module Zxcvbn
38
38
  def build_matchers
39
39
  matchers = []
40
40
  dictionary_matchers = @data.ranked_dictionaries.map do |name, dictionary|
41
- Matchers::Dictionary.new(name, dictionary)
41
+ trie = @data.dictionary_tries[name]
42
+ Matchers::Dictionary.new(name, dictionary, trie)
42
43
  end
43
44
  l33t_matcher = Matchers::L33t.new(dictionary_matchers)
44
45
  matchers += dictionary_matchers
data/lib/zxcvbn/scorer.rb CHANGED
@@ -96,7 +96,7 @@ module Zxcvbn
96
96
  pattern: 'bruteforce',
97
97
  i: i,
98
98
  j: j,
99
- token: password[i..j],
99
+ token: password.slice(i, j - i + 1),
100
100
  entropy: lg(bruteforce_cardinality**(j - i + 1)),
101
101
  cardinality: bruteforce_cardinality
102
102
  )
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zxcvbn
4
+ # A trie (prefix tree) data structure for efficient dictionary matching.
5
+ # Provides fast prefix-based lookups to eliminate unnecessary substring checks.
6
+ #
7
+ # @see https://en.wikipedia.org/wiki/Trie
8
+ class Trie
9
+ def initialize
10
+ @root = {}
11
+ end
12
+
13
+ # Insert a word and its rank into the trie
14
+ # @param word [String] the word to insert
15
+ # @param rank [Integer] the rank/frequency of the word
16
+ def insert(word, rank)
17
+ node = @root
18
+ word.each_char do |char|
19
+ node[char] ||= {}
20
+ node = node[char]
21
+ end
22
+ node[:rank] = rank
23
+ end
24
+
25
+ # Search for all words in the text starting from a given position
26
+ # @param text [String] the text to search in
27
+ # @param start_pos [Integer] the starting position
28
+ # @return [Array<Array>] array of [word, rank, start, end] tuples
29
+ def search_prefixes(text, start_pos)
30
+ results = []
31
+ node = @root
32
+
33
+ (start_pos...text.length).each do |i|
34
+ char = text[i]
35
+ break unless node[char]
36
+
37
+ node = node[char]
38
+ results << [text[start_pos..i], node[:rank], start_pos, i] if node[:rank]
39
+ end
40
+
41
+ results
42
+ end
43
+ end
44
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zxcvbn
4
- VERSION = '1.2.4'
4
+ VERSION = '1.4.0'
5
5
  end
data/sig/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # RBS Type Signatures
2
+
3
+ This directory contains [RBS](https://github.com/ruby/rbs) type signatures for the zxcvbn-ruby gem.
4
+
5
+ ## What is RBS?
6
+
7
+ RBS is Ruby's type signature language. It provides a way to describe the structure of Ruby programs with:
8
+ - Class and module definitions
9
+ - Method signatures with parameter and return types
10
+ - Instance variables and constants
11
+ - Duck typing and union types
12
+
13
+ ## Usage
14
+
15
+ ### Validating Type Signatures
16
+
17
+ To validate that the RBS files are syntactically correct:
18
+
19
+ ```bash
20
+ bundle exec rake rbs:validate
21
+ ```
22
+
23
+ ### Runtime Type Checking
24
+
25
+ To run runtime type checking against the actual Ruby code during tests:
26
+
27
+ ```bash
28
+ bundle exec rake rbs:test
29
+ ```
30
+
31
+ This runs the RSpec test suite with RBS type checking enabled, verifying that method calls match their type signatures at runtime. Note: This takes about 2 minutes to run.
32
+
33
+ ### Other Useful Commands
34
+
35
+ List all Zxcvbn types:
36
+ ```bash
37
+ bundle exec rake rbs:list
38
+ ```
39
+
40
+ Check syntax of RBS files:
41
+ ```bash
42
+ bundle exec rake rbs:parse
43
+ ```
44
+
45
+ ## File Structure
46
+
47
+ The signatures mirror the structure of the `lib/` directory:
48
+
49
+ - `sig/zxcvbn.rbs` - Main Zxcvbn module
50
+ - `sig/zxcvbn/*.rbs` - Core classes (Tester, Score, Match, etc.)
51
+ - `sig/zxcvbn/matchers/*.rbs` - Pattern matcher classes
52
+
53
+ ## Adding New Signatures
54
+
55
+ When adding new classes or methods to the codebase, remember to:
56
+
57
+ 1. Create or update the corresponding `.rbs` file in the `sig/` directory
58
+ 2. Run `bundle exec rake rbs_validate` to ensure the syntax is correct
59
+ 3. Keep type signatures in sync with the actual implementation
60
+
61
+ ## Resources
62
+
63
+ - [RBS Documentation](https://github.com/ruby/rbs)
64
+ - [RBS Syntax Guide](https://github.com/ruby/rbs/blob/master/docs/syntax.md)
65
+ - [Ruby Signature Collection](https://github.com/ruby/gem_rbs_collection)
@@ -0,0 +1,13 @@
1
+ module Zxcvbn
2
+ module CrackTime
3
+ SINGLE_GUESS: Float
4
+ NUM_ATTACKERS: Integer
5
+ SECONDS_PER_GUESS: Float
6
+
7
+ def entropy_to_crack_time: (Numeric entropy) -> Float
8
+
9
+ def crack_time_to_score: (Numeric seconds) -> Integer
10
+
11
+ def display_time: (Numeric seconds) -> String
12
+ end
13
+ end
@@ -0,0 +1,31 @@
1
+ module Zxcvbn
2
+ class Data
3
+ type ranked_dictionary = Hash[String, Integer]
4
+ type adjacency_graph = Hash[String, Array[String?]]
5
+ type graph_stats = Hash[String, { average_degree: Float, starting_positions: Integer }]
6
+
7
+ @ranked_dictionaries: Hash[String, ranked_dictionary]
8
+ @adjacency_graphs: Hash[String, adjacency_graph]
9
+ @dictionary_tries: Hash[String, Trie]
10
+ @graph_stats: graph_stats
11
+
12
+ attr_reader ranked_dictionaries: Hash[String, ranked_dictionary]
13
+ attr_reader adjacency_graphs: Hash[String, adjacency_graph]
14
+ attr_reader dictionary_tries: Hash[String, Trie]
15
+ attr_reader graph_stats: graph_stats
16
+
17
+ def initialize: () -> void
18
+
19
+ def add_word_list: (String name, Array[String] list) -> void
20
+
21
+ private
22
+
23
+ def read_word_list: (String file) -> Array[String]
24
+
25
+ def build_tries: () -> Hash[String, Trie]
26
+
27
+ def build_trie: (ranked_dictionary ranked_dictionary) -> Trie
28
+
29
+ def compute_graph_stats: () -> graph_stats
30
+ end
31
+ end
@@ -0,0 +1,7 @@
1
+ module Zxcvbn
2
+ class DictionaryRanker
3
+ def self.rank_dictionaries: (Hash[String | Symbol, Array[String]] lists) -> Hash[String | Symbol, Hash[String, Integer]]
4
+
5
+ def self.rank_dictionary: (Array[String] words) -> Hash[String, Integer]
6
+ end
7
+ end
@@ -0,0 +1,33 @@
1
+ module Zxcvbn
2
+ module Entropy
3
+ include Zxcvbn::Math
4
+
5
+ def calc_entropy: (Match match) -> Float
6
+
7
+ def repeat_entropy: (Match match) -> Float
8
+
9
+ def sequence_entropy: (Match match) -> Float
10
+
11
+ def digits_entropy: (Match match) -> Float
12
+
13
+ def year_entropy: (Match? match) -> Float
14
+
15
+ def date_entropy: (Match match) -> Float
16
+
17
+ def dictionary_entropy: (Match match) -> Float
18
+
19
+ def extra_uppercase_entropy: (Match match) -> Numeric
20
+
21
+ def extra_l33t_entropy: (Match match) -> Numeric
22
+
23
+ def spatial_entropy: (Match match) -> Float
24
+
25
+ NUM_YEARS: Integer
26
+ NUM_MONTHS: Integer
27
+ NUM_DAYS: Integer
28
+ START_UPPER: Regexp
29
+ END_UPPER: Regexp
30
+ ALL_UPPER: Regexp
31
+ ALL_LOWER: Regexp
32
+ end
33
+ end
@@ -0,0 +1,8 @@
1
+ module Zxcvbn
2
+ class Feedback
3
+ attr_accessor warning: String?
4
+ attr_accessor suggestions: Array[String]
5
+
6
+ def initialize: (?warning: String?, ?suggestions: Array[String]) -> void
7
+ end
8
+ end
@@ -0,0 +1,13 @@
1
+ module Zxcvbn
2
+ class FeedbackGiver
3
+ NAME_DICTIONARIES: Array[String]
4
+ DEFAULT_FEEDBACK: Feedback
5
+ EMPTY_FEEDBACK: Feedback
6
+
7
+ def self.get_feedback: (Integer? score, Array[Match] sequence) -> Feedback
8
+
9
+ def self.get_match_feedback: (Match match, bool is_sole_match) -> Feedback?
10
+
11
+ def self.get_dictionary_match_feedback: (Match match, bool is_sole_match) -> Feedback
12
+ end
13
+ end
@@ -0,0 +1,38 @@
1
+ module Zxcvbn
2
+ class Match
3
+ attr_accessor pattern: String?
4
+ attr_accessor i: Integer?
5
+ attr_accessor j: Integer?
6
+ attr_accessor token: String?
7
+ attr_accessor matched_word: String?
8
+ attr_accessor rank: Integer?
9
+ attr_accessor dictionary_name: String?
10
+ attr_accessor reversed: bool?
11
+ attr_accessor l33t: bool?
12
+ attr_accessor sub: Hash[String, String]?
13
+ attr_accessor sub_display: String?
14
+ attr_accessor l: Integer?
15
+ attr_accessor entropy: Numeric?
16
+ attr_accessor base_entropy: Numeric?
17
+ attr_accessor uppercase_entropy: Numeric?
18
+ attr_accessor l33t_entropy: Numeric?
19
+ attr_accessor repeated_char: String?
20
+ attr_accessor sequence_name: String?
21
+ attr_accessor sequence_space: Integer?
22
+ attr_accessor ascending: bool?
23
+ attr_accessor graph: String?
24
+ attr_accessor turns: Integer?
25
+ attr_accessor shifted_count: Integer?
26
+ attr_accessor shiffted_count: Integer?
27
+ attr_accessor year: Integer?
28
+ attr_accessor month: Integer?
29
+ attr_accessor day: Integer?
30
+ attr_accessor separator: String?
31
+ attr_accessor cardinality: Integer?
32
+ attr_accessor offset: Integer?
33
+
34
+ def initialize: (**untyped attributes) -> void
35
+
36
+ def to_hash: () -> Hash[String, untyped]
37
+ end
38
+ end
@@ -0,0 +1,13 @@
1
+ module Zxcvbn
2
+ module Math
3
+ def bruteforce_cardinality: (String password) -> Integer
4
+
5
+ def lg: (Numeric n) -> Float
6
+
7
+ def nCk: (Integer n, Integer k) -> Integer
8
+
9
+ def average_degree_for_graph: (String graph_name) -> Float
10
+
11
+ def starting_positions_for_graph: (String graph_name) -> Integer
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ module Zxcvbn
2
+ class Omnimatch
3
+ @data: Data
4
+ @matchers: Array[untyped]
5
+
6
+ def initialize: (Data data) -> void
7
+
8
+ def matches: (String password, ?Array[String] user_inputs) -> Array[Match]
9
+
10
+ private
11
+
12
+ def user_input_matchers: (Array[String] user_inputs) -> Array[untyped]
13
+
14
+ def build_matchers: () -> Array[untyped]
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ module Zxcvbn
2
+ class PasswordStrength
3
+ @omnimatch: Omnimatch
4
+ @scorer: Scorer
5
+
6
+ def initialize: (Data data) -> void
7
+
8
+ def test: (String? password, ?Array[untyped] user_inputs) -> Score
9
+ end
10
+ end
@@ -0,0 +1,15 @@
1
+ module Zxcvbn
2
+ class Score
3
+ attr_accessor entropy: Numeric?
4
+ attr_accessor crack_time: Numeric?
5
+ attr_accessor crack_time_display: String?
6
+ attr_accessor score: Integer?
7
+ attr_accessor pattern: String?
8
+ attr_accessor match_sequence: Array[Match]?
9
+ attr_accessor password: String?
10
+ attr_accessor calc_time: Float?
11
+ attr_accessor feedback: Feedback?
12
+
13
+ def initialize: (?entropy: Numeric?, ?crack_time: Numeric?, ?crack_time_display: String?, ?score: Integer?, ?match_sequence: Array[Match]?, ?password: String?) -> void
14
+ end
15
+ end
@@ -0,0 +1,20 @@
1
+ module Zxcvbn
2
+ class Scorer
3
+ include Entropy
4
+ include CrackTime
5
+
6
+ @data: Data
7
+
8
+ attr_reader data: Data
9
+
10
+ def initialize: (Data data) -> void
11
+
12
+ def minimum_entropy_match_sequence: (String password, Array[Match] matches) -> Score
13
+
14
+ def score_for: (String password, Array[Match] match_sequence, Array[Float] up_to_k) -> Score
15
+
16
+ def pad_with_bruteforce_matches: (Array[Match] match_sequence, String password, Integer bruteforce_cardinality) -> Array[Match]
17
+
18
+ def make_bruteforce_match: (String password, Integer i, Integer j, Integer bruteforce_cardinality) -> Match
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ module Zxcvbn
2
+ class Tester
3
+ @data: Data
4
+
5
+ def initialize: () -> void
6
+
7
+ def test: (String? password, ?Array[untyped] user_inputs) -> Score
8
+
9
+ def add_word_lists: (Hash[String, Array[untyped]] lists) -> void
10
+
11
+ def inspect: () -> String
12
+
13
+ private
14
+
15
+ def sanitize: (Array[untyped] user_inputs) -> Array[String]
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ module Zxcvbn
2
+ class Trie
3
+ type node = Hash[untyped, untyped]
4
+
5
+ @root: node
6
+
7
+ def initialize: () -> void
8
+
9
+ def insert: (String word, Integer rank) -> void
10
+
11
+ def search_prefixes: (String text, Integer start_pos) -> Array[[String, Integer?, Integer, Integer]]
12
+ end
13
+ end
data/sig/zxcvbn.rbs ADDED
@@ -0,0 +1,10 @@
1
+ module Zxcvbn
2
+ VERSION: String
3
+
4
+ DATA_PATH: Pathname
5
+
6
+ type word_list = Hash[String, Array[String]]
7
+ type user_inputs = Array[String]
8
+
9
+ def self.test: (String? password, ?user_inputs user_inputs, ?word_list word_lists) -> Score
10
+ end
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.4
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steve Hodgkiss
@@ -52,16 +52,34 @@ files:
52
52
  - lib/zxcvbn/score.rb
53
53
  - lib/zxcvbn/scorer.rb
54
54
  - lib/zxcvbn/tester.rb
55
+ - lib/zxcvbn/trie.rb
55
56
  - lib/zxcvbn/version.rb
56
- homepage: http://github.com/envato/zxcvbn-ruby
57
+ - sig/README.md
58
+ - sig/zxcvbn.rbs
59
+ - sig/zxcvbn/crack_time.rbs
60
+ - sig/zxcvbn/data.rbs
61
+ - sig/zxcvbn/dictionary_ranker.rbs
62
+ - sig/zxcvbn/entropy.rbs
63
+ - sig/zxcvbn/feedback.rbs
64
+ - sig/zxcvbn/feedback_giver.rbs
65
+ - sig/zxcvbn/match.rbs
66
+ - sig/zxcvbn/math.rbs
67
+ - sig/zxcvbn/omnimatch.rbs
68
+ - sig/zxcvbn/password_strength.rbs
69
+ - sig/zxcvbn/score.rbs
70
+ - sig/zxcvbn/scorer.rbs
71
+ - sig/zxcvbn/tester.rbs
72
+ - sig/zxcvbn/trie.rbs
73
+ homepage: https://github.com/envato/zxcvbn-ruby
57
74
  licenses:
58
75
  - MIT
59
76
  metadata:
77
+ allowed_push_host: https://rubygems.org
60
78
  bug_tracker_uri: https://github.com/envato/zxcvbn-ruby/issues
61
79
  changelog_uri: https://github.com/envato/zxcvbn-ruby/blob/HEAD/CHANGELOG.md
62
- documentation_uri: https://github.com/envato/zxcvbn-ruby/blob/HEAD/README.md
80
+ documentation_uri: https://www.rubydoc.info/gems/zxcvbn-ruby/1.4.0
63
81
  homepage_uri: https://github.com/envato/zxcvbn-ruby
64
- source_code_uri: https://github.com/envato/zxcvbn-ruby
82
+ source_code_uri: https://github.com/envato/zxcvbn-ruby/tree/v1.4.0
65
83
  rdoc_options: []
66
84
  require_paths:
67
85
  - lib
@@ -76,7 +94,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
76
94
  - !ruby/object:Gem::Version
77
95
  version: '0'
78
96
  requirements: []
79
- rubygems_version: 4.0.0
97
+ rubygems_version: 4.0.4
80
98
  specification_version: 4
81
99
  summary: ''
82
100
  test_files: []