Repository: rails/rails
File: activerecord/lib/active_record/signed_id.rb (Ruby)

Code before:
# frozen_string_literal: true

module ActiveRecord
  # = Active Record Signed Id
  module SignedId
    extend ActiveSupport::Concern

    included do
      mattr_accessor :signed_id_verifier

      # Set the secret used for the signed id verifier instance when using Active Record outside of \Rails.
      mattr_accessor :signed_id_verifier_secret, instance_writer: false
    end

    module RelationMethods # :nodoc:
      def find_signed(...)
        scoping { model.find_signed(...) }
      end

      def find_signed!(...)
        scoping { model.find_signed!(...) }
      end
    end

    module ClassMethods
      # Lets you find a record based on a signed id that's safe to put into the world without risk of tampering.
      # This is particularly useful for things like password reset or email verification, where you want
      # the bearer of the signed id to be able to interact with the underlying record, but usually only within
      # a certain time period.
      #
      # You set the time period that the signed id is valid for during generation, using the instance method
      # <tt>signed_id(expires_in: 15.minutes)</tt>. If the time has elapsed before a signed find is attempted,
      # the signed id will no longer be valid, and nil is returned.
      #
      # It's possible to further restrict the use of a signed id with a purpose. This helps when you have a
      # general base model, like a User, which might have signed ids for several things, like password reset
      # or email verification. The purpose that was set during generation must match the purpose set when

Review comment (security):
`mattr_accessor` is not the same as using a singleton instance variable such as in the current definition of `signed_id_verifier`.  Changing `signed_id_verifier` or `signed_id_verifier_secret` to `mattr_accessor` will break subclassing behavior.

Did you try my suggestion from https://github.com/rails/rails/pull/54422#discussion_r1943343277?  Did you run into an issue?

Code after:
# frozen_string_literal: true

module ActiveRecord
  # = Active Record Signed Id
  module SignedId
    extend ActiveSupport::Concern

    included do
      class_attribute :_signed_id_verifier, instance_accessor: false, instance_predicate: false

      ##
      # :singleton-method:
      # Set the secret used for the signed id verifier instance when using Active Record outside of \Rails.
      # Within \Rails, this is automatically set using the \Rails application key generator.
      class_attribute :signed_id_verifier_secret, instance_writer: false
      module DeprecateSignedIdVerifierSecret
        def signed_id_verifier_secret=(secret)
          ActiveRecord.deprecator.warn(<<~MSG)
            ActiveRecord::Base.signed_id_verifier_secret is deprecated and will be removed in the future.

            If the secret is model-specific, set Model.signed_id_verifier instead.

            Otherwise, configure Rails.application.message_verifiers (or ActiveRecord.message_verifiers) with the secret.
          MSG

          super
        end
      end
      singleton_class.prepend DeprecateSignedIdVerifierSecret
    end

    module RelationMethods # :nodoc:
      def find_signed(...)
        scoping { model.find_signed(...) }
      end

      def find_signed!(...)