• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

valkyrjaio / valkyrja / 15659546660

15 Jun 2025 04:39AM UTC coverage: 47.202% (-0.4%) from 47.589%
15659546660

push

github

MelechMizrachi
Update Config.

5331 of 11294 relevant lines covered (47.2%)

16.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/Valkyrja/Auth/Repository/Repository.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of the Valkyrja Framework package.
7
 *
8
 * (c) Melech Mizrachi <melechmizrachi@gmail.com>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13

14
namespace Valkyrja\Auth\Repository;
15

16
use Exception;
17
use Valkyrja\Auth\Adapter\Contract\Adapter;
18
use Valkyrja\Auth\Config;
19
use Valkyrja\Auth\Constant\SessionId;
20
use Valkyrja\Auth\Entity\Contract\LockableUser;
21
use Valkyrja\Auth\Entity\Contract\User;
22
use Valkyrja\Auth\Exception\InvalidAuthenticationException;
23
use Valkyrja\Auth\Exception\InvalidCurrentAuthenticationException;
24
use Valkyrja\Auth\Exception\InvalidPasswordConfirmationException;
25
use Valkyrja\Auth\Model\Contract\AuthenticatedUsers;
26
use Valkyrja\Auth\Repository\Contract\Repository as Contract;
27
use Valkyrja\Exception\RuntimeException;
28
use Valkyrja\Http\Message\Request\Contract\ServerRequest;
29
use Valkyrja\Session\Contract\Session as SessionManager;
30
use Valkyrja\Session\Driver\Contract\Driver as Session;
31

32
use function assert;
33
use function is_int;
34
use function is_string;
35
use function serialize;
36
use function unserialize;
37

38
/**
39
 * Class Repository.
40
 *
41
 * @author Melech Mizrachi
42
 */
43
class Repository implements Contract
44
{
45
    /**
46
     * The session.
47
     *
48
     * @var Session
49
     */
50
    protected Session $session;
51

52
    /**
53
     * The user entity.
54
     *
55
     * @var class-string<User>
56
     */
57
    protected string $userEntityName;
58

59
    /**
60
     * The authenticated users model.
61
     *
62
     * @var class-string<AuthenticatedUsers>
63
     */
64
    protected string $usersModel;
65

66
    /**
67
     * The current authenticated user.
68
     *
69
     * @var User|null
70
     */
71
    protected User|null $user = null;
72

73
    /**
74
     * The current authenticated users.
75
     *
76
     * @var AuthenticatedUsers
77
     */
78
    protected AuthenticatedUsers $users;
79

80
    /**
81
     * Determine if a user is authenticated.
82
     *
83
     * @var bool
84
     */
85
    protected bool $isAuthenticated = false;
86

87
    /**
88
     * Repository constructor.
89
     *
90
     * @param class-string<User> $user The user class
91
     */
92
    public function __construct(
93
        protected Adapter $adapter,
94
        SessionManager $session,
95
        protected Config $config,
96
        string $user
97
    ) {
98
        assert(is_a($user, User::class, true));
×
99

100
        $this->session        = $session->use();
×
101
        $this->userEntityName = $user;
×
102

103
        $this->usersModel = $user::getAuthCollection() ?? \Valkyrja\Auth\Model\AuthenticatedUsers::class;
×
104
        $this->users      = new $this->usersModel();
×
105
    }
106

107
    /**
108
     * @inheritDoc
109
     */
110
    public function isAuthenticated(): bool
111
    {
112
        return $this->isAuthenticated;
×
113
    }
114

115
    /**
116
     * @inheritDoc
117
     */
118
    public function getUser(): User
119
    {
120
        return $this->user ?? new $this->userEntityName();
×
121
    }
122

123
    /**
124
     * @inheritDoc
125
     */
126
    public function setUser(User $user): static
127
    {
128
        $this->setAuthenticatedUser($user);
×
129

130
        return $this;
×
131
    }
132

133
    /**
134
     * @inheritDoc
135
     */
136
    public function getUsers(): AuthenticatedUsers
137
    {
138
        if ($this->config->shouldKeepUserFresh) {
×
139
            foreach ($this->users->all() as $user) {
×
140
                $dbUser = $this->adapter->retrieveById($user);
×
141

142
                $user->updateProperties($dbUser->asStorableArray());
×
143
            }
144
        }
145

146
        return $this->users;
×
147
    }
148

149
    /**
150
     * @inheritDoc
151
     */
152
    public function setUsers(AuthenticatedUsers $users): static
153
    {
154
        $this->users = $users;
×
155
        $this->user  = $users->getCurrent();
×
156

157
        $this->isAuthenticated = $users->hasCurrent();
×
158

159
        return $this;
×
160
    }
161

162
    /**
163
     * @inheritDoc
164
     */
165
    public function authenticate(User $user): static
166
    {
167
        if (! $this->adapter->authenticate($user)) {
×
168
            throw new InvalidAuthenticationException('Invalid user credentials.');
×
169
        }
170

171
        $this->setAuthenticatedUser($user);
×
172

173
        return $this;
×
174
    }
175

176
    /**
177
     * @inheritDoc
178
     */
179
    public function authenticateFromSession(): static
180
    {
181
        return $this->authenticateWithUser($this->getUserFromSession());
×
182
    }
183

184
    /**
185
     * @inheritDoc
186
     */
187
    public function authenticateFromRequest(ServerRequest $request): static
188
    {
189
        /** @var class-string<User> $userClassName */
190
        $userClassName = $this->userEntityName;
×
191
        /** @var array<string, mixed> $requestParams */
192
        $requestParams = $request->onlyParsedBody(...$userClassName::getAuthenticationFields());
×
193

194
        if (empty($requestParams)) {
×
195
            throw new InvalidAuthenticationException('No authentication fields');
×
196
        }
197

198
        $requestParams[$userClassName::getPasswordField()]
×
199
            = $request->getParsedBodyParam($userClassName::getPasswordField());
×
200

201
        $user = $userClassName::fromArray($requestParams);
×
202

203
        return $this->authenticate($user);
×
204
    }
205

206
    /**
207
     * @inheritDoc
208
     */
209
    public function unAuthenticate(User|null $user = null): static
210
    {
211
        if ($this->isAuthenticated) {
×
212
            $this->resetAfterUnAuthentication($user);
×
213
        }
214

215
        return $this;
×
216
    }
217

218
    /**
219
     * @inheritDoc
220
     */
221
    public function setSession(): static
222
    {
223
        $this->session->set(
×
224
            $this->userEntityName::getUserSessionId(),
×
225
            serialize($this->users)
×
226
        );
×
227

228
        return $this;
×
229
    }
230

231
    /**
232
     * @inheritDoc
233
     */
234
    public function unsetSession(): static
235
    {
236
        $this->session->remove($this->userEntityName::getUserSessionId());
×
237

238
        return $this;
×
239
    }
240

241
    /**
242
     * @inheritDoc
243
     */
244
    public function register(User $user): static
245
    {
246
        $this->adapter->create($user);
×
247

248
        return $this;
×
249
    }
250

251
    /**
252
     * @inheritDoc
253
     *
254
     * @throws Exception
255
     */
256
    public function forgot(User $user): static
257
    {
258
        $dbUser = $this->adapter->retrieve($user);
×
259

260
        if (! $dbUser) {
×
261
            throw new InvalidAuthenticationException('No user found.');
×
262
        }
263

264
        $this->adapter->updateResetToken($dbUser);
×
265

266
        $user->updateProperties($dbUser->asStorableArray());
×
267

268
        return $this;
×
269
    }
270

271
    /**
272
     * @inheritDoc
273
     *
274
     * @throws Exception
275
     */
276
    public function reset(string $resetToken, string $password): static
277
    {
278
        if (! $user = $this->adapter->retrieveByResetToken(new $this->userEntityName(), $resetToken)) {
×
279
            throw new InvalidAuthenticationException('Invalid reset token.');
×
280
        }
281

282
        $this->adapter->updatePassword($user, $password);
×
283
        $this->setUser($user);
×
284

285
        return $this;
×
286
    }
287

288
    /**
289
     * @inheritDoc
290
     */
291
    public function lock(LockableUser $user): static
292
    {
293
        $this->lockUnlock($user, true);
×
294

295
        return $this;
×
296
    }
297

298
    /**
299
     * @inheritDoc
300
     */
301
    public function unlock(LockableUser $user): static
302
    {
303
        $this->lockUnlock($user, false);
×
304

305
        return $this;
×
306
    }
307

308
    /**
309
     * @inheritDoc
310
     */
311
    public function confirmPassword(string $password): static
312
    {
313
        if ($this->user === null) {
×
314
            throw new InvalidCurrentAuthenticationException('No user currently authenticated.');
×
315
        }
316

317
        if (! $this->adapter->verifyPassword($this->user, $password)) {
×
318
            throw new InvalidPasswordConfirmationException('Invalid password confirmation.');
×
319
        }
320

321
        return $this;
×
322
    }
323

324
    /**
325
     * @inheritDoc
326
     */
327
    public function isReAuthenticationRequired(): bool
328
    {
329
        $passwordConfirmedTimestamp = $this->session->get(SessionId::PASSWORD_CONFIRMED_TIMESTAMP, 0);
×
330

331
        if (! is_int($passwordConfirmedTimestamp)) {
×
332
            throw new RuntimeException('Password confirmation timestamp should be an int');
×
333
        }
334

335
        $confirmedAt = time() - $passwordConfirmedTimestamp;
×
336

337
        $configTimeout = $this->config->passwordTimeout;
×
338

339
        return $confirmedAt > $configTimeout;
×
340
    }
341

342
    /**
343
     * @inheritDoc
344
     */
345
    public function getAdapter(): Adapter
346
    {
347
        return $this->adapter;
×
348
    }
349

350
    /**
351
     * Get the user stored in session.
352
     *
353
     * @throws InvalidAuthenticationException
354
     *
355
     * @return User
356
     */
357
    protected function getUserFromSession(): User
358
    {
359
        if ($this->user !== null) {
×
360
            return $this->user;
×
361
        }
362

363
        $sessionUsersSerialized = $this->session->get($this->userEntityName::getUserSessionId());
×
364

365
        if (! is_string($sessionUsersSerialized)) {
×
366
            $this->resetAfterUnAuthentication();
×
367

368
            throw new InvalidAuthenticationException('Invalid session');
×
369
        }
370

371
        $sessionUsers = unserialize(
×
372
            $sessionUsersSerialized,
×
373
            ['allowed_classes' => [$this->usersModel]]
×
374
        );
×
375

376
        if (! $sessionUsers instanceof $this->usersModel) {
×
377
            $this->resetAfterUnAuthentication();
×
378

379
            throw new InvalidAuthenticationException('No authenticated users');
×
380
        }
381

382
        /** @var AuthenticatedUsers $sessionUsers */
383
        $this->users = $sessionUsers;
×
384

385
        $current = $this->users->getCurrent();
×
386

387
        if (! $current) {
×
388
            $this->resetAfterUnAuthentication();
×
389

390
            throw new InvalidCurrentAuthenticationException('No current authenticated user.');
×
391
            // Debate whether to use this instead since there are session users so just take the first one... but that
392
            // could be incorrect if that should not be the current user amongst a plethora of possibilities. It should
393
            // be up to the app developer on how to handle this.
394
            //
395
            // $current = $this->users->all()[0];
396
            //
397
            // $this->users->setCurrent($current);
398
        }
399

400
        return $this->user = $current;
×
401
    }
402

403
    /**
404
     * Authenticate with a specific user.
405
     *
406
     * @param User $user The user
407
     *
408
     * @throws InvalidAuthenticationException
409
     *
410
     * @return static
411
     */
412
    protected function authenticateWithUser(User $user): static
413
    {
414
        if ($this->config->shouldAlwaysAuthenticate) {
×
415
            $this->ensureUserValidity($user);
×
416

417
            return $this;
×
418
        }
419

420
        if ($this->config->shouldKeepUserFresh) {
×
421
            $user = $this->adapter->retrieveById($user);
×
422
        }
423

424
        $this->setAuthenticatedUser($user);
×
425

426
        return $this;
×
427
    }
428

429
    /**
430
     * Set the authenticated user.
431
     *
432
     * @param User $user The user
433
     *
434
     * @return void
435
     */
436
    protected function setAuthenticatedUser(User $user): void
437
    {
438
        $this->user            = $user;
×
439
        $this->isAuthenticated = true;
×
440

441
        $this->users->setCurrent($user);
×
442
    }
443

444
    /**
445
     * Reset properties and session after un-authentication.
446
     *
447
     * @param User|null $user [optional] The user to un-authenticate
448
     *
449
     * @return void
450
     */
451
    protected function resetAfterUnAuthentication(User|null $user = null): void
452
    {
453
        $this->isAuthenticated = false;
×
454

455
        if ($user) {
×
456
            $this->users->remove($user);
×
457
        }
458

459
        if ($user && $this->users->hasCurrent()) {
×
460
            $this->user = $this->users->getCurrent();
×
461
        } else {
462
            $this->user  = null;
×
463
            $this->users = new $this->usersModel();
×
464

465
            $this->unsetSession();
×
466
        }
467
    }
468

469
    /**
470
     * Lock or unlock a user.
471
     *
472
     * @param LockableUser $user
473
     * @param bool         $lock
474
     *
475
     * @return void
476
     */
477
    protected function lockUnlock(LockableUser $user, bool $lock): void
478
    {
479
        $user->__set($user::getIsLockedField(), $lock);
×
480

481
        $this->adapter->save($user);
×
482
    }
483

484
    /**
485
     * Ensure a tokenized user is still valid.
486
     *
487
     * @param User $user The tokenized user
488
     *
489
     * @throws InvalidAuthenticationException
490
     *
491
     * @return static
492
     */
493
    protected function ensureUserValidity(User $user): static
494
    {
495
        $passwordField = $user::getPasswordField();
×
496
        // Get a fresh user from the database
497
        $dbUser = $this->adapter->retrieveById($user);
×
498

499
        // If the db password does not match the tokenized user password the token is no longer valid
500
        if ($dbUser->__get($passwordField) !== $user->__get($passwordField)) {
×
501
            $this->resetAfterUnAuthentication();
×
502

503
            throw new InvalidAuthenticationException('User is no longer valid.');
×
504
        }
505

506
        if ($this->config->shouldKeepUserFresh) {
×
507
            $this->setAuthenticatedUser($dbUser);
×
508
        }
509

510
        return $this;
×
511
    }
512
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc