As a user I want a profile page so I can change my password and keep my personal information up to date #35

Closed
opened 2026-03-20 19:01:18 +01:00 by marcel · 0 comments
Owner

Background

Passwords are currently set by the admin. Users have no way to change them. There is also no place to store personal information (display name, birthday, contact details). The email field added here is a prerequisite for the forgot-password feature (#36).

Desired behaviour

  • A /profile page is accessible to every logged-in user
  • Personal information section — user can set and update:
    • First name, last name
    • Date of birth
    • Email address (required for password reset — must be unique across all users)
    • Optional free-text contact field (phone / address / notes)
  • Change password section — user must supply their current password plus the new password twice; the form validates that new passwords match before submitting
  • Saving each section independently (two separate forms / actions) so a failed password change does not discard profile edits and vice versa
  • When a password is changed, all other active sessions for that user are invalidated — only the current session survives. Spring Session JDBC stores sessions in the DB; delete by user ID excluding the current session ID.

Public profile pages

All user profiles are publicly visible to any logged-in user at /users/{id} (read-only). This is the natural entry point from:

  • Document edit history — "edited by Hans Müller" links to /users/{id}
  • Annotation comment authors — clicking a name in a thread links to /users/{id}

The authenticated user's own /profile is the editable version of the same data. No separate user directory is needed — discovery happens organically through history and annotations.

UI / navigation

Replace the plain "Logout" link in the global nav bar with a person icon button. Clicking it opens a small dropdown menu containing:

  • Profile → navigates to /profile
  • Logout → existing logout action

The dropdown must be fully keyboard accessible: Tab to reach the button, Enter/Space to open, arrow keys to navigate items, Escape to close, focus returns to the trigger button on close. On mobile it is tap-to-open; tapping outside or pressing Escape closes it.

The icon should show the user's initials if first/last name are set, or a generic person SVG as fallback (e.g. on first login before the profile is filled in).

Implementation notes

Backend

New Flyway migration — add columns to app_users:

ALTER TABLE app_users ADD COLUMN first_name VARCHAR(100);
ALTER TABLE app_users ADD COLUMN last_name  VARCHAR(100);
ALTER TABLE app_users ADD COLUMN birth_date DATE;
ALTER TABLE app_users ADD COLUMN email      VARCHAR(255) UNIQUE;  -- unique constraint
ALTER TABLE app_users ADD COLUMN contact    TEXT;

New endpoints (all require only the READ_ALL permission — every logged-in user can call them):

Method Path Purpose
GET /api/users/me Returns the current user's profile
PUT /api/users/me Updates personal info fields
POST /api/users/me/password Changes password
GET /api/users/{id} Returns a public profile (any logged-in user)

POST /api/users/me/password body:

{ "currentPassword": "...", "newPassword": "..." }

The service must:

  1. Verify currentPassword against the stored hash — return 400 with a clear error if it does not match
  2. Update the password hash
  3. Invalidate all sessions for this user except the current one (SessionRepository query by principal name, filter out current session ID)

PUT /api/users/me returns 409 Conflict if the supplied email is already in use by another account.

Frontend

  • New route src/routes/profile/ (editable, self only)
  • New route src/routes/users/[id]/ (read-only public view)
  • Global layout: replace the logout link with a person icon + accessible dropdown (profile / logout)
  • Two card sections on the profile page, each with its own <form> and use:enhance
  • i18n keys in de.json / en.json / es.json for all labels and error messages

Testing

  • UserServiceTest — wrong current password returns error; correct current password hashes and stores the new one; password change invalidates other sessions; duplicate email returns conflict
  • @WebMvcTest on the new controller actions — 400 on wrong current password, 409 on duplicate email, 200 on valid change
  • E2E Playwright spec — fill profile form, save, reload, verify values persist; change password, log out, log in with new password; verify public profile is visible at /users/{id}

Dependencies

None — but issue #36 (forgot password) and #38 (edit history) depend on the email column and display name added here.

## Background Passwords are currently set by the admin. Users have no way to change them. There is also no place to store personal information (display name, birthday, contact details). The email field added here is a prerequisite for the forgot-password feature (#36). ## Desired behaviour - A `/profile` page is accessible to every logged-in user - **Personal information section** — user can set and update: - First name, last name - Date of birth - Email address (required for password reset — must be unique across all users) - Optional free-text contact field (phone / address / notes) - **Change password section** — user must supply their current password plus the new password twice; the form validates that new passwords match before submitting - Saving each section independently (two separate forms / actions) so a failed password change does not discard profile edits and vice versa - When a password is changed, **all other active sessions for that user are invalidated** — only the current session survives. Spring Session JDBC stores sessions in the DB; delete by user ID excluding the current session ID. ## Public profile pages All user profiles are **publicly visible** to any logged-in user at `/users/{id}` (read-only). This is the natural entry point from: - Document edit history — "edited by Hans Müller" links to `/users/{id}` - Annotation comment authors — clicking a name in a thread links to `/users/{id}` The authenticated user's own `/profile` is the editable version of the same data. No separate user directory is needed — discovery happens organically through history and annotations. ## UI / navigation Replace the plain "Logout" link in the global nav bar with a **person icon button**. Clicking it opens a small dropdown menu containing: - **Profile** → navigates to `/profile` - **Logout** → existing logout action The dropdown must be fully keyboard accessible: Tab to reach the button, Enter/Space to open, arrow keys to navigate items, Escape to close, focus returns to the trigger button on close. On mobile it is tap-to-open; tapping outside or pressing Escape closes it. The icon should show the user's initials if first/last name are set, or a generic person SVG as fallback (e.g. on first login before the profile is filled in). ## Implementation notes **Backend** New Flyway migration — add columns to `app_users`: ```sql ALTER TABLE app_users ADD COLUMN first_name VARCHAR(100); ALTER TABLE app_users ADD COLUMN last_name VARCHAR(100); ALTER TABLE app_users ADD COLUMN birth_date DATE; ALTER TABLE app_users ADD COLUMN email VARCHAR(255) UNIQUE; -- unique constraint ALTER TABLE app_users ADD COLUMN contact TEXT; ``` New endpoints (all require only the `READ_ALL` permission — every logged-in user can call them): | Method | Path | Purpose | |--------|------|---------| | `GET` | `/api/users/me` | Returns the current user's profile | | `PUT` | `/api/users/me` | Updates personal info fields | | `POST` | `/api/users/me/password` | Changes password | | `GET` | `/api/users/{id}` | Returns a public profile (any logged-in user) | `POST /api/users/me/password` body: ```json { "currentPassword": "...", "newPassword": "..." } ``` The service must: 1. Verify `currentPassword` against the stored hash — return `400` with a clear error if it does not match 2. Update the password hash 3. Invalidate all sessions for this user **except** the current one (`SessionRepository` query by principal name, filter out current session ID) `PUT /api/users/me` returns `409 Conflict` if the supplied email is already in use by another account. **Frontend** - New route `src/routes/profile/` (editable, self only) - New route `src/routes/users/[id]/` (read-only public view) - Global layout: replace the logout link with a person icon + accessible dropdown (profile / logout) - Two card sections on the profile page, each with its own `<form>` and `use:enhance` - i18n keys in `de.json` / `en.json` / `es.json` for all labels and error messages ## Testing - `UserServiceTest` — wrong current password returns error; correct current password hashes and stores the new one; password change invalidates other sessions; duplicate email returns conflict - `@WebMvcTest` on the new controller actions — 400 on wrong current password, 409 on duplicate email, 200 on valid change - E2E Playwright spec — fill profile form, save, reload, verify values persist; change password, log out, log in with new password; verify public profile is visible at `/users/{id}` ## Dependencies None — but issue #36 (forgot password) and #38 (edit history) depend on the `email` column and display name added here.
marcel added the feature label 2026-03-20 19:11:19 +01:00
marcel added the user label 2026-03-20 19:12:38 +01:00
Sign in to join this conversation.
No Label feature user
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#35