Passed
Pull Request — master (#26)
by Jitendra
02:07
created

ApiAuthenticator::findUser()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.2
c 0
b 0
f 0
cc 4
eloc 6
nc 2
nop 3
1
<?php
2
3
namespace PhalconExt\Factory;
4
5
use Phalcon\Config;
0 ignored issues
show
Bug introduced by
The type Phalcon\Config was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
use Phalcon\Db\Adapter as Db;
0 ignored issues
show
Bug introduced by
The type Phalcon\Db\Adapter was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
7
use Phalcon\Security\Random;
0 ignored issues
show
Bug introduced by
The type Phalcon\Security\Random was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use PhalconExt\Contract\ApiAuthenticator as Contract;
9
10
/**
11
 * Factory implementation for ApiAuthenticator. You might want to roll out your own if it doesnt suffice.
12
 *
13
 * Uses `users` table with `id`, `username`, `password`, `scopes`, `created_at` fields.
14
 * And `tokens` table with `id`, `user_id`, `type`, `token`, `created_at`, `expire_at` fields.
15
 */
16
class ApiAuthenticator implements Contract
17
{
18
    protected $db;
19
20
    protected $user;
21
22
    protected $config = [];
23
24
    public function __construct(Db $db)
25
    {
26
        $this->db = $db;
27
    }
28
29
    public function configure(array $config)
30
    {
31
        $this->config = $config;
32
    }
33
34
    public function byCredential(string $username, string $password): bool
35
    {
36
        $this->user = $this->findUser('username', $username, $password);
37
38
        return !empty($this->user);
39
    }
40
41
    protected function findUser(string $column, $value, $password = null): array
42
    {
43
        $user = $this->db->fetchOne("SELECT * FROM users WHERE $column = ?", \PDO::FETCH_ASSOC, [$value]);
44
        $hash = $user['password'] ?? null;
45
46
        if ($password && !\password_verify($password, $hash)) {
47
            return [];
48
        }
49
50
        unset($user['password']);
51
52
        return $user ?: [];
53
    }
54
55
    public function byRefreshToken(string $refreshToken): bool
56
    {
57
        $token = $this->db->fetchOne(
58
            'SELECT * FROM tokens WHERE type = ? AND token = ?',
59
            \PDO::FETCH_ASSOC,
60
            ['refresh', $refreshToken]
61
        );
62
63
        if (!$token /* @todo check if token expired */) {
64
            return false;
65
        }
66
67
        return $this->bySubject($token['user_id']);
68
    }
69
70
    public function bySubject(int $subject): bool
71
    {
72
        $this->user = $this->findUser('id', $subject);
73
74
        return !empty($this->user);
75
    }
76
77
    public function createRefreshToken(): string
78
    {
79
        $this->mustBeAuthenticated();
80
81
        $tokenLen     = \min($this->config['tokenLength'] ?? 0, 32);
82
        $prefix       = \substr($this->config['tokenPrefix'] ?? '', 0, 4);
83
        $random       = (new Random)->bytes($tokenLen - \strlen($prefix));
84
        $refreshToken = $prefix . \bin2hex($random);
85
86
        $this->db->insertAsDict('tokens', [
87
            'type'    => 'refresh',
88
            'token'   => $refreshToken,
89
            'user_id' => $this->user['id'],
90
        ]);
91
92
        return $refreshToken;
93
    }
94
95
    /**
96
     * Throw up if user is not authenticated yet.
97
     *
98
     * @codeCoverageIgnore
99
     *
100
     * @throws \LogicException
101
     */
102
    protected function mustBeAuthenticated()
103
    {
104
        if (!$this->user) {
105
            throw new \LogicException('You must authenticate an user first');
106
        }
107
    }
108
109
    public function getSubject(): int
110
    {
111
        $this->mustBeAuthenticated();
112
113
        return $this->user['id'];
114
    }
115
116
    public function getScopes(): array
117
    {
118
        $this->mustBeAuthenticated();
119
120
        return \explode(',', $this->user['scopes']);
121
    }
122
}
123