- 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
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()andfrom_dict()methods to serialize these fields. - Import
datetimefrom 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:
- Extract current user from JWT token.
- Validate user exists in database.
- Check if already marked for deletion:
- If
marked_for_deletion == True, return error with codeALREADY_MARKED(or make idempotent and return success).
- If
- Set
marked_for_deletion = Trueandmarked_for_deletion_at = datetime.now(timezone.utc). - Save user to database using
users_db.update(). - Trigger SSE event:
send_event_for_current_user('user_marked_for_deletion', { 'user_id': user.id }). - Return success response.
Login Blocking (backend/api/auth_api.py)
In the /api/login endpoint, after validating credentials:
- Check if
user.marked_for_deletion == True. - If yes, return:
with HTTP status
{ "error": "This account has been marked for deletion and cannot be accessed.", "code": "ACCOUNT_MARKED_FOR_DELETION" }403.
Password Reset Blocking (backend/api/user_api.py)
In the /api/user/request-reset endpoint:
- After finding the user by email, check if
user.marked_for_deletion == True. - 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.vuefrontend/vue-app/src/common/models.tsfrontend/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--dangercolor fromcolors.css) - Placement: Below "Change Password" link, with
24pxmargin-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-modelbound toconfirmEmailref.
- Buttons:
- "Cancel" (
.btn-secondary) — closes modal - "Delete My Account" (
.btn-danger) — disabled untilconfirmEmailmatches user email, triggers API call
- "Cancel" (
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
- User clicks "Delete My Account" button.
- Warning modal opens with email confirmation input.
- User types email and clicks "Delete My Account".
- Frontend calls
POST /api/user/mark-for-deletion. - On success:
- Close warning modal.
- Show success modal.
- On "OK" click: call
logoutUser()fromstores/auth.ts, redirect to/auth/login.
- 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_atis set). - Test marking an already-marked account (return error with
ALREADY_MARKEDor 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_deletionandmarked_for_deletion_atfields toUsermodel (backend). - Add matching fields to
Userinterface (frontend). - Update
to_dict()andfrom_dict()methods inUsermodel.
Backend
- Create
POST /api/user/mark-for-deletionendpoint. - Add
ACCOUNT_MARKED_FOR_DELETIONandALREADY_MARKEDerror codes. - Block login for marked users in
/api/login. - Block password reset for marked users in
/api/user/request-reset. - Trigger
user_marked_for_deletionSSE event after marking. - All backend tests pass.
Frontend
- Add "Delete My Account" button to
UserProfile.vuebelow "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_DELETIONandALREADY_MARKEDtoerrorCodes.ts. - All frontend tests pass.