lib/authie/session_model.rb
# frozen_string_literal: true
require 'active_record'
require 'securerandom'
require 'authie/config'
module Authie
class SessionModel < ActiveRecord::Base
attr_accessor :temporary_token
self.table_name = 'authie_sessions'
belongs_to :parent, class_name: 'Authie::SessionModel', optional: true
scope :active, -> { where(active: true) }
scope :asc, -> { order(last_activity_at: :desc) }
scope :for_user, ->(user) { where(user_type: user.class.name, user_id: user.id) }
# Attributes
serialize :data, type: Hash
before_validation :shorten_strings
before_create :set_new_token
# Return the user that
def user
return unless user_id && user_type
return @user if instance_variable_defined?('@user')
@user = user_type.constantize.find_by(id: user_id)
end
# Set the user
def user=(user)
@user = user
if user
self.user_type = user.class.name
self.user_id = user.id
else
self.user_type = nil
self.user_id = nil
end
end
def expired?
expires_at.present? &&
expires_at < Time.now
end
def inactive?
expires_at.nil? &&
last_activity_at.present? &&
last_activity_at < Authie.config.session_inactivity_timeout.ago
end
def persistent?
!!expires_at
end
def activate!
self.active = true
save!
end
def invalidate!
active_now = active?
self.active = false
save!
Authie.notify(:session_invalidate, session: self) if active_now
true
end
def set(key, value)
self.data ||= {}
self.data[key.to_s] = value
save!
end
def get(key)
(self.data ||= {})[key.to_s]
end
def invalidate_others!
self.class.where('id != ?', id).active.for_user(user).each(&:invalidate!)
end
# Have we seen the user's password recently in this sesion?
def recently_seen_password?
!!(password_seen_at && password_seen_at >= Authie.config.sudo_session_timeout.ago)
end
# Is two factor authentication required for this request?
def two_factored?
!!(two_factored_at || parent_id)
end
# Is this the first session for this session's browser?
def first_session_for_browser?
self.class.where('id < ?', id).for_user(user).where(browser_id: browser_id).empty?
end
# Is this the first session for the IP?
def first_session_for_ip?
self.class.where('id < ?', id).for_user(user).where(login_ip: login_ip).empty?
end
# Reset a new token for the session and return the new token
#
# @return [String]
def reset_token
set_new_token
save!
temporary_token
end
private
def shorten_strings
self.user_agent = user_agent[0, 255] if user_agent.is_a?(String)
self.last_activity_path = last_activity_path[0, 255] if last_activity_path.is_a?(String)
end
def set_new_token
self.temporary_token = SecureRandom.alphanumeric(Authie.config.session_token_length)
self.token_hash = self.class.hash_token(temporary_token)
end
class << self
# Find a session from the database for the given controller instance.
# Returns a session object or :none if no session is found.
# Find a session by a token (either from a hash or from the raw token)
def find_session_by_token(token)
return nil if token.blank?
active.where(token_hash: hash_token(token)).first
end
# Cleanup any old sessions.
def cleanup
Authie.notify(:cleanup) do
# Invalidate transient sessions that haven't been used
active.where('expires_at IS NULL AND last_activity_at < ?',
Authie.config.session_inactivity_timeout.ago).each(&:invalidate!)
# Invalidate persistent sessions that have expired
active.where('expires_at IS NOT NULL AND expires_at < ?', Time.now).each(&:invalidate!)
end
true
end
# Return a hash of a given token
def hash_token(token)
Digest::SHA256.hexdigest(token)
end
end
end
end