1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-01 03:25:20 +02:00

Relax API rate limits for self-hosted deployments (#2465)
Some checks are pending
Publish Docker image / ci (push) Waiting to run
Publish Docker image / Build docker image (push) Blocked by required conditions

- Introduced NoopApiRateLimiter to effectively disable API rate limiting for self-hosted mode.
- Updated ApiRateLimiter to delegate to NoopApiRateLimiter when running self-hosted.
- Increased Rack::Attack throttle limits significantly for self-hosted deployments.
- Added tests for NoopApiRateLimiter to ensure correct behavior.
- This allows self-hosted users to make more API requests without restriction, while keeping stricter limits for SaaS deployments.
This commit is contained in:
Juliano Julio Costa 2025-07-23 10:10:11 -04:00 committed by GitHub
parent da2045dbd8
commit 3f92fe0f6f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 114 additions and 4 deletions

View file

@ -98,7 +98,7 @@ class Api::V1::BaseController < ApplicationController
@current_user = @api_key.user @current_user = @api_key.user
@api_key.update_last_used! @api_key.update_last_used!
@authentication_method = :api_key @authentication_method = :api_key
@rate_limiter = ApiRateLimiter.new(@api_key) @rate_limiter = ApiRateLimiter.limit(@api_key)
setup_current_context_for_api setup_current_context_for_api
true true
end end

View file

@ -67,7 +67,17 @@ class ApiRateLimiter
# Class method to get usage for an API key without incrementing # Class method to get usage for an API key without incrementing
def self.usage_for(api_key) def self.usage_for(api_key)
new(api_key).usage_info limit(api_key).usage_info
end
def self.limit(api_key)
if Rails.application.config.app_mode.self_hosted?
# Use NoopApiRateLimiter for self-hosted mode
# This means no rate limiting is applied
NoopApiRateLimiter.new(api_key)
else
new(api_key)
end
end end
private private

View file

@ -0,0 +1,39 @@
class NoopApiRateLimiter
def initialize(api_key)
@api_key = api_key
end
def rate_limit_exceeded?
false
end
def increment_request_count!
# No operation
end
def current_count
0
end
def rate_limit
Float::INFINITY
end
def reset_time
0
end
def usage_info
{
current_count: 0,
rate_limit: Float::INFINITY,
remaining: Float::INFINITY,
reset_time: 0,
tier: :noop
}
end
def self.usage_for(api_key)
new(api_key).usage_info
end
end

View file

@ -9,8 +9,11 @@ class Rack::Attack
request.ip if request.path == "/oauth/token" request.ip if request.path == "/oauth/token"
end end
# Determine limits based on self-hosted mode
self_hosted = Rails.application.config.app_mode.self_hosted?
# Throttle API requests per access token # Throttle API requests per access token
throttle("api/requests", limit: 100, period: 1.hour) do |request| throttle("api/requests", limit: self_hosted ? 10_000 : 100, period: 1.hour) do |request|
if request.path.start_with?("/api/") if request.path.start_with?("/api/")
# Extract access token from Authorization header # Extract access token from Authorization header
auth_header = request.get_header("HTTP_AUTHORIZATION") auth_header = request.get_header("HTTP_AUTHORIZATION")
@ -25,7 +28,7 @@ class Rack::Attack
end end
# More permissive throttling for API requests by IP (for development/testing) # More permissive throttling for API requests by IP (for development/testing)
throttle("api/ip", limit: 200, period: 1.hour) do |request| throttle("api/ip", limit: self_hosted ? 20_000 : 200, period: 1.hour) do |request|
request.ip if request.path.start_with?("/api/") request.ip if request.path.start_with?("/api/")
end end

View file

@ -0,0 +1,58 @@
require "test_helper"
class NoopApiRateLimiterTest < ActiveSupport::TestCase
setup do
@user = users(:family_admin)
# Clean up any existing API keys for this user to ensure tests start fresh
@user.api_keys.destroy_all
@api_key = ApiKey.create!(
user: @user,
name: "Noop Rate Limiter Test Key",
scopes: [ "read" ],
display_key: "noop_rate_limiter_test_#{SecureRandom.hex(8)}"
)
@rate_limiter = NoopApiRateLimiter.new(@api_key)
end
test "should never be rate limited" do
assert_not @rate_limiter.rate_limit_exceeded?
end
test "should not increment request count" do
@rate_limiter.increment_request_count!
assert_equal 0, @rate_limiter.current_count
end
test "should always have zero request count" do
assert_equal 0, @rate_limiter.current_count
end
test "should have infinite rate limit" do
assert_equal Float::INFINITY, @rate_limiter.rate_limit
end
test "should have zero reset time" do
assert_equal 0, @rate_limiter.reset_time
end
test "should provide correct usage info" do
usage_info = @rate_limiter.usage_info
assert_equal 0, usage_info[:current_count]
assert_equal Float::INFINITY, usage_info[:rate_limit]
assert_equal Float::INFINITY, usage_info[:remaining]
assert_equal 0, usage_info[:reset_time]
assert_equal :noop, usage_info[:tier]
end
test "class method usage_for should work" do
usage_info = NoopApiRateLimiter.usage_for(@api_key)
assert_equal 0, usage_info[:current_count]
assert_equal Float::INFINITY, usage_info[:rate_limit]
assert_equal Float::INFINITY, usage_info[:remaining]
assert_equal 0, usage_info[:reset_time]
assert_equal :noop, usage_info[:tier]
end
end