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

daycry / auth / 23341393117

20 Mar 2026 11:45AM UTC coverage: 64.989% (+1.2%) from 63.745%
23341393117

push

github

daycry
Merge branch 'development' of https://github.com/daycry/auth into development

4 of 4 new or added lines in 2 files covered. (100.0%)

315 existing lines in 13 files now uncovered.

3306 of 5087 relevant lines covered (64.99%)

47.03 hits per line

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

0.0
/src/Controllers/PasswordResetController.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\Events\Events;
17
use CodeIgniter\Exceptions\RuntimeException;
18
use CodeIgniter\HTTP\RedirectResponse;
19
use CodeIgniter\HTTP\ResponseInterface;
20
use CodeIgniter\I18n\Time;
21
use Daycry\Auth\Enums\IdentityType;
22
use Daycry\Auth\Libraries\TokenEmailSender;
23
use Daycry\Auth\Models\UserIdentityModel;
24
use Daycry\Auth\Models\UserModel;
25

26
/**
27
 * Handles password reset flow via email token.
28
 *
29
 * The user requests a reset link, receives an email with a token,
30
 * and uses that token to set a new password.
31
 */
32
class PasswordResetController extends BaseAuthController
33
{
34
    /**
35
     * @var UserModel
36
     */
37
    protected $provider;
38

39
    public function __construct()
×
40
    {
41
        /** @var class-string<UserModel> $providerClass */
42
        $providerClass = setting('Auth.userProvider');
×
43

44
        $this->provider = new $providerClass();
×
45
    }
46

47
    /**
48
     * Displays the form to request a password reset email.
49
     */
50
    public function requestView(): ResponseInterface
×
51
    {
52
        if (($redirect = $this->redirectIfLoggedIn()) instanceof RedirectResponse) {
×
53
            return $redirect;
×
54
        }
55

56
        $content = $this->view(setting('Auth.views')['password-reset-request']);
×
57

58
        return $this->response->setBody($content);
×
59
    }
60

61
    /**
62
     * Receives the email, generates a reset token, and sends an email.
63
     */
64
    public function requestAction(): RedirectResponse
×
65
    {
66
        // Validate email format
67
        $rules    = $this->getValidationRules();
×
68
        $postData = $this->request->getPost();
×
69

70
        if (! $this->validateRequest($postData, $rules)) {
×
71
            return $this->handleValidationError('password-reset');
×
72
        }
73

74
        $email = $this->request->getPost('email');
×
75
        $user  = $this->provider->findByCredentials(['email' => $email]);
×
76

77
        if ($user !== null) {
×
UNCOV
78
            $sender = new TokenEmailSender();
×
79

80
            try {
UNCOV
81
                $sender->sendTokenEmail(
×
82
                    $user,
×
UNCOV
83
                    IdentityType::RESET_PASSWORD->value,
×
UNCOV
84
                    setting('AuthSecurity.passwordResetLifetime'),
×
85
                    lang('Auth.passwordResetSubject'),
×
86
                    setting('Auth.views')['password-reset-email'],
×
UNCOV
87
                    ['user' => $user],
×
88
                );
×
89
            } catch (RuntimeException $e) {
×
90
                log_message('error', $e->getMessage());
×
91
            }
92

93
            // Record the attempt
UNCOV
94
            $this->recordLoginAttempt(IdentityType::RESET_PASSWORD->value, $email, true, $user->id);
×
95
        } else {
96
            // Record failed attempt but don't reveal that user doesn't exist
UNCOV
97
            $this->recordLoginAttempt(IdentityType::RESET_PASSWORD->value, $email, false);
×
98
        }
99

100
        // Always redirect to message view (don't reveal if user exists)
UNCOV
101
        return redirect()->route('password-reset-message');
×
102
    }
103

104
    /**
105
     * Shows the "check your email" message view.
106
     */
107
    public function messageView(): ResponseInterface
×
108
    {
109
        $content = $this->view(setting('Auth.views')['password-reset-message']);
×
110

111
        return $this->response->setBody($content);
×
112
    }
113

114
    /**
115
     * Displays the password reset form (with token from query string).
116
     */
UNCOV
117
    public function resetView(): ResponseInterface
×
118
    {
UNCOV
119
        $token = $this->request->getGet('token');
×
120

121
        /** @var UserIdentityModel $identityModel */
UNCOV
122
        $identityModel = model(UserIdentityModel::class);
×
123

UNCOV
124
        $identity = $identityModel->getIdentityBySecret(IdentityType::RESET_PASSWORD->value, $token);
×
125

126
        if ($identity === null) {
×
UNCOV
127
            return redirect()->route('password-reset-request')
×
UNCOV
128
                ->with('error', lang('Auth.passwordResetTokenInvalid'));
×
129
        }
130

UNCOV
131
        if (Time::now()->isAfter($identity->expires)) {
×
132
            // Delete expired token
UNCOV
133
            $identityModel->delete($identity->id);
×
134

UNCOV
135
            return redirect()->route('password-reset-request')
×
136
                ->with('error', lang('Auth.passwordResetTokenExpired'));
×
137
        }
138

UNCOV
139
        $content = $this->view(setting('Auth.views')['password-reset-form'], [
×
140
            'token' => $token,
×
UNCOV
141
        ]);
×
142

UNCOV
143
        return $this->response->setBody($content);
×
144
    }
145

146
    /**
147
     * Handles the password reset form submission.
148
     */
UNCOV
149
    public function resetAction(): RedirectResponse
×
150
    {
151
        $rules = [
×
UNCOV
152
            'token'            => 'required',
×
153
            'password'         => 'required|min_length[8]',
×
UNCOV
154
            'password_confirm' => 'required|matches[password]',
×
155
        ];
×
156

157
        $postData = $this->request->getPost();
×
158

UNCOV
159
        if (! $this->validateRequest($postData, $rules)) {
×
160
            $token = $this->request->getPost('token');
×
161

162
            return redirect()->to(site_url('password-reset/verify?token=' . $token))
×
UNCOV
163
                ->withInput()
×
164
                ->with('errors', $this->validator->getErrors());
×
165
        }
166

UNCOV
167
        $token = $this->request->getPost('token');
×
168

169
        /** @var UserIdentityModel $identityModel */
170
        $identityModel = model(UserIdentityModel::class);
×
171

172
        $identity = $identityModel->getIdentityBySecret(IdentityType::RESET_PASSWORD->value, $token);
×
173

UNCOV
174
        if ($identity === null) {
×
UNCOV
175
            return redirect()->route('password-reset-request')
×
UNCOV
176
                ->with('error', lang('Auth.passwordResetTokenInvalid'));
×
177
        }
178

UNCOV
179
        if (Time::now()->isAfter($identity->expires)) {
×
180
            $identityModel->delete($identity->id);
×
181

182
            return redirect()->route('password-reset-request')
×
183
                ->with('error', lang('Auth.passwordResetTokenExpired'));
×
184
        }
185

186
        // Find the user
UNCOV
187
        $user = $this->provider->findById($identity->user_id);
×
188

189
        if ($user === null) {
×
UNCOV
190
            return redirect()->route('password-reset-request')
×
191
                ->with('error', lang('Auth.passwordResetTokenInvalid'));
×
192
        }
193

194
        // Update the password
UNCOV
195
        $password = $this->request->getPost('password');
×
196
        $user->setPassword($password);
×
197

198
        /** @var UserModel $userModel */
199
        $userModel = model(UserModel::class);
×
UNCOV
200
        $userModel->save($user);
×
201

202
        // Delete the reset token identity
203
        $identityModel->delete($identity->id);
×
204

205
        Events::trigger('passwordReset', $user);
×
206

UNCOV
207
        return redirect()->route('login')
×
208
            ->with('message', lang('Auth.passwordResetSuccess'));
×
209
    }
210

211
    /**
212
     * Returns the rules that should be used for validation.
213
     *
214
     * @return         array<string, array<string, list<string>|string>>
215
     * @phpstan-return array<string, array<string, string|list<string>>>
216
     */
UNCOV
217
    protected function getValidationRules(): array
×
218
    {
219
        return [
×
220
            'email' => config('Auth')->emailValidationRules,
×
UNCOV
221
        ];
×
222
    }
223
}
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