diff --git a/README.md b/README.md index 250c95a..fa171b1 100644 --- a/README.md +++ b/README.md @@ -225,6 +225,20 @@ Complete list of available validators in the library. Use them in your validatio | regex | Custom regex, e.g. regex:/^[0-9]+$/ | | upper | No lowercase letters (digits ok) | +### Password Validators + +| Validator | Description | +| --------------- | ------------------------------------------------------------------ | +| password | Requires uppercase, lowercase, number and special character | +| numericPassword | Requires only numeric characters, suitable for PIN-style passwords | + +```php +$rules = [ + 'password' => 'required|min:8|max:64|password', + 'pin' => 'required|min:4|max:6|numericPassword', +]; +``` + ### Brazilian Data Validators | Validator | Description | diff --git a/composer.json b/composer.json index 08a6970..baefdf4 100644 --- a/composer.json +++ b/composer.json @@ -87,6 +87,11 @@ "name": "Thales Santos", "email": "thales_sblue@hotmail.com", "role": "Developer" + }, + { + "name": "Luca Mattosinho Teixeira", + "email": "lucamattosinho@gmail.com", + "role": "Developer" } ], "support": { diff --git a/src/DependencyInjection/TraitRule.php b/src/DependencyInjection/TraitRule.php index de19ba8..c3006d6 100644 --- a/src/DependencyInjection/TraitRule.php +++ b/src/DependencyInjection/TraitRule.php @@ -17,7 +17,7 @@ private function methodsNoRuleValue(): array 'validateFileUploadMandatory', 'validateDateBrazil', 'validateDateAmerican', 'validateHour', 'validateTimestamp', 'validateWeekend', 'validateArray', 'validateFieldMandatory', 'validateBoolean', 'validateFloating', 'validateJson', 'validateDateNotFuture', 'validateDateIso8601', - 'validateDateUTCWithoutTimezone', + 'validateDateUTCWithoutTimezone', 'validatePassword', 'validateNumericPassword', ]; } @@ -72,6 +72,7 @@ private static function functionsValidationMtoN(): array 'notSpace' => 'validateSpace', 'noWeekend' => 'validateWeekend', 'numeric' => 'validateNumeric', + 'numericPassword' => 'validateNumericPassword', 'numMax' => 'validateNumMax', 'numMin' => 'validateNumMin', 'numMonth' => 'validateNumMonth', @@ -84,6 +85,8 @@ private static function functionsValidationOtoZ(): array 'ddd' => 'validateDdd', 'optional' => 'validateOptional', 'phone' => 'validatePhone', + 'password' => 'validatePassword', + 'ddd' => 'validateDdd', 'plate' => 'validatePlate', 'regex' => 'validateRegex', 'required' => 'validateFieldMandatory', diff --git a/src/DependencyInjection/TraitRuleString.php b/src/DependencyInjection/TraitRuleString.php index 3b7ceb5..c7992ae 100644 --- a/src/DependencyInjection/TraitRuleString.php +++ b/src/DependencyInjection/TraitRuleString.php @@ -339,6 +339,27 @@ protected function validatePhone(string $field = '', string $value = '', ?string $this->errors[$field] = !empty($message) ? $message : "O campo $field não é um telefone válido!"; } + protected function validatePassword(string $field = '', string $value = '', ?string $message = ''): void + { + if ( + !preg_match('@[A-Z]@', $value) + || !preg_match('@[a-z]@', $value) + || !preg_match('@[0-9]@', $value) + || !preg_match('/[^A-Za-z0-9]/', $value) + ) { + $this->errors[$field] = !empty($message) ? + $message : "O campo $field precisa conter maiúscula, minúscula, número e símbolo!"; + } + } + + protected function validateNumericPassword(string $field = '', string $value = '', ?string $message = ''): void + { + if (!ctype_digit($value)) { + $this->errors[$field] = !empty($message) ? + $message : "O campo $field precisa conter apenas números!"; + } + } + protected function validateRegex( string $rule = '', string $field = '', diff --git a/tests/StringTest.php b/tests/StringTest.php index b28064b..82c5c31 100644 --- a/tests/StringTest.php +++ b/tests/StringTest.php @@ -348,6 +348,75 @@ public function testRegex(): void self::assertValidatorErrorCount(1, $array, $rules); } + public function testPassword(): void + { + $array = [ + 'testValid' => 'Password1!', + 'testErrorNoUppercase' => 'password1!', + 'testErrorNoLowercase' => 'PASSWORD1!', + 'testErrorNoNumber' => 'Password!', + 'testErrorNoSymbol' => 'Password1', + ]; + $rules = array_fill_keys(array_keys($array), 'password'); + + self::assertValidatorErrorCount(4, $array, $rules); + } + + public function testNumericPassword(): void + { + $array = [ + 'testValidWithLeadingZero' => '012345', + 'testValid' => '123456', + 'testErrorLetters' => '123a56', + 'testErrorDecimal' => '123.56', + 'testErrorWhitespace' => '123 56', + 'testErrorEmpty' => '', + ]; + $rules = array_fill_keys(array_keys($array), 'numericPassword'); + + self::assertValidatorErrorCount(4, $array, $rules); + } + + public function testPasswordWithCustomMessage(): void + { + $message = 'A senha não atende aos requisitos de segurança.'; + $validator = $this->validate( + ['password' => 'password1!'], + ['password' => 'password, ' . $message] + ); + + self::assertSame($message, $validator->getErros()['password']); + } + + public function testNumericPasswordWithCustomMessage(): void + { + $message = 'O PIN deve conter apenas números.'; + $validator = $this->validate( + ['pin' => '12a4'], + ['pin' => 'numericPassword, ' . $message] + ); + + self::assertSame($message, $validator->getErros()['pin']); + } + + public function testPasswordValidatorsWithLengthRules(): void + { + $array = [ + 'validPassword' => 'Password1!', + 'shortPassword' => 'Aa1!', + 'validPin' => '0123', + 'longPin' => '1234567', + ]; + $rules = [ + 'validPassword' => 'required|min:8|max:64|password', + 'shortPassword' => 'required|min:8|max:64|password', + 'validPin' => 'required|min:4|max:6|numericPassword', + 'longPin' => 'required|min:4|max:6|numericPassword', + ]; + + self::assertValidatorErrorCount(2, $array, $rules); + } + public function testNotSpace(): void { $array = [