Skip to content

Add user profile pages with configurable form, templates and password rules - #201

Open
pierredup wants to merge 2 commits into
mainfrom
user-profile-section
Open

Add user profile pages with configurable form, templates and password rules#201
pierredup wants to merge 2 commits into
mainfrom
user-profile-section

Conversation

@pierredup

Copy link
Copy Markdown
Member

What this adds

A user profile section in PlatformBundle, reachable from a new Profile entry leading the user dropdown.

Page Route Path
Profile details solidworx_platform_profile_show /profile
Edit profile solidworx_platform_profile_edit /profile/edit
Change password solidworx_platform_profile_change_password /profile/password

The profile page shows name, surname, email and mobile, and carries a Security card linking to the change-password page and — only when platform.security.two_factor.enabled is on — to the existing 2FA configuration page. Changing a password is its own page, behind the current password, rather than a section of the profile form.

Everything is standard Tabler/Bootstrap 5 markup.

Security model

The profile routes take no user identifier — no {id} in the path, no user in the query string, no hidden field in the form. Every page acts on getUser(), resolved from the session token, so "can user A edit user B's profile" isn't a check that could be forgotten; it's a question the code can't be asked.

On top of that:

  • every page requires IS_AUTHENTICATED_FULLY, so a remember-me cookie alone doesn't open them;
  • the form type is the mass-assignment boundary — roles, enabled, verified and password aren't fields;
  • both forms are Symfony forms, so they carry and check a CSRF token;
  • the change-password form requires the current password (UserPassword, checked against the authenticated user), so a hijacked session can't lock the owner out;
  • the session id is rotated on a successful password change, invalidating a cookie that leaked before it;
  • the change-password form is unmapped: the controller hashes the new password and only the hash reaches the entity;
  • ProfileType carries a UniqueEntity constraint on email, so a taken address is a field error rather than a unique-index violation at flush.

Two things are deliberately left to the application, and documented as such: verifying a changed email address, and signing other devices out.

Configurable

New platform.profile section:

platform:
  profile:
    form_type: SolidWorx\Platform\PlatformBundle\Form\Type\Profile\ProfileType
    templates:
      show: '@SolidWorxPlatform/Profile/show.html.twig'
      edit: '@SolidWorxPlatform/Profile/edit.html.twig'
      change_password: '@SolidWorxPlatform/Profile/change_password.html.twig'
    password:
      min_length: 12
      strength: medium        # none | weak | medium | strong | very_strong
      check_compromised: true
  • The form. Adding a field to a custom user class needs only a FormTypeExtension on ProfileType, which keeps the platform fields and the unique-email constraint. form_type replaces it outright when the shape of the form is different; the value is validated as a FormTypeInterface at container build.
  • The templates. Each page is built from named blocks (profile_detail_rows, profile_security_items, profile_sections_extra, profile_form_fields, password_requirements, …) and show.html.twig exports detail() and security_item() macros, so the common case is extending the shipped template and overriding one block. The config keys also take an unrelated template for full control.
  • The password rules. PasswordPolicyInterface owns both the constraints that validate a submission and the sentences rendered on the page, so the two can't drift apart. Decorate it for a rule the configuration doesn't cover. The breach check is built with skipOnError: true — an outage at haveibeenpwned must never block a rotation.

Also adds a ProfileConfigBuilder for the fluent PHP config format, and regenerates platform-schema.json.

Three changes outside the strict scope

All three are in UPGRADE.md:

  • Model\UserInterface gained setPassword(string): static — the platform has to be able to rotate a password on the user's behalf.
  • Model\User::setMobile() now accepts null — the column has always been nullable and clearing an optional field submits null; it would otherwise be a TypeError.
  • @SolidWorxPlatform/Form/theme.html.twig now {% use %}s bootstrap_5_layout.html.twig — the theme decorates blocks with {{ parent() }}, which Twig forbids in a template that neither extends nor uses another, so the theme could not be registered at all before this. docs/form-types/text-editor.md told applications to register it, so that path was broken too. The rendering test in this PR is what surfaced it.

Tests

ecs, rector and 483 tests (1517 assertions) pass. New coverage:

  • the two form types, the password policy, the strength enum, the menu builder;
  • the config tree (defaults, an invalid form type, an unknown strength, a sub-minimum length) and the config builder;
  • a DI test that cross-checks every #[Autowire(param:)] name against what the extension actually sets — the two sides are strings that nothing else reconciles until compile time;
  • a rendering test that puts all three templates through the real Twig runtime, so a broken block, a renamed macro or a dropped CSRF token fails here.

phpstan reports 8 errors, all generics.notGeneric on @extends AbstractType<…>symfony/form 8.1.6 has no generics. Six already exist on main; the two new ones come from this repo's own AddGenericTemplateExtendsRector, which re-adds the annotation if it's removed (verified). Pre-existing environment mismatch, not a new class of problem.

Copilot AI lite review requested due to automatic review settings September 8, 2026 12:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new profile controllers currently pass FormInterface into Twig where the templates require FormView, and there is also an incorrect menu priority assertion in a new test.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds a configurable user profile feature to PlatformBundle, including profile/detail/edit pages, a dedicated change-password flow with policy-driven validation + UI requirements, and a new “Profile” entry in the user dropdown. The change is integrated via new configuration (platform.profile), new Twig templates, new controllers/form types, and extensive tests/docs.

Changes:

  • Added /profile, /profile/edit, and /profile/password pages (controllers + templates) operating exclusively on the authenticated user.
  • Introduced configurable profile form/template wiring and a centralized PasswordPolicyInterface/implementation for both constraints and displayed requirements.
  • Updated UI/docs/config schema and added comprehensive test coverage for menus, config, forms, password policy, and template rendering.
File summaries
File Description
UPGRADE.md Documents upgrade-impacting interface/model/twig-theme changes and new profile feature.
platform.yaml Adds commented reference section for platform.profile configuration.
platform-schema.json Extends JSON schema to include the new platform.profile section and defaults.
docs/security/profile.md New guide documenting profile pages, security model, customization, and password rules.
docs/security/index.md Links the new profile documentation from security index.
docs/index.md Links the new profile documentation from the main docs index.
docs/frontend/layouts.md Updates user-menu docs to reflect platform-provided Profile entry and priority ordering.
docs/form-types/text-editor.md Fixes the form theme template path to @SolidWorxPlatform/Form/theme.html.twig.
src/Bundle/Ui/templates/Layout/partials/_user_menu.html.twig Updates docs/comments to describe platform-provided Profile entry in dropdown.
src/Bundle/Platform/Controller/BaseController.php Adds currentUser() helper enforcing platform user contract for authenticated-only pages.
src/Bundle/Platform/Controller/Profile/ShowProfile.php New profile landing-page controller rendering configured template and 2FA flag.
src/Bundle/Platform/Controller/Profile/EditProfile.php New edit-profile controller binding configured form type to current user.
src/Bundle/Platform/Controller/Profile/ChangePassword.php New change-password controller using policy + hasher and rotating session on success.
src/Bundle/Platform/Resources/views/Profile/show.html.twig New profile details page template with extensibility blocks and macros.
src/Bundle/Platform/Resources/views/Profile/edit.html.twig New edit-profile template rendering a configurable form type via blocks.
src/Bundle/Platform/Resources/views/Profile/change_password.html.twig New change-password template showing requirements and rendering fields explicitly.
src/Bundle/Platform/Resources/views/Form/theme.html.twig Fixes theme to {% use %} Bootstrap 5 layout so parent() is valid.
src/Bundle/Platform/Resources/config/services.php Registers PasswordPolicyInterface alias to default PasswordPolicy.
src/Bundle/Platform/Model/UserInterface.php Adds setPassword(string): static to support password rotation.
src/Bundle/Platform/Model/User.php Widens setMobile() to accept ?string to allow clearing optional field.
src/Bundle/Platform/Menu/UserMenu.php Introduces PRIORITY_PROFILE and updates docs about menu priority ordering.
src/Bundle/Platform/Menu/ProfileMenuBuilder.php Adds a Profile entry to the user dropdown at PRIORITY_PROFILE.
src/Bundle/Platform/Form/Type/Profile/ProfileType.php New profile edit form type with platform fields and UniqueEntity(email) constraint.
src/Bundle/Platform/Form/Type/Profile/ChangePasswordType.php New unmapped change-password form type validating current password + policy constraints.
src/Bundle/Platform/Security/Password/PasswordPolicyInterface.php Defines contract for password constraints + user-facing requirements.
src/Bundle/Platform/Security/Password/PasswordPolicy.php Default implementation driven by config (length/strength/breach-check).
src/Bundle/Platform/Enum/PasswordStrengthLevel.php New enum mapping config strength levels to Symfony PasswordStrength scores + requirement text.
src/Bundle/Platform/Config/PlatformConfiguration.php Adds platform.profile config tree (form type, templates, password rules).
src/Bundle/Platform/DependencyInjection/SolidWorxPlatformExtension.php Wires profile configuration into container parameters (incl. enum conversion).
src/Bundle/Platform/Config/Builder/ProfileConfigBuilder.php Adds fluent builder for platform.profile section.
src/Bundle/Platform/Config/Builder/PlatformConfigBuilder.php Adds profile() builder integration and emits profile section when set.
tests/Bundle/PlatformBundle/Profile/ProfileTestKernel.php Minimal kernel for rendering profile templates with real Twig/Form runtime.
tests/Bundle/PlatformBundle/Profile/ProfileRenderingTest.php Renders templates and asserts blocks/macros/CSRF/autocomplete/conditional 2FA link behavior.
tests/Bundle/PlatformBundle/Profile/fixtures/build/entrypoints.json Fixtures for Webpack Encore entrypoints used during rendering tests.
tests/Bundle/PlatformBundle/Fixtures/ProfileUser.php Test user entity extending platform base user for form/template tests.
tests/Bundle/PlatformBundle/Fixtures/Form/ConstraintsOptionExtension.php Defines constraints option in TypeTestCase without enabling validation side effects.
tests/Bundle/PlatformBundle/Form/Type/Profile/ProfileTypeTest.php Tests profile form fields, mapping behavior, clearing mobile, and UniqueEntity constraint presence.
tests/Bundle/PlatformBundle/Form/Type/Profile/ChangePasswordTypeTest.php Tests change-password form structure, constraints presence, repeated mismatch, and unchanged-password rejection.
tests/Bundle/PlatformBundle/Security/Password/PasswordPolicyTest.php Tests default policy constraints and requirements text alignment.
tests/Bundle/PlatformBundle/Enum/PasswordStrengthLevelTest.php Tests enum score mapping, requirement presence, and accepted values list.
tests/Bundle/PlatformBundle/Menu/ProfileMenuBuilderTest.php Tests Profile menu entry wiring and relative priority ordering.
tests/Bundle/PlatformBundle/DependencyInjection/ProfileServicesTest.php Validates DI definitions/parameters for profile-related services and defaults.
tests/Bundle/PlatformBundle/Config/PlatformConfigurationTest.php Tests new config defaults and validation for profile form type and password rules.
tests/Bundle/PlatformBundle/Config/Builder/PlatformConfigBuilderTest.php Tests config builder emits profile section only when explicitly configured.
Review details
  • Files reviewed: 44/44 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +92 to +98
return $this->render(
$this->template,
[
'form' => $form,
'user' => $user,
'password_requirements' => $this->passwordPolicy->requirements(),
],
Comment on lines +78 to +83
return $this->render(
$this->template,
[
'form' => $form,
'user' => $user,
],
Comment on lines +78 to +83
public function testItOutranksTheTwoFactorEntry(): void
{
self::assertGreaterThan(
self::priorityOf(TwoFactorMenuBuilder::class),
self::priorityOf(ProfileMenuBuilder::class),
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants