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

AuthController::refresh()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 0
dl 0
loc 24
ccs 0
cts 14
cp 0
crap 12
rs 9.536
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
29
/**
30
 * Class AuthController.
31
 *
32
 * @package Canvas\Api\Controllers
33
 *
34
 * @property Users $userData
35
 * @property Request $request
36
 * @property Config $config
37
 * @property \Baka\Mail\Message $mail
38
 * @property Apps $app
39
 */
40
class AuthController extends \Baka\Auth\AuthController
41
{
42
    /**
43
     * Auth Trait.
44
     */
45
    use AuthTrait;
46
    use TokenTrait;
47
    use SocialLoginTrait;
48
49
    /**
50
     * Setup for this controller.
51
     *
52
     * @return void
53
     */
54
    public function onConstruct()
55
    {
56
        $this->userLinkedSourcesModel = new UserLinkedSources();
57
        $this->userModel = new Users();
58
59
        if (!isset($this->config->jwt)) {
60
            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...
61
        }
62
    }
63
64
    /**
65
     * User Login.
66
     * @method POST
67
     * @url /v1/auth
68
     *
69
     * @return Response
70
     */
71
    public function login() : Response
72
    {
73
        $request = $this->request->getPostData();
74
75
        $userIp = !defined('API_TESTS') ? $this->request->getClientAddress() : '127.0.0.1'; //help getting the client ip on scrutinizer :(
76
        $admin = 0;
77
        $remember = 1;
78
79
        //Ok let validate user password
80
        $validation = new CanvasValidation();
81
        $validation->add('email', new EmailValidator(['message' => _('The email is not valid')]));
82
        $validation->add('password', new PresenceOf(['message' => _('The password is required.')]));
83
84
        $validation->setFilters('name', 'trim');
85
        $validation->setFilters('password', 'trim');
86
87
        //validate this form for password
88
        $validation->validate($request);
89
90
        $email = $validation->getValue('email');
91
        $password = $validation->getValue('password');
92
93
        /**
94
         * Login the user via ecosystem or app.
95
         */
96
        $auth = Factory::create($this->app->ecosystemAuth());
97
        $userData = $auth::login($email, $password, $remember, $admin, $userIp);
98
        $token = $userData->getToken();
99
100
        //start session
101
        $session = new Sessions();
102
        $session->start($userData, $token['sessionId'], $token['token'], $userIp, 1);
103
104
        return $this->response([
105
            'token' => $token['token'],
106
            'refresh_token' => $token['refresh_token'],
107
            'time' => date('Y-m-d H:i:s'),
108
            'expires' => date('Y-m-d H:i:s', time() + $this->config->jwt->payload->exp),
109
            'refresh_token_expires' => date('Y-m-d H:i:s', time() + 31536000),
110
            'id' => $userData->getId()
111
        ]);
112
    }
113
114
    /**
115
     * User Signup.
116
     *
117
     * @method POST
118
     * @url /v1/users
119
     *
120
     * @return Response
121
     */
122
    public function signup() : Response
123
    {
124
        $user = $this->userModel;
125
126
        $request = $this->request->getPostData();
127
128
        //Ok let validate user password
129
        $validation = new CanvasValidation();
130
        $validation->add('password', new PresenceOf(['message' => _('The password is required.')]));
131
        $validation->add('firstname', new PresenceOf(['message' => _('The firstname is required.')]));
132
        $validation->add('lastname', new PresenceOf(['message' => _('The lastname is required.')]));
133
        $validation->add('email', new EmailValidator(['message' => _('The email is not valid.')]));
134
135
        $validation->add(
136
            'password',
137
            new StringLength([
138
                'min' => 8,
139
                'messageMinimum' => _('Password is too short. Minimum 8 characters.'),
140
            ])
141
        );
142
143
        $validation->add('password', new Confirmation([
144
            'message' => _('Password and confirmation do not match.'),
145
            'with' => 'verify_password',
146
        ]));
147
148
        $validation->setFilters('password', 'trim');
149
        $validation->setFilters('displayname', 'trim');
150
        $validation->setFilters('default_company', 'trim');
151
152
        //validate this form for password
153
        $validation->validate($request);
154
155
        $user->email = $validation->getValue('email');
156
        $user->firstname = $validation->getValue('firstname');
157
        $user->lastname = $validation->getValue('lastname');
158
        $user->password = $validation->getValue('password');
159
        $userIp = !defined('API_TESTS') ? $this->request->getClientAddress() : '127.0.0.1'; //help getting the client ip on scrutinizer :(
160
        $user->displayname = $validation->getValue('displayname');
161
        $user->defaultCompanyName = $validation->getValue('default_company');
162
163
        //user registration
164
        try {
165
            $this->db->begin();
166
167
            $user->signUp();
168
169
            $this->db->commit();
170
        } catch (Exception $e) {
171
            $this->db->rollback();
172
173
            throw new Exception($e->getMessage());
174
        }
175
176
        $token = $user->getToken();
177
178
        //start session
179
        $session = new Sessions();
180
        $session->start($user, $token['sessionId'], $token['token'], $userIp, 1);
181
182
        $authSession = [
183
            'token' => $token['token'],
184
            'time' => date('Y-m-d H:i:s'),
185
            'expires' => date('Y-m-d H:i:s', time() + $this->config->jwt->payload->exp),
186
            'id' => $user->getId(),
187
        ];
188
189
        $user->password = null;
190
        $this->sendEmail($user, 'signup');
191
192
        return $this->response([
193
            'user' => $user,
194
            'session' => $authSession
195
        ]);
196
    }
197
198
    /**
199
     * Refresh user auth.
200
     *
201
     * @return Response
202
     * @todo Validate acces_token and refresh token, session's user email and relogin
203
     */
204
    public function refresh(): Response
205
    {
206
        $request = $this->request->getPostData();
207
        $accessToken = $this->getToken($request['access_token']);
208
        $refreshToken = $this->getToken($request['refresh_token']);
209
210
        if (time() != $accessToken->getClaim('exp')) {
211
            throw new ServerErrorHttpException('Issued Access Token has not expired');
1 ignored issue
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...
212
        }
213
214
        //Check if both tokens relate to the same user's email
215
        if ($accessToken->getClaim('sessionId') == $refreshToken->getClaim('sessionId')) {
216
            $user = Users::getByEmail($accessToken->getClaim('email'));
217
        }
218
219
        $token = Sessions::restart($user, $refreshToken->getClaim('sessionId'), (string)$this->request->getClientAddress());
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...
220
221
        return $this->response([
222
            'token' => $token['token'],
223
            'time' => date('Y-m-d H:i:s'),
224
            'expires' => date('Y-m-d H:i:s', time() + $this->config->jwt->payload->exp),
225
            'id' => $user->getId(),
226
        ]);
227
    }
228
229
    /**
230
     * Send email to change current email for user.
231
     * @param int $id
232
     * @return Response
233
     */
234
    public function sendEmailChange(int $id): Response
235
    {
236
        //Search for user
237
        $user = Users::getById($id);
238
239
        if (!is_object($user)) {
240
            throw new NotFoundHttpException(_('User not found'));
241
        }
242
243
        //Send email
244
        $this->sendEmail($user, 'email-change');
245
246
        return $this->response($user);
247
    }
248
249
    /**
250
     * Change user's email.
251
     * @param string $hash
252
     * @return Response
253
     */
254
    public function changeUserEmail(string $hash): Response
255
    {
256
        $request = $this->request->getPostData();
257
258
        //Ok let validate user password
259
        $validation = new CanvasValidation();
260
        $validation->add('password', new PresenceOf(['message' => _('The password is required.')]));
261
        $validation->add('new_email', new EmailValidator(['message' => _('The email is not valid.')]));
262
263
        $validation->add(
264
            'password',
265
            new StringLength([
266
                'min' => 8,
267
                'messageMinimum' => _('Password is too short. Minimum 8 characters.'),
268
            ])
269
        );
270
271
        //validate this form for password
272
        $validation->setFilters('password', 'trim');
273
        $validation->setFilters('default_company', 'trim');
274
        $validation->validate($request);
275
276
        $newEmail = $validation->getValue('new_email');
277
        $password = $validation->getValue('password');
278
279
        //Search user by key
280
        $user = Users::getByUserActivationEmail($hash);
281
282
        if (!is_object($user)) {
283
            throw new NotFoundHttpException(_('User not found'));
284
        }
285
286
        $this->db->begin();
287
288
        $user->email = $newEmail;
289
290
        if (!$user->update()) {
291
            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...
292
        }
293
294
        if (!$userData = $this->loginUsers($user->email, $password)) {
295
            $this->db->rollback();
296
        }
297
298
        $this->db->commit();
299
300
        return $this->response($userData);
301
    }
302
303
    /**
304
     * Login user using Access Token.
305
     * @return Response
306
     */
307
    public function loginBySocial(): Response
308
    {
309
        $request = $this->request->getPostData();
310
311
        $source = Sources::findFirstOrFail([
312
            'title = ?0 and is_deleted = 0',
313
            'bind' => [$request['provider']]
314
        ]);
315
316
        return $this->response($this->providerLogin($source, $request['social_id'], $request));
317
    }
318
319
    /**
320
     * Send the user how filled out the form to the specify email
321
     * a link to reset his password.
322
     *
323
     * @return Response
324
     */
325
    public function recover(): Response
326
    {
327
        $request = $this->request->getPostData();
328
329
        $validation = new CanvasValidation();
330
        $validation->add('email', new EmailValidator(['message' => _('The email is not valid.')]));
331
332
        $validation->validate($request);
333
334
        $email = $validation->getValue('email');
335
336
        $recoverUser = Users::getByEmail($email);
337
        $recoverUser->generateForgotHash();
338
339
        $recoverUser->notify(new ResetPassword($recoverUser));
340
341
        return $this->response(_('Check your email to recover your password'));
342
    }
343
344
    /**
345
     * Reset the user password.
346
     * @method PUT
347
     * @url /v1/reset
348
     *
349
     * @return Response
350
     */
351
    public function reset(string $key) : Response
352
    {
353
        //is the key empty or does it existe?
354
        if (empty($key) || !$userData = Users::findFirst(['user_activation_forgot = :key:', 'bind' => ['key' => $key]])) {
355
            throw new Exception(_('This Key to reset password doesn\'t exist'));
356
        }
357
358
        $request = $this->request->getPostData();
359
360
        // Get the new password and the verify
361
        $newPassword = trim($request['new_password']);
362
        $verifyPassword = trim($request['verify_password']);
363
364
        //Ok let validate user password
365
        PasswordValidation::validate($newPassword, $verifyPassword);
366
367
        // Has the password and set it
368
        $userData->resetPassword($newPassword);
369
        $userData->user_activation_forgot = '';
370
        $userData->updateOrFail();
371
372
        //log the user out of the site from all devices
373
        $session = new Sessions();
374
        $session->end($userData);
375
376
        $userData->notify(new PasswordUpdate($userData));
377
378
        return $this->response(_('Password Updated'));
379
    }
380
381
    /**
382
    * Set the email config array we are going to be sending.
383
    *
384
    * @todo deprecated move to notifications
385
    * @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...
386
    * @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...
387
    * @return void
388
    */
389
    protected function sendEmail(BakaUsers $user, string $type): void
390
    {
391
        $send = true;
392
        $subject = null;
393
        $body = null;
394
        switch ($type) {
395
            case 'recover':
396
                $recoveryLink = $this->config->app->frontEndUrl . '/users/reset-password/' . $user->user_activation_forgot;
397
                $subject = _('Password Recovery');
398
                $body = sprintf(_('Click %shere%s to set a new password for your account.'), '<a href="' . $recoveryLink . '" target="_blank">', '</a>');
399
                // send email to recover password
400
                break;
401
            case 'reset':
402
                $activationUrl = $this->config->app->frontEndUrl . '/user/activate/' . $user->user_activation_key;
403
                $subject = _('Password Updated!');
404
                $body = sprintf(_('Your password was update please, use this link to activate your account: %sActivate account%s'), '<a href="' . $activationUrl . '">', '</a>');
405
                // send email that password was update
406
                break;
407
            case 'email-change':
408
                $emailChangeUrl = $this->config->app->frontEndUrl . '/user/' . $user->user_activation_email . '/email';
409
                $subject = _('Email Change Request');
410
                $body = sprintf(_('Click %shere%s to set a new email for your account.'), '<a href="' . $emailChangeUrl . '">', '</a>');
411
                break;
412
            default:
413
                $send = false;
414
                break;
415
        }
416
417
        if ($send) {
418
            $this->mail
419
            ->to($user->email)
420
            ->subject($subject)
421
            ->content($body)
422
            ->sendNow();
423
        }
424
    }
425
}
426