fix(profile): member delete must not destroy the shared account#52
Merged
Merged
Conversation
#50) Members joining via workspace invite share the owner's account_id. ProfileController::destroy was unconditionally calling $account->delete() in every profile-deletion path, so any member could wipe the whole organization (cascade: workspaces, posts, social accounts, signatures, labels) just by clicking Delete on their own profile. Gate the account/subscription teardown behind isAccountOwner(). For members the path now only detaches them from workspaces and deletes the user row — owner's data is untouched. Tested in both SELF_HOSTED=true and false.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Closes #50.
Bug
Members joining via workspace invite share the owner's
account_id. `ProfileController::destroy` was unconditionally calling `$account->delete()` in every profile-deletion path, so any member could wipe the whole organization — workspaces, posts, social accounts, signatures, labels — just by clicking Delete on their own profile.Fix
Gate the account/subscription teardown behind `isAccountOwner()`. For members the destroy path now only detaches them from workspaces and deletes the user row. Owner's data is untouched.
(Subscription cancellation moved into the same gate — it's an owner-only concern; for a member there's no subscription to touch.)
Tests
3 new tests, each parametrized over `SELF_HOSTED=true` and `false` (6 runs total):
Both modes pass because the path no longer depends on `self_hosted` — `isAccountOwner()` is a pure model check, and `$account->subscribed(...)` returns false on self-hosted (no Stripe records), so the cancel/delete-subscription calls naturally no-op.
Full suite: 1603 passing.
🤖 Generated with Claude Code