Skip to content

feat(account): let users delete their own account - #6831

Merged
waleedlatif1 merged 3 commits into
stagingfrom
feat/gdpr-account-deletion
Aug 19, 2026
Merged

feat(account): let users delete their own account#6831
waleedlatif1 merged 3 commits into
stagingfrom
feat/gdpr-account-deletion

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Adds self-serve account deletion for GDPR: Settings → General → Account → Delete, behind a confirmation dialog that requires retyping the account email.
  • GET /api/users/me/deletion previews what deletion removes; POST performs it. Both go through an application use case, session-only — an API key or delegated service can never erase the account behind it.
  • Deletion refuses while the account is still entangled instead of reassigning its content. Most tables reference user.id with ON DELETE CASCADE, and those cascades don't distinguish content in your own workspace from content you created inside someone else's — so each blocker names the existing action that untangles it (leave the workspace, leave the organization, cancel the plan), all of which already hand work over on their own tested paths.
  • Blockers: sole owner of a paid org, organization membership, active paid plan, workspaces shared with others, org-owned workspaces, and data drains you created. For an ordinary individual account there are none and it's a single dialog.
  • Ordering is load-bearing: workspace.billed_account_user_id is NO ACTION and Postgres evaluates it before the owner_id cascade that would remove the same workspace, so anchored workspaces are torn down or handed over before the user row is touched.
  • Stored objects are purged before the rows. The retention sweep is driven entirely by rows the cascade deletes, so without this every file, document and KB object would be orphaned in storage permanently — the opposite of what an erasure request means.
  • Better Auth's own deleteUser stays disabled and its beforeDelete now refuses unconditionally, so there is exactly one deletion path and flipping the flag can't route around the preflight, purge, teardown or audit.
  • Records an account.deleted audit entry with no actor identity — a compliance record that doesn't retain the person who just exercised their right to erasure.
  • Adds disabledTooltip to ChipConfirmAction so a blocked confirmation can state its own remedy.

Type of Change

  • New feature

Testing

Tested manually. 16 unit tests covering the classifier (every blocker, both workspace buckets, name formatting) and the use case (email confirmation, non-session principals); verified the suite fails when the classifier is broken. turbo run type-check, bun run lint, bun run check:audits (29/29) and the emcn chip-modal suite all pass.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

Adds a GDPR self-serve account deletion path: a preflight that reports
what deletion would remove and every reason it would be refused, and a
confirmed delete that erases the account and everything only it can reach.

Deletion refuses while the account is still entangled rather than
reassigning its content. Most tables reference user.id with ON DELETE
CASCADE, and those cascades do not distinguish content in the account's
own workspace from content it created inside somebody else's, so each
blocker names the existing action that untangles it (leave the workspace,
leave the organization, cancel the plan) — all of which already hand work
over on their own tested paths.
@vercel

vercel Bot commented Aug 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Aug 19, 2026 12:28am

Request Review

@cursor

cursor Bot commented Aug 18, 2026

Copy link
Copy Markdown

PR Summary

High Risk
Irreversible user and workspace teardown, billing/ownership transfers, and storage purge; mistakes or race handling could delete shared data or leave billing orphans, though blockers and transactional guards aim to prevent that.

Overview
Adds self-serve account deletion (Settings → General → Account) backed by GET/POST /api/users/me/deletion, session-only use cases, and email retype confirmation.

The server preflights a deletion plan (blockers, workspaces to delete vs transfer) and refuses while the account is still entangled—paid org ownership, org membership, active personal plan, shared/org workspaces, or owned data drains—instead of relying on broad ON DELETE CASCADE on user-linked content. Execution collects storage keys first, runs workspace deletes, billing/ownership handovers, and user removal in one transaction (with race guards), then purges cloud objects; workspace reassignment helpers accept an optional transaction executor for that path.

Better Auth deleteUser stays disabled and beforeDelete always errors so deletion cannot bypass this flow. UI includes a DeleteAccountModal that refetches the plan on each open, plus disabledTooltip on ChipConfirmModal confirm actions. An account.deleted audit event is recorded without actor identity after erasure.

Reviewed by Cursor Bugbot for commit 1f06819. Configure here.

@greptile-apps

greptile-apps Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds session-only self-service account deletion with blocker previews, transactional workspace teardown and reassignment, post-commit object cleanup, audit recording, and a confirmation UI.

  • Adds GET and POST account-deletion endpoints and application use cases.
  • Classifies organization, subscription, workspace, and data-drain blockers before deletion.
  • Performs guarded workspace deletion, anchor reassignment, and user deletion in one transaction.
  • Adds Settings UI, query hooks, contracts, audit types, and confirmation-tooltip support.
  • Disables Better Auth’s independent deletion path.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported partial-deletion, stale-membership, and committed-transfer issues are addressed by the current transactional ordering and guarded deletion.

Important Files Changed

Filename Overview
apps/sim/lib/users/account-deletion.ts Implements deletion planning, guarded transactional teardown, workspace-anchor reassignment, and post-commit object cleanup; the previously reported transaction and stale-membership failures are addressed.
apps/sim/lib/workspaces/utils.ts Adds transaction-executor support to billing and ownership reassignment helpers so their writes participate in account-deletion rollback.
apps/sim/lib/users/application/delete-account.ts Enforces session-only deletion and email confirmation while coordinating deletion and actor-less auditing.
apps/sim/app/api/users/me/deletion/route.ts Exposes session-authenticated preview and deletion endpoints through typed route contracts.
apps/sim/app/workspace/[workspaceId]/settings/components/general/components/delete-account-modal.tsx Adds the blocker-aware account-deletion confirmation dialog and post-deletion client cleanup.
apps/sim/lib/users/account-deletion.test.ts Covers blocker classification, workspace categorization, error mapping, and profile-picture key extraction.

Sequence Diagram

sequenceDiagram
  participant U as User
  participant UI as Settings UI
  participant API as Account deletion API
  participant DB as Postgres
  participant S as Object storage
  participant A as Audit

  U->>UI: Open deletion dialog
  UI->>API: GET /api/users/me/deletion
  API->>DB: Load blockers and workspace plan
  DB-->>API: Deletion facts
  API-->>UI: Plan or blockers
  U->>UI: Confirm account email
  UI->>API: POST /api/users/me/deletion
  API->>DB: Collect storage keys
  API->>DB: Begin transaction
  API->>DB: Guarded workspace deletes
  API->>DB: Reassign billing and ownership
  API->>DB: Delete user
  DB-->>API: Commit
  API->>S: Purge collected objects
  API->>A: Record actor-less account.deleted event
  API-->>UI: Success
  UI->>U: Clear local state and redirect to login
Loading

Reviews (3): Last reviewed commit: "fix(account): close deletion gaps found ..." | Re-trigger Greptile

Comment thread apps/sim/lib/users/account-deletion.ts Outdated
Comment thread apps/sim/lib/users/account-deletion.ts Outdated
Comment thread apps/sim/lib/users/account-deletion.ts Outdated
Comment thread apps/sim/lib/users/account-deletion.ts
Comment thread apps/sim/lib/users/account-deletion.ts
Reorders the teardown so nothing irreversible happens before the deletion
is certain: anchors are handed over first (the fallible step, while
everything is still recoverable), the workspace and user deletes now share
one transaction, and the object-storage purge runs only after that commits.

The workspace delete also re-checks inside the transaction that each
workspace is still private, so a membership granted between the preview and
the delete aborts the whole thing instead of destroying the new member's
access.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/lib/users/account-deletion.ts Outdated
Comment thread apps/sim/lib/users/account-deletion.ts Outdated
- Run the whole teardown in one transaction. The billing and ownership
  handovers now take the caller's transaction, so a refused deletion can no
  longer leave a workspace reassigned for a deletion that never happened.
- Fail closed on a subscription read error. getHighestPriorityPersonalSubscription
  defaulted to returning null, which read as "no plan" and would have erased an
  account Stripe was still billing.
- Erase the account's profile picture. It is personal data under our own
  storage prefix; an external provider avatar is left alone.
- Enforce the storage purge cap while collecting keys rather than after, so an
  oversized account cannot exhaust memory before the cap applies.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 1f06819. Configure here.

@waleedlatif1
waleedlatif1 merged commit 9328e66 into staging Aug 19, 2026
30 checks passed
@waleedlatif1
waleedlatif1 deleted the feat/gdpr-account-deletion branch August 19, 2026 00:36
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.

1 participant