class RsAuthorizationCode < ApplicationRecord belongs_to :user CODE_TTL = 60.seconds serialize :permissions, coder: JSON attr_accessor :code before_validation :generate_code validates :code_challenge, presence: true, format: { with: /\A[A-Za-z0-9\-._~]{43,128}\z/ } validates :code_challenge_method, inclusion: { in: %w[S256] } scope :active, -> { where(expires_at: Time.current..) } def self.find_by_code(raw_code) return nil if raw_code.blank? active.find_by(code_digest: Digest::SHA256.hexdigest(raw_code)) end private def generate_code return if code_digest.present? self.code = SecureRandom.urlsafe_base64(32) self.code_digest = Digest::SHA256.hexdigest(code) self.expires_at ||= CODE_TTL.from_now end end