GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Login::doLogIn()   C
last analyzed

Complexity

Conditions 11
Paths 54

Size

Total Lines 78
Code Lines 39

Duplication

Lines 11
Ratio 14.1 %

Importance

Changes 0
Metric Value
dl 11
loc 78
rs 5.3526
c 0
b 0
f 0
cc 11
eloc 39
nc 54
nop 5

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Login Class
5
 *
6
 * @license    http://opensource.org/licenses/MIT The MIT License (MIT)
7
 * @author     Omar El Gabry <[email protected]>
8
 */
9
10
class Login extends Model{
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
11
12
    /**
13
     * register a new user
14
     *
15
     * @access public
16
     * @param  string  $name
17
     * @param  string  $email
18
     * @param  string  $password
19
     * @param  string  $confirmPassword
20
     * @param  array   $captcha holds the user's text and original captcha in session
21
     * @return bool
22
     *
23
     */
24
    public function register($name, $email, $password, $confirmPassword, $captcha){
25
26
        $isValid = true;
27
        $validation = new Validation();
28
29
        if(!$validation->validate([
30
            "User Name" => [$name, "required|alphaNumWithSpaces|minLen(4)|maxLen(30)"],
31
            "Email" => [$email, "required|email|emailUnique|maxLen(50)"],
32
            'Password' => [$password, "required|equals(".$confirmPassword.")|minLen(6)|password"],
33
            'Password Confirmation' => [$confirmPassword, 'required']])) {
34
35
            $this->errors = $validation->errors();
36
            $isValid = false;
37
        }
38
39
        // validate captcha
40
        if(empty($captcha['user']) || strtolower($captcha['user']) !== strtolower($captcha['session'])){
41
            $this->errors[] = "The entered characters for captcha don't match";
42
            $isValid = false;
43
        }
44
45
        if(!$isValid){
46
            return false;
47
        }
48
49
        $database = Database::openConnection();
50
        $hashedPassword = password_hash($password, PASSWORD_DEFAULT, array('cost' => Config::get('HASH_COST_FACTOR')));
51
52
        // it's very important to use transaction to ensure both:
53
        // 1. user will be inserted to database
54
        // 2. the verification email will be sent
55
        $database->beginTransaction();
56
        $query = "INSERT INTO users (name, email, role, hashed_password, email_token, email_last_verification) ".
57
                 "VALUES (:name, :email, :role, :hashed_password, :email_token, :email_last_verification)";
58
59
        $database->prepare($query);
60
        $database->bindValue(':name', $name);
61
        $database->bindValue(':email', $email);
62
        $database->bindValue(':role', "user");
63
        $database->bindValue(':hashed_password', $hashedPassword);
64
65
        // email token and time of generating it
66
        $token = sha1(uniqid(mt_rand(), true));
67
        $database->bindValue(':email_token', $token);
68
        $database->bindValue(':email_last_verification', time());
69
70
        $database->execute();
71
72
        $id = $database->lastInsertedId();
73
        Email::sendEmail(Config::get('EMAIL_EMAIL_VERIFICATION'), $email, ["name" => $name, "id" => $id], ["email_token" => $token]);
74
75
        $database->commit();
76
77
        return true;
78
    }
79
80
    /**
81
     * login
82
     *
83
     * @param string $email
84
     * @param string $password
85
     * @param bool   $rememberMe
86
     * @param string $userIp
87
     * @param string $userAgent
88
     * @return bool
89
     */
90
    public function doLogIn($email, $password, $rememberMe, $userIp, $userAgent){
91
92
        // 1. check if user is blocked
93
        if($this->isIpBlocked($userIp)) {
94
            $this->errors[] = "Your IP Address has been blocked";
95
            return false;
96
        }
97
98
        // 2. validate only presence
99
        $validation = new Validation();
100 View Code Duplication
        if(!$validation->validate([
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
101
            "Your Email" => [$email, 'required'],
102
            "Your Password" => [$password, 'required']])){
103
            $this->errors = $validation->errors();
104
            return false;
105
        }
106
107
        // 3. check if user has previous failed login attempts
108
        $database = Database::openConnection();
109
        $database->getByUserEmail("failed_logins", $email);
110
        $failedLogin = $database->fetchAssociative();
111
112
        $last_time   = isset($failedLogin["last_failed_login"])? $failedLogin["last_failed_login"]: null;
113
        $count       = isset($failedLogin["failed_login_attempts"])? $failedLogin["failed_login_attempts"]: null;
114
115
        // check if the failed login attempts exceeded limits
116
        // @see Validation::attempts()
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
117 View Code Duplication
        if(!$validation->validate([
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
118
            'Failed Login' => [["last_time" => $last_time, "count" => $count], 'attempts']])){
119
            $this->errors = $validation->errors();
120
            return false;
121
        }
122
123
        // 4. get user from database
124
        $database->prepare("SELECT * FROM users WHERE email = :email AND is_email_activated = 1 LIMIT 1");
125
        $database->bindValue(':email', $email);
126
        $database->execute();
127
        $user = $database->fetchAssociative();
128
129
        $userId = isset($user["id"])? $user["id"]: null;
130
        $hashedPassword = isset($user["hashed_password"])? $user["hashed_password"]: null;
131
132
        // 5. validate data returned from users table
133
        if(!$validation->validate([
134
            "Login" => [["user_id" => $userId, "hashed_password" => $hashedPassword, "password" => $password], 'credentials']])){
135
136
            // if not valid, then increment number of failed logins
137
            $this->incrementFailedLogins($email, $failedLogin);
0 ignored issues
show
Bug introduced by
It seems like $failedLogin defined by $database->fetchAssociative() on line 110 can also be of type boolean; however, Login::incrementFailedLogins() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
138
139
            // also, check if current IP address is trying to login using multiple accounts,
140
            // if so, then block it, if not, just add a new record to database
141
            $this->handleIpFailedLogin($userIp, $email);
142
143
            $this->errors = $validation->errors();
144
            return false;
145
        }
146
147
        // reset session
148
        Session::reset(["user_id" => $userId, "role" => $user["role"], "ip" => $userIp, "user_agent" => $userAgent]);
149
150
        // if remember me checkbox is checked, then save data to cookies as well
151
        if(!empty($rememberMe) && $rememberMe === "rememberme"){
152
153
            // reset cookie, Cookie token usable only once
154
            Cookie::reset($userId);
155
156
        } else {
157
158
            Cookie::remove($userId);
159
        }
160
161
        // if user credentials are valid then,
162
        // reset failed logins & forgotten password tokens
163
        $this->resetFailedLogins($email);
164
        $this->resetPasswordToken($userId);
165
166
        return true;
167
    }
168
169
    /**
170
     * block IP Address
171
     *
172
     * @access private
173
     * @param  string   $userIp
174
     *
175
     */
176
    private function blockIp($userIp){
177
178
        // if user is already blocked, this method won't be triggered
179
        /*if(!$this->isIpBlocked($userIp)){}*/
0 ignored issues
show
Unused Code Comprehensibility introduced by
92% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
180
181
        $database = Database::openConnection();
182
        $database->prepare("INSERT INTO blocked_ips (ip) VALUES (:ip)");
183
184
        $database->bindValue(":ip", $userIp);
185
        $database->execute();
186
    }
187
188
    /**
189
     * is IP Address blocked?
190
     *
191
     * @access private
192
     * @param  string   $userIp
193
     * @return bool
194
     */
195 View Code Duplication
    private function isIpBlocked($userIp){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
196
197
        $database = Database::openConnection();
198
        $database->prepare("SELECT ip FROM blocked_ips WHERE ip = :ip LIMIT 1");
199
200
        $database->bindValue(":ip", $userIp);
201
        $database->execute();
202
203
        return $database->countRows() >= 1;
204
    }
205
206
    /**
207
     * Adds a new record(if not exists) to ip_failed_logins table,
208
     * Also block the IP Address if number of attempts exceeded
209
     *
210
     * @access private
211
     * @param  string   $userIp
212
     * @param  string   $email
213
     */
214
    private function handleIpFailedLogin($userIp, $email){
215
216
        $database = Database::openConnection();
217
        $database->prepare("SELECT ip, user_email FROM ip_failed_logins WHERE ip = :ip ");
218
        $database->bindValue(":ip", $userIp);
219
        $database->execute();
220
221
        $ips   = $database->fetchAllAssociative();
222
        $count = count($ips);
223
224
        // block IP if there were failed login attempts using different emails(>= 10) from the same IP address
225
        if($count >= 10){
226
227
            $this->blockIp($userIp);
228
229
        } else {
230
231
            // check if ip_failed_logins already has a record with current ip + email
232
            // if not, then insert it.
233
            if(!in_array(["ip" => $userIp, "user_email" => $email], $ips, true)){
234
                $database->prepare("INSERT INTO ip_failed_logins (ip, user_email) VALUES (:ip, :user_email)");
235
                $database->bindValue(":ip", $userIp);
236
                $database->bindValue(":user_email", $email);
237
                $database->execute();
238
            }
239
        }
240
    }
241
242
    /**
243
     * Increment number of failed logins.
244
     *
245
     * @access private
246
     * @param  string   $email
247
     * @param  array    $failedLogin It determines if there was a previous record in the database or not
248
     * @throws Exception If couldn't increment failed logins
249
     *
250
     */
251
    private function incrementFailedLogins($email, $failedLogin){
252
253
        $database = Database::openConnection();
254
255
        if(!empty($failedLogin)){
256
            $query = "UPDATE failed_logins SET last_failed_login = :last_failed_login, " .
257
                     "failed_login_attempts = failed_login_attempts+1 WHERE user_email = :user_email";
258
        }else{
259
            $query = "INSERT INTO failed_logins (user_email, last_failed_login, failed_login_attempts) ".
260
                     "VALUES (:user_email, :last_failed_login, 1)";
261
        }
262
263
        // Remember? the user_email we are using here is not a foreign key from users table
264
        // Why? because this will block even un registered users
265
        $database->prepare($query);
266
        $database->bindValue(':last_failed_login', time());
267
        $database->bindValue(':user_email', $email);
268
        $result = $database->execute();
269
270
        if(!$result){
271
            throw new Exception("FAILED LOGIN", "Couldn't increment failed logins of User Email: " . $email, __FILE__, __LINE__);
272
        }
273
    }
274
275
    /**
276
     * Reset failed logins.
277
     *
278
     * @access private
279
     * @param  string   $email
280
     * @throws Exception If couldn't reset failed logins
281
     */
282 View Code Duplication
    private function resetFailedLogins($email){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
283
284
        $database = Database::openConnection();
285
        $query = "UPDATE failed_logins SET last_failed_login = NULL, " .
286
                 "failed_login_attempts = 0 WHERE user_email = :user_email";
287
288
        $database->prepare($query);
289
        $database->bindValue(':user_email', $email);
290
        $result = $database->execute();
291
292
        if(!$result){
293
            throw new Exception("Couldn't reset failed logins for User Email " . $email);
294
        }
295
    }
296
297
    /**
298
     * What if user forgot his password?
299
     *
300
     * @param  string  $email
301
     * @return bool
302
     */
303
    public function forgotPassword($email){
304
305
        $validation = new Validation();
306
        if(!$validation->validate(['Email' => [$email, 'required|email']])) {
307
            $this->errors = $validation->errors();
308
            return false;
309
        }
310
311
        if($this->isEmailExists($email)){
312
313
            // depends on the last query made by isEmailExists()
314
            $database = Database::openConnection();
315
            $user     = $database->fetchAssociative();
316
317
            // If no previous records in forgotten_passwords, So, $forgottenPassword will be FALSE.
318
            $database->getByUserId("forgotten_passwords", $user["id"]);
319
            $forgottenPassword = $database->fetchAssociative();
320
321
            $last_time = isset($forgottenPassword["password_last_reset"])? $forgottenPassword["password_last_reset"]: null;
322
            $count     = isset($forgottenPassword["forgotten_password_attempts"])? $forgottenPassword["forgotten_password_attempts"]: null;
323
324 View Code Duplication
            if(!$validation->validate(['Failed Login' => [["last_time" => $last_time, "count" => $count], 'attempts']])){
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
325
                $this->errors = $validation->errors();
326
                return false;
327
            }
328
329
            // You need to get the new password token from the database after updating/inserting it
330
            $newPasswordToken = $this->generateForgottenPasswordToken($user["id"], $forgottenPassword);
0 ignored issues
show
Bug introduced by
It seems like $forgottenPassword defined by $database->fetchAssociative() on line 319 can also be of type boolean; however, Login::generateForgottenPasswordToken() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
331
332
            Email::sendEmail(Config::get('EMAIL_PASSWORD_RESET'), $user["email"], ["id" => $user["id"], "name" => $user["name"]], $newPasswordToken);
333
        }
334
335
        // This will return true even if the email doesn't exists,
336
        // because you don't want to give any clue
337
        // to (un)authenticated user if email is actually exists or not
338
        return true;
339
    }
340
341
    /**
342
     * Checks if email exists and activated in the database or not
343
     *
344
     * @access private
345
     * @param  string  $email
346
     * @return boolean
347
     *
348
     */
349 View Code Duplication
    private function isEmailExists($email){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
350
351
        // email is already unique in the database,
352
        // So, we can't have more than 2 users with the same emails
353
        $database = Database::openConnection();
354
        $database->prepare("SELECT * FROM users WHERE email = :email AND is_email_activated = 1 LIMIT 1");
355
        $database->bindValue(':email', $email);
356
        $database->execute();
357
358
        return $database->countRows() === 1;
359
    }
360
361
    /**
362
     * Insert or Update(if already exists)
363
     *
364
     * @access private
365
     * @param  integer  $userId
366
     * @param  array    $forgottenPassword  It determines if there was a previous record in the database or not
367
     * @return array    new generated forgotten Password token
368
     * @throws Exception If couldn't generate the token.
369
     */
370
    private function generateForgottenPasswordToken($userId, $forgottenPassword){
371
372
        $database = Database::openConnection();
373
374
        if(!empty($forgottenPassword)){
375
            $query = "UPDATE forgotten_passwords SET password_token = :password_token, " .
376
                     "password_last_reset = :password_last_reset, forgotten_password_attempts = forgotten_password_attempts+1 ".
377
                     "WHERE user_id = :user_id";
378
        }else{
379
            $query = "INSERT INTO forgotten_passwords (user_id, password_token, password_last_reset, forgotten_password_attempts) ".
380
                     "VALUES (:user_id, :password_token, :password_last_reset, 1)";
381
        }
382
383
        // generate random hash for email verification (40 char string)
384
        $passwordToken = sha1(uniqid(mt_rand(), true));
385
386
        $database->prepare($query);
387
        $database->bindValue(':password_token', $passwordToken);
388
        $database->bindValue(':password_last_reset', time());
389
        $database->bindValue(':user_id', $userId);
390
        $result = $database->execute();
391
392
        if(!$result){
393
            throw new Exception("Couldn't generate token");
394
        }
395
396
        return ["password_token" => $passwordToken];
397
    }
398
399
    /**
400
     * Checks if forgotten password token is valid or not.
401
     *
402
     * @access public
403
     * @param  integer  $userId
404
     * @param  string   $passwordToken
405
     * @return boolean
406
     */
407
    public function isForgottenPasswordTokenValid($userId, $passwordToken){
408
409
        if (empty($userId) || empty($passwordToken)) {
410
            return false;
411
        }
412
413
        $database = Database::openConnection();
414
        $database->prepare("SELECT * FROM forgotten_passwords WHERE user_id = :user_id AND password_token = :password_token LIMIT 1");
415
        $database->bindValue(':user_id', $userId);
416
        $database->bindValue(':password_token', $passwordToken);
417
        $database->execute();
418
        $forgottenPassword = $database->fetchAssociative();
419
420
        // It's bad to send the users any passwords, because you can't be sure if the email will be secured,
421
        // Also don't send plain text password,
422
        // So, sending a token that will be expired after 24 hours is better.
423
        $expiry_time = (24 * 60 * 60);
424
        $time_elapsed = time() - $forgottenPassword['password_last_reset'];
425
426
        if ($database->countRows() === 1 && $time_elapsed < $expiry_time) {
427
428
            // reset token only after the user enters his password.
429
            return true;
430
431
        } else if($database->countRows() === 1 && $time_elapsed > $expiry_time){
432
433
            // reset if the user id & token exists in the database, but exceeded the $expiry_time
434
            $this->resetPasswordToken($userId);
435
            return false;
436
437
        }else {
438
439
            // reset the token if invalid,
440
            // But, if the user id was invalid, this won't make any affect on database
441
            $this->resetPasswordToken($userId);
442
            Logger::log("PASSWORD TOKEN", "User ID ". $userId . " is trying to reset password using invalid token: " . $passwordToken, __FILE__, __LINE__);
443
            return false;
444
        }
445
    }
446
447
    /**
448
     * update password after validating the password token.
449
     *
450
     * @access public
451
     * @param  integer  $userId
452
     * @param  string   $password
453
     * @param  string   $confirmPassword
454
     * @return bool
455
     * @throws Exception If password couldn't be updated
456
     *
457
     */
458
    public function updatePassword($userId, $password, $confirmPassword){
459
460
        $validation = new Validation();
461 View Code Duplication
        if(!$validation->validate([
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
462
            'Password' => [$password, "required|equals(".$confirmPassword.")|minLen(6)|password"],
463
            'Password Confirmation' => [$confirmPassword, 'required']])){
464
            $this->errors = $validation->errors();
465
            return false;
466
        }
467
468
        $hashedPassword = password_hash($password, PASSWORD_DEFAULT, array('cost' => Config::get('HASH_COST_FACTOR')));
469
        $database = Database::openConnection();
470
471
        $query = "UPDATE users SET hashed_password = :hashed_password WHERE id = :id LIMIT 1";
472
        $database->prepare($query);
473
        $database->bindValue(':hashed_password', $hashedPassword);
474
        $database->bindValue(':id', $userId);
475
        $result = $database->execute();
476
477
        if(!$result){
478
            throw new Exception("Couldn't update password");
479
        }
480
481
        // resetting the password token comes ONLY after successful updating password
482
        $this->resetPasswordToken($userId);
483
484
        return true;
485
    }
486
487
    /**
488
     * Reset forgotten password token
489
     *
490
     * @access private
491
     * @param  integer   $userId
492
     * @throws Exception  If couldn't reset password token
493
     */
494 View Code Duplication
    private function resetPasswordToken($userId){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
495
496
        $database = Database::openConnection();
497
        $query = "UPDATE forgotten_passwords SET password_token = NULL, " .
498
                 "password_last_reset = NULL, forgotten_password_attempts = 0 ".
499
                 "WHERE user_id = :user_id LIMIT 1";
500
501
        $database->prepare($query);
502
        $database->bindValue(':user_id', $userId);
503
        $result = $database->execute();
504
        if(!$result){
505
            throw new Exception("Couldn't reset password token");
506
        }
507
    }
508
509
    /**
510
     * It checks if the token for email verification is valid or not.
511
     *
512
     * @access public
513
     * @param  integer $userId
514
     * @param  string  $emailToken Email Token
515
     * @return boolean If valid, it will return true, and vice-versa.
516
     *
517
     */
518
    public function isEmailVerificationTokenValid($userId, $emailToken){
519
520
        if (empty($userId) || empty($emailToken)) {
521
            return false;
522
        }
523
524
        $database = Database::openConnection();
525
        $database->prepare("SELECT * FROM users WHERE id = :id LIMIT 1");
526
        $database->bindValue(':id', $userId);
527
        $database->execute();
528
        $user = $database->fetchAssociative();
529
        $isTokenValid = ($user["email_token"] === $emailToken)? true: false;
530
531
        // check if user is already verified
532
        if(!empty($user["is_email_activated"])){
533
            $this->resetEmailVerificationToken($userId, true);
534
            return false;
535
        }
536
537
        // setting expiry time on email verification is much better,
538
        // you can't be sure if the email will be secured,
539
        // also any user can register with email of another person,
540
        // so this person won't be able to register at all!.
541
        $expiry_time = (24 * 60 * 60);
542
        $time_elapsed = time() - $user['email_last_verification'];
543
544
        // token is usable only once.
545
        if($database->countRows() === 1 && $isTokenValid && $time_elapsed < $expiry_time) {
546
547
            $this->resetEmailVerificationToken($userId, true);
548
            return true;
549
550
        }else if($database->countRows() === 1 && $isTokenValid && $time_elapsed > $expiry_time) {
551
552
            $this->resetEmailVerificationToken($userId, false);
553
            return false;
554
555 View Code Duplication
        }else{
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
556
557
            // reset token if invalid,
558
            // But, if the user id was invalid, this won't make any affect on database
559
            $this->resetEmailVerificationToken($userId, false);
560
            Logger::log("EMAIL TOKEN", "User ID ". $userId . " is trying to access using invalid email token " . $emailToken, __FILE__, __LINE__);
561
            return false;
562
        }
563
    }
564
565
    /**
566
     * Reset the email verification token.
567
     * Resetting the token depends on whether the email token was valid or not.
568
     *
569
     * @access private
570
     * @param  integer $userId
571
     * @param boolean $isValid
572
     * @throws Exception If couldn't reset email verification token
573
     */
574 View Code Duplication
    public function resetEmailVerificationToken($userId, $isValid){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
575
576
        $database = Database::openConnection();
577
578
        if($isValid){
579
            $query = "UPDATE users SET email_token = NULL, " .
580
                "email_last_verification = NULL, is_email_activated = 1 ".
581
                "WHERE id = :id LIMIT 1";
582
        }else{
583
            $query = "DELETE FROM users WHERE id = :id";
584
        }
585
586
        $database->prepare($query);
587
        $database->bindValue(':id', $userId);
588
        $result = $database->execute();
589
        if(!$result){
590
            throw new Exception("Couldn't reset email verification token");
591
        }
592
    }
593
594
    /**
595
     * Logout by removing the Session and Cookies.
596
     *
597
     * @access public
598
     * @param  integer $userId
599
     *
600
     */
601
    public function logOut($userId){
602
603
        Session::remove();
604
        Cookie::remove($userId);
605
    }
606
607
}