mirror of
https://github.com/discourse/discourse.git
synced 2024-11-29 15:45:19 +08:00
93 lines
2.7 KiB
Ruby
93 lines
2.7 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class UserPassword < ActiveRecord::Base
|
|
MAX_PASSWORD_LENGTH = 200
|
|
TARGET_PASSWORD_ALGORITHM =
|
|
"$pbkdf2-#{Rails.configuration.pbkdf2_algorithm}$i=#{Rails.configuration.pbkdf2_iterations},l=32$"
|
|
PASSWORD_SALT_LENGTH = 16
|
|
|
|
belongs_to :user, required: true
|
|
|
|
validates :user_id, uniqueness: true
|
|
validate :password_validator
|
|
before_save :ensure_password_is_hashed
|
|
after_save :clear_raw_password
|
|
|
|
def password
|
|
# this getter method is still required, but we store the set password in @raw_password instead of making it easily accessible from the getter
|
|
nil
|
|
end
|
|
|
|
def password=(pw)
|
|
return if pw.blank?
|
|
|
|
self.password_hash_will_change!
|
|
@raw_password = pw
|
|
end
|
|
|
|
def password_validation_required?
|
|
@raw_password.present?
|
|
end
|
|
|
|
def confirm_password?(pw)
|
|
# nothing to confirm if this record has not been persisted yet
|
|
return false if !persisted?
|
|
return false if password_hash != hash_password(pw, password_salt, password_algorithm)
|
|
regen_password!(pw) if password_algorithm != TARGET_PASSWORD_ALGORITHM
|
|
|
|
true
|
|
end
|
|
|
|
private
|
|
|
|
def clear_raw_password
|
|
@raw_password = nil
|
|
end
|
|
|
|
def password_validator
|
|
UserPasswordValidator.new(attributes: :password).validate_each(self, :password, @raw_password)
|
|
end
|
|
|
|
def hash_password(pw, salt, algorithm)
|
|
raise StandardError.new("password is too long") if pw.size > MAX_PASSWORD_LENGTH
|
|
PasswordHasher.hash_password(password: pw, salt: salt, algorithm: algorithm)
|
|
end
|
|
|
|
def ensure_password_is_hashed
|
|
return if @raw_password.blank?
|
|
|
|
self.password_salt = SecureRandom.hex(PASSWORD_SALT_LENGTH)
|
|
self.password_algorithm = TARGET_PASSWORD_ALGORITHM
|
|
self.password_hash = hash_password(@raw_password, password_salt, password_algorithm)
|
|
self.password_expired_at = nil
|
|
end
|
|
|
|
def regen_password!(pw)
|
|
# Regenerate password_hash with new algorithm and persist, we skip validation here since it has already run once when the hash was persisted the first time
|
|
salt = SecureRandom.hex(PASSWORD_SALT_LENGTH)
|
|
update_columns(
|
|
password_algorithm: TARGET_PASSWORD_ALGORITHM,
|
|
password_salt: salt,
|
|
password_hash: hash_password(pw, salt, TARGET_PASSWORD_ALGORITHM),
|
|
)
|
|
end
|
|
end
|
|
|
|
# == Schema Information
|
|
#
|
|
# Table name: user_passwords
|
|
#
|
|
# id :integer not null, primary key
|
|
# user_id :integer not null
|
|
# password_hash :string(64) not null
|
|
# password_salt :string(32) not null
|
|
# password_algorithm :string(64) not null
|
|
# password_expired_at :datetime
|
|
# created_at :datetime not null
|
|
# updated_at :datetime not null
|
|
#
|
|
# Indexes
|
|
#
|
|
# index_user_passwords_on_user_id (user_id) UNIQUE
|
|
#
|