Passed
Push — develop ( 8abaf8...e15637 )
by nguereza
03:33
created

JWTAuthentication::logout()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 0
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 2
rs 10
1
<?php
2
3
/**
4
 * Platine Framework
5
 *
6
 * Platine Framework is a lightweight, high-performance, simple and elegant
7
 * PHP Web framework
8
 *
9
 * This content is released under the MIT License (MIT)
10
 *
11
 * Copyright (c) 2020 Platine Framework
12
 *
13
 * Permission is hereby granted, free of charge, to any person obtaining a copy
14
 * of this software and associated documentation files (the "Software"), to deal
15
 * in the Software without restriction, including without limitation the rights
16
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
 * copies of the Software, and to permit persons to whom the Software is
18
 * furnished to do so, subject to the following conditions:
19
 *
20
 * The above copyright notice and this permission notice shall be included in all
21
 * copies or substantial portions of the Software.
22
 *
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
 * SOFTWARE.
30
 */
31
32
/**
33
 *  @file JWTAuthentication.php
34
 *
35
 *  The Authentication using JWT class
36
 *
37
 *  @package    Platine\Framework\Auth\Authentication
38
 *  @author Platine Developers team
39
 *  @copyright  Copyright (c) 2020
40
 *  @license    http://opensource.org/licenses/MIT  MIT License
41
 *  @link   http://www.iacademy.cf
42
 *  @version 1.0.0
43
 *  @filesource
44
 */
45
46
declare(strict_types=1);
47
48
namespace Platine\Framework\Auth\Authentication;
49
50
use DateTime;
51
use Platine\Config\Config;
52
use Platine\Framework\Auth\ApiAuthenticationInterface;
53
use Platine\Framework\Auth\Exception\AccountLockedException;
54
use Platine\Framework\Auth\Exception\AccountNotFoundException;
55
use Platine\Framework\Auth\Exception\InvalidCredentialsException;
56
use Platine\Framework\Auth\Exception\MissingCredentialsException;
57
use Platine\Framework\Auth\IdentityInterface;
58
use Platine\Framework\Auth\Repository\TokenRepository;
59
use Platine\Framework\Auth\Repository\UserRepository;
60
use Platine\Framework\Security\JWT\Exception\JWTException;
61
use Platine\Framework\Security\JWT\JWT;
62
use Platine\Http\ServerRequestInterface;
63
use Platine\Logger\LoggerInterface;
64
use Platine\Security\Hash\HashInterface;
65
use Platine\Stdlib\Helper\Str;
66
67
/**
68
 * @class JWTAuthentication
69
 * @package Platine\Framework\Auth\Authentication
70
 * @template T
71
 */
72
class JWTAuthentication implements ApiAuthenticationInterface
73
{
74
75
    /**
76
     * The JWT instance
77
     * @var JWT
78
     */
79
    protected JWT $jwt;
80
81
    /**
82
     * The logger instance
83
     * @var LoggerInterface
84
     */
85
    protected LoggerInterface $logger;
86
87
    /**
88
     * The configuration instance
89
     * @var Config<T>
90
     */
91
    protected Config $config;
92
93
    /**
94
     * The user repository instance
95
     * @var UserRepository
96
     */
97
    protected UserRepository $userRepository;
98
99
    /**
100
     * The token repository
101
     * @var TokenRepository
102
     */
103
    protected TokenRepository $tokenRepository;
104
105
    /**
106
     * Hash instance to use
107
     * @var HashInterface
108
     */
109
    protected HashInterface $hash;
110
111
    /**
112
     * The server request instance
113
     * @var ServerRequestInterface
114
     */
115
    protected ServerRequestInterface $request;
116
117
    /**
118
     * Create new instance
119
     * @param JWT $jwt
120
     * @param LoggerInterface $logger
121
     * @param Config<T> $config
122
     * @param HashInterface $hash
123
     * @param UserRepository $userRepository
124
     * @param TokenRepository $tokenRepository
125
     * @param ServerRequestInterface $request
126
     */
127
    public function __construct(
128
        JWT $jwt,
129
        LoggerInterface $logger,
130
        Config $config,
131
        HashInterface $hash,
132
        UserRepository $userRepository,
133
        TokenRepository $tokenRepository,
134
        ServerRequestInterface $request
135
    ) {
136
        $this->jwt = $jwt;
137
        $this->logger = $logger;
138
        $this->config = $config;
139
        $this->hash = $hash;
140
        $this->userRepository = $userRepository;
141
        $this->tokenRepository = $tokenRepository;
142
        $this->request = $request;
143
    }
144
145
    /**
146
     * {@inheritdoc}
147
     */
148
    public function getUser(): IdentityInterface
149
    {
150
        if (!$this->isAuthenticated($this->request)) {
151
            throw new AccountNotFoundException('User not logged', 401);
152
        }
153
154
        $payload = $this->jwt->getPayload();
155
        $id = (int) $payload['sub'] ?? -1;
156
157
        $user = $this->userRepository->find($id);
158
159
        if (!$user) {
160
            throw new AccountNotFoundException(
161
                'Can not find the logged user information, may be data is corrupted',
162
                401
163
            );
164
        }
165
166
        return $user;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $user returns the type Platine\Orm\Entity which is incompatible with the type-hinted return Platine\Framework\Auth\IdentityInterface.
Loading history...
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     */
172
    public function isAuthenticated(ServerRequestInterface $request): bool
173
    {
174
        $headerName = $this->config->get('api.auth.headers.name', 'Authorization');
175
        $tokenHeader = $request->getHeaderLine($headerName);
176
        if (empty($tokenHeader)) {
177
            $this->logger->error('API authentication failed missing token header');
178
179
            return false;
180
        }
181
        $tokenType = $this->config->get('api.auth.headers.token_type', 'Bearer');
182
        $secret = $this->config->get('api.sign.secret', '');
183
184
        $token = Str::replaceFirst($tokenType . ' ', '', $tokenHeader);
185
186
        $this->jwt->setSecret($secret);
187
        try {
188
            $this->jwt->decode($token);
189
190
            return true;
191
        } catch (JWTException $ex) {
192
            $this->logger->error('API authentication failed: {message}', [
193
                'message' => $ex->getMessage(),
194
            ]);
195
        }
196
197
        return false;
198
    }
199
200
    /**
201
     * {@inheritdoc}
202
     */
203
    public function login(array $credentials = []): array
204
    {
205
        if (!isset($credentials['username']) || !isset($credentials['password'])) {
206
            throw new MissingCredentialsException(
207
                'Missing username or password information',
208
                401
209
            );
210
        }
211
212
        $username = $credentials['username'];
213
        $password = $credentials['password'];
214
        $user = $this->userRepository
215
                    ->with('roles.permissions')
216
                    ->findBy(['username' => $username]);
217
218
        if (!$user) {
219
            throw new AccountNotFoundException('Can not find the user with the given information', 401);
220
        } elseif ($user->status === 'D') {
221
            throw new AccountLockedException(
222
                'User is locked',
223
                401
224
            );
225
        }
226
227
        if (!$this->hash->verify($password, $user->password)) {
228
            throw new InvalidCredentialsException(
229
                'Invalid credentials',
230
                401
231
            );
232
        }
233
234
        $permissions = [];
235
236
        $roles = $user->roles;
237
        foreach ($roles as $role) {
238
            $rolePermissions = $role->permissions;
239
            foreach ($rolePermissions as $permission) {
240
                $permissions[] = $permission->code;
241
            }
242
        }
243
244
        $secret = $this->config->get('api.sign.secret');
245
        $expire = $this->config->get('api.auth.token_expire', 900);
246
        $refreshExpire = $this->config->get('api.auth.refresh_token_expire', 30 * 86400);
247
        $tokenExpire = time() + $expire;
248
        $refreshTokenExpire = time() + $refreshExpire;
249
        $this->jwt->setSecret($secret)
0 ignored issues
show
Bug introduced by
It seems like $secret can also be of type null; however, parameter $secret of Platine\Framework\Security\JWT\JWT::setSecret() 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

249
        $this->jwt->setSecret(/** @scrutinizer ignore-type */ $secret)
Loading history...
250
                  ->setPayload([
251
                      'sub' => $user->id,
252
                      'exp' => $tokenExpire,
253
                  ])
254
                  ->sign();
255
        $refreshToken = Str::randomToken(24);
256
        $jwtToken = $this->jwt->getToken();
257
258
        $token = $this->tokenRepository->create([
259
            'token' => $jwtToken,
260
            'refresh_token' => $refreshToken,
261
            'expire_at' => (new DateTime())->setTimestamp($refreshTokenExpire),
262
            'user_id' => $user->id,
263
        ]);
264
265
        $this->tokenRepository->save($token);
266
267
        $data = [
268
          'user' => [
269
            'id' => $user->id,
270
            'username' => $user->username,
271
            'lastname' => $user->lastname,
272
            'firstname' => $user->firstname,
273
            'permissions' => array_unique($permissions),
274
          ],
275
          'token' => $jwtToken,
276
          'refresh_token' => $refreshToken,
277
        ];
278
279
        return $data;
280
    }
281
}
282