Skip to content

fix(auth): handle P2002 race condition in OAuth sign-in to prevent account lockout#28844

Open
pmartin-dev wants to merge 1 commit intocalcom:mainfrom
pmartin-dev:fix/oauth-p2002-race-condition
Open

fix(auth): handle P2002 race condition in OAuth sign-in to prevent account lockout#28844
pmartin-dev wants to merge 1 commit intocalcom:mainfrom
pmartin-dev:fix/oauth-p2002-race-condition

Conversation

@pmartin-dev
Copy link
Copy Markdown

@pmartin-dev pmartin-dev commented Apr 12, 2026

What does this PR do?

When multiple concurrent OAuth sign-in requests hit the system (e.g., after rapid API automation), Prisma throws
P2002 unique constraint errors on user.create() or account.create(). The current catch-all handler masks these as
a generic "Error creating a new user", leaving the account in a broken state where subsequent logins fail
indefinitely.

This PR adds targeted P2002 recovery at two levels:

  1. next-auth-custom-adapter.tslinkAccount(): On P2002, looks up the existing account instead of crashing.
    This prevents the adapter from propagating unhandled errors upstream.

  2. next-auth-options.tssignIn callback: On P2002 during user creation (email/username conflict), recovers
    by finding the existing user and linking the OAuth account normally, instead of returning a fatal error redirect.

Both fixes follow the same P2002 handling pattern already used in calcomSignupHandler.ts and
selfHostedHandler.ts.

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

  1. Run the unit tests: TZ=UTC yarn vitest run packages/features/auth/lib/next-auth-custom-adapter.test.ts

  2. All 9 tests should pass across 3 describe blocks:

    signIn callback – full flow (4 tests):

    • "BEFORE fix: user.create P2002 with generic catch returns error page" — proves old code never attempts recovery
    • "AFTER fix: user.create P2002 recovers by linking existing user" — proves the fix
    • "AFTER fix: non-email P2002 still returns error page" — no recovery on unknown fields
    • "AFTER fix: non-P2002 error still returns error page" — no recovery on non-P2002

    linkAccount before fix (1 test):

    • "unprotected linkAccount throws on P2002 (the bug)" — proves old adapter crashes

    linkAccount after fix (4 tests):

    • "creates account on first call" — normal path works
    • "recovers on P2002 by returning the existing account" — proves the fix
    • "throws P2002 if existing account not found after conflict" — edge case safety
    • "re-throws non-P2002 errors" — no silent error swallowing
  3. To reproduce the original bug: trigger ~40-50 rapid API calls, then attempt OAuth dashboard login. Without the
    fix, login fails with "Error creating a new user". With the fix, the race condition is recovered gracefully.

Checklist

  • My code follows the style guidelines of this project
  • I have commented my code, particularly in hard-to-understand areas
  • I have checked that my changes generate no new warnings

@pmartin-dev pmartin-dev requested a review from a team as a code owner April 12, 2026 08:27
@github-actions github-actions bot added the 🐛 bug Something isn't working label Apr 12, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 10e04e09-31a1-445c-91d3-c6ba1fe6c088

📥 Commits

Reviewing files that changed from the base of the PR and between 009906a and d67f5a3.

📒 Files selected for processing (3)
  • packages/features/auth/lib/next-auth-custom-adapter.test.ts
  • packages/features/auth/lib/next-auth-custom-adapter.ts
  • packages/features/auth/lib/next-auth-options.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/features/auth/lib/next-auth-custom-adapter.ts

📝 Walkthrough

Walkthrough

Adds tests exercising recovery from Prisma P2002 unique-constraint errors during concurrent sign-in/new-user creation. Updates the adapter’s linkAccount to catch P2002, look up an existing account by provider and providerAccountId, and return it when found. Updates the sign-in callback to catch P2002 on user creation, look up the existing user by case-insensitive email, link the OAuth account via the adapter when identity providers match, and continue the sign-in flow; otherwise it falls back to the existing error redirect. Imports Prisma for typed error checks.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: handling P2002 race condition errors in OAuth sign-in to prevent account lockout, which directly aligns with the core objective of the PR.
Description check ✅ Passed The description is well-written and directly related to the changeset, explaining the problem (P2002 race conditions), the solution (recovery handlers), and providing testing instructions.
Linked Issues check ✅ Passed The PR successfully addresses issue #28269 by implementing automatic P2002 recovery in both linkAccount() and the signIn callback, preventing account lockout caused by concurrent OAuth requests.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the P2002 race condition: adapter modifications for recovery logic, signIn callback handler updates, and comprehensive test coverage. No out-of-scope changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pmartin-dev pmartin-dev force-pushed the fix/oauth-p2002-race-condition branch from 915b1fc to 009906a Compare April 12, 2026 08:31
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/features/auth/lib/next-auth-custom-adapter.test.ts (1)

46-90: Exercise the real sign-in callback instead of a copy.

simulateSignInNewUserCreation() mirrors the production catch branch from next-auth-options.ts, so these tests can stay green even when the real callback drifts. For example, failures from the shipped recovery path after adapter.linkAccount() would not be caught here unless the duplicate helper is updated the same way. Prefer extracting the recovery logic into a shared helper or invoking getOptions(...).callbacks.signIn with mocks so this suite validates the code that actually ships.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/features/auth/lib/next-auth-custom-adapter.test.ts` around lines 46
- 90, The test currently implements a standalone simulateSignInNewUserCreation()
that duplicates the sign-in recovery logic; replace it by invoking the actual
sign-in callback from your production config (e.g., call
getOptions(...).callbacks.signIn or extract the shared recovery helper used by
next-auth-options.ts) with the same mocked prisma and adapter to exercise real
behavior. Specifically, remove simulateSignInNewUserCreation, import the
function that returns NextAuth options (or the shared recovery helper),
construct the same mocks for PrismaClient and CalComAdapter, call the signIn
callback with a mock account/profile/context, and assert that on Prisma P2002
the adapter.linkAccount and the returned value match production; update test
names and fixtures accordingly so tests validate the shipped logic rather than a
duplicated implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/features/auth/lib/next-auth-options.ts`:
- Around line 1455-1458: The recovery lookup uses prisma.user.findFirst to set
existingUser but currently matches email with exact equality; update the where
clause to use the equals predicate with mode: "insensitive" (i.e., where: {
email: { equals: user.email, mode: "insensitive" } }) so it matches the
preflight/existence check semantics used elsewhere; keep the same select fields
(id, email, twoFactorEnabled) and the same variable name existingUser.
- Around line 1452-1469: On Prisma P2002 for email/username, do not directly
construct AdapterAccountPresenter.fromCalAccount and call
calcomAdapter.linkAccount; instead, fetch the existing record with
prisma.user.findFirst and route that existingUser back into the same
email-conflict merge/decision helper used earlier in this file (the routine that
runs the pre-create checks/merge logic between lines ~1199-1355), so the same
unverified-CAL / provider/invite/conversion branches are applied; only after
that helper returns success should you call calcomAdapter.linkAccount (and keep
its errors propagated so they hit the outer catch and surface as
/auth/error?error=user-creation-error); preserve the two-factor fast-path by
still calling loginWithTotp(existingUser.email) if
existingUser.twoFactorEnabled.

---

Nitpick comments:
In `@packages/features/auth/lib/next-auth-custom-adapter.test.ts`:
- Around line 46-90: The test currently implements a standalone
simulateSignInNewUserCreation() that duplicates the sign-in recovery logic;
replace it by invoking the actual sign-in callback from your production config
(e.g., call getOptions(...).callbacks.signIn or extract the shared recovery
helper used by next-auth-options.ts) with the same mocked prisma and adapter to
exercise real behavior. Specifically, remove simulateSignInNewUserCreation,
import the function that returns NextAuth options (or the shared recovery
helper), construct the same mocks for PrismaClient and CalComAdapter, call the
signIn callback with a mock account/profile/context, and assert that on Prisma
P2002 the adapter.linkAccount and the returned value match production; update
test names and fixtures accordingly so tests validate the shipped logic rather
than a duplicated implementation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4d6448a3-de1f-44ef-be60-74001e7bb5d3

📥 Commits

Reviewing files that changed from the base of the PR and between d08f4a0 and 009906a.

📒 Files selected for processing (3)
  • packages/features/auth/lib/next-auth-custom-adapter.test.ts
  • packages/features/auth/lib/next-auth-custom-adapter.ts
  • packages/features/auth/lib/next-auth-options.ts

@pmartin-dev pmartin-dev force-pushed the fix/oauth-p2002-race-condition branch from 009906a to d67f5a3 Compare April 12, 2026 08:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐛 bug Something isn't working size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Title: Account locked out after API automation — "Error creating a new user" on login

1 participant