Passed
Push — Auth ( 4e652a...51f521 )
by Stone
01:54
created

UserModel::getToken()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace App\Models;
4
5
use App\Modules\Token;
0 ignored issues
show
Bug introduced by
The type App\Modules\Token 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 Core\BlogocException;
7
use Core\Constant;
8
use Core\Container;
9
use Core\Model;
10
11
class UserModel extends Model
12
{
13
14
    private $userTbl;
15
    private $roleTbl;
16
17
    /**
18
     * UserModel constructor.
19
     * @param Container $container
20
     */
21
    public function __construct(Container $container)
22
    {
23
        parent::__construct($container);
24
        $this->userTbl = $this->getTablePrefix("users");
25
        $this->roleTbl = $this->getTablePrefix("roles");
26
    }
27
28
    private function baseSqlSelect(): string
29
    {
30
        $sql = "
31
            SELECT idusers, username, avatar, email, surname, name, creation_date, last_update, locked_out, bad_login_time, bad_login_tries, role_name, role_level
32
            FROM $this->userTbl
33
            INNER JOIN $this->roleTbl ON $this->userTbl.roles_idroles = $this->roleTbl.idroles 
34
        ";
35
        return $sql;
36
    }
37
38
    /**
39
     * get the password from the user email. mainly for login purposes
40
     * @param string $email
41
     * @return string
42
     * @throws BlogocException
43
     */
44
    private function getUserPassword(string $email): string
45
    {
46
        if (!$this->isEmailUsed($email)) {
47
            throw new BlogocException("Email not present in Database");
48
        }
49
        $sql = "SELECT password FROM $this->userTbl WHERE email = :email";
50
        $this->query($sql);
51
        $this->bind(':email', $email);
52
        $this->execute();
53
        return $this->stmt->fetchColumn();
54
    }
55
56
    /**
57
     * called when authentication failed
58
     * @param $user
59
     * @throws \Exception
60
     */
61
    private function addToBadLoginTries($user): void
62
    {
63
        $badLoginTries = $user->bad_login_tries + 1;
64
        $sql = "
65
            UPDATE $this->userTbl
66
            SET
67
              bad_login_time = NOW(),
68
              bad_login_tries = :badLoginTries
69
            WHERE idusers = :userId
70
        ";
71
        $this->query($sql);
72
        $this->bind(':badLoginTries', $badLoginTries);
73
        $this->bind(':userId', $user->idusers);
74
        $this->execute();
75
    }
76
77
    /**
78
     * reset the bad login count
79
     * @param $user
80
     * @throws \Exception
81
     */
82
    private function resetBadLogin($user): void
83
    {
84
        $sql = "
85
            UPDATE $this->userTbl
86
            SET
87
              bad_login_tries = 0
88
            WHERE idusers = :userId
89
        ";
90
        $this->query($sql);
91
        $this->bind(':userId', $user->idusers);
92
        $this->execute();
93
    }
94
95
    private function isAccountPasswordBlocked($user)
96
    {
97
        if ($user->bad_login_tries < Constant::NUMBER_OF_BAD_PASSWORD_TRIES) {
98
            //not enough bad tries yet
99
            return false;
100
        }
101
102
        $blockTime = strtotime($user->bad_login_time);
103
        $currentTime = time();
104
        if ($currentTime - $blockTime > Constant::LOCKOUT_MINUTES * 60) {
105
            //we have outlived the timeout, connection authorised
106
            return false;
107
        }
108
        //the account is timed out
109
        return true;
110
    }
111
112
    /**
113
     * Get all the useful data about a user from his ID
114
     * @param int $userId
115
     * @return mixed
116
     * @throws \Exception
117
     */
118
    public function getUserDetailsById(int $userId)
119
    {
120
        $sql = $this->baseSqlSelect();
121
        $sql .= "
122
            WHERE idusers = :userId
123
        ";
124
        $this->query($sql);
125
        $this->bind(':userId', $userId);
126
        $this->execute();
127
        return $this->fetch();
128
    }
129
130
    /**
131
     * Get all the useful data about a user from his mail
132
     * @param string $email
133
     * @return mixed
134
     * @throws BlogocException
135
     */
136
    public function getUserDetailsByEmail(string $email)
137
    {
138
        //check if email is valid for sanity
139
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
140
            $email = htmlspecialchars($email);
141
            throw new BlogocException("invalid email " . $email);
142
        }
143
        $sql = $this->baseSqlSelect();
144
        $sql .= "
145
            WHERE email = :email
146
        ";
147
        $this->query($sql);
148
        $this->bind(':email', $email);
149
        $this->execute();
150
        return $this->fetch();
151
    }
152
153
    /**
154
     * check if the email is present in the database
155
     * @param string $email
156
     * @return bool
157
     * @throws \Exception
158
     */
159
    public function isEmailUsed(string $email)
160
    {
161
        return $this->getUserDetailsByEmail($email) !== false;
162
    }
163
164
    /**
165
     * register a new user
166
     * @param \stdClass $userData
167
     * @return int
168
     * @throws \Exception
169
     */
170
    public function registerUser(\stdClass $userData): int
171
    {
172
173
        //TODO need to get the default user role. Config ??
174
        $passwordHash = password_hash($userData->password, PASSWORD_DEFAULT);
175
176
        $sql = "
177
            INSERT INTO $this->userTbl (username, email, password, surname, name, creation_date, last_update, roles_idroles, locked_out, bad_login_tries)
178
            VALUES (:username, :email, :password, :surname, :name, NOW(), NOW(), :roles_idroles, 0, 0)
179
        ";
180
        $this->query($sql);
181
        $this->bind(':username', $userData->username);
182
        $this->bind(':email', $userData->email);
183
        $this->bind(':password', $passwordHash);
184
        $this->bind(':surname', $userData->surname);
185
        $this->bind(':name', $userData->name);
186
        $this->bind(':roles_idroles', 1);
187
        $this->execute();
188
189
        return (int)$this->dbh->lastInsertId();
190
    }
191
192
    /**
193
     * verify the user connection mail/password and login if ok
194
     * @param string $email
195
     * @param string $password
196
     * @return bool|mixed
197
     * @throws BlogocException
198
     */
199
    public function authenticateUser(string $email, string $password): \stdClass
200
    {
201
        $response = new \stdClass();
202
        $response->success = false;
203
        $response->message = "";
204
205
        $user = $this->getUserDetailsByEmail($email);
206
207
        if ($user === false) //no user exists
208
        {
209
            $response->message = "email doesn't exist, register a new account?";
210
            return $response;
211
        }
212
213
        //check if the user has validated his email
214
        if ($user->locked_out) {
215
            $response->message = "the email has not been verified, please check your inbox or click on 'reset your password'";
216
            return $response;
217
        }
218
219
        if ($this->isAccountPasswordBlocked($user)) {
220
            $response->message = "too many bad passwords, account is blocked for " . Constant::LOCKOUT_MINUTES . " minutes";
221
            return $response;
222
        }
223
224
        if (!password_verify($password, $this->getUserPassword($email))) {
225
            $response->message = "password is incorrect";
226
            $this->addToBadLoginTries($user);
227
            return $response;
228
        }
229
230
231
        //all ok, send user back for login
232
        $this->resetBadLogin($user);
233
        $response->user = $user;
234
        $response->success = true;
235
        return $response;
236
    }
237
238
239
240
}