Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.15% covered (success)
96.15%
50 / 52
75.00% covered (warning)
75.00%
6 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
Authenticator
96.15% covered (success)
96.15%
50 / 52
75.00% covered (warning)
75.00%
6 / 8
26
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
 findIdentity
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 findIdentityByToken
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 findUserByLogin
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
3.01
 attemptAuthentication
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
7.02
 authenticate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isBlocked
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hydrateUser
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
9
1<?php
2
3declare(strict_types=1);
4
5namespace App\Domain\Auth\Service;
6
7use App\Domain\Auth\Entity\UserRecord;
8use App\Domain\Auth\ValueObject\AuthCause;
9use App\Domain\Auth\ValueObject\AuthResult;
10use Yiisoft\Auth\IdentityInterface;
11use Yiisoft\Auth\IdentityRepositoryInterface;
12use Yiisoft\Db\Connection\ConnectionInterface;
13use Yiisoft\Db\Query\Query;
14
15 class Authenticator implements IdentityRepositoryInterface
16{
17    private const TABLE_USERS = '{{%users_records}}';
18    private const USER_SELECT_FIELDS = ['id', 'username', 'email', 'password_hash', 'auth_key', 'status', 'created_at', 'updated_at'];
19
20    public function __construct(private ConnectionInterface $db) {}
21
22    public function findIdentity(string $id): ?IdentityInterface
23    {
24        $query = new Query($this->db);
25        $row = $query->select(self::USER_SELECT_FIELDS)
26            ->from(self::TABLE_USERS)
27            ->where(['id' => (int) $id, 'status' => UserRecord::STATUS_ACTIVE])
28            ->one();
29
30        if (!is_array($row)) {
31            return null;
32        }
33
34        return $this->hydrateUser($row);
35    }
36
37    public function findIdentityByToken(string $token, ?string $type = null): ?IdentityInterface
38    {
39        $query = new Query($this->db);
40        $row = $query->select(self::USER_SELECT_FIELDS)
41            ->from(self::TABLE_USERS)
42            ->where(['auth_key' => $token, 'status' => UserRecord::STATUS_ACTIVE])
43            ->one();
44
45        if (!is_array($row)) {
46            return null;
47        }
48
49        return $this->hydrateUser($row);
50    }
51
52    public function findUserByLogin(string $login): ?UserRecord
53    {
54        if ($login === '') {
55            return null;
56        }
57
58        $query = new Query($this->db);
59        $row = $query->select(self::USER_SELECT_FIELDS)
60            ->from(self::TABLE_USERS)
61            ->where(['or', ['username' => $login], ['email' => $login]])
62            ->one();
63
64        if (!is_array($row)) {
65            return null;
66        }
67
68        return $this->hydrateUser($row);
69    }
70
71    public function attemptAuthentication(string $login, string $password, string $ipAddress = ''): AuthResult
72    {
73        $result = new AuthResult(null, AuthCause::LOGIN_NOT_FOUND, null);
74
75        if ($this->isBlocked()) {
76            $result = new AuthResult(null, AuthCause::BLOCKED, null);
77        } elseif ($login !== '' && $password !== '') {
78            $user = $this->findUserByLogin($login);
79            if ($user !== null) {
80                $userId = (int) $user->id;
81                if ((int) $user->status !== UserRecord::STATUS_ACTIVE) {
82                    $result = new AuthResult(null, AuthCause::USER_INACTIVE, $userId);
83                } elseif (!$user->validatePassword($password)) {
84                    $result = new AuthResult(null, AuthCause::INVALID_PASSWORD, $userId);
85                } else {
86                    $result = new AuthResult($user, null, $userId);
87                }
88            }
89        }
90
91        return $result;
92    }
93
94    public function authenticate(string $login, string $password): ?UserRecord
95    {
96        return $this->attemptAuthentication($login, $password)->user;
97    }
98
99    private function isBlocked(): bool
100    {
101        // Rezerwa na system blokad IP/RateLimiter
102        return false;
103    }
104
105    /**
106     * @param array<string, mixed> $row
107     */
108    private function hydrateUser(array $row): UserRecord
109    {
110        return new UserRecord(
111            is_numeric($row['id'] ?? null) ? (int) $row['id'] : 0,
112            is_string($row['username'] ?? null) ? $row['username'] : '',
113            is_string($row['email'] ?? null) ? $row['email'] : '',
114            is_string($row['password_hash'] ?? null) ? $row['password_hash'] : '',
115            is_string($row['auth_key'] ?? null) ? $row['auth_key'] : '',
116            is_numeric($row['status'] ?? null) ? (int) $row['status'] : 0,
117            is_numeric($row['created_at'] ?? null) ? (int) $row['created_at'] : 0,
118            is_numeric($row['updated_at'] ?? null) ? (int) $row['updated_at'] : 0
119        );
120    }
121}