Skip to content

feat(web): audit OAuth account-link conflicts - #1223

Closed
msukkari wants to merge 2 commits into
mainfrom
michael/audit-oauth-not-linked-SOU-1184
Closed

feat(web): audit OAuth account-link conflicts#1223
msukkari wants to merge 2 commits into
mainfrom
michael/audit-oauth-not-linked-SOU-1184

Conversation

@msukkari

@msukkari msukkari commented May 22, 2026

Copy link
Copy Markdown
Contributor

Fixes SOU-1184

Summary

  • Adds a NextAuth callbacks.signIn hook that detects the OAuth account-link conflict case (a currently signed-in user tries to authenticate with an OAuth identity whose (provider, providerAccountId) is already linked to a different Sourcebot user) and emits an audit row with action account.link_failed_already_linked. The callback returns true unconditionally so @auth/core still raises OAuthAccountNotLinked downstream and the login-page error UX is unchanged.
  • The audit row captures actor = attemptingUser, target = { id: alreadyLinkedToUserId, type: "user" }, and metadata = { provider, providerAccountId }. Two new optional fields (provider, providerAccountId) were added to auditMetadataSchema to carry the upstream identity.
  • Decision logic is extracted into a pure computeOAuthLinkConflictAudit helper in authUtils.ts so it can be unit-tested without mocking NextAuth's request scope. The I/O (Prisma lookup, session resolution, audit emission) lives in the signIn callback and is wrapped in try/catch so any failure stays best-effort and never blocks auth.

Before this change, the rejection was only visible as [auth][error] in the web container logs — a forensics gap for identity-hijack attempts.

Test plan

  • yarn workspace @sourcebot/web test — 383/383 pass, including 7 new tests in authUtils.test.ts covering: conflict detected for oauth and oidc accounts, no audit when the same user re-authenticates, no audit when no session exists, no audit when the upstream identity is not yet linked, no audit for non-OAuth providers (credentials, email, webauthn), and no audit when account fields are missing.
  • yarn workspace @sourcebot/web lint — no new errors.
  • Manual verification on a dev stack: sign in as user A via credentials, attempt to sign in via an OAuth provider whose providerAccountId already belongs to user B, confirm a new account.link_failed_already_linked row appears in the Audit table with the expected actor/target/metadata.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Enhanced OAuth and OIDC account-linking with conflict detection. The system now detects and audits when account-link attempts fail because the upstream identity is already linked to a different user account.
  • Tests

    • Added comprehensive test coverage for OAuth account-link conflict detection scenarios, including edge cases.

Review Change Stack

When NextAuth would throw OAuthAccountNotLinked because a signed-in user
attempts to add an OAuth identity that is already linked to a different
Sourcebot user, emit an audit row with action
`account.link_failed_already_linked` so the rejection is recoverable
from the audit log instead of only the `[auth][error]` container logs.

Fixes SOU-1184

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

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: 6cc9b7af-b122-4995-ba57-2a40c8553aca

📥 Commits

Reviewing files that changed from the base of the PR and between 8292882 and b44830e.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • packages/web/src/auth.ts
  • packages/web/src/ee/features/audit/types.ts
  • packages/web/src/lib/authUtils.test.ts
  • packages/web/src/lib/authUtils.ts

Walkthrough

This PR adds OAuth/OIDC account-link conflict detection to the authentication flow. It extends the audit metadata schema with provider context fields, implements a pure conflict-detection utility with comprehensive tests, and integrates conflict auditing into the NextAuth signIn callback to detect when an upstream OAuth identity is already linked to a different user.

Changes

OAuth Link Conflict Detection and Auditing

Layer / File(s) Summary
Audit metadata schema extension
packages/web/src/ee/features/audit/types.ts
auditMetadataSchema gains optional provider and providerAccountId fields to store OAuth provider context in audit events.
OAuth link conflict detection utility and tests
packages/web/src/lib/authUtils.ts, packages/web/src/lib/authUtils.test.ts
New exported computeOAuthLinkConflictAudit helper evaluates OAuth/OIDC account-link attempts and returns a conflict audit event (with action account.link_failed_already_linked) when the upstream identity is linked to a different user, or null otherwise. Tests cover all branches: conflict detection, same-user re-authentication, unlinked identities, non-OAuth providers (credentials, email, webauthn), and missing/invalid account fields.
NextAuth signIn callback integration
packages/web/src/auth.ts
Adds module-level logger and a signIn callback that, for OAuth/OIDC attempts, queries the existing linked user and fetches the current session user in parallel, computes a conflict audit event, persists it when present, logs errors, and always returns true to preserve downstream Auth.js error handling.
Changelog entry
CHANGELOG.md
Documents the new account.link_failed_already_linked audit action emitted during rejected OAuth account-link attempts.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

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 'feat(web): audit OAuth account-link conflicts' clearly and concisely summarizes the main change: adding audit logging for OAuth account-link conflicts.
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 michael/audit-oauth-not-linked-SOU-1184

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.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@msukkari msukkari closed this May 22, 2026
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