You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(membership): revoke a removed member's PATs in the org - #2048
Removing a user from an organization now also revokes the PATs they created for that organization. Closes#1970.
Changes
New userpat.Service.OnOrgMemberRemoved revokes every PAT a user holds in an organization. Each PAT is marked deleted and its policies are removed. Its pat.revoked audit record carries reason: member_removed in the target metadata.
It reads the user's PATs 1000 at a time and reads every page before revoking any.
revokeOrgPAT is renamed revokePAT and takes the audit target metadata. Organization delete passes none, so its records do not change.
RemoveOrganizationMember calls it for users, after the member's own policies are removed. It also calls it when the user is no longer a member, then returns not-a-member. The membership UserPATService interface and its mock change with it.
User delete does not call it. It removes the user from each organization through RemoveOrganizationMemberOnDelete, then revokes all of the user's PATs once, as before.
Technical Details
Expired PATs are revoked too. They keep their policies and can be regenerated, and they do not count toward the per-organization limit.
1000 is far above the per-organization limit, so one read covers almost every user.
The audit record's actor is whoever removed the member.
Each PAT goes through the same revoke step as organization delete. A PAT its owner deletes at the same moment is skipped, with no error and no second audit record.
If a revoke fails, the request returns an error after the member is already removed. Removing the user again returns not-a-member and revokes the remaining PATs.
Removing a user who is no longer a member also clears PATs left by removals before this change.
After the removal the user has no get on the organization, so they cannot create a new PAT there. Only a PAT created at the same moment as the removal can be missed, and removing the user again revokes it.
If the PAT service is not wired, removal logs an error and goes on, like the resource service check above it.
Test Plan
go test ./core/userpat/ passes. New TestService_OnOrgMemberRemoved: each PAT is revoked with the reason in its audit record; with more than 1000 PATs it reads every page before revoking any; it stops at the first PAT it cannot revoke. Organization delete records carry no reason.
go test ./core/membership/ passes. Removing a user revokes their PATs. Removing a user who is no longer a member still revokes their PATs and returns not-a-member, and a failed revoke there returns the error. A non-user principal and user delete never reach the PAT step. A missing PAT service still removes the member.
e2e TestPATOfRemovedOrgMemberIsRevoked: an organization owner (not a superuser) removes a member who has an org-scoped PAT and a project-scoped PAT. For both tokens: the token is rejected, hidden from its owner, holds no policy, and is gone from the organization's token list. Each pat.revoked record names the owner and reason: member_removed. After the member is added back with the same access, neither token works or has access in SpiceDB. Without the PAT step, every check fails for both tokens.
RemoveOrganizationMember now revokes the PATs the removed user holds in that org, each with reason member_removed in its audit record. User delete is unchanged and still revokes all of the user's PATs at the end.
Removing a user from an organization now revokes their personal access tokens, including tokens with organization or project access, so they can no longer use them to access resources.
Revoked tokens stay revoked if the user is later added back to the organization; the user must create new tokens to regain token-based access.
Removing a user from an organization now triggers revocation of that user’s organization-scoped PATs and their policies. Revocation records include member_removed metadata. Tests cover service behavior, membership removal, and the end-to-end flow.
Changes: Organization member PAT cleanup
Layer / File(s)
Summary
PAT revocation contract and implementation core/membership/service.go, core/userpat/service.go, core/userpat/service_test.go
UserPATService adds OnOrgMemberRemoved. The PAT service lists the user’s organization PATs in pages of 1,000 and revokes them with member_removed metadata. DeleteAllByOrg uses the shared revocation path. Unit tests cover pagination, failures, and audit metadata.
Member removal invokes PAT cleanup for user principals. If the PAT service is unavailable, removal logs the condition and continues. A revocation error is returned. Tests cover these cases and skip revocation for PAT principals.
The regression test verifies that member removal revokes organization- and project-scoped PATs, removes their policies and organization-list entries, and records audit events. Re-adding the member does not restore either token.
raystack/frontier#2031: Changes user deletion to use RemoveOrganizationMemberOnDelete, a separate path for membership removal during user deletion.
raystack/frontier#2027: Adds PAT cleanup to user deletion while removing the user from organizations.
Suggested reviewers:rohilsurana
Merge Risk: Low · up to ddfe0
Removing an organization member now revokes that user's organization PATs. If policy cleanup fails partway through, a retry may not remove the leftover policies. The window is narrow and the cleanup can happen separately, so this is mergeable with owner awareness.
Pre-merge checks: 2
Check name
Status
Explanation
Linked Issues check
Issue #1970 requires removal of a user’s organization PATs and their policies when membership ends. RemoveOrganizationMember calls OnOrgMemberRemoved after member-policy removal. The service reads…
Out of Scope Changes check
The membership interface and mock support the new callback. The audit metadata parameter preserves organization-delete behavior. Unit and e2e tests verify the required revocation and regression behavi…
Full details: Linked Issues check
Explanation
Issue #1970 requires removal of a user’s organization PATs and their policies when membership ends. RemoveOrganizationMember calls OnOrgMemberRemoved after member-policy removal. The service reads all PAT pages before revoking them, marks each PAT deleted, removes its policies, and records reason: member_removed. The tests cover pagination, failures, non-user principals, and re-add behavior.
Full details: Out of Scope Changes check
Explanation
The membership interface and mock support the new callback. The audit metadata parameter preserves organization-delete behavior. Unit and e2e tests verify the required revocation and regression behavior. No unrelated change is evident.
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.
Reviewing files that changed from the base of the PR and between f38ac11 and 7266892.
Files selected for processing: 7
core/membership/mocks/user_pat_service.go
core/membership/org.go
core/membership/org_test.go
core/membership/service.go
core/userpat/service.go
core/userpat/service_test.go
test/e2e/regression/pat_test.go
Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 1 remain after this review.
Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes.
Quick fix: rebase this PR. Learn more →
The reason will be displayed to describe this comment to others. Learn more.
Looks good. The fix matches #1970. The PAT revoke reuses the same step as org delete. The e2e test covers the re-add case, which was the real risk. One thing I would fix before merge: a failed revoke cannot be retried today. Details inline.
Two questions that are outside this PR:
DeletePolicy can remove a user's last org policy. That also ends their membership, but their PATs stay. Should that path revoke PATs too, or should PAT auth check that the owner is still a member?
A PAT created at the same moment as the removal can survive. You already call this out. Is a follow up ticket worth it, or is the window small enough to leave?
The reason will be displayed to describe this comment to others. Learn more.
I agree with CodeRabbit here. If the revoke fails, the member is already gone. A retry then stops at ErrNotMember on line 247, so the PATs never get revoked. If the user is added back later, those PATs work again, which is the bug this PR fixes. Could we run revokeMemberPATs when the error is ErrNotMember too, and still return ErrNotMember to the caller? It is a no-op when there are no PATs. A unit test for the retry would be good as well.
The reason will be displayed to describe this comment to others. Learn more.
Done in c0aec7b. RemoveOrganizationMember now runs the PAT step on ErrNotMember too and still returns ErrNotMember. Removing the user again finishes a revoke that failed, and it also clears PATs left by removals before this change. Two unit cases cover it: the retry, and a retry whose revoke fails.
The reason will be displayed to describe this comment to others. Learn more.
Small one: this adds a new sentence to a code comment. We are trying not to add new code comments. Could the "leaves PATs to the user delete" note move to the PR description instead?
The reason will be displayed to describe this comment to others. Learn more.
Keeping this line. The code here can't show it: user delete revokes all of the user's PATs itself, in core/deleter (DeleteAllByUser). Without the line, the missing PAT step next to RemoveOrganizationMember reads like a bug.
The reason will be displayed to describe this comment to others. Learn more.
Added in ddfe0f4. The member now holds a second PAT scoped to a project in the org, and every check runs for both. Without the PAT step, every check fails for both PATs.
RemoveOrganizationMember now runs the PAT step on ErrNotMember too and still returns ErrNotMember, so removing the user again finishes a revoke that failed.
Both PATs belong to the removed member in the same organization. This test also passes if cleanup incorrectly revokes every PAT in the organization or every PAT owned by the member.
Add another member’s PAT in this organization and this member’s PAT in another organization. After removal, assert that both control PATs still authenticate and retain their policies.
Based on learnings, filtered-deletion tests should include similar-but-distinct records and assert that they are preserved.
Reviewing files that changed from the base of the PR and between 7266892 and ddfe0f4.
Files selected for processing: 3
core/membership/org.go
core/membership/org_test.go
test/e2e/regression/pat_test.go
Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 1 remain after this review.
If policy cleanup fails after revokePAT soft-deletes a PAT, the next member-removal attempt skips that PAT because UserPATRepository.List excludes deleted rows. Its policies can remain until a separate cleanup occurs.
Make pending cleanup reachable on retry without changing normal PAT listings.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Removing a user from an organization now also revokes the PATs they created for that organization. Closes #1970.
Changes
userpat.Service.OnOrgMemberRemovedrevokes every PAT a user holds in an organization. Each PAT is marked deleted and its policies are removed. Itspat.revokedaudit record carriesreason: member_removedin the target metadata.revokeOrgPATis renamedrevokePATand takes the audit target metadata. Organization delete passes none, so its records do not change.RemoveOrganizationMembercalls it for users, after the member's own policies are removed. It also calls it when the user is no longer a member, then returns not-a-member. The membershipUserPATServiceinterface and its mock change with it.RemoveOrganizationMemberOnDelete, then revokes all of the user's PATs once, as before.Technical Details
geton the organization, so they cannot create a new PAT there. Only a PAT created at the same moment as the removal can be missed, and removing the user again revokes it.Test Plan
go test ./core/userpat/passes. NewTestService_OnOrgMemberRemoved: each PAT is revoked with the reason in its audit record; with more than 1000 PATs it reads every page before revoking any; it stops at the first PAT it cannot revoke. Organization delete records carry no reason.go test ./core/membership/passes. Removing a user revokes their PATs. Removing a user who is no longer a member still revokes their PATs and returns not-a-member, and a failed revoke there returns the error. A non-user principal and user delete never reach the PAT step. A missing PAT service still removes the member.TestPATOfRemovedOrgMemberIsRevoked: an organization owner (not a superuser) removes a member who has an org-scoped PAT and a project-scoped PAT. For both tokens: the token is rejected, hidden from its owner, holds no policy, and is gone from the organization's token list. Eachpat.revokedrecord names the owner andreason: member_removed. After the member is added back with the same access, neither token works or has access in SpiceDB. Without the PAT step, every check fails for both tokens.go test ./test/e2e/...passes.make testpasses.golangci-lint runreports no issues.