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

daycry / auth / 22527658769

28 Feb 2026 07:41PM UTC coverage: 63.267% (-3.6%) from 66.864%
22527658769

push

github

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

Implement TOTP 2FA, JWT auth, device session tracking, and docs overhaul

465 of 1168 new or added lines in 52 files covered. (39.81%)

129 existing lines in 46 files 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/UserSecurityController.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\RedirectResponse;
17
use Daycry\Auth\Libraries\TOTP;
18
use Daycry\Auth\Models\DeviceSessionModel;
19

20
/**
21
 * UserSecurityController
22
 *
23
 * Lets authenticated users manage their own security settings:
24
 *   - Active device sessions (list + revoke)
25
 *   - TOTP 2FA setup / disable
26
 *
27
 * Routes to add in your app (example):
28
 *   $routes->group('account/security', ['filter' => 'session'], static function ($routes): void {
29
 *       $routes->get('/',                  'Daycry\Auth\Controllers\UserSecurityController::index',             ['as' => 'security']);
30
 *       $routes->post('sessions/revoke',   'Daycry\Auth\Controllers\UserSecurityController::revokeSession',     ['as' => 'security-revoke-session']);
31
 *       $routes->post('sessions/revoke-all','Daycry\Auth\Controllers\UserSecurityController::revokeAllSessions',['as' => 'security-revoke-all']);
32
 *       $routes->get('totp/setup',         'Daycry\Auth\Controllers\UserSecurityController::totpSetup',        ['as' => 'totp-setup']);
33
 *       $routes->post('totp/setup/confirm','Daycry\Auth\Controllers\UserSecurityController::totpSetupConfirm', ['as' => 'totp-setup-confirm']);
34
 *       $routes->post('totp/disable',      'Daycry\Auth\Controllers\UserSecurityController::totpDisable',      ['as' => 'totp-disable']);
35
 *       $routes->post('sessions/password', 'Daycry\Auth\Controllers\UserSecurityController::changePassword',   ['as' => 'security-change-password']);
36
 *       $routes->get('email/change',       'Daycry\Auth\Controllers\UserSecurityController::changeEmailView',  ['as' => 'security-change-email']);
37
 *       $routes->post('email/change',      'Daycry\Auth\Controllers\UserSecurityController::changeEmail',      ['as' => 'security-change-email-action']);
38
 *       $routes->get('email/confirm',      'Daycry\Auth\Controllers\UserSecurityController::confirmEmailChange',['as' => 'security-confirm-email']);
39
 *       $routes->post('oauth/unlink',      'Daycry\Auth\Controllers\UserSecurityController::unlinkOauth',      ['as' => 'security-unlink-oauth']);
40
 *   });
41
 */
42
class UserSecurityController extends BaseAuthController
43
{
NEW
44
    protected function getValidationRules(): array
×
45
    {
NEW
46
        return [];
×
47
    }
48

49
    /**
50
     * Security overview: device sessions + TOTP status.
51
     */
NEW
52
    public function index(): string
×
53
    {
NEW
54
        $user = auth()->user();
×
55

56
        /** @var DeviceSessionModel $deviceModel */
NEW
57
        $deviceModel = model(DeviceSessionModel::class);
×
58

NEW
59
        $sessions   = $deviceModel->getActiveForUser($user);
×
NEW
60
        $currentSid = session_id();
×
61

NEW
62
        return $this->view('Daycry\Auth\Views\profile\security', [
×
NEW
63
            'sessions'    => $sessions,
×
NEW
64
            'currentSid'  => $currentSid,
×
NEW
65
            'totpEnabled' => $user->hasTotpEnabled(),
×
NEW
66
        ]);
×
67
    }
68

69
    /**
70
     * Revoke a single device session.
71
     */
NEW
72
    public function revokeSession(): RedirectResponse
×
73
    {
NEW
74
        $sessionId = (string) $this->request->getPost('session_id');
×
75

NEW
76
        if ($sessionId === '') {
×
NEW
77
            return redirect()->back()->with('error', 'Invalid session.');
×
78
        }
79

NEW
80
        $user = auth()->user();
×
81

82
        /** @var DeviceSessionModel $deviceModel */
NEW
83
        $deviceModel = model(DeviceSessionModel::class);
×
84

85
        // Safety check: ensure the session belongs to the current user
NEW
86
        $session = $deviceModel->findBySessionId($sessionId);
×
87

NEW
88
        if ($session === null || $session->user_id !== $user->id) {
×
NEW
89
            return redirect()->back()->with('error', 'Session not found.');
×
90
        }
91

NEW
92
        $deviceModel->terminateSession($sessionId);
×
93

NEW
94
        return redirect()->back()->with('message', 'Session revoked successfully.');
×
95
    }
96

97
    /**
98
     * Revoke all other active sessions (keep current one).
99
     */
NEW
100
    public function revokeAllSessions(): RedirectResponse
×
101
    {
NEW
102
        $user = auth()->user();
×
103

104
        /** @var DeviceSessionModel $deviceModel */
NEW
105
        $deviceModel = model(DeviceSessionModel::class);
×
NEW
106
        $deviceModel->terminateAllForUser($user, session_id());
×
107

NEW
108
        return redirect()->back()->with('message', 'All other sessions have been revoked.');
×
109
    }
110

111
    /**
112
     * Show the TOTP setup page with QR code.
113
     */
NEW
114
    public function totpSetup(): RedirectResponse|string
×
115
    {
NEW
116
        $user = auth()->user();
×
117

NEW
118
        if ($user->hasTotpEnabled()) {
×
NEW
119
            return redirect()->route('security')->with('error', 'TOTP is already enabled.');
×
120
        }
121

NEW
122
        $otpAuthUrl = $user->enableTotp();
×
NEW
123
        $secret     = $user->getTotpSecret();
×
124

NEW
125
        return $this->view('Daycry\Auth\Views\totp_setup_show', [
×
NEW
126
            'otpAuthUrl' => $otpAuthUrl,
×
NEW
127
            'secret'     => $secret ?? '',
×
NEW
128
            'qrCodeUrl'  => TOTP::getQRCodeUrl($otpAuthUrl),
×
NEW
129
            'confirmUrl' => url_to('totp-setup-confirm'),
×
NEW
130
        ]);
×
131
    }
132

133
    /**
134
     * Confirm TOTP setup by verifying the first code.
135
     */
NEW
136
    public function totpSetupConfirm(): RedirectResponse|string
×
137
    {
NEW
138
        $user = auth()->user();
×
NEW
139
        $code = (string) $this->request->getPost('token');
×
140

NEW
141
        if (! $user->verifyTotpCode($code)) {
×
142
            // Remove the just-generated secret so setup can restart cleanly
NEW
143
            $user->disableTotp();
×
144

NEW
145
            return redirect()->route('totp-setup')
×
NEW
146
                ->with('error', lang('Auth.totpSetupInvalidCode'));
×
147
        }
148

NEW
149
        return $this->view('Daycry\Auth\Views\totp_setup_success', [
×
NEW
150
            'redirectUrl' => url_to('security'),
×
NEW
151
        ]);
×
152
    }
153

154
    /**
155
     * Disable TOTP for the current user.
156
     */
NEW
157
    public function totpDisable(): RedirectResponse
×
158
    {
NEW
159
        $code = (string) $this->request->getPost('token');
×
NEW
160
        $user = auth()->user();
×
161

NEW
162
        if (! $user->verifyTotpCode($code)) {
×
NEW
163
            return redirect()->back()->with('error', lang('Auth.invalidTotpToken'));
×
164
        }
165

NEW
166
        $user->disableTotp();
×
167

NEW
168
        return redirect()->route('security')->with('message', 'Two-factor authentication has been disabled.');
×
169
    }
170
}
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