songtianlun
32ec61fe00
- Implement password reset request and form - Add user validation and expiration checks - Create integration tests for password reset process This commit introduces a complete password reset feature, allowing users to reset their passwords securely. It includes necessary validations to ensure the user is valid and the reset token has not expired. Additionally, integration tests have been added to verify the functionality and edge cases, enhancing overall application security and user experience.
96 lines
2.5 KiB
Ruby
96 lines
2.5 KiB
Ruby
class User < ApplicationRecord
|
|
attr_accessor :remember_token, :activation_token, :reset_token
|
|
# before_save { self.email = email.downcase }
|
|
# before_save { email.downcase! }
|
|
before_save :downcase_email
|
|
before_create :create_activation_digest
|
|
validates :name, presence: true, length: { maximum: 50 }
|
|
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
|
|
validates :email, presence: true, length: { maximum: 255 },
|
|
format: { with: VALID_EMAIL_REGEX },
|
|
uniqueness: true
|
|
has_secure_password
|
|
validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
|
|
|
|
def User.digest(string)
|
|
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
|
|
BCrypt::Engine.cost
|
|
BCrypt::Password.create(string, cost: cost)
|
|
end
|
|
|
|
def User.new_token
|
|
SecureRandom.urlsafe_base64
|
|
end
|
|
|
|
def remember
|
|
self.remember_token = User.new_token
|
|
update_attribute(:remember_digest, User.digest(remember_token))
|
|
remember_digest
|
|
end
|
|
|
|
# 返回一个会话令牌,防止会话劫持
|
|
# 简单起见,直接使用记忆令牌
|
|
def session_token
|
|
remember_digest || remember
|
|
end
|
|
|
|
class << self
|
|
def digest(string)
|
|
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
|
|
BCrypt::Engine.cost
|
|
BCrypt::Password.create(string, cost: cost)
|
|
end
|
|
|
|
def new_token
|
|
SecureRandom.urlsafe_base64
|
|
end
|
|
end
|
|
|
|
# powered by metaprogramming
|
|
def authenticated?(attribute, token)
|
|
digest = send("#{attribute}_digest")
|
|
return false if digest.nil?
|
|
BCrypt::Password.new(digest).is_password?(token)
|
|
end
|
|
|
|
def forget
|
|
update_attribute(:remember_digest, nil)
|
|
end
|
|
|
|
def activate
|
|
# update_attribute(:activated, true)
|
|
# update_attribute(:activated_at, Time.zone.now)
|
|
update_columns(activated: true, activated_at: Time.zone.now)
|
|
end
|
|
|
|
def send_activation_email
|
|
UserMailer.account_activation(self).deliver_now
|
|
end
|
|
|
|
def create_reset_digest
|
|
self.reset_token = User.new_token
|
|
update_columns(reset_digest: User.digest(reset_token),
|
|
reset_send_at: Time.zone.now)
|
|
end
|
|
|
|
def send_password_reset_email
|
|
UserMailer.password_reset(self).deliver_now
|
|
end
|
|
|
|
def password_reset_expired?
|
|
reset_send_at < 2.hours.ago
|
|
end
|
|
|
|
private
|
|
|
|
def downcase_email
|
|
# self.email = email.downcase
|
|
email.downcase!
|
|
end
|
|
|
|
def create_activation_digest
|
|
self.activation_token = User.new_token
|
|
self.activation_digest = User.digest(activation_token)
|
|
end
|
|
end
|