diff --git a/src/Validation/Rules/Confirmed.php b/src/Validation/Rules/Confirmed.php new file mode 100644 index 00000000..99455a9d --- /dev/null +++ b/src/Validation/Rules/Confirmed.php @@ -0,0 +1,23 @@ +getValue(); + $confirmation = $this->data->get($this->confirmationField); + + return $original !== null + && $confirmation !== null + && $original === $confirmation; + } +} diff --git a/src/Validation/Types/Password.php b/src/Validation/Types/Password.php new file mode 100644 index 00000000..529156b1 --- /dev/null +++ b/src/Validation/Types/Password.php @@ -0,0 +1,40 @@ +min(12); + $this->max(48); + + $pattern = '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\w\s]).{12,48}$/'; + $this->regex($pattern); + + return $this; + } + + $this->min(8); + $this->max(12); + + return $this; + } + + public function confirmed(string $confirmationField = 'password_confirmation'): self + { + $this->rules['confirmed'] = new Confirmed($confirmationField); + + return $this; + } +} diff --git a/tests/Unit/Validation/Types/PasswordTest.php b/tests/Unit/Validation/Types/PasswordTest.php new file mode 100644 index 00000000..9346e3e3 --- /dev/null +++ b/tests/Unit/Validation/Types/PasswordTest.php @@ -0,0 +1,89 @@ +setData([ + 'password' => $password, + 'password_confirmation' => $password, + ]) + ->setRules([ + 'password' => Password::required()->secure()->confirmed(), + ]); + + expect($validator->passes())->toBeTrue(); +}); + +it('fails when password confirmation does not match', function (): void { + $password = 'StrongP@ssw0rd!!'; + $validator = (new Validator()) + ->setData([ + 'password' => $password, + 'password_confirmation' => 'WrongP@ssw0rd!!', + ]) + ->setRules([ + 'password' => Password::required()->secure()->confirmed(), + ]); + + expect($validator->fails())->toBeTrue(); + expect(array_keys($validator->failing()))->toContain('password'); +}); + +it('can disable secure defaults', function (): void { + $validator = (new Validator()) + ->setData([ + 'password' => '12345678', + 'password_confirmation' => '12345678', + ]) + ->setRules([ + 'password' => Password::required()->secure(false)->confirmed(), + ]); + + expect($validator->passes())->toBeTrue(); +}); + +it('accepts custom secure closure', function (): void { + $validator = (new Validator()) + ->setData([ + 'password' => 'abcd1234EFGH', + 'password_confirmation' => 'abcd1234EFGH', + ]) + ->setRules([ + 'password' => Password::required()->secure(fn (): bool => false)->confirmed(), + ]); + + expect($validator->passes())->toBeTrue(); +}); + +it('fails when password does not meet default secure regex', function (): void { + $pwd = 'alllowercasepassword'; + $validator = (new Validator()) + ->setData([ + 'password' => $pwd, + 'password_confirmation' => $pwd, + ]) + ->setRules([ + 'password' => Password::required()->secure()->confirmed(), + ]); + + expect($validator->fails())->toBeTrue(); +}); + +it('fails when confirmation field missing', function (): void { + $password = 'StrongP@ssw0rd!!'; + $validator = (new Validator()) + ->setData([ + 'password' => $password, + ]) + ->setRules([ + 'password' => Password::required()->secure()->confirmed(), + ]); + + expect($validator->fails())->toBeTrue(); +});