mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-04-08 22:17:49 +09:00
Address race condition during "first run" account creation
This commit is contained in:
@@ -11,6 +11,8 @@ class FirstRunsController < ApplicationController
|
||||
user = FirstRun.create!(user_params)
|
||||
start_new_session_for user
|
||||
|
||||
redirect_to root_url
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
redirect_to root_url
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
class AddSingletonConstraintToAccounts < ActiveRecord::Migration[8.2]
|
||||
def change
|
||||
add_column :accounts, :singleton_guard, :integer, default: 0, null: false
|
||||
add_index :accounts, :singleton_guard, unique: true
|
||||
end
|
||||
end
|
||||
4
db/schema.rb
generated
4
db/schema.rb
generated
@@ -10,14 +10,16 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.2].define(version: 2025_11_26_130131) do
|
||||
ActiveRecord::Schema[8.2].define(version: 2025_12_12_154340) do
|
||||
create_table "accounts", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.text "custom_styles"
|
||||
t.string "join_code", null: false
|
||||
t.string "name", null: false
|
||||
t.json "settings"
|
||||
t.integer "singleton_guard", default: 0, null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["singleton_guard"], name: "index_accounts_on_singleton_guard", unique: true
|
||||
end
|
||||
|
||||
create_table "action_text_rich_texts", force: :cascade do |t|
|
||||
|
||||
@@ -30,4 +30,28 @@ class FirstRunsControllerTest < ActionDispatch::IntegrationTest
|
||||
|
||||
assert parsed_cookies.signed[:session_token]
|
||||
end
|
||||
|
||||
test "create is not vulnerable to race conditions" do
|
||||
num_attackers = 5
|
||||
url = first_run_url
|
||||
barrier = Concurrent::CyclicBarrier.new(num_attackers)
|
||||
|
||||
num_attackers.times.map do |i|
|
||||
Thread.new do
|
||||
session = ActionDispatch::Integration::Session.new(Rails.application)
|
||||
barrier.wait # All threads wait here, then fire simultaneously
|
||||
|
||||
session.post url, params: {
|
||||
user: {
|
||||
name: "Attacker#{i}",
|
||||
email_address: "attacker#{i}@example.com",
|
||||
password: "password123"
|
||||
}
|
||||
}
|
||||
end
|
||||
end.each(&:join)
|
||||
|
||||
assert_equal 1, Account.count, "Race condition allowed #{Account.count} accounts to be created!"
|
||||
assert_equal 1, User.where(role: :administrator).count, "Race condition allowed multiple admin users!"
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user