mirror of
https://github.com/we-promise/sure.git
synced 2026-04-07 22:34:47 +00:00
* feat: Protect demo monitoring API key from deletion - Add DEMO_MONITORING_KEY constant to ApiKey model - Add `demo_monitoring_key?` method to identify the monitoring key - Add `visible` scope to exclude monitoring key from UI queries - Update controller to use `visible` scope, hiding the monitoring key - Prevent revocation of the monitoring key with explicit error handling - Update Demo::Generator to use the shared constant Users on the demo instance can still create their own API keys, but cannot see or delete the monitoring key used for uptime checks. https://claude.ai/code/session_01RQFsw39K7PB5kztboVdBdB * Linter * Protect demo monitoring API key from deletion * Use monitoring source for demo API key * Add test for demo monitoring revoke guard * Disable Rack::Attack in test and development --------- Co-authored-by: Claude <noreply@anthropic.com>
76 lines
2.3 KiB
Ruby
76 lines
2.3 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class Rack::Attack
|
|
# Enable Rack::Attack
|
|
self.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
|
|
|
|
# Throttle admin endpoints to prevent brute-force attacks
|
|
# More restrictive than general API limits since admin access is sensitive
|
|
throttle("admin/ip", limit: 10, period: 1.minute) do |request|
|
|
request.ip if request.path.start_with?("/admin/")
|
|
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
|