mirror of
https://github.com/System-End/hackatime.git
synced 2026-04-19 22:15:14 +00:00
Add rack attack (#360)
This commit is contained in:
parent
bdafa0f1b4
commit
bce1b6078f
4 changed files with 71 additions and 0 deletions
3
Gemfile
3
Gemfile
|
|
@ -57,6 +57,9 @@ gem "thruster", require: false
|
|||
# For query count tracking
|
||||
gem "query_count"
|
||||
|
||||
# Rate limiting
|
||||
gem "rack-attack"
|
||||
|
||||
# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
|
||||
# gem "image_processing", "~> 1.2"
|
||||
|
||||
|
|
|
|||
|
|
@ -346,6 +346,8 @@ GEM
|
|||
raabro (1.4.0)
|
||||
racc (1.8.1)
|
||||
rack (3.1.16)
|
||||
rack-attack (6.7.0)
|
||||
rack (>= 1.0, < 4)
|
||||
rack-cors (3.0.0)
|
||||
logger
|
||||
rack (>= 3.0.14)
|
||||
|
|
@ -581,6 +583,7 @@ DEPENDENCIES
|
|||
propshaft
|
||||
puma (>= 5.0)
|
||||
query_count
|
||||
rack-attack
|
||||
rack-cors
|
||||
rack-mini-profiler
|
||||
rails (~> 8.0.2)
|
||||
|
|
|
|||
|
|
@ -48,5 +48,6 @@ module Harbor
|
|||
httponly: true
|
||||
|
||||
config.middleware.use HtmlCompressor::Rack
|
||||
config.middleware.use Rack::Attack
|
||||
end
|
||||
end
|
||||
|
|
|
|||
64
config/initializers/rack_attack.rb
Normal file
64
config/initializers/rack_attack.rb
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# config/initializers/rack_attack.rb
|
||||
|
||||
class Rack::Attack
|
||||
# Always allow requests from localhost
|
||||
# (blocklist & throttles are skipped)
|
||||
Rack::Attack.safelist("allow from localhost") do |req|
|
||||
# Requests are allowed if the return value is truthy
|
||||
"127.0.0.1" == req.ip || "::1" == req.ip
|
||||
end
|
||||
|
||||
# Allow an IP address to make 5 requests per second
|
||||
throttle("req/ip", limit: 300, period: 5.minutes) do |req|
|
||||
req.ip
|
||||
end
|
||||
|
||||
# Allow an IP address to make 5 POST requests per second
|
||||
throttle("post/ip", limit: 60, period: 5.minutes) do |req|
|
||||
req.ip if req.post?
|
||||
end
|
||||
|
||||
# Throttle requests to /login by IP address
|
||||
throttle("login/ip", limit: 5, period: 20.seconds) do |req|
|
||||
if req.path == "/login" && req.post?
|
||||
req.ip
|
||||
end
|
||||
end
|
||||
|
||||
# Throttle requests to /api by IP address
|
||||
throttle("api/ip", limit: 100, period: 5.minutes) do |req|
|
||||
if req.path.start_with?("/api")
|
||||
req.ip
|
||||
end
|
||||
end
|
||||
|
||||
# Log blocked requests
|
||||
ActiveSupport::Notifications.subscribe(/rack_attack/) do |name, start, finish, request_id, payload|
|
||||
req = payload[:request]
|
||||
|
||||
case name
|
||||
when "rack_attack.throttle"
|
||||
Rails.logger.warn "[Rack::Attack][Throttle] IP: #{req.ip}, Path: #{req.path}, Discriminator: #{payload[:discriminator]}, Matched: #{payload[:matched]}"
|
||||
when "rack_attack.blocklist"
|
||||
Rails.logger.warn "[Rack::Attack][Blocklist] IP: #{req.ip}, Path: #{req.path}, Discriminator: #{payload[:discriminator]}, Matched: #{payload[:matched]}"
|
||||
when "rack_attack.safelist"
|
||||
Rails.logger.info "[Rack::Attack][Safelist] IP: #{req.ip}, Path: #{req.path}, Discriminator: #{payload[:discriminator]}, Matched: #{payload[:matched]}"
|
||||
end
|
||||
end
|
||||
|
||||
# Custom response for throttled requests
|
||||
self.throttled_response = lambda do |env|
|
||||
retry_after = (env["rack.attack.match_data"] || {})[:period]
|
||||
[
|
||||
429,
|
||||
{
|
||||
"Content-Type" => "application/json",
|
||||
"Retry-After" => retry_after.to_s,
|
||||
"X-RateLimit-Limit" => env["rack.attack.matched"].to_s,
|
||||
"X-RateLimit-Remaining" => "0",
|
||||
"X-RateLimit-Reset" => (Time.now + retry_after).to_i.to_s
|
||||
},
|
||||
[ { error: "Too Many Requests", message: "Rate limit exceeded. Try again later." }.to_json ]
|
||||
]
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue