lib/devise_zxcvbn/model.rb
require "devise_zxcvbn/email_tokeniser"
require "devise_zxcvbn/errors/devise_zxcvbn_error"
require "ostruct"
module Devise
module Models
module Zxcvbnable
extend ActiveSupport::Concern
delegate :min_password_score, to: "self.class"
delegate :zxcvbn_tester, to: "self.class"
included do
validate :strong_password, unless: :skip_password_complexity?
end
def password_score
@password_score = self.class.password_score(self)
end
def password_weak?
password_score.score < min_password_score
end
protected
def skip_password_complexity?
!password_required?
end
private
def strong_password
if errors.messages.blank? && password_weak?
errors.add :password, :weak_password, **i18n_variables
end
end
def i18n_variables
{
feedback: zxcvbn_feedback,
crack_time_display: time_to_crack,
score: password_score.score,
min_password_score: min_password_score
}
end
def zxcvbn_feedback
feedback = password_score.feedback.values.flatten.reject(&:empty?)
return "Add another word or two. Uncommon words are better." if feedback.empty?
feedback.join(". ").gsub(/\.\s*\./, ".")
end
def time_to_crack
password_score.crack_times_display["offline_fast_hashing_1e10_per_second"]
end
class_methods do
Devise::Models.config(self, :min_password_score)
Devise::Models.config(self, :zxcvbn_tester)
def password_score(user, arg_email = nil)
return raise DeviseZxcvbnError, "the object must respond to password" unless user.respond_to?(:password)
password = user.password.to_s
zxcvbn_weak_words = []
if arg_email
zxcvbn_weak_words += [arg_email, *DeviseZxcvbn::EmailTokeniser.split(arg_email)]
end
# User method results are saved locally to prevent repeat calls that might be expensive
if user.respond_to?(:email)
local_email = user.email
zxcvbn_weak_words += [local_email, *DeviseZxcvbn::EmailTokeniser.split(local_email)]
end
if user.respond_to?(:weak_words)
return raise DeviseZxcvbnError, "weak_words must return an Array" unless user.weak_words.is_a?(Array)
local_weak_words = user.weak_words
zxcvbn_weak_words += local_weak_words
end
zxcvbn_tester.test(password, zxcvbn_weak_words)
end
end
end
end
end