diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index 08cfc423ef7..843eed21213 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -278,7 +278,10 @@ class InvitesController < ApplicationController end user = invite.redeem(**attrs) - rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved, Invite::UserExists => e + rescue ActiveRecord::RecordInvalid, + ActiveRecord::RecordNotSaved, + ActiveRecord::LockWaitTimeout, + Invite::UserExists => e return render json: failed_json.merge(message: e.message), status: 412 end diff --git a/app/models/invite_redeemer.rb b/app/models/invite_redeemer.rb index 3fa9ac7313f..9c760415627 100644 --- a/app/models/invite_redeemer.rb +++ b/app/models/invite_redeemer.rb @@ -243,7 +243,10 @@ class InviteRedeemer @invited_user_record = InvitedUser.create!(invite_id: invite.id, redeemed_at: Time.zone.now) if @invited_user_record.present? - Invite.increment_counter(:redemption_count, invite.id) + invite.with_lock("FOR UPDATE NOWAIT") do + Invite.increment_counter(:redemption_count, invite.id) + invite.save! + end delete_duplicate_invites end diff --git a/spec/requests/invites_controller_spec.rb b/spec/requests/invites_controller_spec.rb index 89c0f8bdcae..fabf6c1d304 100644 --- a/spec/requests/invites_controller_spec.rb +++ b/spec/requests/invites_controller_spec.rb @@ -984,6 +984,27 @@ RSpec.describe InvitesController do Fabricate(:invite, email: nil, emailed_status: Invite.emailed_status_types[:not_required]) end + it "does not create multiple users for a single use invite" do + user_count = User.count + + 2 + .times + .map do + Thread.new do + put "/invites/show/#{invite.invite_key}.json", + params: { + email: "test@example.com", + password: "verystrongpassword", + } + end + end + .each(&:join) + + expect(invite.reload.max_redemptions_allowed).to eq(1) + expect(invite.reload.redemption_count).to eq(1) + expect(User.count).to eq(user_count + 1) + end + it "sends an activation email and does not activate the user" do expect { put "/invites/show/#{invite.invite_key}.json",