mirror of
https://github.com/we-promise/sure.git
synced 2026-07-25 11:12:13 +00:00
/sidekiq was mounted `unless Rails.env.production?`, and the Docker image bakes RAILS_ENV=production — so no self-hosted or managed deployment has ever had queue tooling, while the production basic-auth block (with default "sure"/"sure" credentials) was dead code. Worse, any custom non-production env (e.g. staging) got the dashboard with no authentication at all. Now: - Development keeps the open mount for convenience. - Everywhere else the route only exists for a signed-in super admin, via a routing constraint that resolves the signed session cookie the same way Authentication#find_session_by_cookie does. The session's user is always the true user (impersonation is resolved at the Current level and impersonating a super admin is forbidden), and sessions are only created after MFA verification, so neither can bypass it. Fails closed — errors mean 404. - The "sure"/"sure" default credentials are deleted. Basic auth now activates only when BOTH SIDEKIQ_WEB_USERNAME and SIDEKIQ_WEB_PASSWORD are explicitly set, as an optional second layer on top of the constraint. - Documented in .env.example and docs/hosting/docker.md, including warnings that the dashboard is break-glass tooling: never manually retry SimplefinConnectionUpdateJob (single-use token), and deleting jobs does not update the corresponding Sure records. The super-admin bar's Jobs link is feature-detected via sidekiq_web_available? and lights up automatically now that the route exists in production.
83 lines
2.9 KiB
Ruby
83 lines
2.9 KiB
Ruby
require "sidekiq/web"
|
|
|
|
# Optional second authentication layer for /sidekiq. The route itself only
|
|
# exists for signed-in super admins (see SuperAdminConstraint in routes.rb);
|
|
# basic auth is layered on top ONLY when both variables are explicitly set.
|
|
# There are deliberately no default credentials.
|
|
if ENV["SIDEKIQ_WEB_USERNAME"].present? && ENV["SIDEKIQ_WEB_PASSWORD"].present?
|
|
Sidekiq::Web.use(Rack::Auth::Basic) do |username, password|
|
|
configured_username = ::Digest::SHA256.hexdigest(ENV["SIDEKIQ_WEB_USERNAME"])
|
|
configured_password = ::Digest::SHA256.hexdigest(ENV["SIDEKIQ_WEB_PASSWORD"])
|
|
|
|
ActiveSupport::SecurityUtils.secure_compare(::Digest::SHA256.hexdigest(username), configured_username) &
|
|
ActiveSupport::SecurityUtils.secure_compare(::Digest::SHA256.hexdigest(password), configured_password)
|
|
end
|
|
end
|
|
|
|
# Configure Redis connection for Sidekiq
|
|
# Supports both Redis Sentinel (for HA) and direct Redis URL
|
|
redis_config = if ENV["REDIS_SENTINEL_HOSTS"].present?
|
|
# Redis Sentinel configuration for high availability
|
|
# REDIS_SENTINEL_HOSTS should be comma-separated list: "host1:port1,host2:port2,host3:port3"
|
|
sentinels = ENV["REDIS_SENTINEL_HOSTS"].split(",").filter_map do |host_port|
|
|
parts = host_port.strip.split(":", 2)
|
|
host = parts[0]&.strip
|
|
port_str = parts[1]&.strip
|
|
|
|
next if host.blank?
|
|
|
|
# Parse port with validation, default to 26379 if invalid or missing
|
|
port = if port_str.present?
|
|
port_int = port_str.to_i
|
|
(port_int > 0 && port_int <= 65535) ? port_int : 26379
|
|
else
|
|
26379
|
|
end
|
|
|
|
{ host: host, port: port }
|
|
end
|
|
|
|
if sentinels.empty?
|
|
Rails.logger.warn("REDIS_SENTINEL_HOSTS is set but no valid sentinel hosts found, falling back to REDIS_URL")
|
|
{ url: ENV.fetch("REDIS_URL", "redis://localhost:6379/0") }
|
|
else
|
|
{
|
|
url: "redis://#{ENV.fetch('REDIS_SENTINEL_MASTER', 'mymaster')}/0",
|
|
sentinels: sentinels,
|
|
password: ENV["REDIS_PASSWORD"],
|
|
sentinel_username: ENV.fetch("REDIS_SENTINEL_USERNAME", "default"),
|
|
sentinel_password: ENV["REDIS_PASSWORD"],
|
|
role: :master,
|
|
# Recommended timeouts for Sentinel
|
|
connect_timeout: 0.2,
|
|
read_timeout: 1,
|
|
write_timeout: 1,
|
|
reconnect_attempts: 3
|
|
}
|
|
end
|
|
else
|
|
# Standard Redis URL configuration (no Sentinel)
|
|
{ url: ENV.fetch("REDIS_URL", "redis://localhost:6379/0") }
|
|
end
|
|
|
|
Sidekiq.configure_server do |config|
|
|
config.redis = redis_config
|
|
|
|
# Initialize auto-sync scheduler when Sidekiq server starts
|
|
config.on(:startup) do
|
|
AutoSyncScheduler.sync!
|
|
Rails.logger.info("[AutoSyncScheduler] Initialized sync_all_accounts cron job")
|
|
rescue => e
|
|
Rails.logger.error("[AutoSyncScheduler] Failed to initialize: #{e.message}")
|
|
end
|
|
end
|
|
|
|
Sidekiq.configure_client do |config|
|
|
config.redis = redis_config
|
|
end
|
|
|
|
Sidekiq::Cron.configure do |config|
|
|
# 10 min "catch-up" window in case worker process is re-deploying when cron tick occurs
|
|
config.reschedule_grace_period = 600
|
|
end
|