beskar 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +143 -0
  3. data/README.md +987 -21
  4. data/app/controllers/beskar/application_controller.rb +170 -0
  5. data/app/controllers/beskar/banned_ips_controller.rb +280 -0
  6. data/app/controllers/beskar/dashboard_controller.rb +70 -0
  7. data/app/controllers/beskar/security_events_controller.rb +182 -0
  8. data/app/controllers/concerns/beskar/controllers/security_tracking.rb +70 -0
  9. data/app/models/beskar/banned_ip.rb +193 -0
  10. data/app/models/beskar/security_event.rb +64 -0
  11. data/app/services/beskar/banned_ip_manager.rb +78 -0
  12. data/app/views/beskar/banned_ips/edit.html.erb +259 -0
  13. data/app/views/beskar/banned_ips/index.html.erb +361 -0
  14. data/app/views/beskar/banned_ips/new.html.erb +310 -0
  15. data/app/views/beskar/banned_ips/show.html.erb +310 -0
  16. data/app/views/beskar/dashboard/index.html.erb +280 -0
  17. data/app/views/beskar/security_events/index.html.erb +309 -0
  18. data/app/views/beskar/security_events/show.html.erb +307 -0
  19. data/app/views/layouts/beskar/application.html.erb +647 -5
  20. data/config/locales/en.yml +10 -0
  21. data/config/routes.rb +41 -0
  22. data/db/migrate/20251016000001_create_beskar_security_events.rb +25 -0
  23. data/db/migrate/20251016000002_create_beskar_banned_ips.rb +23 -0
  24. data/lib/beskar/configuration.rb +214 -0
  25. data/lib/beskar/engine.rb +105 -0
  26. data/lib/beskar/logger.rb +293 -0
  27. data/lib/beskar/middleware/request_analyzer.rb +305 -0
  28. data/lib/beskar/middleware.rb +4 -0
  29. data/lib/beskar/models/security_trackable.rb +25 -0
  30. data/lib/beskar/models/security_trackable_authenticable.rb +167 -0
  31. data/lib/beskar/models/security_trackable_devise.rb +82 -0
  32. data/lib/beskar/models/security_trackable_generic.rb +355 -0
  33. data/lib/beskar/services/account_locker.rb +263 -0
  34. data/lib/beskar/services/device_detector.rb +250 -0
  35. data/lib/beskar/services/geolocation_service.rb +392 -0
  36. data/lib/beskar/services/ip_whitelist.rb +113 -0
  37. data/lib/beskar/services/rate_limiter.rb +257 -0
  38. data/lib/beskar/services/waf.rb +551 -0
  39. data/lib/beskar/version.rb +1 -1
  40. data/lib/beskar.rb +32 -1
  41. data/lib/generators/beskar/install/install_generator.rb +158 -0
  42. data/lib/generators/beskar/install/templates/initializer.rb.tt +177 -0
  43. data/lib/tasks/beskar_tasks.rake +121 -4
  44. metadata +138 -5
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateBeskarSecurityEvents < ActiveRecord::Migration[8.0]
4
+ def change
5
+ create_table :beskar_security_events do |t|
6
+ t.references :user, polymorphic: true, null: true, index: true
7
+ t.string :event_type, null: false
8
+ t.string :ip_address
9
+ t.string :attempted_email
10
+ t.text :user_agent
11
+ t.json :metadata, default: {}
12
+ t.integer :risk_score
13
+
14
+ t.timestamps
15
+ end
16
+
17
+ add_index :beskar_security_events, :ip_address
18
+ add_index :beskar_security_events, :event_type
19
+ add_index :beskar_security_events, :attempted_email
20
+ add_index :beskar_security_events, :created_at
21
+ add_index :beskar_security_events, :risk_score
22
+ add_index :beskar_security_events, [:ip_address, :event_type, :created_at],
23
+ name: 'index_security_events_on_ip_event_time'
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateBeskarBannedIps < ActiveRecord::Migration[8.0]
4
+ def change
5
+ create_table :beskar_banned_ips do |t|
6
+ t.string :ip_address, null: false
7
+ t.string :reason, null: false
8
+ t.text :details
9
+ t.datetime :banned_at, null: false
10
+ t.datetime :expires_at
11
+ t.boolean :permanent, default: false, null: false
12
+ t.integer :violation_count, default: 1, null: false
13
+ t.text :metadata # Using text to store JSON (compatible with SQLite and PostgreSQL)
14
+
15
+ t.timestamps
16
+ end
17
+
18
+ add_index :beskar_banned_ips, :ip_address, unique: true
19
+ add_index :beskar_banned_ips, :banned_at
20
+ add_index :beskar_banned_ips, :expires_at
21
+ add_index :beskar_banned_ips, [:ip_address, :expires_at]
22
+ end
23
+ end
@@ -0,0 +1,214 @@
1
+ module Beskar
2
+ class Configuration
3
+ attr_accessor :rate_limiting, :security_tracking, :risk_based_locking, :geolocation, :ip_whitelist, :waf, :authentication_models, :emergency_password_reset, :monitor_only, :authenticate_admin
4
+
5
+ def initialize
6
+ @monitor_only = false # Global monitor-only mode - logs everything but doesn't block
7
+ @ip_whitelist = [] # Array of IP addresses or CIDR ranges
8
+
9
+ # Dashboard authentication - configure this to restrict access to the dashboard
10
+ # Example: config.authenticate_admin = proc { authenticate_admin! }
11
+ @authenticate_admin = nil
12
+
13
+ # Authentication models configuration
14
+ # Auto-detect by default, or can be explicitly configured
15
+ @authentication_models = {
16
+ devise: [], # Will be auto-detected: [:devise_user, :admin, etc.]
17
+ rails_auth: [], # Will be auto-detected: [:user, etc.]
18
+ auto_detect: true # Set to false to use only explicitly configured models
19
+ }
20
+
21
+ @waf = {
22
+ enabled: false, # Master switch for WAF
23
+ auto_block: true, # Automatically block IPs after threshold
24
+ score_threshold: 150, # Cumulative risk score before blocking (replaces block_threshold)
25
+ violation_window: 6.hours, # Maximum time window to track violations
26
+ block_durations: [1.hour, 6.hours, 24.hours, 7.days], # Escalating block durations
27
+ permanent_block_after: 500, # Permanent block after cumulative score reaches this (nil = never)
28
+ create_security_events: true, # Create SecurityEvent records
29
+ record_not_found_exclusions: [], # Regex patterns to exclude from RecordNotFound detection
30
+ decay_enabled: true, # Enable exponential decay of violation scores over time
31
+ decay_rates: { # Decay rates by severity (half-life in minutes)
32
+ critical: 360, # Critical violations: 6 hour half-life
33
+ high: 120, # High violations: 2 hour half-life
34
+ medium: 45, # Medium violations: 45 minute half-life
35
+ low: 15 # Low violations: 15 minute half-life
36
+ },
37
+ max_violations_tracked: 50 # Maximum number of violations to track per IP (oldest pruned)
38
+ }
39
+ @security_tracking = {
40
+ enabled: true,
41
+ track_successful_logins: true,
42
+ track_failed_logins: true,
43
+ auto_analyze_patterns: true
44
+ }
45
+ @rate_limiting = {
46
+ ip_attempts: {
47
+ limit: 10,
48
+ period: 1.hour,
49
+ exponential_backoff: true
50
+ },
51
+ account_attempts: {
52
+ limit: 5,
53
+ period: 15.minutes,
54
+ exponential_backoff: true
55
+ },
56
+ global_attempts: {
57
+ limit: 100,
58
+ period: 1.minute,
59
+ exponential_backoff: false
60
+ }
61
+ }
62
+ @risk_based_locking = {
63
+ enabled: false, # Master switch for risk-based locking
64
+ risk_threshold: 75, # Lock account if risk score >= this value
65
+ lock_strategy: :devise_lockable, # Strategy: :devise_lockable, :custom, :none
66
+ auto_unlock_time: 1.hour, # Time until automatic unlock (if supported by strategy)
67
+ notify_user: true, # Send notification on lock
68
+ log_lock_events: true, # Create security event for locks
69
+ immediate_signout: false # Sign out user immediately via Warden callback (requires :lockable)
70
+ }
71
+ @geolocation = {
72
+ provider: :mock, # Provider: :maxmind, :mock
73
+ maxmind_city_db_path: nil, # Path to MaxMind GeoLite2-City.mmdb or GeoIP2-City.mmdb
74
+ cache_ttl: 4.hours # How long to cache geolocation results
75
+ }
76
+ @emergency_password_reset = {
77
+ enabled: false, # Master switch for emergency password reset
78
+ impossible_travel_threshold: 3, # Reset after N impossible travel events in 24h
79
+ suspicious_device_threshold: 5, # Reset after N suspicious device events in 24h
80
+ total_locks_threshold: 5, # Reset after N total locks in 24h (any reason)
81
+ send_notification: true, # Send email to user about reset
82
+ notify_security_team: true, # Alert security team about automatic resets
83
+ require_manual_unlock: false # Require manual admin unlock after reset
84
+ }
85
+ end
86
+
87
+ def security_tracking_enabled?
88
+ @security_tracking[:enabled]
89
+ end
90
+
91
+ def track_successful_logins?
92
+ security_tracking_enabled? && @security_tracking[:track_successful_logins]
93
+ end
94
+
95
+ def track_failed_logins?
96
+ security_tracking_enabled? && @security_tracking[:track_failed_logins]
97
+ end
98
+
99
+ def auto_analyze_patterns?
100
+ security_tracking_enabled? && @security_tracking[:auto_analyze_patterns]
101
+ end
102
+
103
+ # Risk-based locking configuration helpers
104
+ def risk_based_locking_enabled?
105
+ @risk_based_locking[:enabled]
106
+ end
107
+
108
+ def risk_threshold
109
+ @risk_based_locking[:risk_threshold] || 75
110
+ end
111
+
112
+ def lock_strategy
113
+ @risk_based_locking[:lock_strategy] || :devise_lockable
114
+ end
115
+
116
+ def auto_unlock_time
117
+ @risk_based_locking[:auto_unlock_time] || 1.hour
118
+ end
119
+
120
+ def notify_user_on_lock?
121
+ @risk_based_locking[:notify_user] != false
122
+ end
123
+
124
+ def log_lock_events?
125
+ @risk_based_locking[:log_lock_events] != false
126
+ end
127
+
128
+ def immediate_signout?
129
+ @risk_based_locking[:immediate_signout] == true
130
+ end
131
+
132
+ # Geolocation configuration helpers
133
+ def geolocation_provider
134
+ @geolocation[:provider] || :mock
135
+ end
136
+
137
+ def maxmind_city_db_path
138
+ @geolocation[:maxmind_city_db_path]
139
+ end
140
+
141
+ def geolocation_cache_ttl
142
+ @geolocation[:cache_ttl] || 4.hours
143
+ end
144
+
145
+ # WAF configuration helpers
146
+ def waf_enabled?
147
+ @waf && @waf[:enabled]
148
+ end
149
+
150
+ def waf_auto_block?
151
+ waf_enabled? && @waf[:auto_block] && !@monitor_only
152
+ end
153
+
154
+ # General monitor-only mode check (affects all blocking)
155
+ def monitor_only?
156
+ @monitor_only == true
157
+ end
158
+
159
+ # IP Whitelist configuration helpers
160
+ def ip_whitelist_enabled?
161
+ @ip_whitelist.is_a?(Array) && @ip_whitelist.any?
162
+ end
163
+
164
+ # Authentication models helpers
165
+ def devise_scopes
166
+ return @authentication_models[:devise] unless @authentication_models[:auto_detect]
167
+
168
+ # Auto-detect Devise models
169
+ detected = []
170
+ if defined?(Devise)
171
+ Devise.mappings.keys.each do |scope|
172
+ detected << scope
173
+ end
174
+ end
175
+
176
+ # Merge with explicitly configured models
177
+ (detected + Array(@authentication_models[:devise])).uniq
178
+ end
179
+
180
+ def rails_auth_scopes
181
+ return @authentication_models[:rails_auth] unless @authentication_models[:auto_detect]
182
+
183
+ # Auto-detect Rails authentication models (has_secure_password)
184
+ detected = []
185
+ if defined?(ActiveRecord::Base)
186
+ # Try to find models with has_secure_password
187
+ # This is a heuristic - models that have password_digest column
188
+ ActiveRecord::Base.descendants.each do |model|
189
+ next unless model.table_exists?
190
+ if model.column_names.include?("password_digest")
191
+ scope = model.name.underscore.to_sym
192
+ detected << scope unless devise_scopes.include?(scope)
193
+ end
194
+ rescue => e
195
+ # Ignore errors during detection
196
+ Beskar::Logger.debug("Error detecting Rails auth model #{model.name}: #{e.message}")
197
+ end
198
+ end
199
+
200
+ # Merge with explicitly configured models
201
+ (detected + Array(@authentication_models[:rails_auth])).uniq
202
+ end
203
+
204
+ def all_auth_scopes
205
+ (devise_scopes + rails_auth_scopes).uniq
206
+ end
207
+
208
+ def model_class_for_scope(scope)
209
+ scope.to_s.camelize.constantize
210
+ rescue NameError
211
+ nil
212
+ end
213
+ end
214
+ end
data/lib/beskar/engine.rb CHANGED
@@ -1,5 +1,110 @@
1
1
  module Beskar
2
2
  class Engine < ::Rails::Engine
3
3
  isolate_namespace Beskar
4
+
5
+ initializer "beskar.middleware" do |app|
6
+ app.config.middleware.use ::Beskar::Middleware::RequestAnalyzer
7
+ end
8
+
9
+ # Preload banned IPs into cache on startup
10
+ config.after_initialize do
11
+ if defined?(Beskar::BannedIp)
12
+ Rails.application.executor.wrap do
13
+ Beskar::BannedIp.preload_cache!
14
+ Beskar::Logger.info("Preloaded banned IPs into cache")
15
+ rescue => e
16
+ Beskar::Logger.warn("Failed to preload banned IPs: #{e.message}")
17
+ end
18
+ end
19
+ end
20
+
21
+ initializer "beskar.warden_callbacks", after: :load_config_initializers do |app|
22
+ if defined?(Warden)
23
+ # Track successful authentication and check for high-risk locks
24
+ Warden::Manager.after_set_user except: :fetch do |user, auth, opts|
25
+ # Only proceed if Beskar security tracking is available and enabled
26
+ if user.respond_to?(:track_authentication_event) && auth.request
27
+ # Track the authentication event (creates security event)
28
+ security_event = user.track_authentication_event(auth.request, :success)
29
+
30
+ # Check if account was locked due to high risk (only if immediate_signout is enabled)
31
+ # This happens AFTER successful authentication but BEFORE the request completes
32
+ # Requires :lockable module to be enabled on the user model
33
+ if Beskar.configuration.immediate_signout? &&
34
+ Beskar.configuration.risk_based_locking_enabled? &&
35
+ security_event &&
36
+ user_was_just_locked?(user, security_event) &&
37
+ user.respond_to?(:access_locked?) && user.access_locked?
38
+ Beskar::Logger.warn("Signing out user #{user.id} due to high-risk lock")
39
+ auth.logout
40
+ throw :warden, scope: opts[:scope], message: :account_locked_due_to_high_risk
41
+ end
42
+ end
43
+ end
44
+
45
+ # Alternative approach using after_authentication is available but not enabled by default
46
+ # Uncomment this to use the alternative approach (more targeted, only on authentication)
47
+ # Warden::Manager.after_authentication do |user, auth, opts|
48
+ # if user.respond_to?(:check_high_risk_lock_and_signout)
49
+ # user.check_high_risk_lock_and_signout(auth)
50
+ # end
51
+ # end
52
+
53
+ Warden::Manager.before_failure do |env, opts|
54
+ if env
55
+ request = ActionDispatch::Request.new(env)
56
+ scope = opts[:scope]
57
+
58
+ # Try to get model class from configuration
59
+ model_class = Beskar.configuration&.model_class_for_scope(scope)
60
+
61
+ if model_class && model_class.respond_to?(:track_failed_authentication)
62
+ model_class.track_failed_authentication(request, scope)
63
+ else
64
+ Beskar::Logger.debug("No trackable model found for scope: #{scope}")
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ # Helper method to check if user was just locked
72
+ def self.user_was_just_locked?(user, security_event)
73
+ return false unless Beskar.configuration.risk_based_locking_enabled?
74
+ return false unless security_event
75
+ return false unless user&.respond_to?(:security_events)
76
+
77
+ # Check if an account_locked or lock_attempted event was just created
78
+ recent_lock = user.security_events
79
+ .where(event_type: [ "account_locked", "lock_attempted" ])
80
+ .where("created_at >= ?", 10.seconds.ago)
81
+ .order(created_at: :desc)
82
+ .first
83
+
84
+ recent_lock.present?
85
+ end
86
+
87
+ # Add engine migrations to host app's migration paths
88
+ initializer "beskar.append_migrations" do |app|
89
+ # Don't add migrations if we're inside the engine itself (testing)
90
+ if !root.to_s.include?(app.root.to_s) && !app.root.to_s.include?(root.to_s)
91
+ engine_migrations = root.join("db", "migrate").to_s
92
+
93
+ # Add to Rails paths
94
+ app.config.paths["db/migrate"] << engine_migrations
95
+ end
96
+ end
97
+
98
+ # Ensure ActiveRecord sees the engine migrations after initialization
99
+ # config.after_initialize do |app|
100
+ # unless root.to_s.include?(app.root.to_s)
101
+ # engine_migrations = root.join("db", "migrate").to_s
102
+
103
+ # # Update ActiveRecord::Tasks paths
104
+ # current_paths = Array(ActiveRecord::Tasks::DatabaseTasks.migrations_paths)
105
+ # current_paths << engine_migrations
106
+ # ActiveRecord::Tasks::DatabaseTasks.migrations_paths = current_paths.uniq
107
+ # end
108
+ # end
4
109
  end
5
110
  end
@@ -0,0 +1,293 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Beskar
4
+ # Centralized logging module for consistent log formatting and flexible output handling
5
+ module Logger
6
+ class << self
7
+ # Available log levels
8
+ LOG_LEVELS = %i[debug info warn error fatal].freeze
9
+
10
+ # Generate logging methods for each level
11
+ LOG_LEVELS.each do |level|
12
+ define_method(level) do |message, component: nil|
13
+ log(level, message, component: component)
14
+ end
15
+ end
16
+
17
+ # Main logging method that handles formatting and output
18
+ #
19
+ # @param level [Symbol] The log level (:debug, :info, :warn, :error, :fatal)
20
+ # @param message [String] The message to log
21
+ # @param component [String, Symbol, nil] Optional component name for more specific prefixes
22
+ #
23
+ # @example Basic usage
24
+ # Beskar::Logger.info("User authenticated successfully")
25
+ # # => [Beskar] User authenticated successfully
26
+ #
27
+ # @example With component
28
+ # Beskar::Logger.warn("Rate limit exceeded", component: :WAF)
29
+ # # => [Beskar::WAF] Rate limit exceeded
30
+ #
31
+ # @example With class as component
32
+ # Beskar::Logger.error("Failed to lock account", component: self.class)
33
+ # # => [Beskar::AccountLocker] Failed to lock account
34
+ def log(level, message, component: nil)
35
+ return unless should_log?(level)
36
+
37
+ formatted_message = format_message(message, component)
38
+ logger.send(level, formatted_message)
39
+ rescue StandardError => e
40
+ # Fallback to stderr if logging fails
41
+ $stderr.puts "[Beskar::Logger] Failed to log message: #{e.message}"
42
+ $stderr.puts "[Beskar::Logger] Original message: #{formatted_message}"
43
+ end
44
+
45
+ # Configure the logger instance
46
+ #
47
+ # @param logger_instance [Logger, nil] The logger to use, defaults to Rails.logger
48
+ def logger=(logger_instance)
49
+ @logger = logger_instance
50
+ end
51
+
52
+ # Get the current logger instance
53
+ #
54
+ # @return [Logger] The configured logger or Rails.logger as default
55
+ def logger
56
+ @logger ||= default_logger
57
+ end
58
+
59
+ # Configure log level threshold
60
+ #
61
+ # @param level [Symbol, String] Minimum log level to output
62
+ def level=(level)
63
+ @level = level.to_sym if LOG_LEVELS.include?(level.to_sym)
64
+ end
65
+
66
+ # Get the current log level
67
+ #
68
+ # @return [Symbol] Current log level
69
+ def level
70
+ @level ||= :debug
71
+ end
72
+
73
+ # Reset logger configuration to defaults
74
+ def reset!
75
+ @logger = nil
76
+ @level = nil
77
+ @component_aliases = nil
78
+ end
79
+
80
+ # Configure component name aliases for cleaner output
81
+ #
82
+ # @param aliases [Hash] Mapping of classes/modules to display names
83
+ #
84
+ # @example
85
+ # Beskar::Logger.component_aliases = {
86
+ # 'Beskar::Services::Waf' => 'WAF',
87
+ # 'Beskar::Services::AccountLocker' => 'AccountLocker'
88
+ # }
89
+ def component_aliases=(aliases)
90
+ @component_aliases = aliases
91
+ end
92
+
93
+ # Get component aliases
94
+ #
95
+ # @return [Hash] Current component aliases
96
+ def component_aliases
97
+ @component_aliases ||= default_component_aliases
98
+ end
99
+
100
+ private
101
+
102
+ # Format the log message with appropriate prefix
103
+ #
104
+ # @param message [String] The message to format
105
+ # @param component [String, Symbol, Class, nil] Component identifier
106
+ # @return [String] Formatted message with prefix
107
+ def format_message(message, component)
108
+ prefix = build_prefix(component)
109
+ "#{prefix} #{message}"
110
+ end
111
+
112
+ # Build the log prefix based on component
113
+ #
114
+ # @param component [String, Symbol, Class, nil] Component identifier
115
+ # @return [String] Formatted prefix
116
+ def build_prefix(component)
117
+ return "[Beskar]" if component.nil?
118
+
119
+ component_name = normalize_component_name(component)
120
+
121
+ if component_name.nil? || component_name.empty?
122
+ "[Beskar]"
123
+ else
124
+ "[Beskar::#{component_name}]"
125
+ end
126
+ end
127
+
128
+ # Normalize component name from various input types
129
+ #
130
+ # @param component [String, Symbol, Class] Component identifier
131
+ # @return [String, nil] Normalized component name
132
+ def normalize_component_name(component)
133
+ case component
134
+ when String
135
+ apply_component_alias(component)
136
+ when Symbol
137
+ component.to_s
138
+ when Class
139
+ apply_component_alias(component.name)
140
+ when Module
141
+ apply_component_alias(component.name)
142
+ else
143
+ component.to_s
144
+ end
145
+ end
146
+
147
+ # Apply component alias if configured
148
+ #
149
+ # @param component_name [String] Original component name
150
+ # @return [String] Aliased name or original
151
+ def apply_component_alias(component_name)
152
+ return nil if component_name.nil?
153
+
154
+ # First check exact matches
155
+ aliased = component_aliases[component_name]
156
+ return aliased if aliased
157
+
158
+ # Remove Beskar:: prefix if present for lookup
159
+ clean_name = component_name.sub(/^Beskar::/, '')
160
+ aliased = component_aliases[clean_name]
161
+ return aliased if aliased
162
+
163
+ # Check if it's already a simple component name (no ::)
164
+ return clean_name unless clean_name.include?('::')
165
+
166
+ # Extract the last component for nested classes
167
+ # e.g., "Beskar::Services::Waf" -> "Waf"
168
+ last_component = clean_name.split('::').last
169
+ component_aliases[clean_name] || last_component
170
+ end
171
+
172
+ # Check if message should be logged based on current level
173
+ #
174
+ # @param message_level [Symbol] Level of the message
175
+ # @return [Boolean] True if message should be logged
176
+ def should_log?(message_level)
177
+ level_value(message_level) >= level_value(level)
178
+ end
179
+
180
+ # Convert log level to numeric value for comparison
181
+ #
182
+ # @param level_sym [Symbol] Log level
183
+ # @return [Integer] Numeric value
184
+ def level_value(level_sym)
185
+ LOG_LEVELS.index(level_sym) || 0
186
+ end
187
+
188
+ # Get the default logger instance
189
+ #
190
+ # @return [Logger] Default logger (Rails.logger or stdlib Logger)
191
+ def default_logger
192
+ if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
193
+ Rails.logger
194
+ else
195
+ require 'logger'
196
+ ::Logger.new($stdout)
197
+ end
198
+ end
199
+
200
+ # Default component name aliases for cleaner output
201
+ #
202
+ # @return [Hash] Default aliases
203
+ def default_component_aliases
204
+ {
205
+ 'Beskar::Services::Waf' => 'WAF',
206
+ 'Beskar::Services::WAF' => 'WAF',
207
+ 'Services::Waf' => 'WAF',
208
+ 'Services::WAF' => 'WAF',
209
+ 'Beskar::Services::AccountLocker' => 'AccountLocker',
210
+ 'Services::AccountLocker' => 'AccountLocker',
211
+ 'Beskar::Services::RateLimiter' => 'RateLimiter',
212
+ 'Services::RateLimiter' => 'RateLimiter',
213
+ 'Beskar::Services::IpWhitelist' => 'IpWhitelist',
214
+ 'Services::IpWhitelist' => 'IpWhitelist',
215
+ 'Beskar::Services::GeolocationService' => 'GeolocationService',
216
+ 'Services::GeolocationService' => 'GeolocationService',
217
+ 'Beskar::Services::DeviceDetector' => 'DeviceDetector',
218
+ 'Services::DeviceDetector' => 'DeviceDetector',
219
+ 'Beskar::Middleware::RequestAnalyzer' => 'Middleware',
220
+ 'Middleware::RequestAnalyzer' => 'Middleware',
221
+ 'Beskar::Models::SecurityTrackableDevise' => 'SecurityTracking',
222
+ 'Models::SecurityTrackableDevise' => 'SecurityTracking',
223
+ 'Beskar::Models::SecurityTrackableAuthenticable' => 'SecurityTracking',
224
+ 'Models::SecurityTrackableAuthenticable' => 'SecurityTracking',
225
+ 'Beskar::Models::SecurityTrackableGeneric' => 'SecurityTracking',
226
+ 'Models::SecurityTrackableGeneric' => 'SecurityTracking'
227
+ }
228
+ end
229
+ end
230
+
231
+ # Module to include in classes for instance-level logging
232
+ module ClassMethods
233
+ # Log a debug message with automatic component detection
234
+ def log_debug(message)
235
+ Beskar::Logger.debug(message, component: self)
236
+ end
237
+
238
+ # Log an info message with automatic component detection
239
+ def log_info(message)
240
+ Beskar::Logger.info(message, component: self)
241
+ end
242
+
243
+ # Log a warning message with automatic component detection
244
+ def log_warn(message)
245
+ Beskar::Logger.warn(message, component: self)
246
+ end
247
+
248
+ # Log an error message with automatic component detection
249
+ def log_error(message)
250
+ Beskar::Logger.error(message, component: self)
251
+ end
252
+
253
+ # Log a fatal message with automatic component detection
254
+ def log_fatal(message)
255
+ Beskar::Logger.fatal(message, component: self)
256
+ end
257
+ end
258
+
259
+ # Module to include in classes for instance-level logging
260
+ module InstanceMethods
261
+ # Log a debug message with automatic component detection
262
+ def log_debug(message)
263
+ Beskar::Logger.debug(message, component: self.class)
264
+ end
265
+
266
+ # Log an info message with automatic component detection
267
+ def log_info(message)
268
+ Beskar::Logger.info(message, component: self.class)
269
+ end
270
+
271
+ # Log a warning message with automatic component detection
272
+ def log_warn(message)
273
+ Beskar::Logger.warn(message, component: self.class)
274
+ end
275
+
276
+ # Log an error message with automatic component detection
277
+ def log_error(message)
278
+ Beskar::Logger.error(message, component: self.class)
279
+ end
280
+
281
+ # Log a fatal message with automatic component detection
282
+ def log_fatal(message)
283
+ Beskar::Logger.fatal(message, component: self.class)
284
+ end
285
+ end
286
+
287
+ # Convenience method to include both class and instance methods
288
+ def self.included(base)
289
+ base.extend(ClassMethods)
290
+ base.include(InstanceMethods)
291
+ end
292
+ end
293
+ end