# frozen_string_literal: true class Rack::Attack # Enable Rack::Attack enabled = Rails.env.production? || Rails.env.staging? # Throttle requests to the OAuth token endpoint throttle("oauth/token", limit: 10, period: 1.minute) do |request| request.ip if request.path == "/oauth/token" end # Determine limits based on self-hosted mode self_hosted = Rails.application.config.app_mode.self_hosted? # Throttle API requests per access token throttle("api/requests", limit: self_hosted ? 10_000 : 100, period: 1.hour) do |request| if request.path.start_with?("/api/") # Extract access token from Authorization header auth_header = request.get_header("HTTP_AUTHORIZATION") if auth_header&.start_with?("Bearer ") token = auth_header.split(" ").last "api_token:#{Digest::SHA256.hexdigest(token)}" else # Fall back to IP-based limiting for unauthenticated requests "api_ip:#{request.ip}" end end end # More permissive throttling for API requests by IP (for development/testing) throttle("api/ip", limit: self_hosted ? 20_000 : 200, period: 1.hour) do |request| request.ip if request.path.start_with?("/api/") end # Block requests that appear to be malicious blocklist("block malicious requests") do |request| # Block requests with suspicious user agents suspicious_user_agents = [ /sqlmap/i, /nmap/i, /nikto/i, /masscan/i ] user_agent = request.user_agent suspicious_user_agents.any? { |pattern| user_agent =~ pattern } if user_agent end # Configure response for throttled requests self.throttled_responder = lambda do |request| [ 429, # status { "Content-Type" => "application/json", "Retry-After" => "60" }, [ { error: "Rate limit exceeded. Try again later." }.to_json ] ] end # Configure response for blocked requests self.blocklisted_responder = lambda do |request| [ 403, # status { "Content-Type" => "application/json" }, [ { error: "Request blocked." }.to_json ] ] end end