Module: Philiprehberger::Password::Strength

Defined in:
lib/philiprehberger/password/strength.rb

Constant Summary collapse

LABELS =
{
  0 => :terrible,
  1 => :weak,
  2 => :fair,
  3 => :strong,
  4 => :excellent
}.freeze
12

Class Method Summary collapse

Class Method Details

.compute(password) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/philiprehberger/password/strength.rb', line 17

def self.compute(password)
  pwd = password.to_s
  return { score: 0, label: :terrible, entropy: 0.0, feedback: feedback(pwd, 0) } if pwd.empty?

  ent = entropy(pwd)

  s = if ent < 28
        0
      elsif ent < 36
        1
      elsif ent < 60
        2
      elsif ent < 80
        3
      else
        4
      end

  { score: s, label: LABELS[s], entropy: ent.round(2), feedback: feedback(pwd, s) }
end

.entropy(password) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/philiprehberger/password/strength.rb', line 38

def self.entropy(password)
  pwd = password.to_s
  return 0.0 if pwd.empty?

  pool = 0
  pool += 26 if pwd.match?(/[a-z]/)
  pool += 26 if pwd.match?(/[A-Z]/)
  pool += 10 if pwd.match?(/\d/)
  pool += 33 if pwd.match?(/[^a-zA-Z\d]/)

  pwd.length * Math.log2([pool, 1].max)
end

.feedback(password, score) ⇒ Object

Build an actionable list of suggestions for improving a password. Returns an empty array for passwords that already score "strong" (3) or "excellent" (4).



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/philiprehberger/password/strength.rb', line 54

def self.feedback(password, score)
  return [] if score >= 3

  pwd = password.to_s
  tips = []

  tips << 'Avoid using a common password' if CommonPasswords.include?(pwd.downcase)
  tips << "Use at least #{MIN_RECOMMENDED_LENGTH} characters" if pwd.length < MIN_RECOMMENDED_LENGTH

  missing = missing_classes(pwd)
  tips << "Add #{to_sentence(missing)}" unless missing.empty?
  tips.concat(pattern_tips(pwd))

  tips.uniq
end