clavis 0.7.1

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 (74) hide show
  1. checksums.yaml +7 -0
  2. data/.actrc +4 -0
  3. data/.cursor/rules/ruby-gem.mdc +49 -0
  4. data/.gemignore +6 -0
  5. data/.rspec +3 -0
  6. data/.rubocop.yml +88 -0
  7. data/.vscode/settings.json +22 -0
  8. data/CHANGELOG.md +127 -0
  9. data/CODE_OF_CONDUCT.md +3 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +838 -0
  12. data/Rakefile +341 -0
  13. data/UPGRADE.md +57 -0
  14. data/app/assets/stylesheets/clavis.css +133 -0
  15. data/app/controllers/clavis/auth_controller.rb +133 -0
  16. data/config/database.yml +16 -0
  17. data/config/routes.rb +49 -0
  18. data/docs/SECURITY.md +340 -0
  19. data/docs/TESTING.md +78 -0
  20. data/docs/integration.md +272 -0
  21. data/error_handling.md +355 -0
  22. data/file_structure.md +221 -0
  23. data/gemfiles/rails_80.gemfile +17 -0
  24. data/gemfiles/rails_80.gemfile.lock +286 -0
  25. data/implementation_plan.md +523 -0
  26. data/lib/clavis/configuration.rb +196 -0
  27. data/lib/clavis/controllers/concerns/authentication.rb +232 -0
  28. data/lib/clavis/controllers/concerns/session_management.rb +117 -0
  29. data/lib/clavis/engine.rb +191 -0
  30. data/lib/clavis/errors.rb +205 -0
  31. data/lib/clavis/logging.rb +116 -0
  32. data/lib/clavis/models/concerns/oauth_authenticatable.rb +169 -0
  33. data/lib/clavis/oauth_identity.rb +174 -0
  34. data/lib/clavis/providers/apple.rb +135 -0
  35. data/lib/clavis/providers/base.rb +432 -0
  36. data/lib/clavis/providers/custom_provider_example.rb +57 -0
  37. data/lib/clavis/providers/facebook.rb +84 -0
  38. data/lib/clavis/providers/generic.rb +63 -0
  39. data/lib/clavis/providers/github.rb +87 -0
  40. data/lib/clavis/providers/google.rb +98 -0
  41. data/lib/clavis/providers/microsoft.rb +57 -0
  42. data/lib/clavis/security/csrf_protection.rb +79 -0
  43. data/lib/clavis/security/https_enforcer.rb +90 -0
  44. data/lib/clavis/security/input_validator.rb +192 -0
  45. data/lib/clavis/security/parameter_filter.rb +64 -0
  46. data/lib/clavis/security/rate_limiter.rb +109 -0
  47. data/lib/clavis/security/redirect_uri_validator.rb +124 -0
  48. data/lib/clavis/security/session_manager.rb +220 -0
  49. data/lib/clavis/security/token_storage.rb +114 -0
  50. data/lib/clavis/user_info_normalizer.rb +74 -0
  51. data/lib/clavis/utils/nonce_store.rb +14 -0
  52. data/lib/clavis/utils/secure_token.rb +17 -0
  53. data/lib/clavis/utils/state_store.rb +18 -0
  54. data/lib/clavis/version.rb +6 -0
  55. data/lib/clavis/view_helpers.rb +260 -0
  56. data/lib/clavis.rb +132 -0
  57. data/lib/generators/clavis/controller/controller_generator.rb +48 -0
  58. data/lib/generators/clavis/controller/templates/controller.rb.tt +137 -0
  59. data/lib/generators/clavis/controller/templates/views/login.html.erb.tt +145 -0
  60. data/lib/generators/clavis/install_generator.rb +182 -0
  61. data/lib/generators/clavis/templates/add_oauth_to_users.rb +28 -0
  62. data/lib/generators/clavis/templates/clavis.css +133 -0
  63. data/lib/generators/clavis/templates/initializer.rb +47 -0
  64. data/lib/generators/clavis/templates/initializer.rb.tt +76 -0
  65. data/lib/generators/clavis/templates/migration.rb +18 -0
  66. data/lib/generators/clavis/templates/migration.rb.tt +16 -0
  67. data/lib/generators/clavis/user_method/user_method_generator.rb +219 -0
  68. data/lib/tasks/provider_verification.rake +77 -0
  69. data/llms.md +487 -0
  70. data/log/development.log +20 -0
  71. data/log/test.log +0 -0
  72. data/sig/clavis.rbs +4 -0
  73. data/testing_plan.md +710 -0
  74. metadata +258 -0
@@ -0,0 +1,232 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module Clavis
6
+ module Controllers
7
+ module Concerns
8
+ module Authentication
9
+ extend ActiveSupport::Concern
10
+
11
+ def oauth_authorize
12
+ provider_name = params[:provider]
13
+
14
+ # Check if provider is specified
15
+ if provider_name.blank?
16
+ error_message = "No provider specified for OAuth authentication"
17
+ Clavis::Logging.log_error(error_message)
18
+
19
+ # Return a meaningful error to the user
20
+ flash[:alert] = "Authentication provider not specified. Please try again or contact support."
21
+ redirect_to main_app.respond_to?(:root_path) ? main_app.root_path : "/"
22
+ return
23
+ end
24
+
25
+ begin
26
+ # Explicitly validate provider - this will raise Clavis::ProviderNotConfigured if not configured
27
+ Clavis.configuration.validate_provider!(provider_name)
28
+
29
+ # Initialize the provider - if it fails, let the exception bubble up
30
+ provider = Clavis.provider(provider_name)
31
+
32
+ # Validate and store redirect URI if provided
33
+ if params[:redirect_uri].present?
34
+ Clavis::Security::SessionManager.store_redirect_uri(session, params[:redirect_uri])
35
+ end
36
+
37
+ # Generate and store state and nonce in session
38
+ state = Clavis::Security::SessionManager.generate_and_store_state(session)
39
+ nonce = Clavis::Security::SessionManager.generate_and_store_nonce(session)
40
+
41
+ # Log parameters safely
42
+ Clavis::Security::ParameterFilter.log_parameters(
43
+ { provider: provider_name, scope: params[:scope] },
44
+ level: :info,
45
+ message: "Starting OAuth flow"
46
+ )
47
+
48
+ # Validate inputs
49
+ scope = params[:scope] || Clavis.configuration.default_scopes
50
+ Clavis::Security::InputValidator.sanitize(scope)
51
+
52
+ # Generate the authorization URL and redirect
53
+ auth_url = provider.authorize_url(
54
+ state: state,
55
+ nonce: nonce,
56
+ scope: scope
57
+ )
58
+
59
+ redirect_to auth_url, allow_other_host: true
60
+ rescue StandardError => e
61
+ # Only rescue non-configuration errors
62
+ raise if e.is_a?(Clavis::ProviderNotConfigured) || e.is_a?(Clavis::ConfigurationError)
63
+
64
+ # Re-raise configuration errors to make them visible
65
+
66
+ Clavis::Logging.log_error("OAuth flow error: #{e.class.name} - #{e.message}")
67
+ Clavis::Logging.log_error(e.backtrace.join("\n"))
68
+
69
+ flash[:alert] = "An error occurred while setting up authentication. Please try again or contact support."
70
+ redirect_to main_app.respond_to?(:root_path) ? main_app.root_path : "/"
71
+ end
72
+ end
73
+
74
+ def oauth_callback
75
+ provider_name = params[:provider]
76
+
77
+ # Log parameters safely
78
+ Clavis::Security::ParameterFilter.log_parameters(
79
+ params.to_unsafe_h,
80
+ level: :debug,
81
+ message: "OAuth callback received"
82
+ )
83
+
84
+ # Check for error response from provider
85
+ if params[:error].present?
86
+ handle_oauth_error(params[:error], params[:error_description])
87
+ return
88
+ end
89
+
90
+ # Verify state parameter to prevent CSRF
91
+ unless Clavis::Security::SessionManager.valid_state?(session, params[:state], clear_after_validation: true)
92
+ raise Clavis::InvalidState, "Invalid state parameter"
93
+ end
94
+
95
+ # Validate code parameter
96
+ unless Clavis::Security::InputValidator.valid_code?(params[:code])
97
+ raise Clavis::InvalidGrant, "Invalid authorization code"
98
+ end
99
+
100
+ provider = Clavis.provider(provider_name)
101
+
102
+ begin
103
+ # Exchange code for tokens
104
+ auth_hash = provider.process_callback(params[:code])
105
+
106
+ # Find or create the user using the configured class and method
107
+ user_class = Clavis.configuration.user_class.constantize
108
+ finder_method = Clavis.configuration.user_finder_method
109
+
110
+ # Ensure the configured method exists
111
+ unless user_class.respond_to?(finder_method)
112
+ raise Clavis::ConfigurationError,
113
+ "The method '#{finder_method}' is not defined on the #{user_class.name} class. " \
114
+ "Please implement this method to handle user creation from OAuth, or " \
115
+ "configure a different user_finder_method in your Clavis configuration."
116
+ end
117
+
118
+ # Call the configured method to find or create the user
119
+ user = user_class.public_send(finder_method, auth_hash)
120
+
121
+ # Rotate session ID for security
122
+ Clavis::Security::SessionManager.rotate_session(request) if Clavis.configuration.rotate_session_after_login
123
+
124
+ # Store additional information in the session
125
+ Clavis::Security::SessionManager.store_auth_info(session, auth_hash)
126
+
127
+ # Invoke custom claims processor if configured
128
+ if Clavis.configuration.claims_processor.respond_to?(:call)
129
+ Clavis.configuration.claims_processor.call(auth_hash, user)
130
+ end
131
+
132
+ # Yield to block for custom processing
133
+ yield(user, auth_hash) if block_given?
134
+ rescue StandardError => e
135
+ raise Clavis::AuthenticationError, e.message
136
+ end
137
+ end
138
+
139
+ private
140
+
141
+ def handle_oauth_error(error, description = nil)
142
+ # Sanitize error parameters
143
+ error = Clavis::Security::InputValidator.sanitize(error)
144
+ description = Clavis::Security::InputValidator.sanitize(description) if description
145
+
146
+ case error
147
+ when "access_denied"
148
+ raise Clavis::AuthorizationDenied, description
149
+ when "invalid_request", "unauthorized_client",
150
+ "unsupported_response_type", "invalid_scope",
151
+ "server_error", "temporarily_unavailable"
152
+ raise Clavis::AuthenticationError, description || error
153
+ else
154
+ raise Clavis::AuthenticationError, description || "Unknown error: #{error}"
155
+ end
156
+ end
157
+
158
+ def find_or_create_user_from_oauth(auth_hash)
159
+ # If the User class has the find_for_oauth method, use it
160
+ if defined?(User) && User.respond_to?(:find_for_oauth)
161
+ User.find_for_oauth(auth_hash)
162
+ # If there's a User class that includes OauthAuthenticatable, use the module's method
163
+ elsif defined?(User) && User.include?(Clavis::Models::Concerns::OauthAuthenticatable)
164
+ # Find or create the identity
165
+ identity = Clavis::OauthIdentity.find_or_initialize_by(
166
+ provider: auth_hash[:provider],
167
+ uid: auth_hash[:uid]
168
+ )
169
+
170
+ user = if identity.user.present?
171
+ identity.user
172
+ elsif auth_hash.dig(:info, :email).present?
173
+ # Try to find user by email
174
+ user_email_field = User.new.respond_to?(:email) ? :email : :email_address
175
+ User.find_by(user_email_field => auth_hash.dig(:info, :email)) ||
176
+ begin
177
+ new_user = User.new
178
+ if new_user.respond_to?(user_email_field)
179
+ new_user.send(:"#{user_email_field}=", auth_hash.dig(:info, :email))
180
+ end
181
+
182
+ # Set password if applicable
183
+ if new_user.respond_to?(:password=) && new_user.respond_to?(:password_confirmation=)
184
+ password = SecureRandom.hex(16)
185
+ new_user.password = password
186
+ new_user.password_confirmation = password if new_user.respond_to?(:password_confirmation=)
187
+ end
188
+
189
+ # Set name if applicable
190
+ set_user_name_from_auth_hash(new_user, auth_hash) if auth_hash.dig(:info, :name).present?
191
+
192
+ new_user.save!
193
+ new_user
194
+ end
195
+ else
196
+ # No email found, create a new user without email
197
+ User.create!(password: SecureRandom.hex(16))
198
+ end
199
+
200
+ # Update the identity with the latest auth data
201
+ identity.user = user
202
+ identity.auth_data = auth_hash[:info]
203
+ identity.token = auth_hash.dig(:credentials, :token)
204
+ identity.refresh_token = auth_hash.dig(:credentials, :refresh_token)
205
+ identity.expires_at = if auth_hash.dig(:credentials, :expires_at)
206
+ Time.at(auth_hash.dig(:credentials, :expires_at))
207
+ end
208
+ identity.store_standardized_user_info!
209
+ identity.save!
210
+
211
+ user
212
+ else
213
+ # No User class or not proper configuration, just return the auth hash
214
+ auth_hash
215
+ end
216
+ end
217
+
218
+ # Helper method to set user name from auth hash
219
+ def set_user_name_from_auth_hash(user, auth_hash)
220
+ return unless auth_hash.dig(:info, :name).present?
221
+
222
+ name_parts = auth_hash.dig(:info, :name).split
223
+ user.first_name = name_parts.first if user.respond_to?(:first_name=)
224
+
225
+ return unless name_parts.size > 1 && user.respond_to?(:last_name=)
226
+
227
+ user.last_name = name_parts.drop(1).join(" ")
228
+ end
229
+ end
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module Clavis
6
+ module Controllers
7
+ module Concerns
8
+ # SessionManagement provides methods for handling user sessions after OAuth authentication
9
+ # This concern is designed to be included in ApplicationController and provides
10
+ # a simple, Rails-friendly way to handle user sessions.
11
+ module SessionManagement
12
+ extend ActiveSupport::Concern
13
+
14
+ included do
15
+ helper_method :current_user, :authenticated? if respond_to?(:helper_method)
16
+ end
17
+
18
+ # Check if a user is currently authenticated
19
+ # @return [Boolean] Whether the user is authenticated
20
+ def authenticated?
21
+ current_user.present?
22
+ end
23
+
24
+ # Get the current authenticated user, if any
25
+ # @return [User, nil] The current user or nil if not authenticated
26
+ def current_user
27
+ return @current_user if defined?(@current_user)
28
+
29
+ @current_user = find_user_by_cookie
30
+ end
31
+
32
+ # Sign in a user by setting a signed cookie
33
+ # @param user [User] The user to sign in
34
+ def sign_in_user(user)
35
+ # First try to use Devise if it's available
36
+ if respond_to?(:sign_in) && !method("sign_in").owner.is_a?(Method)
37
+ sign_in(user)
38
+ else
39
+ # Use our secure cookie-based approach
40
+ cookies.signed.permanent[:user_id] = {
41
+ value: user.id,
42
+ httponly: true,
43
+ same_site: :lax,
44
+ secure: Rails.env.production?
45
+ }
46
+ end
47
+ end
48
+
49
+ # Sign out the current user by clearing the cookie
50
+ # @return [void]
51
+ def sign_out_user
52
+ # First try to use Devise if it's available
53
+ if respond_to?(:sign_out) && !method("sign_out").owner.is_a?(Method)
54
+ sign_out(current_user)
55
+ else
56
+ # Use our cookie-based approach
57
+ cookies.delete(:user_id)
58
+ end
59
+ end
60
+
61
+ # Store the current URL to return to after authentication
62
+ # @return [void]
63
+ def store_location
64
+ session[:return_to] = request.url if request.get?
65
+ end
66
+
67
+ # Default path to redirect to after successful login
68
+ # @return [String] The path to redirect to
69
+ def after_login_path
70
+ stored_location || default_path
71
+ end
72
+
73
+ # Default path to redirect to after logout
74
+ # @return [String] The path to redirect to
75
+ def after_logout_path
76
+ if respond_to?(:login_path)
77
+ login_path
78
+ else
79
+ default_path
80
+ end
81
+ end
82
+
83
+ private
84
+
85
+ # Get the stored location for redirection
86
+ # @return [String, nil] The stored location or nil
87
+ def stored_location
88
+ location = session.delete(:return_to)
89
+
90
+ # Don't return auth paths to avoid redirect loops
91
+ return unless location.present? && !location.to_s.include?("/auth/")
92
+
93
+ location
94
+ end
95
+
96
+ # Default path when no stored location is available
97
+ # @return [String] The default path
98
+ def default_path
99
+ if defined?(main_app) && main_app.respond_to?(:root_path)
100
+ main_app.root_path
101
+ else
102
+ "/"
103
+ end
104
+ end
105
+
106
+ # Find a user by the signed cookie
107
+ # @return [User, nil] The user or nil if not found
108
+ def find_user_by_cookie
109
+ return nil unless cookies.signed[:user_id]
110
+
111
+ user_class = Clavis.configuration.user_class.constantize
112
+ user_class.find_by(id: cookies.signed[:user_id])
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,191 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails"
4
+ require_relative "controllers/concerns/authentication"
5
+ require_relative "controllers/concerns/session_management"
6
+ require_relative "security/rate_limiter"
7
+
8
+ module Clavis
9
+ class Engine < ::Rails::Engine
10
+ isolate_namespace Clavis
11
+
12
+ # Allow the routes to be namespaced with a unique identifier for each mount point
13
+ # This prevents route name collisions when the engine is mounted multiple times
14
+ mattr_accessor :route_namespace_id
15
+ self.route_namespace_id = "clavis"
16
+
17
+ # Minimum TLS version for secure requests
18
+ # At the application level, this is handled by Rails 7+ directly
19
+ # At the engine level, we need to set it manually for Net::HTTP
20
+ config.before_initialize do |_app|
21
+ require "net/http"
22
+ # Set minimum TLS version to 1.2 for security
23
+ # Can be upgraded to TLS 1.3 when supported by all platforms
24
+ begin
25
+ # Use min_version instead of ssl_version for better compatibility
26
+ if defined?(OpenSSL::SSL::TLS1_2_VERSION)
27
+ OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:min_version] = OpenSSL::SSL::TLS1_2_VERSION
28
+ end
29
+ rescue StandardError => e
30
+ # Log the error but don't crash
31
+ Clavis.logger.warn("Could not set minimum TLS version: #{e.message}")
32
+ end
33
+ end
34
+
35
+ # Expose Clavis view helpers to the host application
36
+ class << self
37
+ attr_accessor :include_view_helpers
38
+ end
39
+
40
+ # Default to true - can be changed in configuration
41
+ self.include_view_helpers = true
42
+
43
+ # Setup routes lambda - will be called if auto_install_routes is true
44
+ # Takes a single argument (app) which is the Rails application
45
+ # Define this before the initializer so it can be overridden if needed
46
+ mattr_accessor :setup_routes, default: lambda { |app|
47
+ # Mount the engine routes at /auth by default
48
+ app.routes.draw do
49
+ # Add mount point to the application routes
50
+ mount Clavis::Engine => "/auth"
51
+ end
52
+ }
53
+
54
+ # Whether to automatically install routes
55
+ mattr_accessor :auto_install_routes, default: true
56
+
57
+ # Setup Rack::Attack middleware for rate limiting
58
+ initializer "clavis.rack_attack", before: :load_config_initializers do |app|
59
+ # Install Rack::Attack if available
60
+ Clavis::Security::RateLimiter.install(app)
61
+ end
62
+
63
+ initializer "clavis.assets" do |app|
64
+ # Add Clavis assets to the asset pipeline
65
+ app.config.assets.paths << root.join("app", "assets", "stylesheets") if app.config.respond_to?(:assets)
66
+ app.config.assets.paths << root.join("app", "assets", "javascripts") if app.config.respond_to?(:assets)
67
+
68
+ # Explicitly precompile clavis assets if precompile array is available
69
+ if app.config.respond_to?(:assets) && app.config.assets.respond_to?(:precompile)
70
+ app.config.assets.precompile += %w[clavis.css clavis.js]
71
+ end
72
+
73
+ # For Rails 7+ with propshaft or jsbundling
74
+ if defined?(Propshaft) || defined?(Jsbundling)
75
+ app.config.assets.precompile << "clavis.css" if app.config.respond_to?(:assets)
76
+ app.config.importmap.pin "clavis", to: "clavis" if app.config.respond_to?(:importmap)
77
+ end
78
+ end
79
+
80
+ initializer "clavis.routes", after: :add_routing_paths do |app|
81
+ # Only install routes automatically if enabled
82
+ if auto_install_routes && setup_routes.respond_to?(:call)
83
+ # Call the setup_routes lambda to add routes to the parent application
84
+ setup_routes.call(app)
85
+ end
86
+ end
87
+
88
+ initializer "clavis.helpers" do
89
+ ActiveSupport.on_load(:action_controller) do
90
+ include Clavis::Controllers::Concerns::Authentication
91
+ include Clavis::Controllers::Concerns::SessionManagement
92
+ end
93
+
94
+ # Include view helpers based on configuration (default: true)
95
+ ActiveSupport.on_load(:action_view) do
96
+ include Clavis::ViewHelpers
97
+ end
98
+
99
+ # Make ViewHelpers available to ApplicationHelper
100
+ ActiveSupport.on_load(:after_initialize) do
101
+ if defined?(ApplicationHelper) && ApplicationHelper.included_modules.exclude?(Clavis::ViewHelpers)
102
+ ApplicationHelper.include(Clavis::ViewHelpers)
103
+ end
104
+ end
105
+ end
106
+
107
+ initializer "clavis.logger" do
108
+ config.after_initialize do
109
+ Clavis::Logging.logger = Rails.logger if defined?(Rails) && Rails.respond_to?(:logger)
110
+ end
111
+ end
112
+
113
+ initializer "clavis.security" do |_app|
114
+ # Install parameter filters
115
+ Clavis::Security::ParameterFilter.install_rails_filter
116
+
117
+ # Set default allowed redirect hosts from Rails application host
118
+ if Clavis.configuration.allowed_redirect_hosts.empty? && Rails.application.config.respond_to?(:hosts)
119
+ Clavis.configuration.allowed_redirect_hosts = Array(Rails.application.config.hosts)
120
+ end
121
+
122
+ # Only log critical security warnings
123
+ if Clavis.configuration.allowed_redirect_hosts.empty?
124
+ Clavis::Logging.security_warning("No allowed redirect hosts configured. All redirect URIs will be rejected.")
125
+ end
126
+
127
+ # Check for insecure redirect URIs in production
128
+ if Rails.env.production?
129
+ Clavis.configuration.providers.each do |provider_name, config|
130
+ next unless config[:redirect_uri] && !Clavis::Security::HttpsEnforcer.https?(config[:redirect_uri])
131
+
132
+ message = "Non-HTTPS redirect URI detected for #{provider_name}: "
133
+ message += config[:redirect_uri].to_s
134
+ Clavis::Logging.security_warning(message)
135
+ end
136
+ end
137
+ end
138
+
139
+ initializer "clavis.parameter_filter" do |app|
140
+ if Clavis.configuration.parameter_filter_enabled
141
+ # Add sensitive parameters to the filter parameters
142
+ app.config.filter_parameters += %i[
143
+ code token access_token refresh_token id_token
144
+ client_secret private_key encryption_key
145
+ ]
146
+ end
147
+ end
148
+
149
+ initializer "clavis.security_warnings" do
150
+ # Only log critical security warnings
151
+ if !Clavis.configuration.encrypt_tokens && Rails.env.production?
152
+ Clavis::Logging.security_warning("Token encryption disabled in production (not recommended)")
153
+ end
154
+
155
+ if Clavis.configuration.encrypt_tokens &&
156
+ !Clavis.configuration.use_rails_credentials &&
157
+ !Clavis.configuration.encryption_key.present?
158
+ Clavis::Logging.security_warning("Token encryption enabled but no encryption key provided")
159
+ end
160
+
161
+ if !Clavis.configuration.enforce_https && Rails.env.production?
162
+ Clavis::Logging.security_warning("HTTPS enforcement disabled in production (not recommended)")
163
+ end
164
+
165
+ if !Clavis.configuration.verify_ssl && Rails.env.production?
166
+ Clavis::Logging.security_warning("SSL certificate verification disabled in production (not recommended)")
167
+ end
168
+
169
+ if !Clavis.configuration.validate_inputs && Rails.env.production?
170
+ Clavis::Logging.security_warning("Input validation disabled in production (not recommended)")
171
+ end
172
+
173
+ if !Clavis.configuration.sanitize_inputs && Rails.env.production?
174
+ Clavis::Logging.security_warning("Input sanitization disabled in production (not recommended)")
175
+ end
176
+
177
+ if !Clavis.configuration.rotate_session_after_login && Rails.env.production?
178
+ Clavis::Logging.security_warning("Session rotation after login disabled in production (not recommended)")
179
+ end
180
+
181
+ if !Clavis.configuration.rate_limiting_enabled && Rails.env.production?
182
+ Clavis::Logging.security_warning("Rate limiting disabled in production (not recommended)")
183
+ end
184
+ end
185
+
186
+ config.to_prepare do
187
+ # Make the view helpers available to the application
188
+ ApplicationController.helper(Clavis::ViewHelpers) if defined?(ApplicationController)
189
+ end
190
+ end
191
+ end