StandardRB fixes
This commit is contained in:
@@ -24,16 +24,16 @@ module Clinch
|
||||
# config.time_zone = "Central Time (US & Canada)"
|
||||
# config.eager_load_paths << Rails.root.join("extras")
|
||||
|
||||
# Configure SMTP settings using environment variables
|
||||
# Configure SMTP settings using environment variables
|
||||
config.action_mailer.delivery_method = :smtp
|
||||
config.action_mailer.smtp_settings = {
|
||||
address: ENV.fetch('SMTP_ADDRESS', 'localhost'),
|
||||
port: ENV.fetch('SMTP_PORT', 587),
|
||||
domain: ENV.fetch('SMTP_DOMAIN', 'localhost'),
|
||||
user_name: ENV.fetch('SMTP_USERNAME', nil),
|
||||
password: ENV.fetch('SMTP_PASSWORD', nil),
|
||||
authentication: ENV.fetch('SMTP_AUTHENTICATION', 'plain').to_sym,
|
||||
enable_starttls_auto: ENV.fetch('SMTP_STARTTLS_AUTO', 'true') == 'true',
|
||||
address: ENV.fetch("SMTP_ADDRESS", "localhost"),
|
||||
port: ENV.fetch("SMTP_PORT", 587),
|
||||
domain: ENV.fetch("SMTP_DOMAIN", "localhost"),
|
||||
user_name: ENV.fetch("SMTP_USERNAME", nil),
|
||||
password: ENV.fetch("SMTP_PASSWORD", nil),
|
||||
authentication: ENV.fetch("SMTP_AUTHENTICATION", "plain").to_sym,
|
||||
enable_starttls_auto: ENV.fetch("SMTP_STARTTLS_AUTO", "true") == "true",
|
||||
openssl_verify_mode: OpenSSL::SSL::VERIFY_PEER
|
||||
}
|
||||
end
|
||||
|
||||
@@ -20,7 +20,7 @@ Rails.application.configure do
|
||||
if Rails.root.join("tmp/caching-dev.txt").exist?
|
||||
config.action_controller.perform_caching = true
|
||||
config.action_controller.enable_fragment_cache_logging = true
|
||||
config.public_file_server.headers = { "cache-control" => "public, max-age=#{2.days.to_i}" }
|
||||
config.public_file_server.headers = {"cache-control" => "public, max-age=#{2.days.to_i}"}
|
||||
else
|
||||
config.action_controller.perform_caching = false
|
||||
end
|
||||
@@ -39,10 +39,10 @@ Rails.application.configure do
|
||||
config.action_mailer.perform_caching = false
|
||||
|
||||
# Set localhost to be used by links generated in mailer templates.
|
||||
config.action_mailer.default_url_options = { host: "localhost", port: 3000 }
|
||||
config.action_mailer.default_url_options = {host: "localhost", port: 3000}
|
||||
|
||||
# Log with request_id as a tag (same as production).
|
||||
config.log_tags = [ :request_id ]
|
||||
config.log_tags = [:request_id]
|
||||
|
||||
# Print deprecation notices to the Rails logger.
|
||||
config.active_support.deprecation = :log
|
||||
@@ -61,7 +61,6 @@ Rails.application.configure do
|
||||
|
||||
# Use async processor for background jobs in development
|
||||
config.active_job.queue_adapter = :async
|
||||
|
||||
|
||||
# Highlight code that triggered redirect in logs.
|
||||
config.action_dispatch.verbose_redirect_logs = true
|
||||
|
||||
@@ -16,7 +16,7 @@ Rails.application.configure do
|
||||
config.action_controller.perform_caching = true
|
||||
|
||||
# Cache assets for far-future expiry since they are all digest stamped.
|
||||
config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" }
|
||||
config.public_file_server.headers = {"cache-control" => "public, max-age=#{1.year.to_i}"}
|
||||
|
||||
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
|
||||
# config.asset_host = "http://assets.example.com"
|
||||
@@ -34,16 +34,16 @@ Rails.application.configure do
|
||||
# Note: Rails already sets X-Content-Type-Options: nosniff by default
|
||||
# Note: Permissions-Policy is configured in config/initializers/permissions_policy.rb
|
||||
config.action_dispatch.default_headers.merge!(
|
||||
'X-Frame-Options' => 'DENY', # Override default SAMEORIGIN to prevent clickjacking
|
||||
'Referrer-Policy' => 'strict-origin-when-cross-origin' # Control referrer information
|
||||
"X-Frame-Options" => "DENY", # Override default SAMEORIGIN to prevent clickjacking
|
||||
"Referrer-Policy" => "strict-origin-when-cross-origin" # Control referrer information
|
||||
)
|
||||
|
||||
# Skip http-to-https redirect for the default health check endpoint.
|
||||
# config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } }
|
||||
|
||||
# Log to STDOUT with the current request id as a default log tag.
|
||||
config.log_tags = [ :request_id ]
|
||||
config.logger = ActiveSupport::TaggedLogging.logger(STDOUT)
|
||||
config.log_tags = [:request_id]
|
||||
config.logger = ActiveSupport::TaggedLogging.logger($stdout)
|
||||
|
||||
# Change to "debug" to log everything (including potentially personally-identifiable information!).
|
||||
config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info")
|
||||
@@ -66,7 +66,7 @@ Rails.application.configure do
|
||||
|
||||
# Set host to be used by links generated in mailer templates.
|
||||
config.action_mailer.default_url_options = {
|
||||
host: ENV.fetch('CLINCH_HOST', 'example.com')
|
||||
host: ENV.fetch("CLINCH_HOST", "example.com")
|
||||
}
|
||||
|
||||
# Specify outgoing SMTP server. Remember to add smtp/* credentials via bin/rails credentials:edit.
|
||||
@@ -86,13 +86,13 @@ Rails.application.configure do
|
||||
config.active_record.dump_schema_after_migration = false
|
||||
|
||||
# Only use :id for inspections in production.
|
||||
config.active_record.attributes_for_inspect = [ :id ]
|
||||
config.active_record.attributes_for_inspect = [:id]
|
||||
|
||||
# Helper method to extract domain from CLINCH_HOST (removes protocol if present)
|
||||
def self.extract_domain(host)
|
||||
return host if host.blank?
|
||||
# Remove protocol (http:// or https://) if present
|
||||
host.gsub(/^https?:\/\//, '')
|
||||
host.gsub(/^https?:\/\//, "")
|
||||
end
|
||||
|
||||
# Helper method to ensure URL has https:// protocol
|
||||
@@ -105,11 +105,11 @@ Rails.application.configure do
|
||||
# Enable DNS rebinding protection and other `Host` header attacks.
|
||||
# Configure allowed hosts based on deployment scenario
|
||||
allowed_hosts = [
|
||||
extract_domain(ENV.fetch('CLINCH_HOST', 'auth.example.com')), # External domain (auth service itself)
|
||||
extract_domain(ENV.fetch("CLINCH_HOST", "auth.example.com")) # External domain (auth service itself)
|
||||
]
|
||||
|
||||
# Use PublicSuffix to extract registrable domain and allow all subdomains
|
||||
host_domain = extract_domain(ENV.fetch('CLINCH_HOST', 'auth.example.com'))
|
||||
host_domain = extract_domain(ENV.fetch("CLINCH_HOST", "auth.example.com"))
|
||||
if host_domain.present?
|
||||
begin
|
||||
# Use PublicSuffix to properly extract the domain
|
||||
@@ -123,20 +123,20 @@ Rails.application.configure do
|
||||
rescue PublicSuffix::DomainInvalid
|
||||
# Fallback to simple domain extraction if PublicSuffix fails
|
||||
Rails.logger.warn "Could not parse domain '#{host_domain}' with PublicSuffix, using fallback"
|
||||
base_domain = host_domain.split('.').last(2).join('.')
|
||||
base_domain = host_domain.split(".").last(2).join(".")
|
||||
allowed_hosts << /.*#{Regexp.escape(base_domain)}/
|
||||
end
|
||||
end
|
||||
|
||||
# Allow Docker service names if running in same compose
|
||||
if ENV['CLINCH_DOCKER_SERVICE_NAME']
|
||||
allowed_hosts << ENV['CLINCH_DOCKER_SERVICE_NAME']
|
||||
if ENV["CLINCH_DOCKER_SERVICE_NAME"]
|
||||
allowed_hosts << ENV["CLINCH_DOCKER_SERVICE_NAME"]
|
||||
end
|
||||
|
||||
# Allow internal IP access for cross-compose or host networking
|
||||
if ENV['CLINCH_ALLOW_INTERNAL_IPS'] == 'true'
|
||||
if ENV["CLINCH_ALLOW_INTERNAL_IPS"] == "true"
|
||||
# Specific host IP
|
||||
allowed_hosts << '192.168.2.246'
|
||||
allowed_hosts << "192.168.2.246"
|
||||
|
||||
# Private IP ranges for internal network access
|
||||
allowed_hosts += [
|
||||
@@ -147,14 +147,14 @@ Rails.application.configure do
|
||||
end
|
||||
|
||||
# Local development fallbacks
|
||||
if ENV['CLINCH_ALLOW_LOCALHOST'] == 'true'
|
||||
allowed_hosts += ['localhost', '127.0.0.1', '0.0.0.0']
|
||||
if ENV["CLINCH_ALLOW_LOCALHOST"] == "true"
|
||||
allowed_hosts += ["localhost", "127.0.0.1", "0.0.0.0"]
|
||||
end
|
||||
|
||||
config.hosts = allowed_hosts
|
||||
|
||||
# Skip DNS rebinding protection for the default health check endpoint.
|
||||
config.host_authorization = { exclude: ->(request) { request.path == "/up" } }
|
||||
config.host_authorization = {exclude: ->(request) { request.path == "/up" }}
|
||||
|
||||
# Sentry configuration for production
|
||||
# Only enabled if SENTRY_DSN environment variable is set
|
||||
|
||||
@@ -16,7 +16,7 @@ Rails.application.configure do
|
||||
config.eager_load = ENV["CI"].present?
|
||||
|
||||
# Configure public file server for tests with cache-control for performance.
|
||||
config.public_file_server.headers = { "cache-control" => "public, max-age=3600" }
|
||||
config.public_file_server.headers = {"cache-control" => "public, max-age=3600"}
|
||||
|
||||
# Show full error reports.
|
||||
config.consider_all_requests_local = true
|
||||
@@ -37,7 +37,7 @@ Rails.application.configure do
|
||||
config.action_mailer.delivery_method = :test
|
||||
|
||||
# Set host to be used by links generated in mailer templates.
|
||||
config.action_mailer.default_url_options = { host: "example.com" }
|
||||
config.action_mailer.default_url_options = {host: "example.com"}
|
||||
|
||||
# Print deprecation notices to the stderr.
|
||||
config.active_support.deprecation = :stderr
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||
# - ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT
|
||||
|
||||
# Use env vars if set, otherwise derive from SECRET_KEY_BASE (deterministic)
|
||||
primary_key = ENV.fetch('ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY') do
|
||||
Rails.application.key_generator.generate_key('active_record_encryption_primary', 32)
|
||||
primary_key = ENV.fetch("ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY") do
|
||||
Rails.application.key_generator.generate_key("active_record_encryption_primary", 32)
|
||||
end
|
||||
deterministic_key = ENV.fetch('ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY') do
|
||||
Rails.application.key_generator.generate_key('active_record_encryption_deterministic', 32)
|
||||
deterministic_key = ENV.fetch("ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY") do
|
||||
Rails.application.key_generator.generate_key("active_record_encryption_deterministic", 32)
|
||||
end
|
||||
key_derivation_salt = ENV.fetch('ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT') do
|
||||
Rails.application.key_generator.generate_key('active_record_encryption_salt', 32)
|
||||
key_derivation_salt = ENV.fetch("ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT") do
|
||||
Rails.application.key_generator.generate_key("active_record_encryption_salt", 32)
|
||||
end
|
||||
|
||||
# Configure Rails 7.1+ ActiveRecord encryption
|
||||
|
||||
@@ -56,14 +56,13 @@ Rails.application.configure do
|
||||
policy.require_trusted_types_for :none
|
||||
|
||||
# CSP reporting using report_uri (supported method)
|
||||
policy.report_uri "/api/csp-violation-report"
|
||||
policy.report_uri "/api/csp-violation-report"
|
||||
end
|
||||
|
||||
|
||||
# Start with CSP in report-only mode for testing
|
||||
# Set to false after verifying everything works in production
|
||||
config.content_security_policy_report_only = Rails.env.development?
|
||||
|
||||
# Report CSP violations (optional - uncomment to enable)
|
||||
# config.content_security_policy_report_uri = "/csp-violations"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,7 +8,7 @@ Rails.application.config.after_initialize do
|
||||
# Configure log rotation
|
||||
csp_logger = Logger.new(
|
||||
csp_log_path,
|
||||
'daily', # Rotate daily
|
||||
"daily", # Rotate daily
|
||||
30 # Keep 30 old log files
|
||||
)
|
||||
|
||||
@@ -16,7 +16,7 @@ Rails.application.config.after_initialize do
|
||||
|
||||
# Format: [TIMESTAMP] LEVEL MESSAGE
|
||||
csp_logger.formatter = proc do |severity, datetime, progname, msg|
|
||||
"[#{datetime.strftime('%Y-%m-%d %H:%M:%S')}] #{severity} #{msg}\n"
|
||||
"[#{datetime.strftime("%Y-%m-%d %H:%M:%S")}] #{severity} #{msg}\n"
|
||||
end
|
||||
|
||||
module CspViolationLocalLogger
|
||||
@@ -25,9 +25,9 @@ Rails.application.config.after_initialize do
|
||||
|
||||
# Skip logging if there's no meaningful violation data
|
||||
return if csp_data.empty? ||
|
||||
(csp_data[:violated_directive].nil? &&
|
||||
csp_data[:blocked_uri].nil? &&
|
||||
csp_data[:document_uri].nil?)
|
||||
(csp_data[:violated_directive].nil? &&
|
||||
csp_data[:blocked_uri].nil? &&
|
||||
csp_data[:document_uri].nil?)
|
||||
|
||||
# Build a structured log message
|
||||
violated_directive = csp_data[:violated_directive] || "unknown"
|
||||
@@ -69,7 +69,6 @@ Rails.application.config.after_initialize do
|
||||
|
||||
# Also log to main Rails logger for visibility
|
||||
Rails.logger.info "CSP violation logged to csp_violations.log: #{violated_directive} - #{blocked_uri}"
|
||||
|
||||
rescue => e
|
||||
# Ensure logger errors don't break the CSP reporting flow
|
||||
Rails.logger.error "Failed to log CSP violation to file: #{e.message}"
|
||||
@@ -81,12 +80,12 @@ Rails.application.config.after_initialize do
|
||||
csp_log_path = Rails.root.join("log", "csp_violations.log")
|
||||
logger = Logger.new(
|
||||
csp_log_path,
|
||||
'daily', # Rotate daily
|
||||
"daily", # Rotate daily
|
||||
30 # Keep 30 old log files
|
||||
)
|
||||
logger.level = Logger::INFO
|
||||
logger.formatter = proc do |severity, datetime, progname, msg|
|
||||
"[#{datetime.strftime('%Y-%m-%d %H:%M:%S')}] #{severity} #{msg}\n"
|
||||
"[#{datetime.strftime("%Y-%m-%d %H:%M:%S")}] #{severity} #{msg}\n"
|
||||
end
|
||||
logger
|
||||
end
|
||||
@@ -120,9 +119,8 @@ Rails.application.config.after_initialize do
|
||||
|
||||
# Test write to ensure permissions are correct
|
||||
csp_logger.info "CSP Logger initialized at #{Time.current}"
|
||||
|
||||
rescue => e
|
||||
Rails.logger.error "Failed to initialize CSP local logger: #{e.message}"
|
||||
Rails.logger.error "CSP violations will only be sent to Sentry (if configured)"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
|
||||
Rails.application.config.permissions_policy do |f|
|
||||
# Disable sensitive browser features for security
|
||||
f.camera :none
|
||||
f.gyroscope :none
|
||||
f.microphone :none
|
||||
f.payment :none
|
||||
f.usb :none
|
||||
f.magnetometer :none
|
||||
f.camera :none
|
||||
f.gyroscope :none
|
||||
f.microphone :none
|
||||
f.payment :none
|
||||
f.usb :none
|
||||
f.magnetometer :none
|
||||
|
||||
# You can enable specific features as needed:
|
||||
# f.fullscreen :self
|
||||
|
||||
@@ -74,7 +74,7 @@ Rails.application.configure do
|
||||
app_environment: Rails.env,
|
||||
# Add CSP policy status
|
||||
csp_enabled: defined?(Rails.application.config.content_security_policy) &&
|
||||
Rails.application.config.content_security_policy.present?
|
||||
Rails.application.config.content_security_policy.present?
|
||||
}
|
||||
end
|
||||
|
||||
@@ -120,13 +120,13 @@ Rails.application.configure do
|
||||
if breadcrumb[:data]
|
||||
breadcrumb[:data].reject! { |key, value|
|
||||
key.to_s.match?(/password|secret|token|key|authorization/i) ||
|
||||
value.to_s.match?(/password|secret/i)
|
||||
value.to_s.match?(/password|secret/i)
|
||||
}
|
||||
end
|
||||
|
||||
# Mark CSP-related events
|
||||
if breadcrumb[:message]&.include?("CSP Violation") ||
|
||||
breadcrumb[:category]&.include?("csp")
|
||||
breadcrumb[:category]&.include?("csp")
|
||||
breadcrumb[:data] ||= {}
|
||||
breadcrumb[:data][:security_event] = true
|
||||
breadcrumb[:data][:csp_violation] = true
|
||||
@@ -137,4 +137,4 @@ Rails.application.configure do
|
||||
|
||||
# Only send errors in production unless explicitly enabled
|
||||
config.sentry.enabled = Rails.env.production? || ENV["SENTRY_ENABLED_IN_DEVELOPMENT"] == "true"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -47,7 +47,7 @@ Rails.application.config.after_initialize do
|
||||
timestamp: csp_data[:timestamp]
|
||||
}
|
||||
},
|
||||
user: csp_data[:current_user_id] ? { id: csp_data[:current_user_id] } : nil
|
||||
user: csp_data[:current_user_id] ? {id: csp_data[:current_user_id]} : nil
|
||||
)
|
||||
|
||||
# Log to Rails logger for redundancy
|
||||
@@ -69,10 +69,10 @@ Rails.application.config.after_initialize do
|
||||
parsed.host
|
||||
rescue URI::InvalidURIError
|
||||
# Handle cases where URI might be malformed or just a path
|
||||
if uri.start_with?('/')
|
||||
if uri.start_with?("/")
|
||||
nil # It's a relative path, no domain
|
||||
else
|
||||
uri.split('/').first # Best effort extraction
|
||||
uri.split("/").first # Best effort extraction
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -117,4 +117,4 @@ Rails.application.config.after_initialize do
|
||||
else
|
||||
Rails.logger.info "Sentry not initialized - CSP violations will only be logged locally"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
# Derived from SECRET_KEY_BASE - no storage needed, deterministic output
|
||||
# Optional: Set OIDC_TOKEN_PREFIX_HMAC env var to override with explicit key
|
||||
module TokenHmac
|
||||
KEY = ENV['OIDC_TOKEN_PREFIX_HMAC'] || Rails.application.key_generator.generate_key('oidc_token_prefix', 32)
|
||||
KEY = ENV["OIDC_TOKEN_PREFIX_HMAC"] || Rails.application.key_generator.generate_key("oidc_token_prefix", 32)
|
||||
end
|
||||
|
||||
@@ -68,4 +68,4 @@ end
|
||||
# CLINCH_RP_NAME="Example Company Identity Provider"
|
||||
# CLINCH_WEBAUTHN_ATTESTATION=none
|
||||
# CLINCH_WEBAUTHN_USER_VERIFICATION=preferred
|
||||
# CLINCH_WEBAUTHN_RESIDENT_KEY=preferred
|
||||
# CLINCH_WEBAUTHN_RESIDENT_KEY=preferred
|
||||
|
||||
@@ -31,7 +31,6 @@ threads threads_count, threads_count
|
||||
# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
|
||||
port ENV.fetch("PORT", 3000)
|
||||
|
||||
|
||||
# Allow puma to be restarted by `bin/rails restart` command.
|
||||
plugin :tmp_restart
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ Rails.application.routes.draw do
|
||||
|
||||
# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
|
||||
# Can be used by load balancers and uptime monitors to verify that the app is live.
|
||||
get "up" => "rails/health#show", as: :rails_health_check
|
||||
get "up" => "rails/health#show", :as => :rails_health_check
|
||||
|
||||
# Authentication routes
|
||||
get "/signup", to: "users#new", as: :signup
|
||||
@@ -61,21 +61,21 @@ Rails.application.routes.draw do
|
||||
end
|
||||
|
||||
# TOTP (2FA) routes
|
||||
get '/totp/new', to: 'totp#new', as: :new_totp
|
||||
post '/totp', to: 'totp#create', as: :totp
|
||||
delete '/totp', to: 'totp#destroy'
|
||||
get '/totp/backup_codes', to: 'totp#backup_codes', as: :backup_codes_totp
|
||||
post '/totp/verify_password', to: 'totp#verify_password', as: :verify_password_totp
|
||||
get '/totp/regenerate_backup_codes', to: 'totp#regenerate_backup_codes', as: :regenerate_backup_codes_totp
|
||||
post '/totp/regenerate_backup_codes', to: 'totp#create_new_backup_codes', as: :create_new_backup_codes_totp
|
||||
post '/totp/complete_setup', to: 'totp#complete_setup', as: :complete_totp_setup
|
||||
get "/totp/new", to: "totp#new", as: :new_totp
|
||||
post "/totp", to: "totp#create", as: :totp
|
||||
delete "/totp", to: "totp#destroy"
|
||||
get "/totp/backup_codes", to: "totp#backup_codes", as: :backup_codes_totp
|
||||
post "/totp/verify_password", to: "totp#verify_password", as: :verify_password_totp
|
||||
get "/totp/regenerate_backup_codes", to: "totp#regenerate_backup_codes", as: :regenerate_backup_codes_totp
|
||||
post "/totp/regenerate_backup_codes", to: "totp#create_new_backup_codes", as: :create_new_backup_codes_totp
|
||||
post "/totp/complete_setup", to: "totp#complete_setup", as: :complete_totp_setup
|
||||
|
||||
# WebAuthn (Passkeys) routes
|
||||
get '/webauthn/new', to: 'webauthn#new', as: :new_webauthn
|
||||
post '/webauthn/challenge', to: 'webauthn#challenge'
|
||||
post '/webauthn/create', to: 'webauthn#create'
|
||||
delete '/webauthn/:id', to: 'webauthn#destroy', as: :webauthn_credential
|
||||
get '/webauthn/check', to: 'webauthn#check'
|
||||
get "/webauthn/new", to: "webauthn#new", as: :new_webauthn
|
||||
post "/webauthn/challenge", to: "webauthn#challenge"
|
||||
post "/webauthn/create", to: "webauthn#create"
|
||||
delete "/webauthn/:id", to: "webauthn#destroy", as: :webauthn_credential
|
||||
get "/webauthn/check", to: "webauthn#check"
|
||||
|
||||
# Admin routes
|
||||
namespace :admin do
|
||||
|
||||
Reference in New Issue
Block a user