All playbooksConsumer Subscription App

Cover new data in account deletion

When the app stores new user data or files, extend account deletion so that nothing of the user is left behind and only the verified user can trigger it.

When to use it

A new table, bucket or external service holds data of a user, for example journal entries, uploaded documents or a newsletter subscription. "Delete account" in the privacy controls must remove it too.

Rules to keep

  • Account deletion removes everything the product stores for the user and then the Auth user, as PRODUCT.md promises.
  • Only the delete-account Edge Function deletes, for the user of the JWT it has verified. public.delete_my_account stays executable only by service_role; never take the user id from the request body.
  • Files are removed through the Storage API. A delete from storage.objects only removes the metadata and leaves the file in the bucket.
  • Every step that fails stops with an error before the Auth user is deleted, so the user can retry.
  • Migrations are append-only: redefine the function in a new migration.

Steps

  1. Give every new table a user_id uuid not null references auth.users(id) on delete cascade (or a reference to public.profiles(id)), so its rows can never outlive the account.

  2. Redefine public.delete_my_account in a new migration with create or replace function. Copy the body from the latest migration that defines it (now 202611020001_fix_account_deletion.sql) and add the new table, so the function remains the complete list of what deletion removes:

    delete from public.journal_entries where user_id = p_user_id;
    

    Keep the signature and security definer set search_path = '', and repeat the revoke execute ... from public, anon, authenticated and grant execute ... to service_role lines.

  3. Store files under <user-id>/..., as the avatars bucket does. In supabase/functions/delete-account/index.ts remove them like the avatars, before the RPC: list(user.id) returns at most 100 entries and does not enter subfolders, so repeat list and remove until the list is empty, or keep the folder flat.

  4. For data in an external service, call its deletion API from the Edge Function with a key kept in the function secrets, before the RPC. If it fails, answer 500 as the other steps do.

  5. Extend supabase/tests/database/delete_my_account.test.sql: as the test owner insert rows for two users, call public.delete_my_account for the first one and check that only their rows are gone. Keep the privilege checks that are already there.

  6. Run supabase db reset and supabase test db, then delete a test account from the app and check in the dashboard that no rows or files remain.

  7. Describe the new data in DATABASE.md and its retention in PRODUCT.md.

Done when

  • the verification commands and supabase test db pass;
  • deleting a test account leaves no rows, files or external records;
  • delete_my_account is still not executable by anon or authenticated;
  • DATABASE.md and PRODUCT.md describe the new data.