120 lines
8.4 KiB
Markdown
120 lines
8.4 KiB
Markdown
|
|
# Implementation Plan: User Profile
|
|||
|
|
|
|||
|
|
## Overview
|
|||
|
|
|
|||
|
|
This plan implements the user profile feature in three phases: backend API routes first (profile endpoint and password change endpoint on the existing auth router), then the frontend components (UserProfilePanel modal and UserMenu theming/integration), and finally wiring everything together. Each task builds incrementally on the previous one, and testing tasks are placed close to the code they validate.
|
|||
|
|
|
|||
|
|
## Tasks
|
|||
|
|
|
|||
|
|
- [ ] 1. Add backend profile and password change routes to `routes/auth.js`
|
|||
|
|
- [x] 1.1 Add `GET /api/auth/profile` route
|
|||
|
|
- Add a new route inside `createAuthRouter` that queries the `users` table for `id, username, email, user_group, created_at, last_login` using the session user's ID
|
|||
|
|
- Return `{ id, username, email, group, created_at, last_login }` on success
|
|||
|
|
- Return 401 if the account is inactive (with `is_active = 0`), clearing the session cookie
|
|||
|
|
- Use the existing `requireAuth(db)` middleware for authentication
|
|||
|
|
- _Requirements: 4.1, 4.2, 4.3_
|
|||
|
|
|
|||
|
|
- [x] 1.2 Add `POST /api/auth/change-password` route with rate limiting
|
|||
|
|
- Add `express-rate-limit` middleware scoped to this route: 5 requests per 15-minute window, keyed by `req.cookies.session_id`
|
|||
|
|
- Validate request body has `currentPassword` and `newPassword` fields; return 400 if missing
|
|||
|
|
- Validate `newPassword` is at least 8 characters; return 400 if too short
|
|||
|
|
- Query the user's `password_hash` and `is_active` from the database; return 401 if account is inactive
|
|||
|
|
- Use `bcrypt.compare` to verify `currentPassword`; return 401 if incorrect
|
|||
|
|
- Hash the new password with `bcrypt.hash(newPassword, 10)` and update the `password_hash` column
|
|||
|
|
- Call `logAudit` with action `password_change`, entityType `auth`
|
|||
|
|
- Return `{ message: 'Password changed successfully' }` on success
|
|||
|
|
- Return 429 with appropriate message when rate limit is exceeded
|
|||
|
|
- _Requirements: 2.2, 2.3, 2.7, 2.8, 5.1, 5.2, 5.3, 5.4_
|
|||
|
|
|
|||
|
|
- [x] 1.3 Write property tests for password change round-trip (backend)
|
|||
|
|
- **Property 3: Password change round-trip** — For any valid current password and any new password of 8+ characters, after a successful change, `bcrypt.compare(newPassword, storedHash)` returns true
|
|||
|
|
- **Validates: Requirements 2.2, 2.7**
|
|||
|
|
|
|||
|
|
- [x] 1.4 Write property tests for incorrect password rejection (backend)
|
|||
|
|
- **Property 4: Incorrect current password is always rejected** — For any password string that does not match the user's current password, the endpoint returns 401 and the stored hash remains unchanged
|
|||
|
|
- **Validates: Requirements 2.3**
|
|||
|
|
|
|||
|
|
- [x] 1.5 Write property tests for short password rejection (backend)
|
|||
|
|
- **Property 6 (server-side): Short passwords are rejected** — For any string of length 0–7, `POST /api/auth/change-password` returns 400 and the stored hash remains unchanged
|
|||
|
|
- **Validates: Requirements 2.5, 5.4**
|
|||
|
|
|
|||
|
|
- [x] 2. Checkpoint — Verify backend routes
|
|||
|
|
- Ensure all tests pass, ask the user if questions arise.
|
|||
|
|
|
|||
|
|
- [x] 3. Create `UserProfilePanel.js` frontend component
|
|||
|
|
- [x] 3.1 Create the `UserProfilePanel` modal component
|
|||
|
|
- Create `frontend/src/components/UserProfilePanel.js`
|
|||
|
|
- Accept `isOpen` and `onClose` props
|
|||
|
|
- On open, fetch `GET /api/auth/profile` with `credentials: 'include'` and display loading state
|
|||
|
|
- Render profile info section showing: username, email, group, created_at (formatted), last_login (formatted)
|
|||
|
|
- Use dark theme inline styles matching the design system (intel-card gradient background, accent borders, light text colors from `DESIGN_SYSTEM.md`)
|
|||
|
|
- Include a close button (X icon from lucide-react) and click-outside-to-close behavior
|
|||
|
|
- Display error state with retry option if profile fetch fails
|
|||
|
|
- Use icons from lucide-react (User, Mail, Shield, Calendar, Clock)
|
|||
|
|
- _Requirements: 1.1, 1.2, 1.3, 1.4_
|
|||
|
|
|
|||
|
|
- [x] 3.2 Add password change form to `UserProfilePanel`
|
|||
|
|
- Add a password change section below the profile info with three fields: current password, new password, confirm new password
|
|||
|
|
- Implement client-side validation: new password must match confirm password; new password must be >= 8 characters
|
|||
|
|
- Display inline validation errors below the relevant fields
|
|||
|
|
- On submit, call `POST /api/auth/change-password` with `{ currentPassword, newPassword }`
|
|||
|
|
- Handle API error responses (401 wrong password, 429 rate limited, 400 validation, 500 server error) and display appropriate messages
|
|||
|
|
- On success, show success message and clear all form fields
|
|||
|
|
- Style the form with dark theme inline styles (intel-input styling, intel-button-primary for submit)
|
|||
|
|
- _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6_
|
|||
|
|
|
|||
|
|
- [x] 3.3 Write property test for profile panel field rendering
|
|||
|
|
- **Property 1: Profile panel displays all required fields** — For any valid profile object with arbitrary username, email, group, created_at, and last_login values, rendering UserProfilePanel displays all five values in the output
|
|||
|
|
- **Validates: Requirements 1.2**
|
|||
|
|
|
|||
|
|
- [x] 3.4 Write property test for mismatched password confirmation
|
|||
|
|
- **Property 5: Mismatched password confirmation is rejected client-side** — For any two distinct strings used as newPassword and confirmPassword, the form displays a validation error and does not submit a request
|
|||
|
|
- **Validates: Requirements 2.4**
|
|||
|
|
|
|||
|
|
- [x] 3.5 Write property test for short password client-side rejection
|
|||
|
|
- **Property 6 (client-side): Short passwords are rejected** — For any string of length 0–7, the form displays a minimum-length validation error and does not submit a request
|
|||
|
|
- **Validates: Requirements 2.5**
|
|||
|
|
|
|||
|
|
- [x] 4. Checkpoint — Verify frontend component
|
|||
|
|
- Ensure all tests pass, ask the user if questions arise.
|
|||
|
|
|
|||
|
|
- [x] 5. Modify `UserMenu.js` for dark theming and profile integration
|
|||
|
|
- [x] 5.1 Convert `UserMenu.js` from light theme to dark theme
|
|||
|
|
- Replace Tailwind light-theme classes with inline dark-theme style objects
|
|||
|
|
- Button: `hover:bg-gray-100` → `rgba(14, 165, 233, 0.1)` hover background
|
|||
|
|
- Username text: `text-gray-900` → `#F8FAFC` (design system `--text-primary`)
|
|||
|
|
- Group label text: `text-gray-500` → `#E2E8F0` (design system `--text-secondary`)
|
|||
|
|
- Chevron icon: `text-gray-500` → `#E2E8F0`
|
|||
|
|
- Dropdown panel: `bg-white` → intel-card gradient background; `border-gray-200` → `rgba(14, 165, 233, 0.3)`
|
|||
|
|
- Dropdown items: `text-gray-700` → `#F8FAFC`; `hover:bg-gray-50` → `rgba(14, 165, 233, 0.1)`
|
|||
|
|
- Sign out text: `text-red-600` → `#F87171`; `hover:bg-red-50` → `rgba(239, 68, 68, 0.1)`
|
|||
|
|
- Dropdown header: `text-gray-900` → `#F8FAFC`; `text-gray-500` → `#94A3B8`; `border-gray-100` → `rgba(14, 165, 233, 0.2)`
|
|||
|
|
- Retain existing group badge color-coding logic
|
|||
|
|
- _Requirements: 3.1, 3.2, 3.3, 3.4, 6.1, 6.2, 6.3, 6.4, 6.5_
|
|||
|
|
|
|||
|
|
- [x] 5.2 Add "My Profile" menu item and wire `UserProfilePanel`
|
|||
|
|
- Import `UserProfilePanel` component
|
|||
|
|
- Add `showProfile` state variable
|
|||
|
|
- Add a "My Profile" menu item with `User` icon between the dropdown header and the admin-only actions
|
|||
|
|
- On click, close the dropdown and set `showProfile` to `true`
|
|||
|
|
- Render `<UserProfilePanel isOpen={showProfile} onClose={() => setShowProfile(false)} />` in the component output
|
|||
|
|
- _Requirements: 1.1, 1.3_
|
|||
|
|
|
|||
|
|
- [x] 5.3 Write property test for profile API data completeness
|
|||
|
|
- **Property 2: Profile API returns complete user data matching database** — For any active user record, a GET request to `/api/auth/profile` with that user's valid session returns an object with `id`, `username`, `email`, `group`, `created_at`, and `last_login` fields matching the database
|
|||
|
|
- **Validates: Requirements 4.1**
|
|||
|
|
|
|||
|
|
- [x] 6. Final checkpoint — Ensure all tests pass
|
|||
|
|
- Ensure all tests pass, ask the user if questions arise.
|
|||
|
|
|
|||
|
|
## Notes
|
|||
|
|
|
|||
|
|
- Tasks marked with `*` are optional and can be skipped for faster MVP
|
|||
|
|
- Each task references specific requirements for traceability
|
|||
|
|
- Checkpoints ensure incremental validation
|
|||
|
|
- Property tests use `fast-check` (already installed in `frontend/package.json` as a devDependency)
|
|||
|
|
- Backend tests for properties 3, 4, and 6 (server-side) will need `fast-check` added to the root `package.json` devDependencies
|
|||
|
|
- The existing `express-rate-limit` package (already in root `package.json`) is used for the password change rate limiter
|
|||
|
|
- No database migrations are needed — the existing `users` table has all required columns
|
|||
|
|
- All styling follows the dark theme design system documented in `DESIGN_SYSTEM.md`
|