Skip to content

Commit b32c58b

Browse files
authored
Merge pull request laravel-doctrine#508 from rosamarsky/506-fix-password-resets-hashing-issue
[FIX] Hashing password-reset tokens before storing
2 parents 832ebf5 + af5d1ec commit b32c58b

File tree

3 files changed

+61
-23
lines changed

3 files changed

+61
-23
lines changed

src/Auth/Passwords/DoctrineTokenRepository.php

+31-14
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Doctrine\DBAL\Schema\Table;
88
use Illuminate\Auth\Passwords\TokenRepositoryInterface;
99
use Illuminate\Contracts\Auth\CanResetPassword;
10+
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
1011
use Illuminate\Support\Str;
1112

1213
class DoctrineTokenRepository implements TokenRepositoryInterface
@@ -18,6 +19,13 @@ class DoctrineTokenRepository implements TokenRepositoryInterface
1819
*/
1920
protected $connection;
2021

22+
/**
23+
* The Hasher implementation.
24+
*
25+
* @var HasherContract
26+
*/
27+
protected $hasher;
28+
2129
/**
2230
* The token database table.
2331
*
@@ -49,14 +57,22 @@ class DoctrineTokenRepository implements TokenRepositoryInterface
4957
/**
5058
* Create a new token repository instance.
5159
*
52-
* @param Connection $connection
53-
* @param string $table
54-
* @param string $hashKey
55-
* @param int $expires
60+
* @param Connection $connection
61+
* @param HasherContract $hasher
62+
* @param string $table
63+
* @param string $hashKey
64+
* @param int $expires
5665
*/
57-
public function __construct(Connection $connection, $table, $hashKey, $expires = 60, $throttle = 60)
58-
{
66+
public function __construct(
67+
Connection $connection,
68+
HasherContract $hasher,
69+
$table,
70+
$hashKey,
71+
$expires = 60,
72+
$throttle = 60
73+
) {
5974
$this->table = $table;
75+
$this->hasher = $hasher;
6076
$this->hashKey = $hashKey;
6177
$this->expires = $expires * 60;
6278
$this->connection = $connection;
@@ -89,7 +105,7 @@ public function create(CanResetPassword $user)
89105
])
90106
->setParameters([
91107
'email' => $email,
92-
'token' => $token,
108+
'token' => $this->hasher->make($token),
93109
'date' => new Carbon('now')
94110
])
95111
->execute();
@@ -123,27 +139,28 @@ public function exists(CanResetPassword $user, $token)
123139
{
124140
$email = $user->getEmailForPasswordReset();
125141

126-
$token = $this->getTable()
142+
$record = $this->getTable()
127143
->select('*')
128144
->from($this->table)
129145
->where('email = :email')
130-
->andWhere('token = :token')
131146
->setParameter('email', $email)
132-
->setParameter('token', $token)
147+
->setMaxResults(1)
133148
->execute()->fetch();
134149

135-
return $token && !$this->tokenExpired($token);
150+
return $record
151+
&& !$this->tokenExpired($record['created_at'])
152+
&& $this->hasher->check($token, $record['token']);
136153
}
137154

138155
/**
139156
* Determine if the token has expired.
140157
*
141-
* @param array $token
158+
* @param string $createdAt
142159
* @return bool
143160
*/
144-
protected function tokenExpired($token)
161+
protected function tokenExpired($createdAt)
145162
{
146-
$expiresAt = Carbon::parse($token['created_at'])->addSeconds($this->expires);
163+
$expiresAt = Carbon::parse($createdAt)->addSeconds($this->expires);
147164

148165
return $expiresAt->isPast();
149166
}

src/Auth/Passwords/PasswordBrokerManager.php

+11-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace LaravelDoctrine\ORM\Auth\Passwords;
44

5+
use Illuminate\Support\Str;
6+
57
class PasswordBrokerManager extends \Illuminate\Auth\Passwords\PasswordBrokerManager
68
{
79
/**
@@ -13,12 +15,19 @@ class PasswordBrokerManager extends \Illuminate\Auth\Passwords\PasswordBrokerMan
1315
*/
1416
protected function createTokenRepository(array $config)
1517
{
16-
$connection = isset($config['connection']) ? $config['connection'] : null;
18+
$hashKey = $this->app['config']['app.key'];
19+
20+
if (Str::startsWith($hashKey, 'base64:')) {
21+
$hashKey = base64_decode(substr($hashKey, 7));
22+
}
23+
24+
$connection = $config['connection'] ?? null;
1725

1826
return new DoctrineTokenRepository(
1927
$this->app->make('registry')->getConnection($connection),
28+
$this->app->make('hash'),
2029
$config['table'],
21-
$this->app['config']['app.key'],
30+
$hashKey,
2231
$config['expire'],
2332
$config['throttle'] ?? 60
2433
);

tests/Auth/Passwords/DoctrineTokenRepositoryTest.php

+19-7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Doctrine\DBAL\Query\QueryBuilder;
66
use Doctrine\DBAL\Schema\AbstractSchemaManager;
77
use Illuminate\Contracts\Auth\CanResetPassword;
8+
use Illuminate\Contracts\Hashing\Hasher;
89
use LaravelDoctrine\ORM\Auth\Passwords\DoctrineTokenRepository;
910
use Mockery as m;
1011
use Mockery\Mock;
@@ -32,6 +33,11 @@ class DoctrineTokenRepositoryTest extends TestCase
3233
*/
3334
protected $connection;
3435

36+
/**
37+
* @var Mock
38+
*/
39+
protected $hasher;
40+
3541
/**
3642
* @var Mock
3743
*/
@@ -40,6 +46,7 @@ class DoctrineTokenRepositoryTest extends TestCase
4046
protected function setUp(): void
4147
{
4248
$this->connection = m::mock(Connection::class);
49+
$this->hasher = m::mock(Hasher::class);
4350
$this->builder = m::mock(QueryBuilder::class);
4451
$this->schema = m::mock(AbstractSchemaManager::class);
4552

@@ -52,6 +59,7 @@ protected function setUp(): void
5259

5360
$this->repository = new DoctrineTokenRepository(
5461
$this->connection,
62+
$this->hasher,
5563
'password_resets',
5664
'hashkey',
5765
60
@@ -64,6 +72,10 @@ public function test_can_create_a_token()
6472
->twice()
6573
->andReturn($this->builder);
6674

75+
$this->hasher->shouldReceive('make')
76+
->once()
77+
->andReturn('token');
78+
6779
$this->builder->shouldReceive('delete')
6880
->once()
6981
->with('password_resets')
@@ -108,6 +120,11 @@ public function test_can_check_if_exists()
108120
->once()
109121
->andReturn($this->builder);
110122

123+
$this->hasher->shouldReceive('check')
124+
->once()
125+
->with('token', 'token')
126+
->andReturn(true);
127+
111128
$this->builder->shouldReceive('select')
112129
->once()
113130
->with('*')
@@ -123,21 +140,16 @@ public function test_can_check_if_exists()
123140
->with('email = :email')
124141
->andReturnSelf();
125142

126-
$this->builder->shouldReceive('andWhere')
143+
$this->builder->shouldReceive('setMaxResults')
127144
->once()
128-
->with('token = :token')
145+
->with(1)
129146
->andReturnSelf();
130147

131148
$this->builder->shouldReceive('setParameter')
132149
->once()
133150
->with('email', '[email protected]')
134151
->andReturnSelf();
135152

136-
$this->builder->shouldReceive('setParameter')
137-
->once()
138-
->with('token', 'token')
139-
->andReturnSelf();
140-
141153
$this->builder->shouldReceive('execute')
142154
->once()
143155
->andReturnSelf();

0 commit comments

Comments
 (0)