From: Tom Hughes Date: Sun, 27 Aug 2023 08:46:10 +0000 (+0100) Subject: Merge remote-tracking branch 'upstream/pull/4198' X-Git-Tag: live~613 X-Git-Url: https://git.openstreetmap.org/rails.git/commitdiff_plain/3422bb541db16f6b6e7374689e82e982bd78664b?hp=c7a31ebc5d130bfebc21d04937bdc05f1370272c Merge remote-tracking branch 'upstream/pull/4198' --- diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 0f9e16767..9d4b3d258 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -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) diff --git a/config/initializers/rate_limits.rb b/config/initializers/rate_limits.rb new file mode 100644 index 000000000..5caa3007f --- /dev/null +++ b/config/initializers/rate_limits.rb @@ -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 diff --git a/config/settings.yml b/config/settings.yml index d9910ce28..8ac27df40 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -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: diff --git a/lib/rate_limiter.rb b/lib/rate_limiter.rb new file mode 100644 index 000000000..438f5a1e2 --- /dev/null +++ b/lib/rate_limiter.rb @@ -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