Rework dictionary validator, refactoring (#20)

* Update meta

* Rewrite to rule-based validation

* New tests and testing framework

* Minor restructure and cleanup

* Update README.md

* Copy-tweaks. Drop PHP 7.3

* Fix message redout in older Laravel-versions
This commit is contained in:
Marcus Olsson 2022-09-08 14:17:02 +02:00 committed by GitHub
parent 1ea671c7fe
commit 3d6e0448e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 343 additions and 268 deletions

2
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,2 @@
github: olssonm
custom: https://marcusolsson.me/kontakta

View File

@ -21,8 +21,6 @@ jobs:
illuminate: ^8.0 illuminate: ^8.0
- php: 7.4 - php: 7.4
illuminate: ^7.0 illuminate: ^7.0
- php: 7.3
illuminate: ^7.0
name: PHP ${{ matrix.php }} - Illuminate ${{ matrix.illuminate }} name: PHP ${{ matrix.php }} - Illuminate ${{ matrix.illuminate }}

View File

@ -1,6 +1,6 @@
# The MIT License (MIT) # The MIT License (MIT)
Copyright (c) 2020 Marcus Olsson <contact@marcusolsson.me> Copyright (c) 2022 Marcus Olsson <contact@marcusolsson.me>
> Permission is hereby granted, free of charge, to any person obtaining a copy > Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal > of this software and associated documentation files (the "Software"), to deal

132
README.md
View File

@ -9,7 +9,7 @@
A simple implementation of zxcvbn for Laravel. This package allows you to access "zxcvbn-related" data on a passphrase in the application and also to use zxcvbn as a standard validator. A simple implementation of zxcvbn for Laravel. This package allows you to access "zxcvbn-related" data on a passphrase in the application and also to use zxcvbn as a standard validator.
Uses [Zxcvbn-PHP](https://github.com/bjeavons/zxcvbn-php) by [@bjeavons](https://github.com/bjeavons) and [@mkopinsky](https://github.com/mkopinsky), which in turn is inspired by [zxcvbn](https://github.com/dropbox/zxcvbn) by [@dropbox](https://github.com/dropbox). Uses [Zxcvbn-PHP](https://github.com/bjeavons/zxcvbn-php) by [@bjeavons](https://github.com/bjeavons), which in turn is inspired by [zxcvbn](https://github.com/dropbox/zxcvbn) by [@dropbox](https://github.com/dropbox).
## Install ## Install
@ -33,9 +33,7 @@ If you've added `Olssonm\Zxcvbn` as an alias, your can access Zxcvbn easily from
### "In app" ### "In app"
```php ``` php
<?php
use Zxcvbn; use Zxcvbn;
class MyClass extends MyOtherClass class MyClass extends MyOtherClass
@ -47,14 +45,29 @@ class MyClass extends MyOtherClass
// array:9 [ // array:9 [
// "password" => "password" // "password" => "password"
// "guesses" => 3 // "guesses" => 3.0
// "guesses_log10" => 0.47712125471966 // "guesses_log10" => 0.47712125471966
// "sequence" => array:1 [] // "sequence" => [],
// "crack_times_seconds" => array:4 [] // "crack_times_seconds" => array:4 [
// "crack_times_display" => array:4 [] // "online_throttling_100_per_hour" => 108.0
// "online_no_throttling_10_per_second" => 0.3
// "offline_slow_hashing_1e4_per_second" => 0.0003
// "offline_fast_hashing_1e10_per_second" => 3.0E-10
// ]
// "crack_times_display" => array:4 [
// "online_throttling_100_per_hour" => "2 minutes"
// "online_no_throttling_10_per_second" => "less than a second"
// "offline_slow_hashing_1e4_per_second" => "less than a second"
// "offline_fast_hashing_1e10_per_second" => "less than a second"
// ]
// "score" => 0 // "score" => 0
// "feedback" => array:2 [] // "feedback" => array:2 [
// "calc_time" => 0.042769908905029 // "warning" => "This is a top-10 common password"
// "suggestions" => array:1 [
// 0 => "Add another word or two. Uncommon words are better."
// ]
// ]
// "calc_time" => 0.020488977432251
// ] // ]
} }
} }
@ -64,71 +77,68 @@ Play around with different passwords and phrases, the results may surprise you.
### As a validator ### As a validator
The package gives you two different validation rules that you may use; `zxcvbn_min` and `zxcvbn_dictionary`. The package makes two types of validations available for your application. `zxcvbn` and `zxcvbn_dictionary`.
#### zxcvbn_min ### zxcvbn
`zxcvbn_min` allows you to set up a rule for minimum score that the value beeing tested should adhere to. With this rule you set the lowest score that the phrase need to score wuth Zxcvbn to pass.
**Syntax** **Syntax**
input' => 'zxcvbn_min:min_value' ``` php
'input' => 'zxcvbn:min_value'
**Example**
```php
<?php
$data = ['password' => 'password'];
$validator = Validator::make($data, [
'password' => 'zxcvbn_min:3|required',
], [
'password.zxcvbn_min' => 'Your password is not strong enough!'
]);
``` ```
In this example the password should at least have a "score" of three (3) to pass the validation. Of course, you should probably use the zxcvbn-library on the front-end too to allow the user to know this before posting the form... **Examples**
#### zxcvbn_dictionary ``` php
$request->validate([
'password' => 'required|zxcvbn:3'
]);
```
This is a bit more interesting. `zxcvbn_dictionary` allows you to input both the users username and/or email, and their password. The validator checks that the password doesn't exist in the username, or that they are too similar. You may also initialize the rule as an object:
``` php
use Olssonm\Zxcvbn\Rules\Zxcvbn;
function rules()
{
return [
'password' => ['required', new Zxcvbn($minScore = 3)]
];
}
```
In this example the password should at least have a "score" of three (3) to pass the validation. Of course, you should probably use the [zxcvbn-library](https://github.com/dropbox/zxcvbn) on the front-end too to allow the user to know this before posting the form.
### zxcvbn_dictionary
This is a bit more interesting. `zxcvbn_dictionary` allows you to input both the users username and/or email together with their password (you need suply one piece of user input). The validator checks that the password doesn't exist in the username, or that they are too similar.
**Syntax** **Syntax**
'input' => 'xcvbn_dictionary:username,email' ``` php
'input' => 'zxcvbn_dictionary:input1,input2'
```
**Example** **Examples**
```php ``` php
<?php $request->validate([
/** 'password' => sprintf('required|zxcvbn_dictionary:%s,%s', $request->username, $request->email)
* Example 1, pass ]);
*/ ```
$password = '31??2sa//"dhjd2askjd19sad19!!&!#"';
$data = [ ``` php
'username' => 'user', use Olssonm\Zxcvbn\Rules\ZxcvbnDictionary;
'email' => 'trash@thedumpster.com'
function rules()
{
return [
'password' => ['required', new ZxcvbnDictionary($this->username)]
]; ];
$validator = Validator::make($password, [ }
'password' => sprintf('required|zxcvbn_dictionary:%s,%s', $data['username'], $data['email'])
]);
dd($validator->passes());
// true
/**
* Example 2, fail
*/
$password = 'mycomplicatedphrase';
$data = [
'username' => 'mycomplicatedphrase',
'email' => 'mycomplicatedphrase@thedumpster.com'
];
$validator = Validator::make($password, [
'password' => sprintf('required|zxcvbn_dictionary:%s,%s', $data['username'], $data['email'])
]);
dd($validator->passes());
// false
``` ```
## Testing ## Testing
@ -147,7 +157,7 @@ $ phpunit
The MIT License (MIT). Please see the [License File](LICENSE.md) for more information. The MIT License (MIT). Please see the [License File](LICENSE.md) for more information.
© 2020 [Marcus Olsson](https://marcusolsson.me). © 2022 [Marcus Olsson](https://marcusolsson.me).
[ico-version]: https://img.shields.io/packagist/v/olssonm/l5-zxcvbn.svg?style=flat-square [ico-version]: https://img.shields.io/packagist/v/olssonm/l5-zxcvbn.svg?style=flat-square

View File

@ -20,13 +20,15 @@
} }
], ],
"require": { "require": {
"php": "^7.3|^8.0", "php": "^7.4|^8.0",
"illuminate/support": "^7.0|^8.0|^9.0", "illuminate/support": "^7.0|^8.0|^9.0",
"bjeavons/zxcvbn-php": "^1.2" "bjeavons/zxcvbn-php": "^1.2"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^8.0|^9.0", "phpunit/phpunit": "^8.0|^9.0",
"orchestra/testbench": ">=4.0" "orchestra/testbench": ">=4.0",
"pestphp/pest": "^1.22",
"squizlabs/php_codesniffer": "^3.7"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
@ -39,11 +41,14 @@
} }
}, },
"scripts": { "scripts": {
"test": "phpunit" "test": "./vendor/bin/pest",
"coverage": "XDEBUG_MODE=coverage; ./vendor/bin/pest --coverage",
"phpsniff": "vendor/bin/phpcs --standard=\"PSR12\" ./src",
"phpfix": "vendor/bin/phpcbf --standard=\"PSR12\" ./src"
}, },
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "4.x-dev" "dev-master": "5.x-dev"
}, },
"laravel": { "laravel": {
"providers": [ "providers": [
@ -52,5 +57,10 @@
} }
}, },
"minimum-stability": "dev", "minimum-stability": "dev",
"prefer-stable": true "prefer-stable": true,
"config": {
"allow-plugins": {
"pestphp/pest-plugin": true
}
}
} }

50
src/Rules/Zxcvbn.php Normal file
View File

@ -0,0 +1,50 @@
<?php
namespace Olssonm\Zxcvbn\Rules;
use Illuminate\Contracts\Validation\Rule;
use ZxcvbnPhp\Zxcvbn as ZxcvbnPhp;
class Zxcvbn implements Rule
{
private $target;
/**
* Create a new rule instance.
*
* @return void
*/
public function __construct($target = 5)
{
$this->target = $target;
}
public static function handle(): string
{
return 'zxcvbn';
}
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
*
* @return bool
*/
public function passes($attribute, $value)
{
$zxcvbn = (new ZxcvbnPhp())->passwordStrength($value);
return ($zxcvbn['score'] >= $this->target);
}
/**
* Get the validation error message.
*
* @return string
*/
public function message()
{
return 'The :attribute is not strong enough.';
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace Olssonm\Zxcvbn\Rules;
use Illuminate\Contracts\Validation\Rule;
use ZxcvbnPhp\Matchers\DictionaryMatch;
class ZxcvbnDictionary implements Rule
{
protected array $input;
/**
* Create a new rule instance.
*
* @return void
*/
public function __construct($input1 = null, $input2 = null)
{
$this->input = array_filter([$input1, $input2]);
}
public static function handle(): string
{
return 'zxcvbn_dictionary';
}
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
*
* @return bool
*/
public function passes($attribute, $value)
{
$matches = DictionaryMatch::match($value, $this->input);
$matches = array_values(array_filter($matches, function ($match) {
return $match->dictionaryName === 'user_inputs';
}));
return count($matches) === 0;
}
/**
* Get the validation error message.
*
* @return string
*/
public function message()
{
return 'The :attribute is too simililar to another field.';
}
}

View File

@ -2,71 +2,15 @@
namespace Olssonm\Zxcvbn; namespace Olssonm\Zxcvbn;
use ZxcvbnPhp\Zxcvbn as ZxcvbnPhp; use Illuminate\Contracts\Validation\Factory;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Olssonm\Zxcvbn\Rules\Zxcvbn;
use Olssonm\Zxcvbn\Rules\ZxcvbnDictionary;
use Validator; use Validator;
use ZxcvbnPhp\Zxcvbn as ZxcvbnPhp;
class ZxcvbnServiceProvider extends ServiceProvider class ZxcvbnServiceProvider extends ServiceProvider
{ {
/**
* Perform post-registration booting of services.
*
* @return void
*/
public function boot()
{
/**
* Extend the Laravel Validator with the "zxcvbn_min" rule
*/
Validator::extend('zxcvbn_min', function($attribute, $value, $parameters) {
$zxcvbn = new ZxcvbnPhp();
$zxcvbn = $zxcvbn->passwordStrength($value);
$target = 5;
if (isset($parameters[0])) {
$target = $parameters[0];
}
return ($zxcvbn['score'] >= $target);
}, 'Your :attribute is not secure enough.');
Validator::replacer('zxcvbn_min', function($message, $attribute) {
$message = str_replace(':attribute', $attribute, $message);
return $message;
});
/**
* Extend the Laravel Validator with the "zxcvbn_dictionary" rule
*/
Validator::extend('zxcvbn_dictionary', function($attribute, $value, $parameters) {
$email = null;
$username = null;
if (isset($parameters[0])) {
$email = $parameters[0];
$username = $parameters[1];
}
$zxcvbn = new ZxcvbnPhp();
$zxcvbn = $zxcvbn->passwordStrength($value, [$username, $email]);
if (isset($zxcvbn['sequence'][0])) {
$dictionary = $zxcvbn['sequence'][0];
if (isset($dictionary->dictionaryName)) {
return false;
}
}
return true;
}, 'Your :attribute is insecure. It either matches a commonly used password, or you have used a similar username/password combination.');
Validator::replacer('zxcvbn_dictionary', function($message, $attribute) {
$message = str_replace(':attribute', $attribute, $message);
return $message;
});
}
/** /**
* Register any package services. * Register any package services.
* *
@ -74,8 +18,18 @@ class ZxcvbnServiceProvider extends ServiceProvider
*/ */
public function register() public function register()
{ {
$this->app->bind('zxcvbn', function() { $this->app->bind('zxcvbn', function () {
return new ZxcvbnPhp(); return new ZxcvbnPhp();
}); });
} }
public function boot()
{
foreach ([Zxcvbn::class, ZxcvbnDictionary::class] as $rule) {
$this->app['validator']->extend($rule::handle(), function ($attribute, $value, $parameters) use ($rule) {
return (new $rule(...$parameters))->passes($attribute, $value);
},
(new $rule())->message());
}
}
} }

5
tests/Pest.php Normal file
View File

@ -0,0 +1,5 @@
<?php
namespace Olssonm\Zxcvbn\Test;
uses(TestCase::class)->in(__DIR__);

21
tests/TestCase.php Normal file
View File

@ -0,0 +1,21 @@
<?php
namespace Olssonm\Zxcvbn\Test;
use Olssonm\Zxcvbn\ZxcvbnServiceProvider;
use Orchestra\Testbench\TestCase as OrchestraTestCase;
abstract class TestCase extends OrchestraTestCase
{
protected function setUp(): void
{
parent::setUp();
}
protected function getPackageProviders($app)
{
return [
ZxcvbnServiceProvider::class
];
}
}

View File

@ -1,160 +1,131 @@
<?php namespace Olssonm\Zxcvbn\Tests; <?php
use Validator; namespace Olssonm\Zxcvbn\Test;
use Zxcvbn; use Illuminate\Support\Facades\Validator;
use Olssonm\Zxcvbn\Facades\Zxcvbn;
use Olssonm\Zxcvbn\Rules\Zxcvbn as ZxcvbnRule;
use Olssonm\Zxcvbn\Rules\ZxcvbnDictionary as ZxcvbnDictionaryRule;
use Olssonm\Zxcvbn\ZxcvbnServiceProvider;
use ZxcvbnPhp\Zxcvbn as ZxcvbnPhp;
class ZxcvbnTest extends \Orchestra\Testbench\TestCase { it('loads package', function () {
$providers = $this->app->getLoadedProviders();
$this->assertTrue(array_key_exists(ZxcvbnServiceProvider::class, $providers));
});
public function setUp(): void { it('loads facade', function () {
parent::setUp(); $facade = $this->app['zxcvbn'];
} $this->assertTrue(is_a($facade, ZxcvbnPhp::class));
});
/** it('can perform zxcvbn basics', function () {
* Load the package $zxcvbn = Zxcvbn::passwordStrength('password');
* @return array the packages
*/
protected function getPackageProviders($app)
{
return [
'Olssonm\Zxcvbn\ZxcvbnServiceProvider'
];
}
/** $testVar1 = Zxcvbn::passwordStrength('test');
* Load the alias
* @return array the aliases
*/
protected function getPackageAliases($app)
{
return [
'Zxcvbn' => 'Olssonm\Zxcvbn\Facades\Zxcvbn'
];
}
/** // Check keys
* Just run som standard tests to see that Zxcvbn is up to snuff and working $this->assertArrayHasKey('score', $testVar1);
* @test $this->assertArrayHasKey('sequence', $testVar1);
*/ $this->assertArrayHasKey('crack_times_seconds', $testVar1);
public function test_zxcvbn_basics() $this->assertArrayHasKey('crack_times_display', $testVar1);
{ $this->assertArrayHasKey('calc_time', $testVar1);
$zxcvbn = Zxcvbn::passwordStrength('password'); $this->assertArrayHasKey('guesses', $testVar1);
$testVar1 = Zxcvbn::passwordStrength('test'); // Check score-value
$this->assertEquals(0, $testVar1['score']);
// Check keys // Run some more tests
$this->assertArrayHasKey('score', $testVar1); $testVar2 = Zxcvbn::passwordStrength('dadaurka');
$this->assertArrayHasKey('sequence', $testVar1); $testVar3 = Zxcvbn::passwordStrength('staple horse battery');
$this->assertArrayHasKey('crack_times_seconds', $testVar1); $testVar4 = Zxcvbn::passwordStrength('7E6k9axB*gwGHa&aZTohmD9Wr&NVs[b4'); //<-- 32
$this->assertArrayHasKey('crack_times_display', $testVar1);
$this->assertArrayHasKey('calc_time', $testVar1);
$this->assertArrayHasKey('guesses', $testVar1);
// Check score-value // Check score-value
$this->assertEquals(0, $testVar1['score']); $this->assertEquals(2, $testVar2['score']);
$this->assertEquals(4, $testVar3['score']);
$this->assertEquals(4, $testVar4['score']);
});
// Run some more tests it('can validate min-rule', function () {
$testVar2 = Zxcvbn::passwordStrength('dadaurka'); // Fails: returns message
$testVar3 = Zxcvbn::passwordStrength('staple horse battery'); $this->assertEquals('Just a test message', min_validation('test', 4, 'Just a test message'));
$testVar4 = Zxcvbn::passwordStrength('7E6k9axB*gwGHa&aZTohmD9Wr&NVs[b4'); //<-- 32 $this->assertEquals('Just another test message', min_validation('test', 4, 'Just another test message'));
$this->assertEquals('The password is not strong enough.', min_validation('staple horse battery', 5, null));
// Check score-value // Passes: returns true
$this->assertEquals(2, $testVar2['score']); $this->assertEquals(true, min_validation('test', 0));
$this->assertEquals(4, $testVar3['score']); $this->assertEquals(true, min_validation('staple horse battery', 3));
$this->assertEquals(4, $testVar4['score']); $this->assertEquals(true, min_validation('staple horse battery', 4));
} });
/** @test */ it('can validate dictionary-rule', function () {
public function test_password_strength() // Fails: returns message
{ $this->assertEquals('The password is too simililar to another field.', dictionary_validation('dadaurka', 'test@test.com', 'dadaurka', null));
// Standard tests $this->assertEquals('The password is too simililar to another field.', dictionary_validation('dadaurka', 'dadaurka', null, null));
$this->assertEquals(true, $this->validate_without_message_min('test', 0)); $this->assertEquals('Just a message', dictionary_validation('test', 'test@test.com', 'test', 'Just a message'));
$this->assertEquals(false, $this->validate_without_message_min('test', 4));
$this->assertEquals(true, $this->validate_without_message_min('staple horse battery', 3)); // Passes: returns true
$this->assertEquals(true, $this->validate_without_message_min('staple horse battery', 4)); $this->assertEquals(true, dictionary_validation('d5=:r+AEl5?+', 'dadaurka@test.com', 'dadaurka', null));
$this->assertEquals(false, $this->validate_without_message_min('staple horse battery', 5)); $this->assertEquals(true, dictionary_validation('Mo]R^v@vYo]I', 'myemail@test.com', 'username', null));
} $this->assertEquals(true, dictionary_validation('%!/%^Qz1q&KH', 'trash@thedumpster.com', 'username', null));
$this->assertEquals(true, dictionary_validation('O`l}/RqR9$.S','trash@thedumpster.com', null, null));
});
/** @test */ it('can validate rules as objects', function() {
public function test_password_strength_with_message() // Pass min-rule, fail dictionary-rule
{ $this->assertEquals('The password is too simililar to another field.', rule_validator(3, 'gagadododaka', 'gagadododaka@test.com', 'gagadododaka', null));
// Standard message
$this->assertEquals('Your password is not secure enough.', $this->validate_with_message_min('staple horse battery', 5, null));
$this->assertEquals('Just a message', $this->validate_with_message_min('test', 4, 'Just a message'));
}
/** @test */ // Fail min-rule, pass dictionary-rule
public function test_password_dictionary() $this->assertEquals('The password is not strong enough.', rule_validator(4, 'test', 'trash@thedumpster.com', 'username', null));
{
// Standard tests
$this->assertEquals(false, $this->validate_without_message_dictionary('password', 'test@test.com', 'test'));
$this->assertEquals(false, $this->validate_without_message_dictionary('test', 'test@test.com', 'test'));
$this->assertEquals(false, $this->validate_without_message_dictionary('721ahsa!', '721ahsa@test.com', '721ahsa'));
$this->assertEquals(true, $this->validate_without_message_dictionary('721ahsa!', 'dadaurka@test.com', 'dadaurka')); // Pass both rules
$this->assertEquals(true, $this->validate_without_message_dictionary('asd912j!', 'myemail@test.com', 'username')); $this->assertEquals('The password is not strong enough.', rule_validator(7, 'O`l}/RqR9$.S', 'trash@thedumpster.com', null));
$this->assertEquals(true, $this->validate_without_message_dictionary('asd912j!', 'trash@thedumpster.com', 'username')); });
$this->assertEquals(true, $this->validate_without_message_dictionary('asd912j!', null, 'username')); /** @note validation helper */
$this->assertEquals(true, $this->validate_without_message_dictionary('asd912j!', null, null)); function min_validation($password, $min, $message = null)
} {
$data = ['password' => $password];
$validator = Validator::make($data, [
'password' => ['required', 'zxcvbn:' . $min],
], $message ? ['password.zxcvbn' => $message] : []);
/** @test */ if (!$validator->passes()) {
public function test_password_dictionary_with_message() $errors = $validator->errors('password');
{
// Standard message
$this->assertEquals('Your password is insecure. It either matches a commonly used password, or you have used a similar username/password combination.', $this->validate_with_message_dictionary('password', 'test@test.com', 'test', null));
$this->assertEquals('Just a message', $this->validate_with_message_dictionary('test', 'test@test.com', 'test', 'Just a message'));
}
/** @note validation helper */
private function validate_without_message_min($password, $min)
{
$data = ['password' => $password];
$validator = Validator::make($data, [
'password' => 'zxcvbn_min:' . $min . '|required',
]);
return $validator->passes();
}
/** @note validation helper */
private function validate_with_message_min($password, $min, $message)
{
$data = ['password' => $password];
$validator = Validator::make($data, [
'password' => 'zxcvbn_min:' . $min . '|required',
], [
'password.zxcvbn_min' => $message
]);
$errors = $validator->errors();
return $errors->first('password'); return $errors->first('password');
} }
/** @note validation helper */ return $validator->passes();
private function validate_without_message_dictionary($password, $email, $username) }
{
$data = ['password' => $password]; /** @note validation helper */
$validator = Validator::make($data, [ function dictionary_validation($password, $email, $username, $message = null)
'password' => 'zxcvbn_dictionary:' . $username . ',' . $email . '|required', {
]); $data = ['password' => $password];
$validator = Validator::make($data, [
return $validator->passes(); 'password' => 'zxcvbn_dictionary:' . $username . ',' . $email . '|required',
} ], $message ? ['password.zxcvbn_dictionary' => $message] : []);
/** @note validation helper */ if (!$validator->passes()) {
private function validate_with_message_dictionary($password, $email, $username, $message) $errors = $validator->errors('password');
{ return $errors->first('password');
$data = ['password' => $password]; }
$validator = Validator::make($data, [
'password' => 'zxcvbn_dictionary:' . $username . ',' . $email . '|required', return $validator->passes();
], [ }
'password.zxcvbn_dictionary' => $message
]); /** @note object rule validator */
function rule_validator($min, $password, $email, $username, $message = null)
$errors = $validator->errors(); {
return $errors->first('password'); $data = ['password' => $password];
} $validator = Validator::make($data, [
'password' => ['required', new ZxcvbnDictionaryRule($username, $email), new ZxcvbnRule($min)],
], $message ? ['password.zxcvbn' => $message] : []);
if (!$validator->passes()) {
$errors = $validator->errors('password');
return $errors->first('password');
}
return $validator->passes();
} }