Skip to content

feat: GDPR-compliant user self-deletion (anonymization)#928

Open
socksprox wants to merge 3 commits into
cedar2025:masterfrom
socksprox:gdpr-account-deletion
Open

feat: GDPR-compliant user self-deletion (anonymization)#928
socksprox wants to merge 3 commits into
cedar2025:masterfrom
socksprox:gdpr-account-deletion

Conversation

@socksprox
Copy link
Copy Markdown
Contributor

User Self-Deletion (GDPR Art. 17 – Right to Erasure)

Users currently have no way to delete their own accounts. This PR adds a self-service account deletion endpoint that satisfies the GDPR right to erasure via anonymization — not hard deletion. Records that serve a legitimate legal or financial purpose are retained, but all personal identifiers are scrubbed from the user record itself.

What is anonymization deletion?

Unlike a hard delete, anonymization deletion replaces all PII on the user row with placeholder values and soft-deletes the record. The user identity is gone, but historical records that reference them remain intact and consistent.

What gets scrubbed

  • emaildeleted_{id}@deleted.invalid
  • password → random hash
  • password_algo, password_salt, last_login_ip, telegram_id → null
  • uuid, token → regenerated (subscribe URL immediately invalidated)
  • All Sanctum tokens and sessions deleted (instant logout)
  • All invite codes deleted
  • plan_id, group_id nulled; banned set to true

What is retained

  • Orders and payment history (legal/financial obligation)
  • Commission logs
  • Support tickets and messages
  • Traffic statistics and gift card usage

These records keep their user_id FK but the row they point to contains no personal data.

Changes

  • Migration: nullable deleted_at integer column on v2_user (integer to match dateFormat = 'U')
  • User model: SoftDeletes trait — deleted users automatically excluded from all queries including broadcast/reminder emails
  • POST /api/v1/user/destroy: new endpoint, requires password confirmation, blocks admin accounts
  • Admin views: Ticket.user(), Order.user(), Order.invite_user() use withTrashed() so admins can still see historical records tied to deleted users
  • transformUserData(): null-safe for soft-deleted user references
  • Admin destroy + clear:user: updated to forceDelete() to preserve existing hard-delete behaviour

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