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
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"
!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)
--- /dev/null
+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
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:
--- /dev/null
+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