• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

daycry / auth / 22540946991

01 Mar 2026 09:55AM UTC coverage: 63.267%. Remained the same
22540946991

push

github

web-flow
Merge pull request #37 from daycry/development

Refactor Auth configuration and update CHANGELOG for v4.0.0

32 of 42 new or added lines in 18 files covered. (76.19%)

1 existing line in 1 file now uncovered.

3064 of 4843 relevant lines covered (63.27%)

41.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/Controllers/JwtController.php
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of Daycry Auth.
7
 *
8
 * (c) Daycry <daycry9@proton.me>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace Daycry\Auth\Controllers;
15

16
use CodeIgniter\HTTP\ResponseInterface;
17
use CodeIgniter\I18n\Time;
18
use Daycry\Auth\Authentication\Authenticators\Session;
19
use Daycry\Auth\Entities\User;
20
use Daycry\Auth\Interfaces\JWTAdapterInterface;
21
use Daycry\Auth\Models\UserIdentityModel;
22
use Daycry\Auth\Models\UserModel;
23
use Daycry\Auth\Validation\ValidationRules;
24

25
/**
26
 * JwtController
27
 *
28
 * Stateless JWT authentication with refresh token rotation.
29
 * Access tokens are short-lived JWTs; refresh tokens are
30
 * long-lived opaque tokens stored in the identities table.
31
 *
32
 * Routes (add to your app):
33
 *   $routes->post('auth/jwt/login',   'Daycry\Auth\Controllers\JwtController::login',   ['as' => 'jwt-login']);
34
 *   $routes->post('auth/jwt/refresh', 'Daycry\Auth\Controllers\JwtController::refresh', ['as' => 'jwt-refresh']);
35
 *   $routes->post('auth/jwt/logout',  'Daycry\Auth\Controllers\JwtController::logout',  ['as' => 'jwt-logout']);
36
 */
37
class JwtController extends BaseAuthController
38
{
39
    /**
40
     * {@inheritDoc}
41
     */
42
    protected function getValidationRules(): array
×
43
    {
44
        $rules = new ValidationRules();
×
45

46
        return $rules->getLoginRules();
×
47
    }
48

49
    /**
50
     * Authenticate with email+password.
51
     *
52
     * Returns a short-lived JWT access token and a long-lived refresh token.
53
     *
54
     * POST body: email, password
55
     * Response JSON: access_token, refresh_token, token_type
56
     */
57
    public function login(): ResponseInterface
×
58
    {
59
        $rules    = $this->getValidationRules();
×
60
        $postData = $this->request->getPost();
×
61

62
        if (! $this->validateRequest($postData, $rules)) {
×
63
            return $this->response->setStatusCode(422)->setJSON([
×
64
                'message' => $this->validator->getErrors(),
×
65
            ]);
×
66
        }
67

68
        $credentials = $this->extractLoginCredentials();
×
69

70
        /** @var Session $authenticator */
71
        $authenticator = $this->getSessionAuthenticator();
×
72
        $result        = $authenticator->check($credentials);
×
73

74
        if (! $result->isOK()) {
×
75
            return $this->response->setStatusCode(401)->setJSON([
×
76
                'message' => $result->reason(),
×
77
            ]);
×
78
        }
79

80
        $user = $result->extraInfo();
×
81

82
        return $this->response->setJSON($this->buildTokenResponse($user));
×
83
    }
84

85
    /**
86
     * Exchange a valid refresh token for a new access token and rotated refresh token.
87
     *
88
     * POST body: user_id, refresh_token
89
     * Response JSON: access_token, refresh_token, token_type
90
     */
91
    public function refresh(): ResponseInterface
×
92
    {
93
        $userId       = (int) $this->request->getPost('user_id');
×
94
        $refreshToken = (string) $this->request->getPost('refresh_token');
×
95

96
        if ($userId === 0 || $refreshToken === '') {
×
97
            return $this->response->setStatusCode(401)->setJSON([
×
98
                'message' => lang('Auth.invalidRefreshToken'),
×
99
            ]);
×
100
        }
101

102
        /** @var UserIdentityModel $identityModel */
103
        $identityModel = model(UserIdentityModel::class);
×
104
        $identity      = $identityModel->getJwtRefreshToken($userId, $refreshToken);
×
105

106
        if ($identity === null) {
×
107
            return $this->response->setStatusCode(401)->setJSON([
×
108
                'message' => lang('Auth.invalidRefreshToken'),
×
109
            ]);
×
110
        }
111

112
        // Revoke the used refresh token (rotation — one-time use)
113
        $identityModel->revokeIdentityById((int) $identity->id);
×
114

115
        /** @var UserModel $userModel */
116
        $userModel = model(UserModel::class);
×
117
        $user      = $userModel->findById($userId);
×
118

119
        if ($user === null) {
×
120
            return $this->response->setStatusCode(401)->setJSON([
×
121
                'message' => lang('Auth.invalidUser'),
×
122
            ]);
×
123
        }
124

125
        return $this->response->setJSON($this->buildTokenResponse($user));
×
126
    }
127

128
    /**
129
     * Revoke the refresh token (stateless logout).
130
     *
131
     * POST body: user_id, refresh_token
132
     * Response JSON: message
133
     */
134
    public function logout(): ResponseInterface
×
135
    {
136
        $userId       = (int) $this->request->getPost('user_id');
×
137
        $refreshToken = (string) $this->request->getPost('refresh_token');
×
138

139
        if ($userId > 0 && $refreshToken !== '') {
×
140
            /** @var UserIdentityModel $identityModel */
141
            $identityModel = model(UserIdentityModel::class);
×
142
            $identity      = $identityModel->getJwtRefreshToken($userId, $refreshToken);
×
143

144
            if ($identity !== null) {
×
145
                $identityModel->revokeIdentityById((int) $identity->id);
×
146
            }
147
        }
148

149
        return $this->response->setJSON(['message' => lang('Auth.successLogout')]);
×
150
    }
151

152
    /**
153
     * Builds the token pair response for a given user.
154
     *
155
     * Generates a JWT access token (via the configured adapter) and a new
156
     * opaque refresh token stored in the identities table.
157
     *
158
     * @param User $user
159
     *
160
     * @return array<string, mixed>
161
     */
162
    private function buildTokenResponse(object $user): array
×
163
    {
164
        // Generate access token via the configured JWT adapter
165
        $adapterClass = setting('Auth.jwtAdapter');
×
166
        /** @var JWTAdapterInterface $adapter */
167
        $adapter     = new $adapterClass();
×
168
        $accessToken = $adapter->encode($user->id);
×
169

170
        // Generate and persist a new refresh token
171
        $rawRefresh = bin2hex(random_bytes(32));
×
172
        $expiresAt  = Time::now()
×
NEW
173
            ->addSeconds((int) setting('AuthSecurity.jwtRefreshLifetime'))
×
174
            ->format('Y-m-d H:i:s');
×
175

176
        /** @var UserIdentityModel $identityModel */
177
        $identityModel = model(UserIdentityModel::class);
×
178
        $identityModel->createJwtRefreshToken((int) $user->id, $rawRefresh, $expiresAt);
×
179

180
        return [
×
181
            'access_token'  => $accessToken,
×
182
            'refresh_token' => $rawRefresh,
×
183
            'user_id'       => $user->id,
×
184
            'token_type'    => 'Bearer',
×
185
        ];
×
186
    }
187
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc