Skip to content

Commit e5e8d33

Browse files
dadachiclaude
andauthored
Rate-limit shopkeeper sign-up to 10/IP/3min (#50)
* Rate-limit shopkeeper sign-up to 5/IP/hour Mass account creation is currently only constrained by the broad 300/IP/5min rack-attack req/ip cap. Add an endpoint-specific limit on POST /shopkeeper_auth using Rails 8's built-in ActionController::RateLimiting: 5 requests per IP per hour. When exceeded, render 429 with a localized JSON error. Switch the test cache from :null_store to :memory_store so the limiter's counters can persist within a request sequence (:null_store no-ops increments). Clear Rails.cache in the standard test setup so throttle state doesn't leak between tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Tune sign-up rate limit to 10/3min (match jumpstart-pro reference) Aligns with the reference codebase (jumpstart-pro-rails uses `to: 10, within: 3.minutes` on user sign-up). The earlier 5/1.hour was overly strict for legit users — a confused user with bad password rules or autofill misfires could exhaust the quota and get locked out for an hour. 10/3min absorbs realistic retry flows while still constraining bots; per-IP limits are not the primary defense against rotating-IP attackers anyway. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 08c840f commit e5e8d33

5 files changed

Lines changed: 40 additions & 1 deletion

File tree

app/controllers/shopkeeper_auth/registrations_controller.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
class ShopkeeperAuth::RegistrationsController < DeviseTokenAuth::RegistrationsController
2+
rate_limit to: 10, within: 3.minutes, only: :create,
3+
with: -> {
4+
render json: {code: 429, error_message: I18n.t("errors.messages.too_many_signups")},
5+
status: :too_many_requests
6+
}
7+
28
before_action :set_confirm_success_url, only: %i[create]
39
before_action :configure_permitted_parameters
410

config/environments/test.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222

2323
# Show full error reports and disable caching.
2424
config.consider_all_requests_local = true
25-
config.cache_store = :null_store
25+
# Use memory store so ActionController::RateLimiting can persist counters
26+
# between requests within a test; null_store would no-op the increments.
27+
config.cache_store = :memory_store
2628

2729
# Render exception templates for rescuable exceptions and raise for other exceptions.
2830
config.action_dispatch.show_exceptions = :rescuable

config/locales/en.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,4 @@ en:
142142
limit_count_shop: "You can create up to %{limit_count} shops across all organizations."
143143
limit_count_accounts_shopkeeper: "Organization members can be created up to %{limit_count}. Please contact the organization admin user or owner."
144144
limit_count_item_tag: "You can create up to %{limit_count} item tags."
145+
too_many_signups: "Too many sign-up attempts. Please try again later."
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
require "test_helper"
2+
3+
class SignUpThrottleTest < ActionDispatch::IntegrationTest
4+
def post_sign_up(email)
5+
post shopkeeper_registration_url,
6+
params: {
7+
name: "Throttle Test",
8+
email: email,
9+
password: "password",
10+
password_confirmation: "password",
11+
time_zone: "Tokyo",
12+
current_platform: "ios"
13+
},
14+
as: :json
15+
end
16+
17+
test "the eleventh sign-up from the same IP within the window is rate-limited" do
18+
10.times do |i|
19+
post_sign_up("throttle#{i}@example.com")
20+
assert_not_equal 429, response.status, "request #{i + 1} should not be throttled"
21+
end
22+
23+
post_sign_up("throttle10@example.com")
24+
25+
assert_response :too_many_requests
26+
assert_equal 429, response.parsed_body["code"]
27+
assert_equal I18n.t("errors.messages.too_many_signups"), response.parsed_body["error_message"]
28+
end
29+
end

test/test_helper.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ module ActiveSupport
1414
class TestCase
1515
setup do
1616
Dir[Rails.root.join("db", "fixtures", "test", "*.rb")].sort.each { |s| load s }
17+
Rails.cache.clear
1718
end
1819

1920
# Run tests in parallel with specified workers

0 commit comments

Comments
 (0)