discourse/lib/common_passwords.rb

50 lines
1.5 KiB
Ruby

# frozen_string_literal: true
# CommonPasswords will check a given password against a list of the most commonly used passwords.
# The list comes from https://github.com/danielmiessler/SecLists/tree/master/Passwords
# specifically the list of 10 million passwords, top 100k, filtered by length
#
# The list is stored in Redis at a key that is shared by all sites in a multisite config.
#
# If the password file is changed, you need to add a migration that deletes the list from redis
# so it gets re-populated:
#
# Discourse.redis.without_namespace.del CommonPasswords::LIST_KEY
class CommonPasswords
PASSWORD_FILE = File.join(Rails.root, "lib", "common_passwords", "10-char-common-passwords.txt")
LIST_KEY = "discourse-common-passwords"
@mutex = Mutex.new
def self.common_password?(password)
return false unless password.present?
password_list.include?(password)
end
private
class RedisPasswordList
def include?(password)
CommonPasswords.redis.sismember CommonPasswords::LIST_KEY, password
end
end
def self.password_list
@mutex.synchronize { load_passwords if redis.scard(LIST_KEY) <= 0 }
RedisPasswordList.new
end
def self.redis
Discourse.redis.without_namespace
end
def self.load_passwords
passwords = File.readlines(PASSWORD_FILE)
redis.sadd LIST_KEY, passwords.map!(&:chomp)
rescue Errno::ENOENT
# tolerate this so we don't block signups
Rails.logger.error "Common passwords file #{PASSWORD_FILE} is not found! Common password checking is skipped."
end
end