Skip to content
This repository was archived by the owner on Jul 27, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/controllers/api/v1/base_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def authenticate_api_key
@current_user = @api_key.user
@api_key.update_last_used!
@authentication_method = :api_key
@rate_limiter = ApiRateLimiter.new(@api_key)
@rate_limiter = ApiRateLimiter.limit(@api_key)
setup_current_context_for_api
true
end
Expand Down
12 changes: 11 additions & 1 deletion app/services/api_rate_limiter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,17 @@ def usage_info

# Class method to get usage for an API key without incrementing
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

private
Expand Down
39 changes: 39 additions & 0 deletions app/services/noop_api_rate_limiter.rb
Original file line number Diff line number Diff line change
@@ -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
7 changes: 5 additions & 2 deletions config/initializers/rack_attack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ class Rack::Attack
request.ip if request.path == "/oauth/token"
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", 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/")
# Extract access token from Authorization header
auth_header = request.get_header("HTTP_AUTHORIZATION")
Expand All @@ -25,7 +28,7 @@ class Rack::Attack
end

# 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/")
end

Expand Down
58 changes: 58 additions & 0 deletions test/services/noop_api_rate_limiter_test.rb
Original file line number Diff line number Diff line change
@@ -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