DatabaseTokenRepository::recentlyCreatedToken()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 5
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 8
ccs 6
cts 6
cp 1
crap 2
rs 10
1
<?php
2
3
namespace EmailChangeVerification\Token;
4
5
use EmailChangeVerification\User\HasEmailChangeVerification as HasEmailChangeVerificationContract;
6
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
7
use Illuminate\Database\ConnectionInterface;
8
use Illuminate\Support\Carbon;
9
use Illuminate\Support\Str;
10
11
class DatabaseTokenRepository implements TokenRepositoryInterface
12
{
13
    /**
14
     * The database connection instance.
15
     *
16
     * @var \Illuminate\Database\ConnectionInterface
17
     */
18
    protected $connection;
19
20
    /**
21
     * The Hasher implementation.
22
     *
23
     * @var \Illuminate\Contracts\Hashing\Hasher
24
     */
25
    protected $hasher;
26
27
    /**
28
     * The token database table.
29
     *
30
     * @var string
31
     */
32
    protected $table;
33
34
    /**
35
     * The hashing key.
36
     *
37
     * @var string
38
     */
39
    protected $hashKey;
40
41
    /**
42
     * The number of seconds a token should last.
43
     *
44
     * @var int
45
     */
46
    protected $expires;
47
48
    /**
49
     * Minimum number of seconds before re-redefining the token.
50
     *
51
     * @var int
52
     */
53
    protected $throttle;
54
55
    /**
56
     * Create a new token repository instance.
57
     *
58
     * @param  \Illuminate\Database\ConnectionInterface  $connection
59
     * @param  \Illuminate\Contracts\Hashing\Hasher  $hasher
60
     * @param  string  $table
61
     * @param  string  $hashKey
62
     * @param  int  $expires
63
     * @param  int  $throttle
64
     * @return void
65
     */
66 19
    public function __construct(
67
        ConnectionInterface $connection,
68
        HasherContract $hasher,
69
        $table,
70
        $hashKey,
71
        $expires = 60,
72
        $throttle = 60
73
    ) {
74 19
        $this->table      = $table;
75 19
        $this->hasher     = $hasher;
76 19
        $this->hashKey    = $hashKey;
77 19
        $this->expires    = $expires * 60;
78 19
        $this->connection = $connection;
79 19
        $this->throttle   = $throttle;
80
    }
81
82
    /**
83
     * @inheritDoc
84
     */
85 13
    public function create(HasEmailChangeVerificationContract $user, string $newEmail): string
86
    {
87 13
        $email = $user->getEmailForChangeEmail();
88
89 13
        $this->deleteExisting($user);
90
91
        // We will create a new, random token for the user so that we can e-mail them
92
        // a safe link to the password reset form. Then we will insert a record in
93
        // the database so that we can verify the token within the actual reset.
94 13
        $token = $this->createNewToken();
95
96 13
        $this->getTable()->insert($this->getPayload($email, $newEmail, $token));
97
98 13
        return $token;
99
    }
100
101
    /**
102
     * Delete all existing reset tokens from the database.
103
     *
104
     * @param  HasEmailChangeVerificationContract  $user
105
     * @return int
106
     */
107 13
    protected function deleteExisting(HasEmailChangeVerificationContract $user)
108
    {
109 13
        return $this->getTable()->where('email', $user->getEmailForChangeEmail())->delete();
110
    }
111
112
    /**
113
     * Build the record payload for the table.
114
     *
115
     * @param  string  $email
116
     * @param  string  $newEmail
117
     * @param  string  $token
118
     * @return array
119
     */
120 13
    protected function getPayload(string $email, string $newEmail, string $token)
121
    {
122 13
        return [
123 13
            'email'      => $email,
124 13
            'new_email'  => $newEmail,
125 13
            'token'      => $this->hasher->make($token),
126 13
            'created_at' => new Carbon,
127 13
        ];
128
    }
129
130
    /**
131
     * @inheritDoc
132
     */
133 5
    public function exists(HasEmailChangeVerificationContract $user, string $token, string $newEmail): bool
134
    {
135 5
        $record = (array) $this->getTable()->where(
136 5
            'email',
137 5
            $user->getEmailForChangeEmail()
138 5
        )->where(
139 5
            'new_email',
140 5
            $newEmail
141 5
        )->first();
142
143 5
        return $record                                     &&
0 ignored issues
show
Bug Best Practice introduced by
The expression $record of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
144 5
               !$this->tokenExpired($record['created_at']) &&
145 5
                 $this->hasher->check($token, $record['token']);
146
    }
147
148
    /**
149
     * Determine if the token has expired.
150
     *
151
     * @param  string  $createdAt
152
     * @return bool
153
     */
154 6
    protected function tokenExpired($createdAt)
155
    {
156 6
        return Carbon::parse($createdAt)->addSeconds($this->expires)->isPast();
157
    }
158
159
    /**
160
     * @inheritDoc
161
     */
162 9
    public function recentlyCreatedToken(HasEmailChangeVerificationContract $user): bool
163
    {
164 9
        $record = (array) $this->getTable()->where(
165 9
            'email',
166 9
            $user->getEmailForChangeEmail()
167 9
        )->first();
168
169 9
        return $record && $this->tokenRecentlyCreated($record['created_at']);
0 ignored issues
show
Bug Best Practice introduced by
The expression $record of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
170
    }
171
172
    /**
173
     * @inheritDoc
174
     */
175 2
    public function lastRequestedEmail(HasEmailChangeVerificationContract $user): ?string
176
    {
177 2
        $record = (array) $this->getTable()->where(
178 2
            'email',
179 2
            $user->getEmailForChangeEmail()
180 2
        )->first();
181
182 2
        if ($record && !$this->tokenExpired($record['created_at'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $record of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
183 1
            return $record['new_email'];
184
        }
185
186 1
        return null;
187
    }
188
189
    /**
190
     * Determine if the token was recently created.
191
     *
192
     * @param  string  $createdAt
193
     * @return bool
194
     */
195 1
    protected function tokenRecentlyCreated($createdAt)
196
    {
197 1
        if ($this->throttle <= 0) {
198
            return false;
199
        }
200
201 1
        return Carbon::parse($createdAt)->addSeconds(
202 1
            $this->throttle
203 1
        )->isFuture();
204
    }
205
206
    /**
207
     * Delete a token record by user.
208
     *
209
     * @param  HasEmailChangeVerificationContract  $user
210
     * @return void
211
     */
212 2
    public function delete(HasEmailChangeVerificationContract $user)
213
    {
214 2
        $this->deleteExisting($user);
215
    }
216
217
    /**
218
     * Delete expired tokens.
219
     *
220
     * @return void
221
     */
222 1
    public function deleteExpired()
223
    {
224 1
        $expiredAt = Carbon::now()->subSeconds($this->expires);
225
226 1
        $this->getTable()->where('created_at', '<', $expiredAt)->delete();
227
    }
228
229
    /**
230
     * Create a new token for the user.
231
     *
232
     * @return string
233
     */
234 13
    public function createNewToken()
235
    {
236 13
        return hash_hmac('sha256', Str::random(40), $this->hashKey);
237
    }
238
239
    /**
240
     * Get the database connection instance.
241
     *
242
     * @return \Illuminate\Database\ConnectionInterface
243
     */
244 2
    public function getConnection()
245
    {
246 2
        return $this->connection;
247
    }
248
249
    /**
250
     * Begin a new database query against the table.
251
     *
252
     * @return \Illuminate\Database\Query\Builder
253
     */
254 14
    protected function getTable()
255
    {
256 14
        return $this->connection->table($this->table);
257
    }
258
259
    /**
260
     * Get the hasher instance.
261
     *
262
     * @return \Illuminate\Contracts\Hashing\Hasher
263
     */
264 1
    public function getHasher()
265
    {
266 1
        return $this->hasher;
267
    }
268
}
269