Add support for rate limiting signup requests

This commit is contained in:
Tom Hughes 2023-08-22 18:32:53 +01:00
parent 7054cea48e
commit 63bf18a3c3
4 changed files with 82 additions and 1 deletions

View file

@ -186,6 +186,9 @@ class UsersController < ApplicationController
end
if current_user.save
SIGNUP_IP_LIMITER&.update(request.remote_ip)
SIGNUP_EMAIL_LIMITER&.update(canonical_email(current_user.email))
flash[:matomo_goal] = Settings.matomo["goals"]["signup"] if defined?(Settings.matomo)
referer = welcome_path
@ -344,7 +347,13 @@ class UsersController < ApplicationController
domain_mx_servers(domain)
end
if blocked = Acl.no_account_creation(request.remote_ip, :domain => domain, :mx => mx_servers)
blocked = Acl.no_account_creation(request.remote_ip, :domain => domain, :mx => mx_servers)
blocked ||= SIGNUP_IP_LIMITER && !SIGNUP_IP_LIMITER.allow?(request.remote_ip)
blocked ||= email && SIGNUP_EMAIL_LIMITER && !SIGNUP_EMAIL_LIMITER.allow?(canonical_email(email))
if blocked
logger.info "Blocked signup from #{request.remote_ip} for #{email}"
render :action => "blocked"
@ -353,6 +362,20 @@ class UsersController < ApplicationController
!blocked
end
def canonical_email(email)
local_part, domain = if email.nil?
nil
else
email.split("@")
end
local_part.sub!(/\+.*$/, "")
local_part.delete!(".") if %w[gmail.com googlemail.com].include?(domain)
"#{local_part}@#{domain}"
end
##
# get list of MX servers for a domains
def domain_mx_servers(domain)

View file

@ -0,0 +1,15 @@
require "rate_limiter"
SIGNUP_IP_LIMITER = if Settings.memcache_servers && Settings.signup_ip_per_day && Settings.signup_ip_max_burst
RateLimiter.new(
Dalli::Client.new(Settings.memcache_servers, :namespace => "rails:signup:ip"),
86400, Settings.signup_ip_per_day, Settings.signup_ip_max_burst
)
end
SIGNUP_EMAIL_LIMITER = if Settings.memcache_servers && Settings.signup_email_per_day && Settings.signup_email_max_burst
RateLimiter.new(
Dalli::Client.new(Settings.memcache_servers, :namespace => "rails:signup:email"),
86400, Settings.signup_email_per_day, Settings.signup_email_max_burst
)
end

View file

@ -140,3 +140,8 @@ smtp_user_name: null
smtp_password: null
# Matomo settings for analytics
#matomo:
# Signup rate limits
#signup_ip_per_day:
#signup_ip_max_burst:
#signup_email_per_day:
#signup_email_max_burst:

38
lib/rate_limiter.rb Normal file
View file

@ -0,0 +1,38 @@
class RateLimiter
def initialize(cache, interval, limit, max_burst)
@cache = cache
@requests_per_second = limit.to_f / interval
@burst_limit = max_burst
end
def allow?(key)
last_update, requests = @cache.get(key)
if last_update
elapsed = Time.now.to_i - last_update
requests -= elapsed * @requests_per_second
else
requests = 0.0
end
requests < @burst_limit
end
def update(key)
now = Time.now.to_i
last_update, requests = @cache.get(key)
if last_update
elapsed = now - last_update
requests -= elapsed * @requests_per_second
requests += 1.0
else
requests = 1.0
end
@cache.set(key, [now, [requests, 1.0].max])
end
end