Repository navigation
feat(api): users can check and delete their own account - #2031
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Deletion cleanup and membership rules core/deleter/*, core/membership/*, core/resource/* |
User deletion uses a guarded membership-removal path and maps last-owner errors to blockers. The cleanup ignores missing memberships, organizations, and policies. |
Current-user deletion endpoints internal/api/v1beta1connect/*, pkg/server/connect_interceptors/authorization.go |
The API adds current-user deletion checks and deletion. It maps check results and errors, clears the session cookie after successful self-deletion, and denies PAT calls to both endpoints. |
Deletion regression coverage test/e2e/regression/api_test.go, test/e2e/regression/pat_test.go |
End-to-end tests cover deletion previews, blockers, successful deletion, session invalidation, audit records, and restrictions on service-user and PAT calls. |
Priority:
Estimated code review effort: 4 (Complex) | ~45 minutes
Change: Feature
Suggested reviewers: rohilsurana, whoabhisheksah
Merge Risk: 
Minimal · up to c6135
-
The identified deletion-cleanup path does not leave access policies behind. No issue requiring a fix before merge was established.
Pre-merge checks: 
2
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
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 @coderabbitai help to get the list of available commands.
Coverage Report for CI Build 40836980855Coverage increased (+0.07%) to 55.992%Details
Uncovered Changes
Coverage Regressions1 previously-covered line in 1 file lost coverage.
Coverage Stats
💛 - Coveralls |
e75f617 to
bf85b84
Compare
2246724 to
981c70f
Compare
rohilsurana
left a comment
There was a problem hiding this comment.
Nit: the PR body lists core/membership/service.go changes that already landed in #2022.
bf85b84 to
16df0ba
Compare
981c70f to
66e779c
Compare
|
@rohilsurana Updated the description: it no longer lists |
16df0ba to
8bdebc5
Compare
6d4daa2 to
d698fea
Compare
CheckCurrentUserDelete reports what deleting the logged-in user's account would do and what blocks it, without changing anything. DeleteCurrentUser runs the same check, refuses with every blocker, and otherwise deletes the account like the admin DeleteUser, then clears the session cookie. Both refuse service users and PATs. A user delete now leaves each org with the guarded member removal, which also works on a disabled org, so it can no longer remove an org's last owner, and force removal is removed. Anything removed by someone else during the delete counts as done.
…rites its audit entries
d698fea to
c6135ac
Compare
Stacked on #2028, which is stacked on #2027. Merge those first. This branch also carries #2026's proto commit (
blockersonOrganizationToLeave). Merge #2026 first; that commit drops out when this branch is rebased.Summary
Two new FrontierService RPCs act on the logged-in user.
CheckCurrentUserDeletereports what deleting the account would do and what blocks it, without changing anything.DeleteCurrentUserruns the same check and refuses with every blocker. Otherwise it deletes the account the same way the adminDeleteUserdoes, then clears the session cookie.A user delete, admin or self, now leaves each org with the guarded member removal, so it can no longer remove an org's last owner. Force removal is removed.
Changes
internal/api/v1beta1connect/user.go:CheckCurrentUserDeletereturnscan_delete, the user blockers, the orgs deleted with the user (each with its owncan_deleteand blockers), the orgs the user leaves (role title and blockers), and the PAT count.DeleteCurrentUserdeletes the logged-in user and clears the session cookie withclearSessionCookie, likeAuthLogout.DeleteUserandDeleteCurrentUsershare one helper. A blocked delete returnsFAILED_PRECONDITIONwith one violation per blocker, and each blocker of an org it would delete starts withorganization[<name>]:(feat(deleter): user delete checks for blockers before deleting anything #2027). A failed check or a failed delete is logged. A successful delete writes theapp.user.deletedaudit log.internal/api/v1beta1connect/authenticate.go:getLoggedInUserreturns the principal only when it is a user. Service users and PATs getPERMISSION_DENIED.CreateCurrentUserPATuses it too.clearSessionCookiesets the header that tells the session interceptor to clear the cookie.AuthLogoutandDeleteCurrentUseruse it.pkg/server/connect_interceptors/authorization.go: both RPCs are on the authorization skip list, because they need only a session, likeGetCurrentUser. Both are on the PAT deny list.core/membership/org.go:ForceRemoveOrganizationMemberis removed.RemoveOrganizationMemberOnDeleteisRemoveOrganizationMemberfor a user delete: the same member and last-owner guards, and it also works on a disabled org.core/deleter/service.go:RemoveUsersFromOrgis removed.DeleteUserleaves each org withRemoveOrganizationMemberOnDelete:SOLE_OWNER_WITH_MEMBERS.core/membership/org.go,core/resource/service.go: removing a member, or a principal's custom-resource access, skips a policy that is already gone.Technical Details
DeleteUserbehaves differently in one case only: when an owner leaves at the same moment, it is refused instead of leaving the org without an owner.NOT_FOUND.can_deletecomes from the local invoice rows, which sync from the billing provider on a timer. The delete asks the provider again, so it can still be refused for an unpaid invoice that hasn't synced yet (feat(deleter): user delete checks for blockers before deleting anything #2027).DeleteUserstays superuser only. A non-superuser still getsUNAVAILABLE.Test Plan
core/deleter/service_test.goTestDeleteUser: the steps run in order with the guarded removal. A user removed from an org since the check, or an org deleted since the check, does not stop the delete. An org that lost its other owner since the check refuses withSOLE_OWNER_WITH_MEMBERS.core/membership/org_test.goTestService_RemoveOrganizationMemberOnDelete: removes an owner from a disabled org through the owner guard, refuses the last owner, returnsErrNotMember, skips policies that are already gone, and passes up a failed org lookup.core/resource/service_test.go: a policy that is already gone counts as deleted.internal/api/v1beta1connect/user_test.go:TestConnectHandler_CheckCurrentUserDelete: the full response mapping, nothing blocking, a failed check, no session, a service user, and a PAT.TestConnectHandler_DeleteCurrentUser: success clears the cookie; a blocked delete returnsFAILED_PRECONDITION; also a failed delete, no session, a service user, and a PAT.TestConnectHandler_DeleteUser: the admin delete leaves the admin's own cookie alone.test/e2e/regression/api_test.goTestDeleteCurrentUser, in order:UNAUTHENTICATED.CheckFederatedResourcePermissionis false on every org and project the user had.ListPoliciesand the policies table hold no live policy for the user.usersnorrole_pairsof the orgs it left.app.user.deletedaudit log and oneuser.deletedaudit record.test/e2e/regression/pat_test.goTestPATCannotCheckOrDeleteItsOwnersAccount: a PAT is refused on both RPCs. The check counts the PAT. The PAT stops working after its owner deletes the account.go test ./test/e2e/...passes:regression(all suites) andsmoke.make lintreports no issues.UNAUTHENTICATEDon bothPERMISSION_DENIEDon both; the service user keeps workingPERMISSION_DENIEDon both; the session check counts the PAT; the PAT stops working after the deleteDeleteUserUNAVAILABLEAll 57 checks
UNAUTHENTICATEDUNAUTHENTICATEDGetUseras a superuser after the deleteNOT_FOUNDSOLE_OWNER_WITH_MEMBERSFAILED_PRECONDITION,SOLE_OWNER_WITH_MEMBERSon the shared orgusersandrole_pairsof the shared and member orgs, beforesidemptied withMax-Age=0GetCurrentUser, check and deleteUNAUTHENTICATEDon all threeGetUseras a superuserNOT_FOUNDListPoliciesusersandrole_pairs, afterapp.user.deletedaudit log anduser.deletedaudit recordNEGATIVE_TOKEN_BALANCE; the org with a blocking resource hasRESOURCES_EXIST; the shared org hasSOLE_OWNER_WITH_MEMBERSFAILED_PRECONDITIONwith all three in one answerFAILED_PRECONDITION, unchangedSOLE_OWNER_WITH_MEMBERSFAILED_PRECONDITION,SOLE_OWNER_WITH_MEMBERSFAILED_PRECONDITIONSOLE_OWNER_WITH_MEMBERSGetCurrentUserPERMISSION_DENIED,PERMISSION_DENIEDGetCurrentUserwith the PATPERMISSION_DENIED, "not allowed with a personal access token"PERMISSION_DENIED, "not allowed with a personal access token"pat_count1UNAUTHENTICATED, 0DeleteUserFAILED_PRECONDITION,SOLE_OWNER_WITH_MEMBERSDeleteUserDeleteUserDeleteUserNOT_FOUND, 0, still disabledDeleteUserUNAVAILABLE, unchangedDeleteUserSQL Safety (if your PR touches
*_repository.goorgoqu.*)Not applicable: no repository or
goqucode changes. The new e2e checks read counts with$1parameters.