Failed Conditions
Pull Request — master (#229)
by Rafael
03:14
created

AuthController::refresh()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 31
ccs 0
cts 19
cp 0
crap 6
rs 9.424
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Canvas\Api\Controllers;
6
7
use Canvas\Models\Users;
8
use Canvas\Models\Sources;
9
use Canvas\Models\UserLinkedSources;
10
use Canvas\Exception\ServerErrorHttpException;
11
use Canvas\Exception\ModelException;
12
use Baka\Auth\Models\Users as BakaUsers;
13
use Canvas\Traits\AuthTrait;
14
use Canvas\Traits\SocialLoginTrait;
15
use Exception;
16
use Phalcon\Http\Response;
17
use Phalcon\Validation\Validator\Confirmation;
18
use Phalcon\Validation\Validator\Email as EmailValidator;
19
use Phalcon\Validation\Validator\PresenceOf;
20
use Phalcon\Validation\Validator\StringLength;
21
use Baka\Auth\Models\Sessions;
22
use Canvas\Auth\Factory;
23
use Canvas\Validation as CanvasValidation;
24
use Canvas\Notifications\ResetPassword;
25
use Canvas\Notifications\PasswordUpdate;
26
use Canvas\Validations\PasswordValidation;
27
use Canvas\Traits\TokenTrait;
28
use Lcobucci\JWT\Builder;
29
use Lcobucci\JWT\Signer\Hmac\Sha512;
30
31
/**
32
 * Class AuthController.
33
 *
34
 * @package Canvas\Api\Controllers
35
 *
36
 * @property Users $userData
37
 * @property Request $request
38
 * @property Config $config
39
 * @property \Baka\Mail\Message $mail
40
 * @property Apps $app
41
 */
42
class AuthController extends \Baka\Auth\AuthController
43
{
44
    /**
45
     * Auth Trait.
46
     */
47
    use AuthTrait;
48
    use TokenTrait;
49
    use SocialLoginTrait;
50
51
    /**
52
     * Setup for this controller.
53
     *
54
     * @return void
55
     */
56
    public function onConstruct()
57
    {
58
        $this->userLinkedSourcesModel = new UserLinkedSources();
59
        $this->userModel = new Users();
60
61
        if (!isset($this->config->jwt)) {
62
            throw new ServerErrorHttpException('You need to configure your app JWT');
0 ignored issues
show
Deprecated Code introduced by
The class Canvas\Exception\ServerErrorHttpException has been deprecated with message: version 0.1.5

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
63
        }
64
    }
65
66
    /**
67
     * User Login.
68
     * @method POST
69
     * @url /v1/auth
70
     *
71
     * @return Response
72
     */
73
    public function login() : Response
74
    {
75
        $request = $this->request->getPostData();
76
77
        $userIp = !defined('API_TESTS') ? $this->request->getClientAddress() : '127.0.0.1'; //help getting the client ip on scrutinizer :(
78
        $admin = 0;
79
        $remember = 1;
80
81
        //Ok let validate user password
82
        $validation = new CanvasValidation();
83
        $validation->add('email', new EmailValidator(['message' => _('The email is not valid')]));
84
        $validation->add('password', new PresenceOf(['message' => _('The password is required.')]));
85
86
        $validation->setFilters('name', 'trim');
87
        $validation->setFilters('password', 'trim');
88
89
        //validate this form for password
90
        $validation->validate($request);
91
92
        $email = $validation->getValue('email');
93
        $password = $validation->getValue('password');
94
95
        /**
96
         * Login the user via ecosystem or app.
97
         */
98
        $auth = Factory::create($this->app->ecosystemAuth());
99
        $userData = $auth::login($email, $password, $remember, $admin, $userIp);
100
        $token = $userData->getToken();
101
102
        //start session
103
        $session = new Sessions();
104
        $session->start($userData, $token['sessionId'], $token['token'], $userIp, 1);
105
106
        return $this->response([
107
            'token' => $token['token'],
108
            'refresh_token' => $token['refresh_token'],
109
            'time' => date('Y-m-d H:i:s'),
110
            'expires' => date('Y-m-d H:i:s', time() + $this->config->jwt->payload->exp),
111
            'refresh_token_expires' => date('Y-m-d H:i:s', time() + 31536000),
112
            'id' => $userData->getId()
113
        ]);
114
    }
115
116
    /**
117
     * User Signup.
118
     *
119
     * @method POST
120
     * @url /v1/users
121
     *
122
     * @return Response
123
     */
124
    public function signup() : Response
125
    {
126
        $user = $this->userModel;
127
128
        $request = $this->request->getPostData();
129
130
        //Ok let validate user password
131
        $validation = new CanvasValidation();
132
        $validation->add('password', new PresenceOf(['message' => _('The password is required.')]));
133
        $validation->add('firstname', new PresenceOf(['message' => _('The firstname is required.')]));
134
        $validation->add('lastname', new PresenceOf(['message' => _('The lastname is required.')]));
135
        $validation->add('email', new EmailValidator(['message' => _('The email is not valid.')]));
136
137
        $validation->add(
138
            'password',
139
            new StringLength([
140
                'min' => 8,
141
                'messageMinimum' => _('Password is too short. Minimum 8 characters.'),
142
            ])
143
        );
144
145
        $validation->add('password', new Confirmation([
146
            'message' => _('Password and confirmation do not match.'),
147
            'with' => 'verify_password',
148
        ]));
149
150
        $validation->setFilters('password', 'trim');
151
        $validation->setFilters('displayname', 'trim');
152
        $validation->setFilters('default_company', 'trim');
153
154
        //validate this form for password
155
        $validation->validate($request);
156
157
        $user->email = $validation->getValue('email');
158
        $user->firstname = $validation->getValue('firstname');
159
        $user->lastname = $validation->getValue('lastname');
160
        $user->password = $validation->getValue('password');
161
        $userIp = !defined('API_TESTS') ? $this->request->getClientAddress() : '127.0.0.1'; //help getting the client ip on scrutinizer :(
162
        $user->displayname = $validation->getValue('displayname');
163
        $user->defaultCompanyName = $validation->getValue('default_company');
164
165
        //user registration
166
        try {
167
            $this->db->begin();
168
169
            $user->signUp();
170
171
            $this->db->commit();
172
        } catch (Exception $e) {
173
            $this->db->rollback();
174
175
            throw new Exception($e->getMessage());
176
        }
177
178
        $token = $user->getToken();
179
180
        //start session
181
        $session = new Sessions();
182
        $session->start($user, $token['sessionId'], $token['token'], $userIp, 1);
183
184
        $authSession = [
185
            'token' => $token['token'],
186
            'time' => date('Y-m-d H:i:s'),
187
            'expires' => date('Y-m-d H:i:s', time() + $this->config->jwt->payload->exp),
188
            'id' => $user->getId(),
189
        ];
190
191
        $user->password = null;
192
        $this->sendEmail($user, 'signup');
193
194
        return $this->response([
195
            'user' => $user,
196
            'session' => $authSession
197
        ]);
198
    }
199
200
    /**
201
     * Refresh user auth.
202
     *
203
     * @return void
0 ignored issues
show
Documentation introduced by
Should the return type not be Response?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
204
     * @todo Validate acces_token and refresh token, session's user email and relogin
205
     */
206
    public function refresh(): Response
207
    {
208
        $request = $this->request->getPostData();
209
        $accessToken = $this->getToken($request['access_token']);
210
        $refreshToken = $this->getToken($request['refresh_token']);
211
212
        // if (time() != $accessToken->getClaim('exp')) {
213
        //     throw new ServerErrorHttpException('Issued Access Token has not expired');
214
        // }
215
216
        //Check if both tokens relate to the same user's email
217
        if ($accessToken->getClaim('sessionId') == $refreshToken->getClaim('sessionId')) {
218
            $user = Users::findFirstOrFail([
219
                'conditions' => 'email = ?0 and is_deleted = 0',
220
                'bind' => [$accessToken->getClaim('email')]
221
            ]);
222
        }
223
224
        //Check if session is active
225
        $session = new Sessions();
226
        $session->check($user, $refreshToken->getClaim('sessionId'), (string)$this->request->getClientAddress(), 1);
0 ignored issues
show
Bug introduced by
The variable $user does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
227
        $token = $this->newAuthSession($refreshToken->getClaim('sessionId'), $user->email);
228
        $session->start($user, $token['sessionId'], $token['token'], (string)$this->request->getClientAddress(), 1);
229
230
        return $this->response([
231
            'token' => $token['token'],
232
            'time' => date('Y-m-d H:i:s'),
233
            'expires' => date('Y-m-d H:i:s', time() + $this->config->jwt->payload->exp),
234
            'id' => $user->getId(),
235
        ]);
236
    }
237
238
    /**
239
     * Create a new session based off the refresh token session id.
240
     *
241
     * @param string $sessionId
242
     * @param string $email
243
     * @return void
0 ignored issues
show
Documentation introduced by
Should the return type not be array?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
244
     */
245
    private function newAuthSession(string $sessionId, string $email)
246
    {
247
        $signer = new Sha512();
248
        $builder = new Builder();
249
        $token = $builder
0 ignored issues
show
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::setIssuer() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::setAudience() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
250
            ->setIssuer(getenv('TOKEN_AUDIENCE'))
251
            ->setAudience(getenv('TOKEN_AUDIENCE'))
252
            ->setId($sessionId, true)
253
            ->setIssuedAt(time())
254
            ->setNotBefore(time() + 500)
255
            ->setExpiration(time() + $this->di->getConfig()->jwt->payload->exp)
256
            ->set('sessionId', $sessionId)
257
            ->set('email', $email)
258
            ->sign($signer, getenv('TOKEN_PASSWORD'))
259
            ->getToken();
260
261
        return [
262
            'sessionId' => $sessionId,
263
            'token' => $token->__toString()
264
        ];
265
    }
266
267
    /**
268
     * Send email to change current email for user.
269
     * @param int $id
270
     * @return Response
271
     */
272
    public function sendEmailChange(int $id): Response
273
    {
274
        //Search for user
275
        $user = Users::getById($id);
276
277
        if (!is_object($user)) {
278
            throw new NotFoundHttpException(_('User not found'));
279
        }
280
281
        //Send email
282
        $this->sendEmail($user, 'email-change');
283
284
        return $this->response($user);
285
    }
286
287
    /**
288
     * Change user's email.
289
     * @param string $hash
290
     * @return Response
291
     */
292
    public function changeUserEmail(string $hash): Response
293
    {
294
        $request = $this->request->getPostData();
295
296
        //Ok let validate user password
297
        $validation = new CanvasValidation();
298
        $validation->add('password', new PresenceOf(['message' => _('The password is required.')]));
299
        $validation->add('new_email', new EmailValidator(['message' => _('The email is not valid.')]));
300
301
        $validation->add(
302
            'password',
303
            new StringLength([
304
                'min' => 8,
305
                'messageMinimum' => _('Password is too short. Minimum 8 characters.'),
306
            ])
307
        );
308
309
        //validate this form for password
310
        $validation->setFilters('password', 'trim');
311
        $validation->setFilters('default_company', 'trim');
312
        $validation->validate($request);
313
314
        $newEmail = $validation->getValue('new_email');
315
        $password = $validation->getValue('password');
316
317
        //Search user by key
318
        $user = Users::getByUserActivationEmail($hash);
319
320
        if (!is_object($user)) {
321
            throw new NotFoundHttpException(_('User not found'));
322
        }
323
324
        $this->db->begin();
325
326
        $user->email = $newEmail;
327
328
        if (!$user->update()) {
329
            throw new ModelException((string)current($user->getMessages()));
0 ignored issues
show
Deprecated Code introduced by
The class Canvas\Exception\ModelException has been deprecated with message: version 0.1.5

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
330
        }
331
332
        if (!$userData = $this->loginUsers($user->email, $password)) {
333
            $this->db->rollback();
334
        }
335
336
        $this->db->commit();
337
338
        return $this->response($userData);
339
    }
340
341
    /**
342
     * Login user using Access Token.
343
     * @return Response
344
     */
345
    public function loginBySocial(): Response
346
    {
347
        $request = $this->request->getPostData();
348
349
        $source = Sources::findFirstOrFail([
350
            'title = ?0 and is_deleted = 0',
351
            'bind' => [$request['provider']]
352
        ]);
353
354
        return $this->response($this->providerLogin($source, $request['social_id'], $request));
355
    }
356
357
    /**
358
     * Send the user how filled out the form to the specify email
359
     * a link to reset his password.
360
     *
361
     * @return Response
362
     */
363
    public function recover(): Response
364
    {
365
        $request = $this->request->getPostData();
366
367
        $validation = new CanvasValidation();
368
        $validation->add('email', new EmailValidator(['message' => _('The email is not valid.')]));
369
370
        $validation->validate($request);
371
372
        $email = $validation->getValue('email');
373
374
        $recoverUser = Users::getByEmail($email);
375
        $recoverUser->generateForgotHash();
376
377
        $recoverUser->notify(new ResetPassword($recoverUser));
378
379
        return $this->response(_('Check your email to recover your password'));
380
    }
381
382
    /**
383
     * Reset the user password.
384
     * @method PUT
385
     * @url /v1/reset
386
     *
387
     * @return Response
388
     */
389
    public function reset(string $key) : Response
390
    {
391
        //is the key empty or does it existe?
392
        if (empty($key) || !$userData = Users::findFirst(['user_activation_forgot = :key:', 'bind' => ['key' => $key]])) {
393
            throw new Exception(_('This Key to reset password doesn\'t exist'));
394
        }
395
396
        $request = $this->request->getPostData();
397
398
        // Get the new password and the verify
399
        $newPassword = trim($request['new_password']);
400
        $verifyPassword = trim($request['verify_password']);
401
402
        //Ok let validate user password
403
        PasswordValidation::validate($newPassword, $verifyPassword);
404
405
        // Has the password and set it
406
        $userData->resetPassword($newPassword);
407
        $userData->user_activation_forgot = '';
408
        $userData->updateOrFail();
409
410
        //log the user out of the site from all devices
411
        $session = new Sessions();
412
        $session->end($userData);
413
414
        $userData->notify(new PasswordUpdate($userData));
415
416
        return $this->response(_('Password Updated'));
417
    }
418
419
    /**
420
    * Set the email config array we are going to be sending.
421
    *
422
    * @todo deprecated move to notifications
423
    * @param String $emailAction
0 ignored issues
show
Bug introduced by
There is no parameter named $emailAction. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
424
    * @param Users  $user
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $user a bit more specific; maybe use BakaUsers.
Loading history...
425
    * @return void
426
    */
427
    protected function sendEmail(BakaUsers $user, string $type): void
428
    {
429
        $send = true;
430
        $subject = null;
431
        $body = null;
432
        switch ($type) {
433
            case 'recover':
434
                $recoveryLink = $this->config->app->frontEndUrl . '/users/reset-password/' . $user->user_activation_forgot;
435
                $subject = _('Password Recovery');
436
                $body = sprintf(_('Click %shere%s to set a new password for your account.'), '<a href="' . $recoveryLink . '" target="_blank">', '</a>');
437
                // send email to recover password
438
                break;
439
            case 'reset':
440
                $activationUrl = $this->config->app->frontEndUrl . '/user/activate/' . $user->user_activation_key;
441
                $subject = _('Password Updated!');
442
                $body = sprintf(_('Your password was update please, use this link to activate your account: %sActivate account%s'), '<a href="' . $activationUrl . '">', '</a>');
443
                // send email that password was update
444
                break;
445
            case 'email-change':
446
                $emailChangeUrl = $this->config->app->frontEndUrl . '/user/' . $user->user_activation_email . '/email';
447
                $subject = _('Email Change Request');
448
                $body = sprintf(_('Click %shere%s to set a new email for your account.'), '<a href="' . $emailChangeUrl . '">', '</a>');
449
                break;
450
            default:
451
                $send = false;
452
                break;
453
        }
454
455
        if ($send) {
456
            $this->mail
457
            ->to($user->email)
458
            ->subject($subject)
459
            ->content($body)
460
            ->sendNow();
461
        }
462
    }
463
}
464