Skip to content

Account Management Page Design

Summary

Add a user-facing account management page at /account where logged-in users can change their email, change their password, and delete their account. Single page with sectioned forms.

Routes & Controller

Single AccountController with these routes:

Method Route Name Purpose
GET /account app_account Render page with all 3 forms
POST /account/change-email app_account_change_email Process email change
GET /account/verify-email/{token} app_account_verify_email Confirm new email
POST /account/cancel-email-change app_account_cancel_email_change Cancel pending email change
POST /account/change-password app_account_change_password Process password change
POST /account/delete app_account_delete Process account deletion

All routes require IS_AUTHENTICATED_FULLY (no remember-me for sensitive operations). Each POST redirects to app_account with a flash message on success, or re-renders the page with form errors on failure.

Forms

  1. ChangeEmailFormType - Field: email (EmailType, pre-filled with current email). Validation: NotBlank, Email.

  2. AccountChangePasswordFormType - Fields: currentPassword (PasswordType) + plainPassword (RepeatedType with same validation as existing registration/reset forms: NotBlank, Length min 8, PasswordStrength WEAK, NotCompromisedPassword). Separate from the existing ChangePasswordFormType used in the reset flow (which has no current password field).

  3. DeleteAccountFormType - Field: password (PasswordType). Validation: NotBlank. Controller verifies the password matches.

Email Change Flow

  1. User submits new email via ChangeEmailFormType.
  2. Controller validates form and checks new email isn't already taken.
  3. Store new email in pendingEmail field on User entity. Current email stays active.
  4. Generate verification token (reuse EmailVerificationService.generateToken()) and store on user.
  5. Send verification email to the new address with confirmation link.
  6. Flash: "A verification link has been sent to {newEmail}. Your email won't change until you confirm."
  7. When user clicks the link (/account/verify-email/{token}):
  8. Validate token (not expired, matches user)
  9. Update email to pendingEmail, clear pendingEmail
  10. Clear verification token
  11. Flash: "Your email has been updated."
  12. Redirect to /account

Cancel Email Change

  • While a pendingEmail exists, the email change form is hidden.
  • An info banner shows: "Pending email change to {pendingEmail}. Check your inbox." with a "Cancel" button.
  • The cancel route clears pendingEmail, verificationToken, and verificationTokenExpiresAt.
  • Flash: "Email change cancelled."

The existing verificationToken and verificationTokenExpiresAt fields are reused since a user won't be doing initial registration verification and email change simultaneously.

Password Change Flow

  1. User submits AccountChangePasswordFormType with current password + new password (repeated).
  2. Controller verifies current password using UserPasswordHasherInterface::isPasswordValid().
  3. If current password is wrong, add form error to currentPassword field.
  4. If valid, hash new password and persist.
  5. Flash: "Your password has been updated."
  6. Redirect to /account.

User stays logged in after password change.

Account Deletion Flow

  1. User submits DeleteAccountFormType with their password.
  2. Controller verifies password.
  3. If wrong, add form error.
  4. If valid:
  5. Remove user from database (hard delete, cascade removes related entities)
  6. Invalidate session (log user out)
  7. Flash: "Your account has been deleted."
  8. Redirect to homepage

Entity Changes

Add to User entity: - pendingEmail (string, 180 chars, nullable) - stores new email awaiting verification

New Doctrine migration to add pending_email column.

Template & UI

Single template templates/account/index.html.twig:

  • Extends base.html.twig
  • Navigation bar: app name left, "Account" link + user email + logout right (visible on all pages when logged in)
  • Page title: "Account Settings"
  • Three stacked sections, each with heading, form, and submit button:
  • "Email" section (with pending email banner when applicable)
  • "Password" section
  • "Delete Account" section (red/danger button, warning text)
  • Flash messages at top (reuse existing styles)
  • Uses .auth-container style widened to max-width 500px

Testing

Functional tests in AccountControllerTest: - Renders account page for authenticated users - Redirects to login for unauthenticated users - Email change: validates form, stores pending email, sends verification email - Cancel email change: clears pending email and token - Email verification: confirms token, updates email - Password change: validates current password, updates password - Account deletion: validates password, removes user, logs out

Red/green approach: write failing tests first, then implement.