Skip to content

fix(users/organizations/auth): prevent 500 on signin when org deleted without cascade (#3709)#3713

Merged
PierreBrisorgueil merged 4 commits into
masterfrom
fix/3709-dangling-org-cascade-and-signin-guard
May 28, 2026
Merged

fix(users/organizations/auth): prevent 500 on signin when org deleted without cascade (#3709)#3713
PierreBrisorgueil merged 4 commits into
masterfrom
fix/3709-dangling-org-cascade-and-signin-guard

Conversation

@PierreBrisorgueil
Copy link
Copy Markdown
Contributor

Summary

Fixes #3709Cannot read properties of null (reading '_id') 500 on signin when a user has an active membership pointing at a deleted organization.

Root cause

When a sole-owner user is deleted, users.service.js:remove() called OrganizationsCrudService.removeById(orgId) (bare DB delete, no cascade). This left co-members with currentOrganization pointing at the now-deleted org. On the co-member's next signin, autoSetCurrentOrganization called MembershipRepository.list() which populates organizationId — Mongoose returns null for deleted orgs — then crashed on null._id.

Three-layer fix

Layer 1 (users.service.js): Replace the bare removeById call with an inline cascade that:

  • Collects all co-members whose currentOrganization = orgId (affected users)
  • Deletes all memberships for the org
  • For each affected co-member, finds their next available org and updates currentOrganization (or sets null if none)
  • Deletes the org via bare OrganizationsRepository.remove (preserving task data, consistent with previous behavior)

Layer 2 (organizations.crud.service.js): Harden autoSetCurrentOrganization:

  • Filter memberships where organizationId is null after Mongoose populate before dereferencing ._id
  • Guard the early-return branch: only trust a membership as still-active when its populated organizationId is non-null

Layer 3 (auth.e2e.tests.js): Add regression test verifying signin returns 200 (not 500) when currentOrganization is a bogus ObjectId (pre-existing data-integrity anomaly scenario, belt-and-suspenders).

Tests added

  • modules/users/tests/users.remove.cascade.e2e.tests.js — full repro: User A (sole owner) deleted → Org X removed → User B (co-member) can sign in without 500
  • modules/organizations/tests/organizations.crud.dangling-org.unit.tests.js — 4 unit tests covering all null-populate edge cases in autoSetCurrentOrganization
  • modules/auth/tests/auth.e2e.tests.js — 1 new test: data-integrity corruption → signin still 200

Test results

  • Unit: 105 suites, 1525 tests — all pass
  • Integration: 28/29 suites pass (1 pre-existing flaky timing test on master too)
  • E2E: 5 suites, 11 tests — all pass

…on (#3709)

Replace the bare OrganizationsCrudService.removeById call with an inline
cascade that properly repairs co-member currentOrganization to their next
available org (or null) instead of clearing it uniformly to null.
…pulated org refs (#3709)

Filter memberships whose organizationId is null after Mongoose populate
(deleted org with dangling ObjectId ref) before dereferencing ._id.
Also guard the early-return branch: only trust a membership as still-active
when its populated organizationId is non-null.
…3709)

Add E2E test verifying signin returns 200 (not 500) when currentOrganization
points to a non-existent org ID (pre-existing corruption scenario). Tasks 1+2
already fix the root cause; this test is the belt-and-suspenders regression guard.
Copilot AI review requested due to automatic review settings May 28, 2026 14:36
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Warning

Review limit reached

@PierreBrisorgueil, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 13 minutes and 25 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 50504991-a279-4acb-b90c-d17915498c3d

📥 Commits

Reviewing files that changed from the base of the PR and between 8a50c8e and 19f69c6.

📒 Files selected for processing (6)
  • modules/auth/tests/auth.e2e.tests.js
  • modules/organizations/services/organizations.crud.service.js
  • modules/organizations/tests/organizations.crud.dangling-org.unit.tests.js
  • modules/users/services/users.service.js
  • modules/users/tests/users.remove.cascade.e2e.tests.js
  • modules/users/tests/users.service.remove.cascade.unit.tests.js
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/3709-dangling-org-cascade-and-signin-guard

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.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 28, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.83%. Comparing base (d87542b) to head (19f69c6).
⚠️ Report is 10 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3713      +/-   ##
==========================================
+ Coverage   89.73%   89.83%   +0.09%     
==========================================
  Files         143      143              
  Lines        4794     4800       +6     
  Branches     1505     1506       +1     
==========================================
+ Hits         4302     4312      +10     
+ Misses        385      383       -2     
+ Partials      107      105       -2     
Flag Coverage Δ
integration 59.47% <91.66%> (+0.05%) ⬆️
unit 66.85% <100.00%> (+0.60%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 8a50c8e...19f69c6. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes a signin crash caused by dangling organization references (memberships populating organizationId: null after an org is hard-deleted), and adds regression coverage around the edge cases that triggered issue #3709.

Changes:

  • Harden autoSetCurrentOrganization to tolerate null-populated organizationId and avoid null._id dereferences.
  • Update users.service.remove() to perform an inline org-deletion cascade (delete org memberships + repair affected users’ currentOrganization) instead of calling the non-cascading removeById.
  • Add E2E + unit regression tests covering deleted-org / bogus-currentOrganization scenarios.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
modules/users/services/users.service.js Implements cascade org cleanup during user deletion to avoid leaving co-members with dangling currentOrganization.
modules/organizations/services/organizations.crud.service.js Filters out null-populated memberships and tightens early-return logic in autoSetCurrentOrganization.
modules/users/tests/users.remove.cascade.e2e.tests.js New E2E repro test: co-member can still sign in after sole-owner deletion removes org.
modules/organizations/tests/organizations.crud.dangling-org.unit.tests.js New unit tests for autoSetCurrentOrganization null-populate edge cases.
modules/auth/tests/auth.e2e.tests.js New E2E test ensuring signin stays 200 even with corrupted currentOrganization.

Comment thread modules/users/services/users.service.js
Co-member cascade in `users.service.remove` was dereferencing
`remaining[0].organizationId._id` without filtering out null-populated
memberships (hard-deleted org), mirroring the same crash fixed in
`autoSetCurrentOrganization`. Apply the identical `liveMemberships` filter
before picking the next current org. Add unit tests covering the null-only
and mixed cases.
@PierreBrisorgueil PierreBrisorgueil merged commit 56b17a0 into master May 28, 2026
8 checks passed
@PierreBrisorgueil PierreBrisorgueil deleted the fix/3709-dangling-org-cascade-and-signin-guard branch May 28, 2026 18:05
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.

Signin 500 (null._id) when a user's active membership points to a deleted org — removeById skips cascade

2 participants