Add user profile pages with configurable form, templates and password rules - #201
Add user profile pages with configurable form, templates and password rules#201pierredup wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
🟡 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/passwordpages (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.
| return $this->render( | ||
| $this->template, | ||
| [ | ||
| 'form' => $form, | ||
| 'user' => $user, | ||
| 'password_requirements' => $this->passwordPolicy->requirements(), | ||
| ], |
| return $this->render( | ||
| $this->template, | ||
| [ | ||
| 'form' => $form, | ||
| 'user' => $user, | ||
| ], |
| public function testItOutranksTheTwoFactorEntry(): void | ||
| { | ||
| self::assertGreaterThan( | ||
| self::priorityOf(TwoFactorMenuBuilder::class), | ||
| self::priorityOf(ProfileMenuBuilder::class), | ||
| ); |
What this adds
A user profile section in
PlatformBundle, reachable from a new Profile entry leading the user dropdown.solidworx_platform_profile_show/profilesolidworx_platform_profile_edit/profile/editsolidworx_platform_profile_change_password/profile/passwordThe 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.enabledis 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, nouserin the query string, no hidden field in the form. Every page acts ongetUser(), 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:
IS_AUTHENTICATED_FULLY, so a remember-me cookie alone doesn't open them;roles,enabled,verifiedandpasswordaren't fields;UserPassword, checked against the authenticated user), so a hijacked session can't lock the owner out;ProfileTypecarries aUniqueEntityconstraint onemail, 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.profilesection:FormTypeExtensiononProfileType, which keeps the platform fields and the unique-email constraint.form_typereplaces it outright when the shape of the form is different; the value is validated as aFormTypeInterfaceat container build.profile_detail_rows,profile_security_items,profile_sections_extra,profile_form_fields,password_requirements, …) andshow.html.twigexportsdetail()andsecurity_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.PasswordPolicyInterfaceowns 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 withskipOnError: true— an outage at haveibeenpwned must never block a rotation.Also adds a
ProfileConfigBuilderfor the fluent PHP config format, and regeneratesplatform-schema.json.Three changes outside the strict scope
All three are in
UPGRADE.md:Model\UserInterfacegainedsetPassword(string): static— the platform has to be able to rotate a password on the user's behalf.Model\User::setMobile()now acceptsnull— the column has always been nullable and clearing an optional field submitsnull; it would otherwise be aTypeError.@SolidWorxPlatform/Form/theme.html.twignow{% use %}sbootstrap_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.mdtold applications to register it, so that path was broken too. The rendering test in this PR is what surfaced it.Tests
ecs,rectorand 483 tests (1517 assertions) pass. New coverage:#[Autowire(param:)]name against what the extension actually sets — the two sides are strings that nothing else reconciles until compile time;phpstanreports 8 errors, allgenerics.notGenericon@extends AbstractType<…>—symfony/form8.1.6 has no generics. Six already exist onmain; the two new ones come from this repo's ownAddGenericTemplateExtendsRector, which re-adds the annotation if it's removed (verified). Pre-existing environment mismatch, not a new class of problem.