feat: Implement account deletion (mark for removal) feature
All checks were successful
Gitea Actions Demo / build-and-push (push) Successful in 23s
All checks were successful
Gitea Actions Demo / build-and-push (push) Successful in 23s
- 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
This commit is contained in:
251
.github/specs/archive/feat-profile-mark-remove-account.md
vendored
Normal file
251
.github/specs/archive/feat-profile-mark-remove-account.md
vendored
Normal file
@@ -0,0 +1,251 @@
|
||||
# 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:
|
||||
|
||||
```python
|
||||
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:
|
||||
|
||||
```typescript
|
||||
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:
|
||||
|
||||
```python
|
||||
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:**
|
||||
|
||||
```json
|
||||
{}
|
||||
```
|
||||
|
||||
(Empty body; user is identified from JWT token)
|
||||
|
||||
**Response:**
|
||||
|
||||
- **Success (200):**
|
||||
```json
|
||||
{ "success": true }
|
||||
```
|
||||
- **Error (400/401/403):**
|
||||
```json
|
||||
{ "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:
|
||||
```json
|
||||
{
|
||||
"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:
|
||||
|
||||
```python
|
||||
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:
|
||||
|
||||
```typescript
|
||||
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`)
|
||||
|
||||
- [x] Test marking a valid user account (200, `marked_for_deletion = True`, `marked_for_deletion_at` is set).
|
||||
- [x] Test marking an already-marked account (return error with `ALREADY_MARKED` or be idempotent).
|
||||
- [x] Test marking with invalid JWT (401).
|
||||
- [x] Test marking with missing JWT (401).
|
||||
- [x] Test login attempt by marked user (403, `ACCOUNT_MARKED_FOR_DELETION`).
|
||||
- [x] Test password reset request by marked user (silently ignored, returns 200 but no email sent).
|
||||
- [x] Test SSE event is triggered after marking.
|
||||
|
||||
### Frontend Tests (`frontend/vue-app/src/components/__tests__/UserProfile.spec.ts`)
|
||||
|
||||
- [x] Test "Delete My Account" button renders.
|
||||
- [x] Test warning modal opens on button click.
|
||||
- [x] Test "Delete My Account" button in modal is disabled until email matches.
|
||||
- [x] Test API call is made when user confirms with correct email.
|
||||
- [x] Test success modal shows after successful API response.
|
||||
- [x] Test error modal shows on API failure (with error message).
|
||||
- [x] Test user is signed out after success (calls `logoutUser()`).
|
||||
- [x] Test redirect to login page after sign-out.
|
||||
- [x] 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
|
||||
|
||||
- [x] Add `marked_for_deletion` and `marked_for_deletion_at` fields to `User` model (backend).
|
||||
- [x] Add matching fields to `User` interface (frontend).
|
||||
- [x] Update `to_dict()` and `from_dict()` methods in `User` model.
|
||||
|
||||
### Backend
|
||||
|
||||
- [x] Create `POST /api/user/mark-for-deletion` endpoint.
|
||||
- [x] Add `ACCOUNT_MARKED_FOR_DELETION` and `ALREADY_MARKED` error codes.
|
||||
- [x] Block login for marked users in `/api/login`.
|
||||
- [x] Block password reset for marked users in `/api/user/request-reset`.
|
||||
- [x] Trigger `user_marked_for_deletion` SSE event after marking.
|
||||
- [x] All backend tests pass.
|
||||
|
||||
### Frontend
|
||||
|
||||
- [x] Add "Delete My Account" button to `UserProfile.vue` below "Change Password".
|
||||
- [x] Implement warning modal with email confirmation.
|
||||
- [x] Implement success modal.
|
||||
- [x] Implement error modal.
|
||||
- [x] Implement loading state during API call.
|
||||
- [x] Sign out user after successful account marking.
|
||||
- [x] Redirect to login page after sign-out.
|
||||
- [x] Add `ACCOUNT_MARKED_FOR_DELETION` and `ALREADY_MARKED` to `errorCodes.ts`.
|
||||
- [x] All frontend tests pass.
|
||||
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Reference in New Issue
Block a user