Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
87.50% covered (warning)
87.50%
14 / 16
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
ReAuthenticationService
93.33% covered (success)
93.33%
14 / 15
75.00% covered (warning)
75.00%
3 / 4
9.02
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasFreshAuth
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 markFreshAuth
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 verifyAndRefresh
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
5.03
1<?php
2
3declare(strict_types=1);
4
5/** @license For full copyright and license information, please see the LICENSE.md file. */
6
7namespace App\Core\Security\StepUp;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Modules\User\Domain\Repository\UserRepositoryInterface;
12use Yiisoft\Session\SessionInterface;
13
14/**
15 * Standard Re-Authentication (Step-Up) Service.
16 *
17 * Implements ReAuthenticationServiceInterface validating authentication freshness
18 * and user credentials prior to executing high-risk operations.
19 *
20 * @package App\Core\Security\StepUp
21 */
22final readonly class ReAuthenticationService implements ReAuthenticationServiceInterface
23{
24    public const string SESSION_AUTH_TIME_KEY = '_ammonly_auth_timestamp';
25
26    /**
27     * ReAuthenticationService constructor.
28     *
29     * @param UserRepositoryInterface $userRepository User repository contract.
30     */
31    public function __construct(
32        private UserRepositoryInterface $userRepository
33    ) {
34    }
35
36    /**
37     * {@inheritdoc}
38     */
39    public function hasFreshAuth(SessionInterface $session, int $maxAgeSeconds = self::DEFAULT_FRESHNESS_WINDOW): bool
40    {
41        $timestamp = (int) ($session->get(self::SESSION_AUTH_TIME_KEY) ?? 0);
42        if ($timestamp <= 0) {
43            return false;
44        }
45
46        return (time() - $timestamp) <= max(1, $maxAgeSeconds);
47    }
48
49    /**
50     * {@inheritdoc}
51     */
52    public function markFreshAuth(SessionInterface $session): void
53    {
54        $session->set(self::SESSION_AUTH_TIME_KEY, time());
55    }
56
57    /**
58     * {@inheritdoc}
59     */
60    public function verifyAndRefresh(int $userId, string $password, SessionInterface $session): bool
61    {
62        if (trim($password) === '') {
63            return false;
64        }
65
66        $user = $this->userRepository->findById($userId);
67        if ($user === null || !$user->isActive()) {
68            return false;
69        }
70
71        if (!$user->verifyPassword($password)) {
72            return false;
73        }
74
75        $this->markFreshAuth($session);
76        return true;
77    }
78}