Skip to content

Commit e9f1d64

Browse files
committed
Initial Commit
0 parents  commit e9f1d64

File tree

6 files changed

+341
-0
lines changed

6 files changed

+341
-0
lines changed

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/.gitignore export-ignore
2+
/.gitattributes export-ignore

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/vendor
2+
composer.lock

LICENSE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) Johan Rosenson
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<p align="center">
2+
<a href="https://packagist.org/packages/devlop/secure-password-rule"><img src="https://img.shields.io/packagist/v/devlop/secure-password-rule" alt="Latest Stable Version"></a>
3+
<a href="https://github.com/devlop-ab/secure-password-rule/blob/master/LICENSE.md"><img src="https://img.shields.io/packagist/l/devlop/secure-password-rule" alt="License"></a>
4+
</p>
5+
6+
# SecurePasswordRule
7+
8+
An extendable password validation rule for Laravel to make it easy to have the same password requirements across the whole system.
9+
10+
The initial settings are very permissive and pretty much only checks the length of the password, see ```Configuration``` for how to
11+
change it for your needs.
12+
13+
# Installation
14+
15+
```composer require devlop/secure-password-rule```
16+
17+
# Usage
18+
19+
Add it to the ```rules``` of a ```FormRequest```
20+
21+
```
22+
<?php
23+
24+
namespace App\Http\Requests;
25+
26+
use Devlop\SecurePasswordRule\SecurePasswordRule;
27+
use Illuminate\Foundation\Http\FormRequest;
28+
29+
class ChangePasswordRequest extends FormRequest
30+
{
31+
/**
32+
* Get the validation rules that apply to the request.
33+
*
34+
* @return array
35+
*/
36+
public function rules()
37+
{
38+
return [
39+
'new_password' => [
40+
'required',
41+
'string',
42+
new SecurePasswordRule,
43+
],
44+
];
45+
}
46+
}
47+
```
48+
49+
# Configuration
50+
51+
The class is open for extension and does not accept any arguments when instantiating since that would open the possibility of
52+
ending up with different password requirements in different parts of your system.
53+
54+
The recommended way is to create your own sub class of SecurePasswordRule and change the parameters you wish to change, and then
55+
reference that sub class instead in your FormRequests.
56+
57+
```
58+
<?php
59+
60+
namespace App\Rules;
61+
62+
use Devlop\SecurePasswordRule\SecurePasswordRule as BaseSecurePasswordRule;
63+
64+
class SecurePasswordRule extends BaseSecurePasswordRule
65+
{
66+
/**
67+
* Require the use of X special characters
68+
*/
69+
protected int $requireSpecial = 10;
70+
}
71+
```

composer.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "devlop/secure-password-rule",
3+
"description": "Extendable SecurePassword rule for Laravel",
4+
"type": "library",
5+
"license": "MIT",
6+
"authors": [
7+
{
8+
"name": "Johan Rosenson",
9+
"email": "johan@devlop.se"
10+
}
11+
],
12+
"require": {
13+
"php": "^7.4|^8.0",
14+
"laravel/framework": "^7.0|^8.0",
15+
"roave/dont": "^1.1"
16+
},
17+
"autoload": {
18+
"psr-4": {
19+
"Devlop\\SecurePasswordRule\\": "src/"
20+
}
21+
}
22+
}

src/SecurePasswordRule.php

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Devlop\SecurePasswordRule;
6+
7+
use Dont\JustDont;
8+
use Illuminate\Contracts\Validation\Rule;
9+
10+
class SecurePasswordRule implements Rule
11+
{
12+
use JustDont;
13+
14+
/**
15+
* The minimum length
16+
*/
17+
protected int $minLength = 6;
18+
19+
/**
20+
* Require the use of at least X letters
21+
*/
22+
protected int $minLetters = 0;
23+
24+
/**
25+
* Require the use of at least X lowercase letters
26+
*/
27+
protected int $minLowercaseLetters = 0;
28+
29+
/**
30+
* Require the use of at least X uppercase letters
31+
*/
32+
protected int $minUppercaseLetters = 0;
33+
34+
/**
35+
* Require the use of at least X numbers
36+
*/
37+
protected int $minNumbers = 0;
38+
39+
/**
40+
* Require the use of at least X special characters
41+
*/
42+
protected int $minSpecial = 0;
43+
44+
/**
45+
* No more than X of consecutive whitespace characters (null to disable check)
46+
*/
47+
protected ?int $maxConsecutiveWhitespace = 2;
48+
49+
/**
50+
* No more than X of consecutive identical characters (null to disable check)
51+
*/
52+
protected ?int $maxConsecutiveIdentical = null;
53+
54+
/**
55+
* The errors for the password being tested
56+
*
57+
* @var array<string>
58+
*/
59+
protected array $errors = [];
60+
61+
/**
62+
* Create a new rule instance.
63+
*
64+
* @return void
65+
*/
66+
public function __construct()
67+
{
68+
//
69+
}
70+
71+
/**
72+
* Determine if the validation rule passes.
73+
*
74+
* @param string $attribute
75+
* @param mixed $value
76+
* @return bool
77+
*/
78+
public function passes($attribute, $value) : bool
79+
{
80+
if (mb_strlen($value) < $this->minLength) {
81+
// must be of a minimum length
82+
$this->errors[] = $this->minLengthMessage(
83+
'The password must be at least :min characters.',
84+
);
85+
}
86+
87+
if (preg_match_all('/\p{L}/', $value) < $this->minLetters) {
88+
$this->errors[] = $this->minLettersMessage(
89+
'The password must contain letters.|The password must contain at least :min letters.',
90+
);
91+
}
92+
93+
if (preg_match_all('/\p{Ll}/', $value) < $this->minLowercaseLetters) {
94+
$this->errors[] = $this->minLowercaseLettersMessage(
95+
'The password must contain lowercase letters.|The password must contain at least :min lowercase letters.',
96+
);
97+
}
98+
99+
if (preg_match_all('/\p{Lu}/', $value) < $this->minUppercaseLetters) {
100+
$this->errors[] = $this->minUppercaseLettersMessage(
101+
'The password must contain uppercase letters.|The password must contain at least :min uppercase letters.',
102+
);
103+
}
104+
105+
if (preg_match_all('/\p{N}/', $value) < $this->minNumbers) {
106+
$this->errors[] = $this->minNumbersMessage(
107+
'The password must contain numbers.|The password must contain at least :min numbers.',
108+
);
109+
}
110+
111+
if (preg_match_all('/[^\p{L}\p{N}]/', $value) < $this->minSpecial) {
112+
$this->errors[] = $this->minSpecialMessage(
113+
'The password must contain special characters.|The password must contain at least :min special characters.',
114+
);
115+
}
116+
117+
// TODO: add check disallowing whitespace totally
118+
119+
if ($this->maxConsecutiveWhitespace !== null && preg_match('/\s{' . max(0, $this->maxConsecutiveWhitespace) . ',}/', $value) === 1) {
120+
$this->errors[] = $this->maxConsecutiveWhitespaceMessage(
121+
'The password can not contain more than :max consecutive space.|The password can not contain more than :max consecutive spaces.',
122+
);
123+
}
124+
125+
if ($this->maxConsecutiveIdentical !== null && preg_match('/(.)\1{' . max(0, $this->maxConsecutiveIdentical) . ',}/', $value) === 1) {
126+
$this->errors[] = $this->maxConsecutiveIdenticalMessage(
127+
'The password can not contain more than :max consecutive identical character.|The password can not contain more than :max consecutive identical characters.',
128+
);
129+
}
130+
131+
return count($this->errors) === 0;
132+
}
133+
134+
/**
135+
* Get the validation error message.
136+
*/
137+
public function message() : string
138+
{
139+
return __('The password is not good enough: :error', [
140+
'error' => $this->errors[0] ?? '',
141+
]);
142+
}
143+
144+
/**
145+
* Get the validation error message for $minLength
146+
*/
147+
protected function minLengthMessage(string $default) : string
148+
{
149+
return trans_choice($default, $this->minLetters, [
150+
'min' => $this->minLetters,
151+
]);
152+
}
153+
154+
/**
155+
* Get the validation error message for $minLetters
156+
*/
157+
protected function minLettersMessage(string $default) : string
158+
{
159+
return trans_choice($default, $this->minLetters, [
160+
'min' => $this->minLetters,
161+
]);
162+
}
163+
164+
/**
165+
* Get the validation error message for $minLowercaseLetters
166+
*/
167+
protected function minLowercaseLettersMessage(string $default) : string
168+
{
169+
return trans_choice($default, $this->minLowercaseLetters, [
170+
'min' => $this->minLowercaseLetters,
171+
]);
172+
}
173+
174+
/**
175+
* Get the validation error message for $minUppercaseLetters
176+
*/
177+
protected function minUppercaseLettersMessage(string $default) : string
178+
{
179+
return trans_choice($default, $this->minUppercaseLetters, [
180+
'min' => $this->minUppercaseLetters,
181+
]);
182+
}
183+
184+
/**
185+
* Get the validation error message for $minNumbers
186+
*/
187+
protected function minNumbersMessage(string $default) : string
188+
{
189+
return trans_choice($default, $this->minNumbers, [
190+
'min' => $this->minNumbers,
191+
]);
192+
}
193+
194+
/**
195+
* Get the validation error message for $minSpecial
196+
*/
197+
protected function minSpecialMessage(string $default) : string
198+
{
199+
return trans_choice($default, $this->minSpecial, [
200+
'min' => $this->minSpecial,
201+
]);
202+
}
203+
204+
/**
205+
* Get the validation error message for $maxConsecutiveWhitespaceMessage
206+
*/
207+
protected function maxConsecutiveWhitespaceMessage(string $default) : string
208+
{
209+
return trans_choice($default, $this->maxConsecutiveWhitespace, [
210+
'max' => $this->maxConsecutiveWhitespace,
211+
]);
212+
}
213+
214+
/**
215+
* Get the validation error message for $maxConsecutiveIdentical
216+
*/
217+
protected function maxConsecutiveIdenticalMessage(string $default) : string
218+
{
219+
return trans_choice($default, $this->maxConsecutiveIdentical, [
220+
'max' => $this->maxConsecutiveIdentical,
221+
]);
222+
}
223+
}

0 commit comments

Comments
 (0)