feat(web): guard EE user deletion behind SCIM and unify user API shape#1425
Conversation
DELETE /api/ee/user now returns membershipManagedByIdpError when SCIM is enabled (the IdP owns membership), matching the existing member-management actions. It also delegates to removeMember, so it does an org-scoped membership removal with last-owner protection and credential revocation instead of a raw global user delete. The self-delete guard is removed; removeMember's last-owner protection covers the risky case. GET /api/ee/user and GET /api/ee/users now return an identical user shape via a shared toPublicUser mapper. The singular GET is now scoped to org membership (404 for non-members) instead of a global user lookup, and lastActivityAt is sourced from UserToOrg.lastActiveAt (dropping a per-member audit query / N+1). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
This comment has been minimized.
This comment has been minimized.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
WalkthroughThis PR refactors EE user endpoints to use organization membership (UserToOrg) records instead of direct user records. A new ChangesEE User Membership Refactor
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant UserRoute
participant Prisma
Client->>UserRoute: GET /api/ee/user
UserRoute->>Prisma: userToOrg.findUnique(orgId, userId)
Prisma-->>UserRoute: membership record (or null)
alt membership found
UserRoute->>UserRoute: toPublicUser(membership)
UserRoute-->>Client: public user response
else not found
UserRoute-->>Client: 404 not a member
end
sequenceDiagram
participant Client
participant UserRoute
participant SCIMCheck
participant RemoveMember
Client->>UserRoute: DELETE /api/ee/user
UserRoute->>SCIMCheck: isScimEnabled(org)
alt SCIM enabled
SCIMCheck-->>UserRoute: true
UserRoute-->>Client: membershipManagedByIdpError()
else SCIM disabled
UserRoute->>RemoveMember: removeMember(org.id, userId, actor)
RemoveMember-->>UserRoute: result or service error
UserRoute-->>Client: success or error response
end
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Hardens and unifies the enterprise user-management API (
/api/ee/userand/api/ee/users).DELETE /api/ee/usermembershipManagedByIdpError(403) — matching the existing member-management server actions (removeMemberFromOrg,suspendMember, etc.).removeMemberinstead of a rawprisma.user.delete. This makes it an org-scoped membership removal that revokes the member's credentials and enforces last-owner protection, rather than a global account delete.removeMember's last-active-owner protection already covers the only risky case, and owners can otherwise remove themselves.GET /api/ee/user+GET /api/ee/userstoPublicUsermapper (inee/utils.ts), so they can't drift. Unified shape:{ id, name, email, role, suspendedAt, createdAt, updatedAt, lastActivityAt }. This only adds fields to each endpoint (non-breaking).UserToOrgmembership (404 for non-members) instead of doing a globaluser.findUniqueby id — required to sourcerole/suspendedAt/lastActivityAt, and it closes a cross-org read.lastActivityAtnow comes fromUserToOrg.lastActiveAt(the throttled "last seen" heartbeat the members table already displays) instead of a per-memberaudit.findFirst. This removes the list endpoint's N+1.Docs
PublicEeUser/PublicEeUserListItemschemas into a singlePublicEeUsercomponent; updated the OpenAPI descriptions and regeneratedsourcebot-public.openapi.json.Behavior changes to note
GET /api/ee/userreturns 404 for a user who isn't a member of the caller's org (previously any user id resolved globally).DELETE /api/ee/useris org-membership removal, not global account deletion, and is blocked under SCIM.lastActivityAtisnullfor suspended members (theirlastActiveAtis reset on suspension) and can lag real activity by up to ~5 min (write throttle).Testing
eslintclean on all changed files.tsc --noEmitreports no errors in any changed file (pre-existing unrelated test-mock errors remain).🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Documentation