You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
docuseal/app/models/security_event.rb

118 lines
3.2 KiB

# frozen_string_literal: true
# == Schema Information
#
# Table name: security_events
#
# id :bigint not null, primary key
# user_id :bigint
# event_type :string not null
# ip_address :string not null
# details :jsonb not null, default: {}
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_security_events_on_user_id (user_id)
# index_security_events_on_event_type (event_type)
# index_security_events_on_created_at (created_at)
#
# Foreign Keys
#
# fk_rails_... (user_id => users.id)
#
class SecurityEvent < ApplicationRecord
belongs_to :user, optional: true
# Event types
EVENT_TYPES = %w[
unauthorized_institution_access
insufficient_privileges
token_validation_failure
rate_limit_exceeded
invitation_accepted
super_admin_demoted
].freeze
# Validations
validates :event_type, presence: true, inclusion: { in: EVENT_TYPES }
validates :ip_address, presence: true
# CRITICAL METHOD: Security event logging
def self.log(event_type, user = nil, details = {})
# Extract IP from details or use default
ip_address = details[:ip_address] || '0.0.0.0'
# Clean details (remove sensitive data)
clean_details = details.except(:ip_address, :password, :token, :raw_token)
create!(
user: user,
event_type: event_type,
ip_address: ip_address,
details: clean_details
)
end
# Alert threshold checking
def self.alert_threshold_exceeded?(event_type, threshold:, time_window: 1.hour)
count = where(event_type: event_type)
.where('created_at > ?', time_window.ago)
.count
count >= threshold
end
# Get recent security events for monitoring
def self.recent(limit: 100)
order(created_at: :desc).limit(limit)
end
# Filter by time range
def self.between(start_time, end_time)
where(created_at: start_time..end_time)
end
# Export for audit
def self.export_csv(start_date: nil, end_date: nil, event_types: nil)
scope = all
scope = scope.between(start_date, end_date) if start_date && end_date
scope = scope.where(event_type: event_types) if event_types.present?
CSV.generate do |csv|
csv << %w[id user_email event_type ip_address details created_at]
scope.find_each do |event|
csv << [
event.id,
event.user&.email,
event.event_type,
event.ip_address,
event.details.to_json,
event.created_at.iso8601
]
end
end
end
# Human-readable description
def description
case event_type
when 'unauthorized_institution_access'
"User attempted to access institution they don't have permission for"
when 'insufficient_privileges'
"User attempted action without required role"
when 'token_validation_failure'
"Invitation token validation failed"
when 'rate_limit_exceeded'
"Rate limit exceeded for invitation attempts"
when 'invitation_accepted'
"Admin invitation successfully accepted"
when 'super_admin_demoted'
"Super admin role removed from user"
else
"Unknown security event"
end
end
end