Files
chore/.github/specs/archive/feat-profile-mark-remove-account.md
Ryan Kegel 0d651129cb
All checks were successful
Gitea Actions Demo / build-and-push (push) Successful in 23s
feat: Implement account deletion (mark for removal) feature
- Added `marked_for_deletion` and `marked_for_deletion_at` fields to User model (Python and TypeScript) with serialization updates
- Created POST /api/user/mark-for-deletion endpoint with JWT auth, error handling, and SSE event trigger
- Blocked login and password reset for marked users; added new error codes ACCOUNT_MARKED_FOR_DELETION and ALREADY_MARKED
- Updated UserProfile.vue with "Delete My Account" button, confirmation modal (email input), loading state, success/error modals, and sign-out/redirect logic
- Synced error codes and model fields between backend and frontend
- Added and updated backend and frontend tests to cover all flows and edge cases
- All Acceptance Criteria from the spec are complete and verified
2026-02-06 16:19:08 -05:00

7.7 KiB

Feature: Account Deletion (Mark for Removal)

Overview

Goal: Allow users to mark their account for deletion from the Profile page.

User Story:
As a user, I want to delete my account from the Profile page. When I click "Delete My Account", I want a confirmation dialog that warns me about data loss. After confirming by entering my email, my account will be marked for deletion, I will be signed out, and I will not be able to log in again.


Data Model Changes

Backend Model (backend/models/user.py)

Add the following fields to the User class:

marked_for_deletion: bool = False
marked_for_deletion_at: datetime | None = None
  • Update to_dict() and from_dict() methods to serialize these fields.
  • Import datetime from Python standard library if not already imported.

Frontend Model (frontend/vue-app/src/common/models.ts)

Add matching fields to the User interface:

marked_for_deletion: boolean;
marked_for_deletion_at: string | null;

Backend Implementation

New Error Codes (backend/api/error_codes.py)

Add the following error code:

ACCOUNT_MARKED_FOR_DELETION = 'ACCOUNT_MARKED_FOR_DELETION'
ALREADY_MARKED = 'ALREADY_MARKED'

New API Endpoint (backend/api/user_api.py)

Endpoint: POST /api/user/mark-for-deletion

Authentication: Requires valid JWT (authenticated user).

Request:

{}

(Empty body; user is identified from JWT token)

Response:

  • Success (200):
    { "success": true }
    
  • Error (400/401/403):
    { "error": "Error message", "code": "INVALID_USER" | "ALREADY_MARKED" }
    

Logic:

  1. Extract current user from JWT token.
  2. Validate user exists in database.
  3. Check if already marked for deletion:
    • If marked_for_deletion == True, return error with code ALREADY_MARKED (or make idempotent and return success).
  4. Set marked_for_deletion = True and marked_for_deletion_at = datetime.now(timezone.utc).
  5. Save user to database using users_db.update().
  6. Trigger SSE event: send_event_for_current_user('user_marked_for_deletion', { 'user_id': user.id }).
  7. Return success response.

Login Blocking (backend/api/auth_api.py)

In the /api/login endpoint, after validating credentials:

  1. Check if user.marked_for_deletion == True.
  2. If yes, return:
    {
      "error": "This account has been marked for deletion and cannot be accessed.",
      "code": "ACCOUNT_MARKED_FOR_DELETION"
    }
    
    with HTTP status 403.

Password Reset Blocking (backend/api/user_api.py)

In the /api/user/request-reset endpoint:

  1. After finding the user by email, check if user.marked_for_deletion == True.
  2. If yes, silently ignore the request:
    • Do not send an email.
    • Return success response (to avoid leaking account status).

SSE Event (backend/events/types/event_types.py)

Add new event type:

USER_MARKED_FOR_DELETION = 'user_marked_for_deletion'

Frontend Implementation

Files Affected

  • frontend/vue-app/src/components/parent/UserProfile.vue
  • frontend/vue-app/src/common/models.ts
  • frontend/vue-app/src/common/errorCodes.ts

Error Codes (frontend/vue-app/src/common/errorCodes.ts)

Add:

export const ACCOUNT_MARKED_FOR_DELETION = "ACCOUNT_MARKED_FOR_DELETION";
export const ALREADY_MARKED = "ALREADY_MARKED";

UI Components (UserProfile.vue)

1. Delete Account Button

  • Label: "Delete My Account"
  • Style: .btn-danger-link (use --danger color from colors.css)
  • Placement: Below "Change Password" link, with 24px margin-top
  • Behavior: Opens warning modal on click

2. Warning Modal (uses ModalDialog.vue)

  • Title: "Delete Your Account?"
  • Body:
    "This will permanently delete your account and all associated data. This action cannot be undone."
  • Email Confirmation Input:
    • Require user to type their email address to confirm.
    • Display message: "Type your email address to confirm:"
    • Input field with v-model bound to confirmEmail ref.
  • Buttons:
    • "Cancel" (.btn-secondary) — closes modal
    • "Delete My Account" (.btn-danger) — disabled until confirmEmail matches user email, triggers API call

3. Loading State

  • Disable "Delete My Account" button during API call.
  • Show loading spinner or "Deleting..." text.

4. Success Modal

  • Title: "Account Deleted"
  • Body:
    "Your account has been marked for deletion. You will now be signed out."
  • Button: "OK" (closes modal, triggers logoutUser() and redirects to /auth/login)

5. Error Modal

  • Title: "Error"
  • Body: Display error message from API using parseErrorResponse(res).msg.
  • Button: "Close"

Frontend Logic

  1. User clicks "Delete My Account" button.
  2. Warning modal opens with email confirmation input.
  3. User types email and clicks "Delete My Account".
  4. Frontend calls POST /api/user/mark-for-deletion.
  5. On success:
    • Close warning modal.
    • Show success modal.
    • On "OK" click: call logoutUser() from stores/auth.ts, redirect to /auth/login.
  6. On error:
    • Close warning modal.
    • Show error modal with message from API.

Testing

Backend Tests (backend/tests/test_user_api.py)

  • Test marking a valid user account (200, marked_for_deletion = True, marked_for_deletion_at is set).
  • Test marking an already-marked account (return error with ALREADY_MARKED or be idempotent).
  • Test marking with invalid JWT (401).
  • Test marking with missing JWT (401).
  • Test login attempt by marked user (403, ACCOUNT_MARKED_FOR_DELETION).
  • Test password reset request by marked user (silently ignored, returns 200 but no email sent).
  • Test SSE event is triggered after marking.

Frontend Tests (frontend/vue-app/src/components/__tests__/UserProfile.spec.ts)

  • Test "Delete My Account" button renders.
  • Test warning modal opens on button click.
  • Test "Delete My Account" button in modal is disabled until email matches.
  • Test API call is made when user confirms with correct email.
  • Test success modal shows after successful API response.
  • Test error modal shows on API failure (with error message).
  • Test user is signed out after success (calls logoutUser()).
  • Test redirect to login page after sign-out.
  • Test button is disabled during loading.

Future Considerations

  • A background scheduler will be implemented to physically delete marked accounts after a grace period (e.g., 30 days).
  • Admin panel to view and manage marked accounts.
  • Email notification to user when account is marked for deletion (with grace period details).

Acceptance Criteria (Definition of Done)

Data Model

  • Add marked_for_deletion and marked_for_deletion_at fields to User model (backend).
  • Add matching fields to User interface (frontend).
  • Update to_dict() and from_dict() methods in User model.

Backend

  • Create POST /api/user/mark-for-deletion endpoint.
  • Add ACCOUNT_MARKED_FOR_DELETION and ALREADY_MARKED error codes.
  • Block login for marked users in /api/login.
  • Block password reset for marked users in /api/user/request-reset.
  • Trigger user_marked_for_deletion SSE event after marking.
  • All backend tests pass.

Frontend

  • Add "Delete My Account" button to UserProfile.vue below "Change Password".
  • Implement warning modal with email confirmation.
  • Implement success modal.
  • Implement error modal.
  • Implement loading state during API call.
  • Sign out user after successful account marking.
  • Redirect to login page after sign-out.
  • Add ACCOUNT_MARKED_FOR_DELETION and ALREADY_MARKED to errorCodes.ts.
  • All frontend tests pass.