Passed
Push — Auth ( d58f03...e4eb07 )
by Stone
01:48
created

UserModel::isAccountPasswordBlocked()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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