luketowers/oc-pwnedpasswords-plugin
An October CMS plugin that checks passwords against the Pwned Passwords (Have I Been Pwned) database to block known-compromised credentials. Adds validation to backend and frontend forms, helping keep accounts secure with minimal setup.
This is a Winter CMS plugin (not a plain Laravel package), so treat it as a Laravel package with a CMS bootstrapping layer on top. It adds a HaveIBeenPwned check — via the k-anonymity range API — to password validation.
composer require winter/wn-pwnedpasswords-plugin
php artisan winter:up # or october:up on October CMS
Then enable Winter.PwnedPasswords under Settings → System → Updates & Plugins (or add it to plugins/ / your composer.json extra.winter provider list if you manage the plugin set declaratively).
Where to look first:
plugins/winter/pwnedpasswords/Plugin.php — this is where the validator extension is registered; note the exact rule alias and any settings/back-end hooks.README.md and lang/en/lang.php — the message shown to users on failure lives there.Settings / backend permission entries — the plugin typically wires itself into the backend user password flow, not just front-end forms.First use case: enforcing the check on backend user passwords. That is the highest-value, zero-code win — install, enable, and backend password changes/resets are validated against the breach corpus. Then extend the same rule to your own front-end registration and password-reset forms.
As a validation rule in your own controllers/forms. The plugin registers a validator extension during boot, so it composes like any Laravel rule:
// Verify the exact alias in Plugin.php — it may be `pwned`, `pwned_password`, etc.
$rules = [
'password' => ['required', 'min:8', 'confirmed', 'pwned'],
];
Use it in FormController/Backend\Widgets\Form validation, in Validator::make() calls, and in custom Plugin::boot() listeners rather than duplicating the HTTP call yourself.
Layered password policy. Pair the breach check with length/entropy rules and October's own password policy so that a leaked-but-complex password is still rejected:
'password' => ['required', 'string', 'min:12', 'pwned', 'confirmed'],
Fail-open vs fail-closed. The rule performs an outbound HTTPS call. Decide explicitly what happens if the HIBP API is unreachable or slow:
Wrap the plugin rule inside a small custom rule if you need this behaviour, since you cannot easily change the plugin's own error handling without forking it.
Caching. Because of k-anonymity, the plugin sends only the first 5 hex chars of the SHA-1 and receives a bucket of suffixes. Cache these buckets (Cache::remember keyed on the 5-char prefix, TTL of hours/days) if you validate passwords at any volume — otherwise every registration/reset is a live round trip.
Testing. In your suite, either swap the rule out for your own fake, or use Laravel's HTTP client fakes if the plugin resolves through the container. For feature tests that aren't about password strength, dropping the rule from the rule array keeps tests hermetic and offline-safe.
Extension: override the user-facing error message through lang/ overrides rather than editing vendor files, and keep the plugin in composer.json rather than vendoring it.
v2.0.0 is a hard rename — this is the breaking change to plan for.
| v1.1.2 and earlier | v2.0.0+ | |
|---|---|---|
| Composer package | luketowers/oc-pwnedpasswords-plugin |
winter/wn-pwnedpasswords-plugin |
| PHP namespace | LukeTowers\PwnedPasswords |
Winter\PwnedPasswords |
Upgrade checklist:
composer remove luketowers/oc-pwnedpasswords-plugin && composer require winter/wn-pwnedpasswords-plugin (a plain composer update will not do this for you).config/, your own Plugin.php files, migrations, seeders, and any class_exists()/App::runningInConsole() guards — for LukeTowers\PwnedPasswords and oc-pwnedpasswords. Update the use statements; a stale import fails at class-load time, not at boot, so it can slip past smoke tests.composer.json extra.october/extra.winter provider entries and any CI scripts referencing the old package name.php artisan cache:clear && php artisan clear-compiled (plus a composer dump-autoload) — October/Winter aggressively caches plugin metadata and stale classmaps are a classic source of "class not found" after a rename.main but unreleased, so v1.1.2 is genuinely the last old-namespace tag. Pin ^2.0 and don't rely on a 1.x patch to pick up the rename.Other pitfalls:
password, 123456789) is actually rejected — a cheap regression guard against the plugin being disabled during a deploy.oc- prefix; if you have tooling that regex-matches plugin package names, update it.How can I help you explore Laravel packages today?