Skip to content

feat: add the PasswordRequirement validator - #3

Merged
tyrsson merged 1 commit into
1.0.xfrom
feat/password-requirement
Oct 2, 2026
Merged

tyrsson merged 1 commit into
1.0.xfrom
feat/password-requirement

Conversation

@tyrsson

@tyrsson tyrsson commented Oct 2, 2026

Copy link
Copy Markdown
Member

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

Checklist:

  • An issue has been created for this pull request.
  • I have added tests for my changes.
  • All new and existing tests passed.
  • Documentation has been updated accordingly.
  • This pull request targets the correct branch.

Description

T1 of webware-usermanager/docs/agent-plan-admin-create-user.md (plan section 16.2): Webware\Validator\PasswordRequirement.

  • src/PasswordRequirement.php — final, extends Laminas\Validator\AbstractValidator (v3). Minimum length (mb_strlen) plus upper / lower / digit / special counts (preg_match_all). Every failed rule is reported, not only the first. A count of 0 (or less) imposes no requirement, because no password can hold fewer than zero matches. The five counts are read through one countOption() accessor and held as protected readonly int properties, which the $messageVariables map exposes as %length%/%upper%/%lower%/%digit%/%special%.
  • src/Container/PasswordRequirementFactory.php — merges the defaults at config[$requestedName] with the call-site $options (call-site wins); with no config service it uses $options alone.
  • src/ConfigProvider.php — registers the validator under validators → factories, and declares the provisional defaults length 8, upper 1, lower 1, digit 1, special 1.
  • composer.json / composer.lock — requires laminas/laminas-validator: ^3.18. Not in the task's file list; the class cannot load without it.

Rebuild differences from the axleus original, per 16.2: no axleus dependency; typed $messageTemplates / $messageVariables; own constructor storing the five counts; no setValue() override (the parent's protected one is used), no getOptions(); mb_strlen over strlen; no adapter and no Laminas\Db coupling.

Two deviations from the task text, both forced by the linter/analyzer and resolved at source (no suppression, no baseline):

  • failedRules() carries #[SensitiveParameter] on its $password parameter (mago sensitive-parameter is an error).
  • The constructor forwards the caller's options to AbstractValidator through a local annotated with the base class's option shape, because a plain array<string, mixed> is too general for the parent's documented parameter type. The factory names its unused PSR-11 parameter $requestedName and uses it as the config key.

Motivation and Context

PasswordRequirement is the password validator the admin create-user work (and the later set-password step) builds on. It lives in this package so that webware-usermanager does not have to own it.

The five provisional defaults are the plan's 8 / 1 / 1 / 1 / 1; the plan marks them as the owner's to confirm.

How has this been tested?

All gates on the WSL host with mago 1.51.0 (the version pinned by webware-tools 1.0.0-beta.8):

Gate Exit Result
mago fmt --check 0 all files formatted
mago lint 0 no issues
mago analyze 0 no issues
mago guard 0 no issues
composer test 0 19 tests, 49 assertions
composer test-integration 0 1 test, 1 assertion
composer mutation-test -- --min-msi=95 --min-covered-msi=95 0 mutation code coverage 100%, covered MSI 97%

Line coverage is 100% (69/69 lines, 7/7 methods, 3/3 classes) from composer test-coverage.

One mutant escapes, and it is equivalent: countOption()'s ?? 0 → ?? -1. A required count of -1 and of 0 are indistinguishable, because matches < 0 is never true. Covered by the 95 floor with room to spare.

Unit tests cover: each rule on its own; all rules together; a password meeting every rule; exact-count and one-below boundaries; multibyte input (mb_strlen, not bytes); non-string and null input; string option values; message overrides reaching the base validator; and the factory merge for config-only, call-site-overrides-config, no-config-service and nothing-configured.

Screenshots (if appropriate):

n/a

- src/PasswordRequirement.php: laminas-validator 3 port of the axleus
  validator. Minimum length plus per-character-class counts, every failed
  rule reported rather than only the first, and a count of 0 imposes no
  requirement.
- src/Container/PasswordRequirementFactory.php: merges the package
  defaults from config with the call-site options, call-site winning.
- src/ConfigProvider.php: registers the validator and its factory under
  validators, with provisional 8/1/1/1/1 defaults.
- tests: each rule alone, all rules together, exact and one-below
  boundaries, multibyte input, non-string input, string option values,
  message overrides, and the factory merge for both config sources.
- composer.json / composer.lock: require laminas/laminas-validator ^3.18.

Signed-off-by: Joey Smith <jsmith@webinertia.net>
@codecov

codecov Bot commented Oct 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@tyrsson tyrsson added the enhancement New feature or request label Oct 2, 2026
@tyrsson tyrsson self-assigned this Oct 2, 2026
@tyrsson
tyrsson merged commit 39ade82 into 1.0.x Oct 2, 2026
@tyrsson
tyrsson deleted the feat/password-requirement branch October 2, 2026 23:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant