8.4 KiB
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-
1.1 Add
GET /api/auth/profileroute- Add a new route inside
createAuthRouterthat queries theuserstable forid, username, email, user_group, created_at, last_loginusing 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
- Add a new route inside
-
1.2 Add
POST /api/auth/change-passwordroute with rate limiting- Add
express-rate-limitmiddleware scoped to this route: 5 requests per 15-minute window, keyed byreq.cookies.session_id - Validate request body has
currentPasswordandnewPasswordfields; return 400 if missing - Validate
newPasswordis at least 8 characters; return 400 if too short - Query the user's
password_hashandis_activefrom the database; return 401 if account is inactive - Use
bcrypt.compareto verifycurrentPassword; return 401 if incorrect - Hash the new password with
bcrypt.hash(newPassword, 10)and update thepassword_hashcolumn - Call
logAuditwith actionpassword_change, entityTypeauth - 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
- Add
-
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
- Property 3: Password change round-trip — For any valid current password and any new password of 8+ characters, after a successful change,
-
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
-
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-passwordreturns 400 and the stored hash remains unchanged - Validates: Requirements 2.5, 5.4
- Property 6 (server-side): Short passwords are rejected — For any string of length 0–7,
-
-
2. Checkpoint — Verify backend routes
- Ensure all tests pass, ask the user if questions arise.
-
3. Create
UserProfilePanel.jsfrontend component-
3.1 Create the
UserProfilePanelmodal component- Create
frontend/src/components/UserProfilePanel.js - Accept
isOpenandonCloseprops - On open, fetch
GET /api/auth/profilewithcredentials: '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
- Create
-
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-passwordwith{ 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
-
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
-
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
-
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
-
-
4. Checkpoint — Verify frontend component
- Ensure all tests pass, ask the user if questions arise.
-
5. Modify
UserMenu.jsfor dark theming and profile integration-
5.1 Convert
UserMenu.jsfrom 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
-
5.2 Add "My Profile" menu item and wire
UserProfilePanel- Import
UserProfilePanelcomponent - Add
showProfilestate variable - Add a "My Profile" menu item with
Usericon between the dropdown header and the admin-only actions - On click, close the dropdown and set
showProfiletotrue - Render
<UserProfilePanel isOpen={showProfile} onClose={() => setShowProfile(false)} />in the component output - Requirements: 1.1, 1.3
- Import
-
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/profilewith that user's valid session returns an object withid,username,email,group,created_at, andlast_loginfields matching the database - Validates: Requirements 4.1
- Property 2: Profile API returns complete user data matching database — For any active user record, a GET request to
-
-
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 infrontend/package.jsonas a devDependency) - Backend tests for properties 3, 4, and 6 (server-side) will need
fast-checkadded to the rootpackage.jsondevDependencies - The existing
express-rate-limitpackage (already in rootpackage.json) is used for the password change rate limiter - No database migrations are needed — the existing
userstable has all required columns - All styling follows the dark theme design system documented in
DESIGN_SYSTEM.md