Auth::lostpassword()   A
last analyzed

Complexity

Conditions 5
Paths 7

Size

Total Lines 40
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 34
c 1
b 0
f 0
nc 7
nop 0
dl 0
loc 40
ccs 0
cts 33
cp 0
crap 30
rs 9.0648
1
<?php
2
3
declare(strict_types=1);
4
5
namespace App\Controllers\Api\V1;
6
7
use App\Interfaces\Controllers\IApi;
8
use App\Reuse\Controllers\AbstractApi;
9
use Nymfonya\Component\Config;
10
use Nymfonya\Component\Container;
11
use Nymfonya\Component\Http\Headers;
12
use Nymfonya\Component\Http\Request;
13
use Nymfonya\Component\Http\Response;
14
use App\Component\Db\Core;
15
use App\Model\Repository\Users;
16
use App\Component\Jwt\Token;
17
use App\Component\Auth\Factory;
18
use App\Component\Crypt;
19
use Egulias\EmailValidator\EmailValidator;
20
use Egulias\EmailValidator\Validation\RFCValidation;
21
/*use Swift_SmtpTransport;
22
use Swift_Mailer;
23
use Swift_Message;*/
24
use App\Component\Mailer\Smtp;
25
use Exception;
26
27
final class Auth extends AbstractApi implements IApi
28
{
29
30
    /**
31
     * core db instance
32
     *
33
     * @var Core
34
     */
35
    protected $db;
36
37
    /**
38
     * user repository
39
     *
40
     * @var Users
41
     */
42
    protected $userRepository;
43
44
    /**
45
     * slugs
46
     *
47
     * @var array
48
     */
49
    protected $slugs;
50
51
    /**
52
     * sql
53
     *
54
     * @var String
55
     */
56
    protected $sql;
57
58
    /**
59
     * sql values to bind statement
60
     *
61
     * @var array
62
     */
63
    protected $bindValues;
64
65
    /**
66
     * error
67
     *
68
     * @var Boolean
69
     */
70
    protected $error;
71
72
    /**
73
     * error message
74
     *
75
     * @var String
76
     */
77
    protected $errorMessage;
78
79
    /**
80
     * instanciate
81
     *
82
     * @param Container $container
83
     */
84 7
    public function __construct(Container $container)
85
    {
86 7
        $this->userRepository = new Users($container);
87 7
        $this->db = new Core($container);
88 7
        $this->db->fromOrm($this->userRepository);
89 7
        $this->error = false;
90 7
        $this->errorMessage = '';
91 7
        parent::__construct($container);
92
    }
93
94
    /**
95
     * login action
96
     *
97
     * @Role anonymous
98
     * @return Auth
99
     */
100 3
    final public function login(): Auth
101
    {
102 3
        $config = $this->getService(Config::class);
103 3
        $logger = $this->getService(\Monolog\Logger::class);
104 3
        $login = filter_var(
105 3
            $this->request->getParam('login'),
106 3
            FILTER_SANITIZE_EMAIL
107
        );
108 3
        $password = filter_var(
109 3
            $this->request->getParam('password'),
110 3
            FILTER_SANITIZE_STRING
111
        );
112 3
        if (false === $this->isValidLogin($login, $password)) {
113 1
            $logger->warning(__FUNCTION__ . ' Invalid arguments');
114 1
            return $this->setErrorResponse(
115 1
                Response::HTTP_BAD_REQUEST,
116 1
                'Invalid arguments'
117
            );
118
        }
119 2
        $authFactory = new Factory($this->getContainer());
120 2
        $authFactory->setAdapter();
121 2
        if ($user = $authFactory->auth($login, $password)) {
122 1
            $jwtToken = new Token($config, $this->request);
123
            $token = $jwtToken
124 1
                ->setIssueAt(time())
125 1
                ->setIssueAtDelay(0)
126 1
                ->setTtl(1200)
127 1
                ->encode(
128 1
                    (int) $user[Factory::_ID],
129 1
                    $user[Factory::_EMAIL],
130 1
                    $user[Factory::_PASSWORD]
131
                );
132 1
            $logger->info(__FUNCTION__ . ' Auth succeed');
133 1
            $this->response
134 1
                ->setCode(Response::HTTP_OK)
135 1
                ->setContent(
136 1
                    [Response::_ERROR => false, 'token' => $token]
137
                );
138 1
            return $this;
139
        }
140 1
        $logger->warning(__FUNCTION__ . ' Auth failed');
141 1
        unset($authFactory);
142 1
        return $this->setErrorResponse(
143 1
            Response::HTTP_UNAUTHORIZED,
144 1
            'Bad credentials'
145
        );
146
    }
147
148
    /**
149
     * register action
150
     *
151
     * @Role anonymous
152
     * @return Auth
153
     */
154
    final public function register(): Auth
155
    {
156
        $config = $this->getService(Config::class);
157
        $logger = $this->getService(\Monolog\Logger::class);
158
        $name = filter_var($this->request->getParam('name'), FILTER_SANITIZE_STRING);
159
        $email = filter_var($this->request->getParam('email'), FILTER_SANITIZE_EMAIL);
160
        $password = filter_var($this->request->getParam('password'), FILTER_SANITIZE_STRING);
161
        if (false === $this->isValidRegistration($name, $email, $password)) {
162
            $this->error = true;
163
            $this->errorMessage = 'Invalid arguments';
164
            $logger->warning(__FUNCTION__ . ' Invalid arguments');
165
            return $this->setRegistrationResponse(__CLASS__, __FUNCTION__);
166
        }
167
        $this->userRepository->emailExists($email);
0 ignored issues
show
Bug introduced by
The method emailExists() does not exist on App\Model\Repository\Users. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

167
        $this->userRepository->/** @scrutinizer ignore-call */ 
168
                               emailExists($email);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
168
        $this->sql = $this->userRepository->getSql();
169
        $this->bindValues = $this->userRepository->getBuilderValues();
170
        $this->db->run($this->sql, $this->bindValues)->hydrate();
171
        $emailCount = $this->db->getRowset();
172
        $emailCounter = (int) $emailCount[0]['counter'];
173
        if ($emailCounter !== 0) {
174
            $this->error = true;
175
            $this->errorMessage = 'Email exists';
176
            $logger->warning(__FUNCTION__ . $this->errorMessage);
177
            return $this->setRegistrationResponse(__CLASS__, __FUNCTION__);
178
        }
179
        $cryptEngine = new Crypt($config);
180
        $cryptedPassword = $cryptEngine->encrypt($password);
181
        unset($cryptEngine);
182
        $this->userRepository->register($name, $email, $cryptedPassword);
0 ignored issues
show
Bug introduced by
The method register() does not exist on App\Model\Repository\Users. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

182
        $this->userRepository->/** @scrutinizer ignore-call */ 
183
                               register($name, $email, $cryptedPassword);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
183
        $this->sql = $this->userRepository->getSql();
184
        $this->bindValues = $this->userRepository->getBuilderValues();
185
        $this->db->run($this->sql, $this->bindValues);
186
        return $this->setRegistrationResponse(__CLASS__, __FUNCTION__);
187
    }
188
189
    /**
190
     * lost password action
191
     *
192
     * @Role anonymous
193
     * @return Auth
194
     */
195
    final public function lostpassword(): Auth
196
    {
197
        $logger = $this->getService(\Monolog\Logger::class);
198
        $email = filter_var($this->request->getParam('email'), FILTER_SANITIZE_EMAIL);
199
        $validator = new EmailValidator();
200
        $isValid = $validator->isValid($email, new RFCValidation());
201
        if (false === $isValid) {
202
            $this->error = true;
203
            $this->errorMessage = 'Invalid email';
204
            $logger->warning(__FUNCTION__ . ' Invalid email');
205
            return $this->setRegistrationResponse(__CLASS__, __FUNCTION__);
206
        }
207
        $this->userRepository->getByEmail($email);
208
        $this->sql = $this->userRepository->getSql();
209
        $this->bindValues = $this->userRepository->getBuilderValues();
210
        $this->db->run($this->sql, $this->bindValues)->hydrate();
211
        $users = $this->db->getRowset();
212
        $emailExists = isset($users[0]);
213
        if (false === $emailExists) {
214
            $this->error = true;
215
            $this->errorMessage = 'Email does not exists';
216
            $logger->warning(__FUNCTION__ . $this->errorMessage);
217
            return $this->setRegistrationResponse(__CLASS__, __FUNCTION__);
218
        }
219
        $user = $users[0];
220
        try {
221
            $mailer = new Smtp($this->getContainer());
222
            $mailer
223
                ->setTo([$email => $user['name']])
224
                ->setMessage('Password retrieval', $user['password'])
225
                ->sendMessage();
226
            if ($mailer->isError()) {
227
                throw new Exception('Lost password : Email was not sent');
228
            }
229
        } catch (Exception $e) {
230
            $this->error = true;
231
            $this->errorMessage = $e->getMessage();
232
            $logger->error($e);
233
        }
234
        return $this->setRegistrationResponse(__CLASS__, __FUNCTION__);
235
    }
236
237
    /**
238
     * return true if request methods are allowed
239
     *
240
     * @return boolean
241
     */
242 1
    protected function isLoginMethodAllowed(): bool
243
    {
244 1
        return in_array(
245 1
            $this->request->getMethod(),
246 1
            [Request::METHOD_POST, Request::METHOD_TRACE]
247
        );
248
    }
249
250
    /**
251
     * return true if login action can be executed
252
     *
253
     * @param string $login
254
     * @param string $password
255
     * @return boolean
256
     */
257 1
    protected function isValidLogin(string $login, string $password): bool
258
    {
259 1
        return $this->isLoginMethodAllowed()
260 1
            && !empty($login)
261 1
            && !empty($password);
262
    }
263
264
    /**
265
     * return true if registration process can be executed
266
     *
267
     * @param string $login
268
     * @param string $password
269
     * @return boolean
270
     */
271
    protected function isValidRegistration(string $name, string $email, string $password): bool
272
    {
273
        $notEmpty = (!empty($name) && !empty($email) && !empty($password));
274
        $isMethodAllow = $this->request->getMethod() === Request::METHOD_POST;
275
        return $notEmpty && $isMethodAllow;
276
    }
277
278
    /**
279
     * return Auth and set response with http code and message
280
     *
281
     * @param integer $code
282
     * @param string $msg
283
     * @return Auth
284
     */
285 1
    protected function setErrorResponse(int $code, string $msg): Auth
286
    {
287 1
        $this->response
288 1
            ->setCode($code)
289 1
            ->setContent([
290 1
                Response::_ERROR => true,
291 1
                Response::_ERROR_CODE => $code,
292 1
                Response::_ERROR_MSG => $msg
293 1
            ])->getHeaderManager()->add(
294 1
                Headers::CONTENT_TYPE,
295 1
                'application/json'
296
            );
297 1
        return $this;
298
    }
299
300
    /**
301
     * set response with for a classname and action
302
     *
303
     * @param string $classname
304
     * @param string $action
305
     * @return Auth
306
     */
307
    protected function setRegistrationResponse(string $classname, string $action): Auth
308
    {
309
        $isError = $this->isError();
310
        $this->response
311
            ->setCode($this->getStatusCode())
312
            ->setContent(
313
                [
314
                    'error' => $this->error,
315
                    'errorMessage' => $this->errorMessage,
316
                    'datas' => [
317
                        'method' => $this->getRequest()->getMethod(),
318
                        'params' => $this->getParams(),
319
                        'controller' => $classname,
320
                        'action' => $action,
321
                        'query' => $isError
322
                            ? ''
323
                            : $this->sql,
324
                        'queryValues' => $isError
325
                            ? [] : $this->bindValues,
326
                        'rowset' => $isError
327
                            ? []
328
                            : $this->db->getRowset()
329
                    ]
330
                ]
331
            );
332
        return $this;
333
    }
334
335
    /**
336
     * returns true if error happened
337
     *
338
     * @return boolean
339
     */
340
    protected function isError(): bool
341
    {
342
        return $this->error === true;
343
    }
344
345
    /**
346
     * returns http status code
347
     *
348
     * @return int
349
     */
350
    protected function getStatusCode(): int
351
    {
352
        return (true === $this->isError())
353
            ? Response::HTTP_BAD_REQUEST
354
            : Response::HTTP_OK;
355
    }
356
}
357