Skip to content

fix(auth): reconcile shadow user by email so api_keys FK holds#11

Merged
hallelx2 merged 1 commit into
mainfrom
halleluyaholudele/fix-apikey-user-fk
Jul 3, 2026
Merged

fix(auth): reconcile shadow user by email so api_keys FK holds#11
hallelx2 merged 1 commit into
mainfrom
halleluyaholudele/fix-apikey-user-fk

Conversation

@hallelx2

@hallelx2 hallelx2 commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Bug: creating an API key failed with insert or update on table "api_keys" violates foreign key constraint "api_keys_user_id_user_id_fk".

Cause: the CP re-issued the user's UUID (after the DB migration). The local user table still held the old UUID under the same email. The shadow upsert did onConflictDoNothing(target: id), so inserting the new UUID hit the email-unique constraint, threw, was swallowed — and no row for the current id existed, breaking the FK.

Fix: reconcile in a transaction — free the email from any other shadow id (rename it uniquely so old dependent rows stay valid), then upsert the current id with the real email. Self-heals on the next authenticated request.

Summary by CodeRabbit

  • Bug Fixes
    • Improved account/session syncing so sign-ins more reliably match the correct local user record.
    • Reduced cases where duplicate or stale shadow user entries could interfere with authentication.
    • Continued to allow login to succeed even if the local sync step encounters an error.

The CP can re-issue a user's UUID (e.g. after the DB migration), leaving
a stale local user row with the same email under the old id. Because
email is unique, the shadow upsert's plain insert of the new id threw on
the email constraint (swallowed), so no row for the current id existed —
and creating an API key failed with api_keys_user_id_user_id_fk.

Reconcile in a transaction: free the email from any other shadow id
(rename it uniquely, keeping old dependents valid), then upsert the
current id with the real email. Self-heals on the next request.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @hallelx2, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@vercel

vercel Bot commented Jul 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
vectorless-web Ready Ready Preview, Comment Jul 3, 2026 5:56pm

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The shadow-user upsert logic in getServerSession was changed from a simple best-effort insert with onConflictDoNothing to a database transaction that first stales any other shadow row sharing the same email, then inserts/upserts the current user by id with onConflictDoUpdate.

Changes

Shadow User Reconciliation

Layer / File(s) Summary
Transactional shadow-user upsert
apps/web/lib/server-auth.ts
Added drizzle-orm helpers (and, eq, ne, sql) and replaced the prior insert-with-onConflictDoNothing logic with a transaction that marks conflicting email rows as stale, then inserts/upserts the current user by id via onConflictDoUpdate, refreshing email/name/verification/image and updatedAt; auth remains best-effort on failure.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant getServerSession
  participant Database

  Client->>getServerSession: request session
  getServerSession->>Database: begin transaction
  getServerSession->>Database: update stale email (same email, different id)
  getServerSession->>Database: insert user, onConflictDoUpdate on id
  Database-->>getServerSession: transaction result
  getServerSession-->>Client: return { user }
Loading

Related issues: None specified.

Related PRs: None specified.

Suggested labels: backend, database, auth

Suggested reviewers: hallelx2

🐰 A shadow user once caused a fright,
Two emails clashing in the night,
Now a transaction sets it right,
Stale marked old, fresh takes flight,
Upserted safe till morning light.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main fix: reconciling shadow users by email to prevent api_keys foreign key failures.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch halleluyaholudele/fix-apikey-user-fk

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

apps/web/lib/server-auth.ts

Oops! Something went wrong! :(

ESLint: 9.39.1

TypeError: Converting circular structure to JSON
--> starting at object with constructor 'Object'
| property 'configs' -> object with constructor 'Object'
| property 'flat' -> object with constructor 'Object'
| ...
| property 'plugins' -> object with constructor 'Object'
--- property 'react' closes the circle
Referenced from: /apps/web/.eslintrc.json
at JSON.stringify ()
at /node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2255:45
at Array.map ()
at ConfigValidator.formatErrors (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2246:23)
at ConfigValidator.validateConfigSchema (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2277:84)
at ConfigArrayFactory._normalizeConfigData (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules

... [truncated 455 characters] ...

c/dist/eslintrc.cjs:3261:25)
at ConfigArrayFactory._normalizeObjectConfigDataBody (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3199:25)
(node:2) ESLintRCWarning: You are using an eslintrc configuration file, which is deprecated and support will be removed in v10.0.0. Please migrate to an eslint.config.js file. See https://eslint.org/docs/latest/use/configure/migration-guide for details. An eslintrc configuration file is used because you have the ESLINT_USE_FLAT_CONFIG environment variable set to false. If you want to use an eslint.config.js file, remove the environment variable. If you want to find the location of the eslintrc configuration file, use the --debug flag.
(Use node --trace-warnings ... to show where the warning was created)


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/lib/server-auth.ts`:
- Around line 101-105: The session reconciliation in server-auth.ts is clearing
optional CP fields by defaulting missing email_verified and avatar_url values to
false/null, which can overwrite existing local data. Update the set block in the
auth refresh flow so the logic around user.email_verified and user.avatar_url
only assigns those fields when the CP response actually includes them, and
otherwise leaves the stored values unchanged. Use the existing mapping in the
user update path as the place to apply this conditional behavior.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 778e0e3b-2180-4417-9239-f33b55e8e5cd

📥 Commits

Reviewing files that changed from the base of the PR and between ad3c339 and a2e9fee.

📒 Files selected for processing (1)
  • apps/web/lib/server-auth.ts

Comment on lines +101 to +105
set: {
email: user.email,
name: user.name || user.email,
emailVerified: !!user.email_verified,
image: user.avatar_url || null,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Avoid clearing optional CP fields during refresh.

Line 104 and Line 105 turn omitted email_verified / avatar_url values into false / null, so an existing local row can lose stored verification or avatar data on any session reconciliation. Only update those fields when the CP response includes them.

Proposed fix
           set: {
             email: user.email,
             name: user.name || user.email,
-            emailVerified: !!user.email_verified,
-            image: user.avatar_url || null,
+            ...(typeof user.email_verified === "boolean"
+              ? { emailVerified: user.email_verified }
+              : {}),
+            ...(user.avatar_url !== undefined
+              ? { image: user.avatar_url || null }
+              : {}),
             updatedAt: new Date(),
           },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
set: {
email: user.email,
name: user.name || user.email,
emailVerified: !!user.email_verified,
image: user.avatar_url || null,
set: {
email: user.email,
name: user.name || user.email,
...(typeof user.email_verified === "boolean"
? { emailVerified: user.email_verified }
: {}),
...(user.avatar_url !== undefined
? { image: user.avatar_url || null }
: {}),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/lib/server-auth.ts` around lines 101 - 105, The session
reconciliation in server-auth.ts is clearing optional CP fields by defaulting
missing email_verified and avatar_url values to false/null, which can overwrite
existing local data. Update the set block in the auth refresh flow so the logic
around user.email_verified and user.avatar_url only assigns those fields when
the CP response actually includes them, and otherwise leaves the stored values
unchanged. Use the existing mapping in the user update path as the place to
apply this conditional behavior.

@hallelx2 hallelx2 merged commit 92f7c13 into main Jul 3, 2026
8 checks passed
@hallelx2 hallelx2 deleted the halleluyaholudele/fix-apikey-user-fk branch July 3, 2026 17:59
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