Skip to content

[codex] Fix IdP-initiated SSO org provisioning#1913

Merged
riderx merged 3 commits intomainfrom
codex/fix-idp-sso-org-provisioning
Apr 15, 2026
Merged

[codex] Fix IdP-initiated SSO org provisioning#1913
riderx merged 3 commits intomainfrom
codex/fix-idp-sso-org-provisioning

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented Apr 15, 2026

Summary (AI generated)

  • provision SSO org membership from a shared frontend helper used by both /sso-callback and the auth guard
  • provision IdP-initiated SSO sessions before org onboarding redirects, and preserve the existing merged-account sign-out flow
  • harden /private/sso/provision-user so invite-only memberships are promoted to real memberships and merge flows fail when the org link cannot be guaranteed
  • add frontend and backend regression coverage for IdP-initiated SSO, invite promotion, merge membership linking, and non-SSO onboarding fallback

Motivation (AI generated)

Some customers entering through IdP-initiated SSO were bypassing the /sso-callback page, so the org-linking call never ran. The auth guard then saw no selectable org and redirected the user to create an organization even though their managed SSO org already existed. The backend also treated invite_* org memberships as completed membership and could silently continue a merge flow without proving the canonical account was linked to the managed org.

Business Impact (AI generated)

This removes a broken first-login path for SSO customers, which reduces onboarding friction for enterprise users and lowers support load on managed SSO setups. It also makes SSO account linking safer by preventing silent partial merges that would strand users without access to their organization.

Test Plan (AI generated)

  • bunx eslint src/services/ssoProvisioning.ts src/composables/useSSOProvisioning.ts src/modules/auth.ts tests/auth-sso-provisioning.unit.test.ts tests/sso.test.ts supabase/functions/_backend/private/sso/provision-user.ts
  • bunx vitest run tests/auth-sso-provisioning.unit.test.ts
  • bun run typecheck
  • bun run supabase:with-env -- bunx vitest run tests/sso.test.ts

Generated with AI

Summary by CodeRabbit

  • New Features

    • Centralized SSO provisioning service and automatic SSO account linking during sign-in, including promotion of invite-only memberships to active roles.
  • Bug Fixes

    • Improved provisioning error handling to ensure correct navigation outcomes (continue, redirect to login when accounts are merged, or abort) and more reliable membership updates.
  • Tests

    • Added unit and integration tests validating SSO provisioning flows, membership promotion, and navigation behavior.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 15, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 140fa771-3fe5-45f3-8163-09e6726fff43

📥 Commits

Reviewing files that changed from the base of the PR and between 1a3b435 and 4a318b1.

📒 Files selected for processing (2)
  • src/modules/auth.ts
  • tests/auth-sso-provisioning.unit.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/auth-sso-provisioning.unit.test.ts

📝 Walkthrough

Walkthrough

Centralized SSO provisioning: added a frontend provisionSsoUser service, integrated SSO provisioning into auth guard flows, refactored backend provisioning to ensure/promote org membership, and added tests covering guard behavior and invite-role promotion.

Changes

Cohort / File(s) Summary
SSO Provisioning Service
src/services/ssoProvisioning.ts
New module: SsoProvisioningResult, isSsoUser, and provisionSsoUser(session) performing authenticated POST to /private/sso/provision-user with structured success/error parsing.
Frontend Composable & Auth Module
src/composables/useSSOProvisioning.ts, src/modules/auth.ts
Composable now delegates to provisionSsoUser(session). src/modules/auth.ts adds maybeProvisionSsoMembership and invokes provisioning in fresh-login and post-auth flows, handling merged, alreadyMember, sign-out+redirect, abort, or continue outcomes.
Backend SSO Provisioning Endpoint
supabase/functions/_backend/private/sso/provision-user.ts
Added OrgMembershipRight types and ensureOrgMembership (promotes invite_* roles, inserts with retry); changed provider lookups to .maybeSingle() with explicit lookup error handling; replaced inline org_users logic with ensureOrgMembership in merge and non-merge flows; broadened error handling.
Tests
tests/auth-sso-provisioning.unit.test.ts, tests/sso.test.ts
New unit tests for auth-guard SSO provisioning behavior (mocked fetch/session/stores) and backend tests asserting invite-role promotion and response shapes.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client/Browser
    participant Guard as Auth Guard
    participant Service as provisionSsoUser (frontend)
    participant API as Backend /private/sso/provision-user
    participant DB as Database

    Client->>Guard: Navigate with session
    Guard->>Guard: Detect SSO user
    Guard->>Service: provisionSsoUser(session)
    Service->>API: POST /private/sso/provision-user (Authorization: Bearer token)
    API->>DB: Lookup provider, org, org_users
    alt invite_* membership exists
        API->>DB: Update user_right (promote invite_* → non-invite)
    else no membership
        API->>DB: Insert org_users (fallback role), retry on duplicate
    end
    API-->>Service: { success, merged?, already_member? }
    Service-->>Guard: { merged, alreadyMember, error }
    alt merged == true
        Guard->>Client: signOut and redirect to /login?message=sso_account_linked
    else error != null
        Guard->>Client: abort navigation (next(false))
    else
        Guard->>Client: continue navigation
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

💰 Rewarded

Poem

🐰 Hopping through the code today,

Linked accounts found their way,
Invites turned into seats at the table,
Guards now guide when things get unstable,
A little carrot for a job quite able.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: fixing IdP-initiated SSO organization provisioning by adding provisioning logic to the auth guard.
Description check ✅ Passed The PR description provides a comprehensive summary, motivation, business impact, and test plan with checkmarks. However, it lacks the original template structure (sections are titled but not explicitly checked against required format).

✏️ 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 codex/fix-idp-sso-org-provisioning

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

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq bot commented Apr 15, 2026

Merging this PR will not alter performance

✅ 28 untouched benchmarks


Comparing codex/fix-idp-sso-org-provisioning (4a318b1) with main (3b5f88d)

Open in CodSpeed

@riderx riderx marked this pull request as ready for review April 15, 2026 15:07
@riderx
Copy link
Copy Markdown
Member Author

riderx commented Apr 15, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 15, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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: 1

🧹 Nitpick comments (1)
tests/sso.test.ts (1)

739-926: Please add the negative merge-case regression too.

This suite now covers merge success, but not the new branch that must fail the merge when the org/provider link cannot be guaranteed. A test where the duplicate SSO user shares the email but no active provider can be resolved for the domain would lock in the hardening added in supabase/functions/_backend/private/sso/provision-user.ts.

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

In `@tests/sso.test.ts` around lines 739 - 926, Add a negative test mirroring the
successful merge case that verifies provision-user refuses to merge when there
is no active SSO provider linked for the email domain: create original password
user and duplicate SSO auth user (same email), but do NOT insert an active
sso_provider for the domain (or insert with status !== 'active' or different
org_id), then call fetchWithRetry on '/private/sso/provision-user' using the
duplicateAuthHeaders and assert response.status === 200 and responseBody.success
=== true but responseBody.merged === false; also assert the duplicate auth user
still exists via
getSupabaseClient().auth.admin.getUserById(duplicateUser.user.id) and that
org_users membership was not transferred to originalUser; use the existing
helpers and DB updates (getSupabaseClient(), pool queries, identity
manipulation, provision-user fetch) to mirror the successful test flow but
expect no merge.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/modules/auth.ts`:
- Around line 100-104: The guard currently logs result.error and then returns
'continue', which allows subsequent onboarding logic (invoked after the
/private/sso/provision-user call) to run; change the early error path so
provisioning failures short-circuit the guard instead of falling through: inside
the auth guard where result.error is checked, log the error and return a
non-'continue' outcome (or throw) to stop further processing (ensure you update
the branch handling that inspects the guard's return value if needed);
reference: the result.error check and the guard's current return 'continue' plus
the /private/sso/provision-user provisioning flow.

---

Nitpick comments:
In `@tests/sso.test.ts`:
- Around line 739-926: Add a negative test mirroring the successful merge case
that verifies provision-user refuses to merge when there is no active SSO
provider linked for the email domain: create original password user and
duplicate SSO auth user (same email), but do NOT insert an active sso_provider
for the domain (or insert with status !== 'active' or different org_id), then
call fetchWithRetry on '/private/sso/provision-user' using the
duplicateAuthHeaders and assert response.status === 200 and responseBody.success
=== true but responseBody.merged === false; also assert the duplicate auth user
still exists via
getSupabaseClient().auth.admin.getUserById(duplicateUser.user.id) and that
org_users membership was not transferred to originalUser; use the existing
helpers and DB updates (getSupabaseClient(), pool queries, identity
manipulation, provision-user fetch) to mirror the successful test flow but
expect no merge.
🪄 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: 02221fe4-257e-4b87-ac74-db80a9173fa0

📥 Commits

Reviewing files that changed from the base of the PR and between 3b5f88d and 50107cc.

📒 Files selected for processing (6)
  • src/composables/useSSOProvisioning.ts
  • src/modules/auth.ts
  • src/services/ssoProvisioning.ts
  • supabase/functions/_backend/private/sso/provision-user.ts
  • tests/auth-sso-provisioning.unit.test.ts
  • tests/sso.test.ts

Comment thread src/modules/auth.ts
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 50107ccbfc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/modules/auth.ts
@riderx
Copy link
Copy Markdown
Member Author

riderx commented Apr 15, 2026

Addressed the auth-guard review note in 1a3b435: provisioning errors now abort navigation instead of falling through to org onboarding, and the unit test suite covers that branch.

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: 1

🧹 Nitpick comments (1)
tests/auth-sso-provisioning.unit.test.ts (1)

167-257: Add coverage for the merged and already-authenticated SSO branches.

This suite only drives the !hadAuth path, and the success case never returns merged: true. That leaves the two highest-risk branches from this change untested: the merged-account sign-out redirect and the hasAuth && main.auth reprovision flow at src/modules/auth.ts Lines 305-317.

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

In `@tests/auth-sso-provisioning.unit.test.ts` around lines 167 - 257, Tests miss
coverage for the merged-account and already-authenticated SSO branches in the
auth guard: add unit tests that simulate the provisioning response including {
merged: true } to assert the sign-out/redirect behavior and a test where the
session indicates hadAuth/hasAuth (the "main.auth" reprovision flow referenced
in src/modules/auth.ts around the provisioning logic) to assert reprovisioning
is attempted and navigation proceeds correctly; use getGuard to create the
guard, stub mockGetSession to return a session with app_metadata showing SSO,
stub fetch to return ok:true with merged:true for the merged-account case (and
verify the expected sign-out redirect/next calls), and for the hadAuth case stub
session with prior auth and fetch to return success to verify reprovisioning and
continued navigation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/modules/auth.ts`:
- Around line 95-97: The code currently calls supabase.auth.signOut() when
result.merged is true and immediately returns 'redirect_login' even if signOut
fails; wrap the signOut call in a try/catch (or check its error return) inside
the same merged branch, and only return 'redirect_login' when signOut completes
successfully; on error, log the failure (including the error object) and return
a distinct outcome (e.g., 'signout_failed' or propagate the error) so the caller
can avoid redirecting with a stale SSO session; update the branch around
result.merged and supabase.auth.signOut() accordingly.

---

Nitpick comments:
In `@tests/auth-sso-provisioning.unit.test.ts`:
- Around line 167-257: Tests miss coverage for the merged-account and
already-authenticated SSO branches in the auth guard: add unit tests that
simulate the provisioning response including { merged: true } to assert the
sign-out/redirect behavior and a test where the session indicates
hadAuth/hasAuth (the "main.auth" reprovision flow referenced in
src/modules/auth.ts around the provisioning logic) to assert reprovisioning is
attempted and navigation proceeds correctly; use getGuard to create the guard,
stub mockGetSession to return a session with app_metadata showing SSO, stub
fetch to return ok:true with merged:true for the merged-account case (and verify
the expected sign-out redirect/next calls), and for the hadAuth case stub
session with prior auth and fetch to return success to verify reprovisioning and
continued navigation.
🪄 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: 406bd95e-c189-4282-80aa-97b51f6ee3d9

📥 Commits

Reviewing files that changed from the base of the PR and between 50107cc and 1a3b435.

📒 Files selected for processing (2)
  • src/modules/auth.ts
  • tests/auth-sso-provisioning.unit.test.ts

Comment thread src/modules/auth.ts
@riderx
Copy link
Copy Markdown
Member Author

riderx commented Apr 15, 2026

Addressed the merged-session sign-out edge case in 4a318b1: the auth guard now aborts navigation if sign-out fails, and the unit tests cover that path.

@sonarqubecloud
Copy link
Copy Markdown

@riderx riderx merged commit c2ee2ad into main Apr 15, 2026
15 checks passed
@riderx riderx deleted the codex/fix-idp-sso-org-provisioning branch April 15, 2026 15:31
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4a318b1b71

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +149 to +153
.from('org_users')
.select('id, user_right')
.eq('user_id', userId)
.eq('org_id', orgId)
.maybeSingle()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Handle multiple org_users rows during SSO membership lookup

ensureOrgMembership() assumes (user_id, org_id) is unique by calling .maybeSingle(), but org_users supports scoped rows (app_id/channel_id) and can legitimately have multiple records for the same user/org. In that case PostgREST returns a multiple-rows error, this function throws membership_check_failed, and the new merge path now returns provision_failed before identity transfer, blocking SSO account merges for affected users. Please scope this query to the org-level row (for example app_id IS NULL and channel_id IS NULL) or otherwise handle multiple rows deterministically.

Useful? React with 👍 / 👎.

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