Class: BetterAuth::RateLimiter
- Inherits:
-
Object
- Object
- BetterAuth::RateLimiter
- Defined in:
- lib/better_auth/rate_limiter.rb
Defined Under Namespace
Classes: MemoryStore
Constant Summary collapse
- MISSING_CLIENT_IP =
"no-trusted-ip"- MEMORY_STORE_MAX_ENTRIES =
100_000- DATABASE_CONSUME_ATTEMPTS =
8- LEGACY_STORAGE_WARNING =
"Rate limiting is best-effort: the configured storage has no atomic `consume`, so concurrent requests may bypass the limit. Provide a storage that implements `consume` for strict enforcement."- MISSING_CLIENT_IP_WARNING =
"Rate limiting could not determine a trustworthy client IP address and is falling back to a " \ "single shared per-path bucket. Ensure your runtime forwards a trusted client IP header, then configure " \ "`advanced.ip_address.ip_address_headers` and, for proxy chains, `advanced.ip_address.trusted_proxies`."
Class Method Summary collapse
Instance Method Summary collapse
- #call(request, context, path) ⇒ Object
-
#initialize(clock: -> { Time.now.to_f }, memory_store: nil) ⇒ RateLimiter
constructor
A new instance of RateLimiter.
Constructor Details
#initialize(clock: -> { Time.now.to_f }, memory_store: nil) ⇒ RateLimiter
Returns a new instance of RateLimiter.
98 99 100 101 102 103 104 |
# File 'lib/better_auth/rate_limiter.rb', line 98 def initialize(clock: -> { Time.now.to_f }, memory_store: nil) @clock = clock @memory_store = memory_store || MemoryStore.new(clock: clock) @warned_missing_client_ip = false @warned_legacy_storage = false @warning_mutex = Mutex.new end |
Class Method Details
.decide_consume(data, window:, max:, now:) ⇒ Object
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/better_auth/rate_limiter.rb', line 76 def self.decide_consume(data, window:, max:, now:) return {next: {key: "", count: 1, last_request: now}, allowed: true, retry_after: nil} unless data last_request = data.fetch(:last_request).to_f if now - last_request > window.to_f return {next: data.merge(count: 1, last_request: now), allowed: true, retry_after: nil} end if data.fetch(:count).to_i >= max.to_i return { next: data, allowed: false, retry_after: [(last_request + window.to_f - now).ceil, 0].max } end { next: data.merge(count: data.fetch(:count).to_i + 1, last_request: now), allowed: true, retry_after: nil } end |
Instance Method Details
#call(request, context, path) ⇒ Object
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/better_auth/rate_limiter.rb', line 106 def call(request, context, path) config = context.rate_limit_config || {} return unless config[:enabled] return if context..advanced.dig(:ip_address, :disable_ip_tracking) ip = client_ip(request, context.) unless ip warn_missing_client_ip(context) ip = MISSING_CLIENT_IP end rule = rate_limit_rule(request, context, config, path) return if rule == false window = (rule[:window] || 10).to_f max = (rule[:max] || 100).to_i key = rate_limit_key(ip, path) decision = consume(storage_for(context, config), key, window: window, max: max, context: context) return if decision[:allowed] rate_limit_response(decision[:retry_after] || window) end |