Skip to content

fix(auth): handle concurrent OAuth callback creating the same auth service - #2780

Open
mroderick wants to merge 1 commit into
masterfrom
fix/auth-callback-race
Open

fix(auth): handle concurrent OAuth callback creating the same auth service#2780
mroderick wants to merge 1 commit into
masterfrom
fix/auth-callback-race

Conversation

@mroderick

Copy link
Copy Markdown
Collaborator

Problem

A double-fired OAuth callback can pass the initial AuthService.find_by(provider:, uid:) lookup concurrently (both see nothing), then both enter the create branch and collide while member.save! saves the nested auth service. The unique (uid, provider) index (already live in prod) plus the auth_service uniqueness validation make one request error — RecordNotUnique on a true TOCTOU race, or RecordInvalid via the app validation when the duplicate is already visible. Result: an errored login request.

What this does

In auth_services_controller#create, wrap member.save! in a rescue of both RecordInvalid and RecordNotUnique, and on collision reuse the already-created auth service:

  • AuthService.find_by!(provider:, uid:) acts as the guard — if no winner exists this isn't a race, so it raises the real error instead of swallowing it.
  • member.toggle!(:can_log_in) only runs on actual creation (created), so the losing callback doesn't flip the winner's flag back.

Verification

  • New request spec (spec/requests/auth_services_callback_spec.rb) deterministically exercises the rescue and asserts the loser reuses the winner's member/service and does not toggle can_log_in.
  • Rubocop clean; existing auth controller specs pass.

Why a separate PR from #2779

The auth_services (uid, provider) index already shipped to prod in the first partial run of the unique-index migration, so this fix is independently urgent and not gated on #2779 — it gets focused auth review on its own clock.

…rvice

A double-fired OAuth callback can pass the initial service lookup concurrently,
then collide on the unique (uid, provider) index (or the auth_service uniqueness
validation) during member.save!, erroring the request. Rescue RecordInvalid and
RecordNotUnique and reuse the already-created auth_service, guarded by find_by!
so a non-race failure still surfaces. Toggle can_log_in only on actual creation
so the losing callback does not flip the winner flag back.
@mroderick
mroderick marked this pull request as ready for review August 4, 2026 11:51
@mroderick
mroderick requested a review from olleolleolle August 4, 2026 12:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant