Skip to content

fix(web): reject OAuth account-linking without a signed-in session#1221

Open
msukkari wants to merge 5 commits into
mainfrom
msukkari/fix-oauth-linking-orphan
Open

fix(web): reject OAuth account-linking without a signed-in session#1221
msukkari wants to merge 5 commits into
mainfrom
msukkari/fix-oauth-linking-orphan

Conversation

@msukkari
Copy link
Copy Markdown
Contributor

@msukkari msukkari commented May 22, 2026

Summary

OAuth providers configured with `purpose: "account_linking"` should only ever attach an identity to an existing signed-in user, never mint a new Sourcebot user. `@auth/core` does not know about this distinction and falls back to `createUser` when its session lookup returns null, which can happen when the user's session cookie expires while they are on the upstream consent screen. The result is a silent orphan `User` row and a confused user.

Add a `signIn` callback that refuses the request in that case.

Test plan

  • TypeScript build clean (`yarn workspace @sourcebot/web build`).
  • Docker image builds and boots cleanly.
  • Live negative path: sign in as Bob, click Disconnect, click Connect, wait past `AUTH_SESSION_MAX_AGE_SECONDS` on the BB consent screen, approve. Expect Sourcebot to land on the error page with no orphan User or Account row created.
  • Live positive path: sign in as Bob, click Connect, approve on BB within the session lifetime. Expect Pan Owner to link cleanly to Bob.
  • Live regression check: credentials login still works (no OAuth account on the request, signIn returns true early).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • OAuth account-linking now rejects sign-in attempts when no active authenticated session exists, preventing orphaned user accounts and accidental account creation.
  • Documentation

    • Changelog updated under Unreleased → Fixed to record this behavior change.

Review Change Stack

If a user clicks "Connect Bitbucket" and their session-token cookie is
missing or expired by the time the BB redirect arrives at our callback,
@auth/core silently falls through to createUser and mints a new orphan
User row from the OAuth profile. The orphan has no email, no UserToOrg,
and the user's session cookie gets rebound to it, leaving them on a
"request access" page.

Add a signIn callback that calls auth() and refuses the request when
the provider's purpose is account_linking and no session is present.
SSO providers and credentials login are unaffected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 22, 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: a395be73-d232-4fca-9ab2-b3e5c9c52d78

📥 Commits

Reviewing files that changed from the base of the PR and between 37833e3 and 18c3dab.

📒 Files selected for processing (1)
  • packages/web/src/auth.ts

Walkthrough

The PR adds session validation to the NextAuth configuration to block OAuth/OIDC account-linking attempts when no authenticated user session exists. A callbacks.signIn handler determines the OAuth provider's configured purpose and rejects sign-ins marked for account-linking without an active session. The changelog documents this security fix.

Changes

OAuth Account-Linking Session Validation

Layer / File(s) Summary
OAuth account-linking session validation
packages/web/src/auth.ts, CHANGELOG.md
A callbacks.signIn handler checks for an existing authenticated session before allowing OAuth/OIDC sign-in requests configured with purpose: "account_linking". The handler resolves the provider via getProviders(), calls auth() to obtain the current session, and denies sign-in when auth() returns null. The changelog documents the fix that prevents orphan User row creation.

Sequence Diagram

sequenceDiagram
  participant Client
  participant NextAuth
  participant ProviderRegistry
  participant AuthResolver
  Client->>NextAuth: OAuth/OIDC provider callback (account)
  NextAuth->>ProviderRegistry: getProviders() -> find provider for account.provider
  NextAuth->>AuthResolver: auth() to resolve current session (if provider.purpose == "account_linking")
  AuthResolver-->>NextAuth: session or null
  NextAuth-->>Client: allow or deny sign-in (deny if account_linking && session == null)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#1219: Changes session validity handling (callbacks.jwt) that affects whether auth() returns null, which interacts with this PR's callbacks.signIn account-linking guard.

Suggested reviewers

  • brendan-kellam
🚥 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 and concisely summarizes the main change: adding OAuth account-linking rejection when no signed-in session exists.
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.

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

✨ 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 msukkari/fix-oauth-linking-orphan

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.

msukkari and others added 4 commits May 21, 2026 20:59
The fix is gated behind the sso entitlement (no OAuth identity
providers are loaded in OSS deployments), so the [EE] prefix is
appropriate per CLAUDE.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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