feat: Implement user validation and ownership checks for image, reward, and task APIs
All checks were successful
Gitea Actions Demo / build-and-push (push) Successful in 36s

- Added `get_validated_user_id` utility function to validate user authentication across multiple APIs.
- Updated image upload, request, and listing endpoints to ensure user ownership and proper error handling.
- Enhanced reward management endpoints to include user validation and ownership checks.
- Modified task management endpoints to enforce user authentication and ownership verification.
- Updated models to include `user_id` for images, rewards, tasks, and children to track ownership.
- Implemented frontend changes to ensure UI reflects the ownership of tasks and rewards.
- Added a new feature specification to prevent deletion of system tasks and rewards.
This commit is contained in:
2026-01-31 19:48:51 -05:00
parent 6f5b61de7f
commit f14de28daa
18 changed files with 361 additions and 121 deletions

View File

@@ -13,22 +13,16 @@
- **Frontend Styling**: Use only `:root` CSS variables from `global.css` for all colors, spacing, and tokens. Example: `--btn-primary`, `--list-item-bg-good`.
- **Scoped Styles**: All `.vue` files must use `<style scoped>`. Reference global variables for theme consistency.
- **Rewards UI**: If `points >= cost`, apply `--item-card-ready-shadow` and `--item-card-ready-border`.
- **API Error Handling**: Backend returns JSON with `error` and `code` (see `backend/api/error_codes.py`). Frontend extracts `{ msg, code }` using `parseErrorResponse(res)` from `api.ts`.
- **Validation**: Use `isEmailValid` and `isPasswordStrong` (min 8 chars, 1 letter, 1 number) from `api.ts` for all user input. Use `sanitize_email()` for directory names and unique IDs (see `backend/api/utils.py`).
- **JWT Auth**: Tokens are stored in HttpOnly, Secure, SameSite=Strict cookies.
## 🚦 Frontend Logic & Event Bus
- **SSE Event Management**: Register listeners in `onMounted`, clean up in `onUnmounted`. Listen for events like `child_task_triggered`, `child_reward_request`, `task_modified`, etc. See `frontend/vue-app/src/common/backendEvents.ts` and `components/BackendEventsListener.vue`.
- **UI Guardrails**:
- Before triggering a task, check for pending rewards. If found, prompt for cancellation before proceeding.
- On `EDIT`, always refetch the full object from the API to ensure state integrity.
- **Layout Hierarchy**: Use `ParentLayout` for admin/management, `ChildLayout` for dashboard/focus views.
## ⚖️ Business Logic & Safeguards
- **Points**: Always enforce `child.points = max(child.points, 0)` after any mutation.
- **Token Expiry**: Verification tokens expire in 4 hours; password reset tokens in 10 minutes.
- **Image Assets**: Models use `image_id` for storage; frontend resolves to `image_url` for rendering.
@@ -36,7 +30,7 @@
- **Backend**: Run Flask with `python -m flask run --host=0.0.0.0 --port=5000` from the `backend/` directory. Main entry: `backend/main.py`.
- **Frontend**: From `frontend/vue-app/`, run `npm install` then `npm run dev`.
- **Tests**: Run backend tests with `pytest` in `backend/`. Frontend tests: `npm run test` in `frontend/vue-app/`.
- **Tests**: Run backend tests with `pytest` in `backend/tests/`. Frontend component tests: `npm run test` in `frontend/vue-app/components/__tests__/`.
- **Debugging**: Use VS Code launch configs or run Flask/Vue dev servers directly. For SSE, use browser dev tools to inspect event streams.
## 📁 Key Files & Directories
@@ -48,7 +42,7 @@
- `frontend/vue-app/` — Vue 3 frontend (see `src/common/`, `src/components/`, `src/layout/`)
- `frontend/vue-app/src/common/models.ts` — TypeScript interfaces (mirror Python models)
- `frontend/vue-app/src/common/api.ts` — API helpers, error parsing, validation
- `web/vue-app/src/common/backendEvents.ts` — SSE event types and handlers
- `frontend/vue-app/src/common/backendEvents.ts` — SSE event types and handlers
## 🧠 Integration & Cross-Component Patterns

View File

@@ -0,0 +1,26 @@
# Feature: Do Not Allow System Tasks or System Rewards To Be Deleted
## Context:
- **Goal:** In Task List view and Reward List view, do not allow items to be deleted by the user if they are system tasks.
- **User Story:** As a [user], I want to only be able to press the delete button on a task or reward if that item is not a system task or reward so that shared system tasks are not deleted for other users.
## Technical Requirements
- **File Affected:** ItemList.vue, TaskView.vue, RewardView.vue, task_api.py, reward_api.py
- **Logic:**
1. Starting with ItemList.vue, we should check to see if any item in the list has an "user_id" property and if that property is null.
2. If the property is null, that means the item is not owned by a user, so do no display a delete button.
3. If the ItemList has it's deletable property as false, don't bother checking each item for user_id as the delete button will not display.
4. As a safeguard, on the backend, the DELETE api requests should check to see if the "user_id" property of the requested task or reward is null. This is done by requesting the item from the database. The request provides the item's id. If the item is a system item, return 403. Let the return tell the requestor that the item is a system item and cannot be deleted.
5. As a safeguard, make PUT/PATCH operations perform a copy-on-edit of the item. This is already implemented.
6. Bulk deletion is not possible, don't make changes for this.
7. For any item in the frontend or backend that does not have a "user_id" property, treat that as a system item (user_id=mull)
8. For both task and reward api create an application level constraint on the database that checks for user_id before mutation logic.
## Acceptance Criteria (The "Definition of Done")
- [ ] Logic: Task or Reward does not display the delete button when props.deletable is true and a list item is a system item.
- [ ] UI: Doesn't show delete button for system items.
- [ ] Backend Tests: Unit tests cover a delete API request for a system task or reward and returns a 403.
- [ ] Frontend Tests: Add vitest for this feature in the frontend to make sure the delete button hidden or shown.