Spec-Zone.ru › Ruby on Rails 8.0

модуль ActiveModel::SecurePassword::ClassMethods

Включенные модули:
ActiveModel::Validations

Публичные методы экземпляров

has_secure_password(attribute = :password, validations: true, reset_token: true) Показать исходный код
# File activemodel/lib/active_model/secure_password.rb, line 116
      def has_secure_password(attribute = :password, validations: true, reset_token: true)
        # Load bcrypt gem only when has_secure_password is used.
        # This is to avoid ActiveModel (and by extension the entire framework)
        # being dependent on a binary library.
        begin
          require "bcrypt"
        rescue LoadError
          warn "You don't have bcrypt installed in your application. Please add it to your Gemfile and run bundle install."
          raise
        end

        include InstanceMethodsOnActivation.new(attribute, reset_token: reset_token)

        if validations
          include ActiveModel::Validations

          # This ensures the model has a password by checking whether the password_digest
          # is present, so that this works with both new and existing records. However,
          # when there is an error, the message is added to the password attribute instead
          # so that the error message will make sense to the end-user.
          validate do |record|
            record.errors.add(attribute, :blank) unless record.public_send("#{attribute}_digest").present?
          end

          validate do |record|
            if challenge = record.public_send(:"#{attribute}_challenge")
              digest_was = record.public_send(:"#{attribute}_digest_was") if record.respond_to?(:"#{attribute}_digest_was")

              unless digest_was.present? && BCrypt::Password.new(digest_was).is_password?(challenge)
                record.errors.add(:"#{attribute}_challenge")
              end
            end
          end

          # Validates that the password does not exceed the maximum allowed bytes for BCrypt (72 bytes).
          validate do |record|
            password_value = record.public_send(attribute)
            if password_value.present? && password_value.bytesize > ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED
              record.errors.add(attribute, :password_too_long)
            end
          end

          validates_confirmation_of attribute, allow_blank: true
        end

        # Only generate tokens for records that are capable of doing so (Active Records, not vanilla Active Models)
        if reset_token && respond_to?(:generates_token_for)
          generates_token_for :"#{attribute}_reset", expires_in: 15.minutes do
            public_send(:"#{attribute}_salt")&.last(10)
          end

          class_eval <<-RUBY, __FILE__, __LINE__ + 1
            silence_redefinition_of_method :find_by_#{attribute}_reset_token
            def self.find_by_#{attribute}_reset_token(token)
              find_by_token_for(:#{attribute}_reset, token)
            end

            silence_redefinition_of_method :find_by_#{attribute}_reset_token!
            def self.find_by_#{attribute}_reset_token!(token)
              find_by_token_for!(:#{attribute}_reset, token)
            end
          RUBY
        end
      end

Добавляет методы для установки и аутентификации пароля с помощью BCrypt. Этот механизм требует наличия атрибута XXX_digest, где XXX — имя атрибута вашего пароля.

Следующие валидации добавляются автоматически:

  • Пароль должен быть указан при создании.

  • Длина пароля должна быть меньше или равна 72 байтам.

  • Подтверждение пароля (используя атрибут XXX_confirmation)

Если валидация подтверждения не нужна, просто оставьте поле для XXX_confirmation пустым (т. е. не предоставляйте для него поле формы). Если в этом атрибуте значение nil, валидация не будет выполнена.

Кроме того, создаётся атрибут XXX_challenge. Если он установлен в значение, отличное от nil, он будет проверен на соответствие сохранённому паролю. Эта валидация основана на отслеживании изменений, как предоставлено ActiveModel::Dirty; если методы отслеживания изменений не определены, эта валидация не пройдёт.

Все вышеперечисленные валидации можно пропустить, передав validations: false в качестве аргумента. Это позволяет полностью настроить поведение валидации.

Наконец, токен сброса пароля, действительный в течение 15 минут после выдачи, автоматически настраивается, когда reset_token установлено в true (по умолчанию), и объект отвечает на generates_token_for (что делают Active Records).

Чтобы использовать has_secure_password, добавьте bcrypt (~> 3.1.7) в свой Gemfile:

gem "bcrypt", "~> 3.1.7"

Примеры

Использование Active Record (которое автоматически включает ActiveModel::SecurePassword)
# Schema: User(name:string, password_digest:string, recovery_password_digest:string)
class User < ActiveRecord::Base
  has_secure_password
  has_secure_password :recovery_password, validations: false
end

user = User.new(name: "david", password: "", password_confirmation: "nomatch")

user.save                                                      # => false, password required
user.password = "vr00m"
user.save                                                      # => false, confirmation doesn't match
user.password_confirmation = "vr00m"
user.save                                                      # => true

user.authenticate("notright")                                  # => false
user.authenticate("vr00m")                                     # => user
User.find_by(name: "david")&.authenticate("notright")          # => false
User.find_by(name: "david")&.authenticate("vr00m")             # => user

user.recovery_password = "42password"
user.recovery_password_digest                                  # => "$2a$04$iOfhwahFymCs5weB3BNH/uXkTG65HR.qpW.bNhEjFP3ftli3o5DQC"
user.save                                                      # => true

user.authenticate_recovery_password("42password")              # => user

user.update(password: "pwn3d", password_challenge: "")         # => false, challenge doesn't authenticate
user.update(password: "nohack4u", password_challenge: "vr00m") # => true

user.authenticate("vr00m")                                     # => false, old password
user.authenticate("nohack4u")                                  # => user
Условное требование пароля
class Account
  include ActiveModel::SecurePassword

  attr_accessor :is_guest, :password_digest

  has_secure_password

  def errors
    super.tap { |errors| errors.delete(:password, :blank) if is_guest }
  end
end

account = Account.new
account.valid? # => false, password required

account.is_guest = true
account.valid? # => true
Использование токена сброса пароля
user = User.create!(name: "david", password: "123", password_confirmation: "123")
token = user.password_reset_token
User.find_by_password_reset_token(token) # returns user

# 16 minutes later...
User.find_by_password_reset_token(token) # returns nil

# raises ActiveSupport::MessageVerifier::InvalidSignature since the token is expired
User.find_by_password_reset_token!(token)

© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API