class AccessKey < ApplicationRecord DEFAULT_ROTATION_FREQUENCY_DAYS = 30 KEY_LENGTH = 32 validates :raw_key, presence: true validates :generated_at, presence: true validates :auto_rotation_frequency_days, numericality: { only_integer: true, greater_than: 0 } def self.current order(generated_at: :desc, id: :desc).first || create_current! end def self.create_current! create!( raw_key: generate_raw_key, generated_at: Time.current, auto_rotate: true, auto_rotation_frequency_days: DEFAULT_ROTATION_FREQUENCY_DAYS ) end def self.rotate! previous_key = current transaction do UserSession.delete_all create!( raw_key: generate_raw_key, generated_at: Time.current, auto_rotate: previous_key.auto_rotate, auto_rotation_frequency_days: previous_key.auto_rotation_frequency_days ) end end def self.rotate_if_needed! key = current return key unless key.auto_rotate? && key.expired? rotate! end def self.generate_raw_key SecureRandom.urlsafe_base64(KEY_LENGTH) end def matches?(submitted_key) ActiveSupport::SecurityUtils.secure_compare(raw_key, submitted_key.to_s) rescue ArgumentError false end def expired? generated_at <= auto_rotation_frequency_days.days.ago end def disable_auto_rotation! update!(auto_rotate: false) end def enable_auto_rotation! update!(auto_rotate: true) end end