Address race condition during "first run" account creation

This commit is contained in:
Mike Dalessio
2025-12-12 09:29:17 -05:00
parent 49c0ce496c
commit 1feb2d94b9
4 changed files with 35 additions and 1 deletions

View File

@@ -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

View File

@@ -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
View File

@@ -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|

View File

@@ -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