class ZxcvbnService @tester_mutex = Mutex.new class << self # Returns an Zxcvbn instance cached between classes instances and between threads. # # The tester weights ~20 Mo, and we'd like to save some memory – so rather # that storing it in a per-thread accessor, we prefer to use a mutex # to cache it between threads. def tester @tester_mutex.synchronize do @tester ||= build_tester end end private # Returns a fully initializer tester from the on-disk dictionary. # # This is slow: loading and parsing the dictionary may take around 1s. def build_tester dictionaries = YAML.safe_load(File.read(Rails.root.join("config", "initializers", "zxcvbn_dictionnaries.yaml"))) tester = Zxcvbn::Tester.new tester.add_word_lists(dictionaries) tester end end def initialize(password) @password = password end def complexity wxcvbn = compute_zxcvbn score = wxcvbn.score length = @password.blank? ? 0 : @password.length vulnerabilities = wxcvbn.match_sequence.map { |m| m.matched_word.nil? ? m.token : m.matched_word }.filter { |s| s.length > 2 }.join(', ') [score, vulnerabilities, length] end def score compute_zxcvbn.score end private def compute_zxcvbn self.class.tester.test(@password) end end