fix: add periodic cleanup of login_attempts hash
Sweep stale entries every 5 minutes to prevent unbounded memory growth from IPs that touch the login endpoint once and never return.
This commit is contained in:
@@ -18,11 +18,24 @@ require_relative "volumen/users"
|
|||||||
# The Sinatra app (`Volumen::Server`) and the CLI (`Volumen::CLI`) are loaded
|
# The Sinatra app (`Volumen::Server`) and the CLI (`Volumen::CLI`) are loaded
|
||||||
# on demand to keep the core library light.
|
# on demand to keep the core library light.
|
||||||
module Volumen
|
module Volumen
|
||||||
|
CLEANUP_INTERVAL = 300 # seconds between full sweeps of login_attempts
|
||||||
|
|
||||||
def self.login_attempts
|
def self.login_attempts
|
||||||
@login_attempts ||= {}
|
@login_attempts ||= {}
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.reset_login_attempts!
|
def self.reset_login_attempts!
|
||||||
@login_attempts = {}
|
@login_attempts = {}
|
||||||
|
@last_cleanup = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.sweep_login_attempts!
|
||||||
|
now = Time.now.to_f
|
||||||
|
return if @last_cleanup && (now - @last_cleanup) < CLEANUP_INTERVAL
|
||||||
|
|
||||||
|
cutoff = now - Volumen::Server::LOGIN_WINDOW
|
||||||
|
login_attempts.each_value { |timestamps| timestamps.select! { |t| t > cutoff } }
|
||||||
|
login_attempts.delete_if { |_ip, timestamps| timestamps.empty? }
|
||||||
|
@last_cleanup = now
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -224,6 +224,7 @@ module Volumen
|
|||||||
end
|
end
|
||||||
|
|
||||||
def check_login_rate_limit!
|
def check_login_rate_limit!
|
||||||
|
Volumen.sweep_login_attempts!
|
||||||
ip = request.ip.to_s
|
ip = request.ip.to_s
|
||||||
now = Time.now.to_f
|
now = Time.now.to_f
|
||||||
cutoff = now - LOGIN_WINDOW
|
cutoff = now - LOGIN_WINDOW
|
||||||
|
|||||||
@@ -221,6 +221,14 @@ class AdminTest < Minitest::Test
|
|||||||
assert File.exist?(File.join(@posts_dir, "by-author.md"))
|
assert File.exist?(File.join(@posts_dir, "by-author.md"))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_login_rate_limit_sweep_removes_stale_entries
|
||||||
|
Volumen.login_attempts["1.2.3.4"] = [Time.now.to_f - 120]
|
||||||
|
Volumen.instance_variable_set(:@last_cleanup, Time.now.to_f - 600)
|
||||||
|
Volumen.sweep_login_attempts!
|
||||||
|
refute Volumen.login_attempts.key?("1.2.3.4"),
|
||||||
|
"sweep should remove IPs with only stale entries"
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def csrf(body)
|
def csrf(body)
|
||||||
|
|||||||
Reference in New Issue
Block a user