Completed
Push — master ( e43f1a...57db71 )
by Arman
26s queued 12s
created

BaseAuth::check()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
/**
4
 * Quantum PHP Framework
5
 *
6
 * An open source software development framework for PHP
7
 *
8
 * @package Quantum
9
 * @author Arman Ag. <[email protected]>
10
 * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
11
 * @link http://quantum.softberg.org/
12
 * @since 2.4.0
13
 */
14
15
namespace Quantum\Libraries\Auth;
16
17
use Quantum\Exceptions\AuthException;
18
use Quantum\Libraries\JWToken\JWToken;
19
use Quantum\Libraries\Hasher\Hasher;
20
use Quantum\Libraries\Mailer\Mailer;
21
use ReflectionClass;
22
23
/**
24
 * Class BaseAuth
25
 * @package Quantum\Libraries\Auth
26
 */
27
abstract class BaseAuth
28
{
29
30
    /**
31
     * One time password key
32
     */
33
    const OTP_KEY = 'otp';
34
35
    /**
36
     * One time password expiry key
37
     */
38
    const OTP_EXPIRY_KEY = 'otpExpiry';
39
40
    /**
41
     * One time password token key
42
     */
43
    const OTP_TOKEN_KEY = 'otpToken';
44
45
    /**
46
     * Username key
47
     */
48
    const USERNAME_KEY = 'username';
49
50
    /**
51
     * Password key
52
     */
53
    const PASSWORD_KEY = 'password';
54
55
    /**
56
     * Access token key
57
     */
58
    const ACCESS_TOKEN_KEY = 'accessToken';
59
60
    /**
61
     * Refresh token key
62
     */
63
    const REFRESH_TOKEN_KEY = 'refreshToken';
64
65
    /**
66
     * Activation token key
67
     */
68
    const ACTIVATION_TOKEN_KEY = 'activationToken';
69
70
    /**
71
     * Reset token key
72
     */
73
    const RESET_TOKEN_KEY = 'resetToken';
74
75
    /**
76
     * Remember token key
77
     */
78
    const REMEMBER_TOKEN_KEY = 'rememberToken';
79
80
    /**
81
     * @var \Quantum\Libraries\Mailer\Mailer
82
     */
83
    protected $mailer;
84
85
    /**
86
     * @var \Quantum\Libraries\Hasher\Hasher
87
     */
88
    protected $hasher;
89
90
    /**
91
     * @var \Quantum\Libraries\JWToken\JWToken
92
     */
93
    protected $jwt;
94
95
    /**
96
     * @var \Quantum\Libraries\Auth\AuthServiceInterface
97
     */
98
    protected $authService;
99
100
    /**
101
     * @var string
102
     */
103
    protected $authUserKey = 'auth_user';
104
105
    /**
106
     * @var int
107
     */
108
    protected $otpLenght = 6;
109
110
    /**
111
     * @var array
112
     */
113
    protected $keyFields = [];
114
115
    /**
116
     * @var array
117
     */
118
    protected $visibleFields = [];
119
120
    /**
121
     * User
122
     * @return \Quantum\Libraries\Auth\User|null
123
     */
124
    protected abstract function user(): ?User;
125
126
    /**
127
     * Verify user schema
128
     * @param array $schema
129
     * @throws \Quantum\Exceptions\AuthException
130
     */
131
    protected function verifySchema(array $schema)
132
    {
133
        $reflectionClass = new ReflectionClass($this);
134
        $constants = $reflectionClass->getConstants();
135
136
        foreach ($constants as $constant) {
137
            if (!in_array($constant, array_keys($schema))) {
138
                throw new AuthException(AuthException::INCORRECT_USER_SCHEMA);
139
            }
140
141
            if (!isset($schema[$constant]['name'])) {
142
                throw new AuthException(AuthException::INCORRECT_USER_SCHEMA);
143
            }
144
145
            $this->keyFields[$constant] = $schema[$constant]['name'];
146
        }
147
148
        foreach ($schema as $field) {
149
            if ($field['visible']) {
150
                $this->visibleFields[] = $field['name'];
151
            }
152
        }
153
    }
154
155
    /**
156
     * Check
157
     * @return bool
158
     */
159
    public function check(): bool
160
    {
161
        return !is_null($this->user());
162
    }
163
164
    /**
165
     * Sign Up
166
     * @param array $userData
167
     * @param array|null $customData
168
     * @return \Quantum\Libraries\Auth\User
169
     */
170
    public function signup(array $userData, array $customData = null): User
171
    {
172
        $activationToken = $this->generateToken();
173
174
        $userData[$this->keyFields[self::PASSWORD_KEY]] = $this->hasher->hash($userData[$this->keyFields[self::PASSWORD_KEY]]);
175
        $userData[$this->keyFields[self::ACTIVATION_TOKEN_KEY]] = $activationToken;
176
177
        $user = $this->authService->add($userData);
178
179
        $body = [
180
            'user' => $user,
181
            'activationToken' => $activationToken
182
        ];
183
184
        if ($customData) {
185
            $body = array_merge($body, $customData);
186
        }
187
188
        $this->mailer->setSubject(t('common.activate_account'));
189
        $this->mailer->setTemplate(base_dir() . DS . 'base' . DS . 'views' . DS . 'email' . DS . 'activate');
190
191
        $this->sendMail($user, $body);
192
193
        return $user;
194
    }
195
196
    /**
197
     * Activate
198
     * @param string $token
199
     */
200
    public function activate(string $token)
201
    {
202
        $this->authService->update(
203
            $this->keyFields[self::ACTIVATION_TOKEN_KEY],
204
            $token,
205
            [$this->keyFields[self::ACTIVATION_TOKEN_KEY] => '']
206
        );
207
    }
208
    
209
    /**
210
     * Forget
211
     * @param string $username
212
     * @return string|null
213
     */
214
    public function forget(string $username): ?string
215
    {
216
        $user = $this->authService->get($this->keyFields[self::USERNAME_KEY], $username);
217
218
        if ($user) {
219
            $resetToken = $this->generateToken();
220
221
            $this->authService->update(
222
                $this->keyFields[self::USERNAME_KEY],
223
                $username,
224
                [$this->keyFields[self::RESET_TOKEN_KEY] => $resetToken]
225
            );
226
227
            $body = [
228
                'user' => $user,
229
                'resetToken' => $resetToken
230
            ];
231
232
            $this->mailer->setSubject(t('common.reset_password'));
233
            $this->mailer->setTemplate(base_dir() . DS . 'base' . DS . 'views' . DS . 'email' . DS . 'reset');
234
235
            $this->sendMail($user, $body);
236
237
            return $resetToken;
238
        }
239
    }
240
241
    /**
242
     * Reset
243
     * @param string $token
244
     * @param string $password
245
     */
246
    public function reset(string $token, string $password)
247
    {
248
        $user = $this->authService->get($this->keyFields[self::RESET_TOKEN_KEY], $token);
249
250
        if ($user) {
251
            if (!$this->isActivated($user)) {
252
                $this->activate($token);
253
            }
254
255
            $this->authService->update(
256
                $this->keyFields[self::RESET_TOKEN_KEY],
257
                $token,
258
                [
259
                    $this->keyFields[self::PASSWORD_KEY] => $this->hasher->hash($password),
260
                    $this->keyFields[self::RESET_TOKEN_KEY] => ''
261
                ]
262
            );
263
        }
264
    }
265
266
    /**
267
     * Resend OTP
268
     * @param string $otpToken
269
     * @return string
270
     * @throws \Quantum\Exceptions\AuthException
271
     * @throws \Exception
272
     */
273
    public function resendOtp(string $otpToken): string
274
    {
275
        $user = $this->authService->get($this->keyFields[self::OTP_TOKEN_KEY], $otpToken);
276
277
        if (!$user) {
278
            throw new AuthException(AuthException::INCORRECT_AUTH_CREDENTIALS);
279
        }
280
281
        return $this->twoStepVerification($user);
282
283
    }
284
285
    /**
286
     * Two Step Verification
287
     * @param \Quantum\Libraries\Auth\User $user
288
     * @return string
289
     * @throws \Exception
290
     */
291
    protected function twoStepVerification(User $user): string
292
    {
293
        $otp = random_number($this->otpLenght);
294
295
        $otpToken = $this->generateToken($user->getFieldValue($this->keyFields[self::USERNAME_KEY]));
296
297
        $time = new \DateTime();
298
299
        $time->add(new \DateInterval('PT' . config()->get('otp_expires') . 'M'));
300
301
        $this->authService->update(
302
            $this->keyFields[self::USERNAME_KEY],
303
            $user->getFieldValue($this->keyFields[self::USERNAME_KEY]),
304
            [
305
                $this->keyFields[self::OTP_KEY] => $otp,
306
                $this->keyFields[self::OTP_EXPIRY_KEY] => $time->format('Y-m-d H:i'),
307
                $this->keyFields[self::OTP_TOKEN_KEY] => $otpToken,
308
            ]
309
        );
310
311
        $body = [
312
            'user' => $user,
313
            'code' => $otp
314
        ];
315
316
        $this->mailer->setSubject(t('common.otp'));
317
        $this->mailer->setTemplate(base_dir() . DS . 'base' . DS . 'views' . DS . 'email' . DS . 'verification');
318
319
        $this->sendMail($user, $body);
320
321
        return $otpToken;
322
    }
323
324
    /**
325
     * Verify and update OTP
326
     * @param int $otp
327
     * @param string $otpToken
328
     * @return \Quantum\Libraries\Auth\User
329
     * @throws \Quantum\Exceptions\AuthException
330
     */
331
    protected function verifyAndUpdateOtp(int $otp, string $otpToken): User
332
    {
333
        $user = $this->authService->get($this->keyFields[self::OTP_TOKEN_KEY], $otpToken);
334
335
        if (!$user || $otp != $user->getFieldValue($this->keyFields[self::OTP_KEY])) {
336
            throw new AuthException(AuthException::INCORRECT_VERIFICATION_CODE);
337
        }
338
339
        if (new \DateTime() >= new \DateTime($user->getFieldValue($this->keyFields[self::OTP_EXPIRY_KEY]))) {
340
            throw new AuthException(AuthException::VERIFICATION_CODE_EXPIRED);
341
        }
342
343
        $this->authService->update(
344
            $this->keyFields[self::USERNAME_KEY],
345
            $user->getFieldValue($this->keyFields[self::USERNAME_KEY]),
346
            [
347
                $this->keyFields[self::OTP_KEY] => null,
348
                $this->keyFields[self::OTP_EXPIRY_KEY] => null,
349
                $this->keyFields[self::OTP_TOKEN_KEY] => null,
350
            ]
351
        );
352
353
        return $user;
354
    }
355
356
    /**
357
     * Filters and gets the visible fields
358
     * @param \Quantum\Libraries\Auth\User $user
359
     * @return array
360
     */
361
    protected function getVisibleFields(User $user): array
362
    {
363
        $userData = $user->getData();
364
365
        if (count($this->visibleFields)) {
366
            foreach ($userData as $field => $value) {
367
                if (!in_array($field, $this->visibleFields)) {
368
                    unset($userData[$field]);
369
                }
370
            }
371
        }
372
373
        return $userData;
374
    }
375
376
    /**
377
     * Is user account activated
378
     * @param \Quantum\Libraries\Auth\User $user
379
     * @return bool
380
     */
381
    protected function isActivated(User $user): bool
382
    {
383
        return empty($user->getFieldValue($this->keyFields[self::ACTIVATION_TOKEN_KEY]));
384
    }
385
386
    /**
387
     * Generate Token
388
     * @param string|null $val
389
     * @return string
390
     */
391
    protected function generateToken(string $val = null): string
392
    {
393
        return base64_encode($this->hasher->hash($val ?: env('APP_KEY')));
394
    }
395
396
    /**
397
     * Send email
398
     * @param \Quantum\Libraries\Auth\User $user
399
     * @param array $body
400
     * @throws \PHPMailer\PHPMailer\Exception
401
     * @throws \Quantum\Exceptions\DiException
402
     */
403
    protected function sendMail(User $user, array $body)
404
    {
405
        $fullName = ($user->hasField('firstname') && $user->hasField('lastname')) ? $user->getFieldValue('firstname') . ' ' . $user->getFieldValue('lastname') : '';
406
407
        $appEmail = config()->get('app_email') ?: '';
408
        $appName = config()->get('app_name') ?: '';
409
410
        $this->mailer->setFrom($appEmail, $appName)
411
            ->setAddress($user->getFieldValue($this->keyFields[self::USERNAME_KEY]), $fullName)
0 ignored issues
show
Bug introduced by
It seems like $user->getFieldValue($th...ds[self::USERNAME_KEY]) can also be of type null; however, parameter $email of Quantum\Libraries\Mailer\Mailer::setAddress() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

411
            ->setAddress(/** @scrutinizer ignore-type */ $user->getFieldValue($this->keyFields[self::USERNAME_KEY]), $fullName)
Loading history...
412
            ->setBody($body)
413
            ->send();
414
    }
415
416
}
417