Skip to content

Commit 54dc6c7

Browse files
authored
Merge pull request #1141 from CakeDC/feature/reset-password-config
Feature/reset password config
2 parents 43ca719 + 7d427bd commit 54dc6c7

File tree

5 files changed

+113
-8
lines changed

5 files changed

+113
-8
lines changed

Docs/Documentation/Authentication.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,3 +237,34 @@ Add the following to your ``config/users.php`` configuration to change the authe
237237
```php
238238
'Auth.Authentication.serviceLoader' => \App\Loader\AppAuthenticationServiceLoader::class,
239239
```
240+
241+
242+
Password Reset
243+
--------------
244+
245+
When a user requests to reset their password, the plugin needs to find their account. By default, it searches using both the `username` and `email` fields.
246+
You can customize which fields are used for this lookup by configuring the `Users.PasswordReset.findWith` setting in your `config/users.php` file.
247+
248+
The value should be an array of column names that exist in your `users` table. The system will dynamically build a query to search using these fields.
249+
250+
For example, if your application only uses email for identification, you can restrict the search to just the `email` column to avoid errors and improve performance:
251+
252+
```php
253+
// in config/users.php
254+
'Users' => [
255+
'PasswordReset' => [
256+
'findWith' => ['email']
257+
],
258+
]
259+
```
260+
261+
If you need to search by `username` and another custom field, you could configure it like this:
262+
263+
```php
264+
// in config/users.php
265+
'Users' => [
266+
'PasswordReset' => [
267+
'findWith' => ['username', 'legacy_user_id']
268+
],
269+
]
270+
```

config/users.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@
5252
'updateLastLogin' => true,
5353
'lastLoginField' => 'last_login',
5454
],
55+
'PasswordReset' => [
56+
// A list of fields to use when looking up a user for password reset.
57+
'findWith' => ['username', 'email'],
58+
],
5559
'Registration' => [
5660
// determines if the register is enabled
5761
'active' => true,

phpstan-baseline.neon

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,6 @@ parameters:
110110
count: 1
111111
path: src/Model/Behavior/LinkSocialBehavior.php
112112

113-
-
114-
message: "#^Call to an undefined method Cake\\\\ORM\\\\Table\\:\\:findByUsernameOrEmail\\(\\)\\.$#"
115-
count: 1
116-
path: src/Model/Behavior/PasswordBehavior.php
117-
118113
-
119114
message: "#^Method CakeDC\\\\Users\\\\Model\\\\Behavior\\\\PasswordBehavior\\:\\:resetToken\\(\\) should return string but returns Cake\\\\Datasource\\\\EntityInterface\\|false\\.$#"
120115
count: 1

src/Model/Behavior/PasswordBehavior.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,23 @@ protected function _sendValidationEmail($user, $options = [])
122122
*/
123123
protected function _getUser($reference)
124124
{
125-
return $this->_table->findByUsernameOrEmail($reference, $reference)->first();
125+
$findWith = (array)Configure::read('Users.PasswordReset.findWith', ['username', 'email']);
126+
$schema = $this->_table->getSchema();
127+
$orConditions = [];
128+
129+
foreach ($findWith as $field) {
130+
if ($schema->hasColumn($field)) {
131+
$orConditions[$field] = $reference;
132+
}
133+
}
134+
135+
if (empty($orConditions)) {
136+
return null;
137+
}
138+
139+
return $this->_table->find()
140+
->where(['OR' => $orConditions])
141+
->first();
126142
}
127143

128144
/**

tests/TestCase/Model/Behavior/PasswordBehaviorTest.php

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
use CakeDC\Users\Exception\UserNotFoundException;
2424
use CakeDC\Users\Model\Behavior\PasswordBehavior;
2525
use CakeDC\Users\Model\Entity\User;
26+
use InvalidArgumentException;
27+
use ReflectionMethod;
2628
use TestApp\Mailer\OverrideMailer;
2729

2830
/**
@@ -41,6 +43,16 @@ class PasswordBehaviorTest extends TestCase
4143
'plugin.CakeDC/Users.Users',
4244
];
4345

46+
/**
47+
* Table
48+
*/
49+
protected $table;
50+
51+
/**
52+
* Behavior
53+
*/
54+
protected $Behavior;
55+
4456
/**
4557
* setup
4658
*
@@ -122,7 +134,7 @@ public function testResetTokenSendEmail()
122134
*/
123135
public function testResetTokenWithNullParams()
124136
{
125-
$this->expectException(\InvalidArgumentException::class);
137+
$this->expectException(InvalidArgumentException::class);
126138
$this->Behavior->resetToken(null);
127139
}
128140

@@ -131,7 +143,7 @@ public function testResetTokenWithNullParams()
131143
*/
132144
public function testResetTokenNoExpiration()
133145
{
134-
$this->expectException(\InvalidArgumentException::class);
146+
$this->expectException(InvalidArgumentException::class);
135147
$this->expectExceptionMessage('Token expiration cannot be empty');
136148
$this->Behavior->resetToken('ref');
137149
}
@@ -237,4 +249,51 @@ public function testEmailOverride()
237249
]);
238250
$this->assertInstanceOf(User::class, $result);
239251
}
252+
253+
/**
254+
* Test getUser finds by email when configured
255+
*/
256+
public function testGetUserFindsByEmailWhenConfigured()
257+
{
258+
Configure::write('Users.PasswordReset.findWith', ['email']);
259+
$user = $this->_executeGetUser('user-1@test.com');
260+
$this->assertNotNull($user);
261+
262+
$user = $this->_executeGetUser('user-1');
263+
$this->assertNull($user);
264+
}
265+
266+
/**
267+
* Test getUser finds by username and email by default
268+
*/
269+
public function testGetUserFindsByUsernameAndEmailByDefault()
270+
{
271+
$userByEmail = $this->_executeGetUser('user-1@test.com');
272+
$this->assertNotNull($userByEmail);
273+
274+
$userByUsername = $this->_executeGetUser('user-1');
275+
$this->assertNotNull($userByUsername);
276+
277+
$this->assertEquals($userByEmail->id, $userByUsername->id);
278+
}
279+
280+
/**
281+
* Execute getUser method
282+
*
283+
* @param string $reference Reference to get user by
284+
* @return \CakeDC\Users\Model\Entity\User|null
285+
*/
286+
protected function _executeGetUser($reference)
287+
{
288+
if ($this->table->hasBehavior('Password')) {
289+
$this->table->removeBehavior('Password');
290+
}
291+
$this->table->addBehavior('CakeDC/Users.Password');
292+
$realBehavior = $this->table->getBehavior('Password');
293+
294+
$method = new ReflectionMethod(get_class($realBehavior), '_getUser');
295+
$method->setAccessible(true);
296+
297+
return $method->invoke($realBehavior, $reference);
298+
}
240299
}

0 commit comments

Comments
 (0)