1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
|
# frozen-string-literal: true
module Rodauth
Feature.define(:disallow_password_reuse, :DisallowPasswordReuse) do
depends :login_password_requirements_base
translatable_method :password_same_as_previous_password_message, "same as previous password"
auth_value_method :previous_password_account_id_column, :account_id
auth_value_method :previous_password_hash_column, :password_hash
auth_value_method :previous_password_hash_table, :account_previous_password_hashes
auth_value_method :previous_password_id_column, :id
auth_value_method :previous_passwords_to_check, 6
auth_methods(
:add_previous_password_hash,
:password_doesnt_match_previous_password?
)
def set_password(password)
hash = super
add_previous_password_hash(hash)
hash
end
def add_previous_password_hash(hash)
ds = previous_password_ds
unless @dont_check_previous_password
keep_before = ds.reverse(previous_password_id_column).
limit(nil, previous_passwords_to_check).
get(previous_password_id_column)
if keep_before
ds.where(Sequel.expr(previous_password_id_column) <= keep_before).
delete
end
end
# This should never raise uniqueness violations, as it uses a serial primary key
ds.insert(previous_password_account_id_column=>account_id, previous_password_hash_column=>hash)
end
def password_meets_requirements?(password)
super &&
(@dont_check_previous_password || password_doesnt_match_previous_password?(password))
end
private
def password_doesnt_match_previous_password?(password)
match = if use_database_authentication_functions?
salts = previous_password_ds.
select_map([previous_password_id_column, Sequel.function(function_name(:rodauth_get_previous_salt), previous_password_id_column).as(:salt)])
return true if salts.empty?
salts.any? do |hash_id, salt|
database_function_password_match?(:rodauth_previous_password_hash_match, hash_id, password, salt)
end
else
# :nocov:
previous_password_ds.select_map(previous_password_hash_column).any? do |hash|
password_hash_match?(hash, password)
end
# :nocov:
end
return true unless match
set_password_requirement_error_message(:password_same_as_previous_password, password_same_as_previous_password_message)
false
end
def after_close_account
super if defined?(super)
previous_password_ds.delete
end
def before_create_account_route
super if defined?(super)
@dont_check_previous_password = true
end
def before_verify_account_route
super if defined?(super)
@dont_check_previous_password = true
end
def after_create_account
if account_password_hash_column && !(respond_to?(:verify_account_set_password?) && verify_account_set_password?)
add_previous_password_hash(password_hash(param(password_param)))
end
super if defined?(super)
end
def previous_password_ds
db[previous_password_hash_table].where(previous_password_account_id_column=>account_id)
end
end
end
|