Passed
Push — master ( 5deb44...ec8b22 )
by Darko
08:20
created

PasswordSecurityController::verify2fa()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 79
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 39
c 1
b 1
f 0
dl 0
loc 79
rs 8.3626
cc 7
nc 5
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace App\Http\Controllers;
4
5
use App\Http\Requests\Disable2faPasswordSecurityRequest;
6
use App\Models\PasswordSecurity;
7
use Illuminate\Contracts\View\Factory;
8
use Illuminate\Contracts\View\View;
9
use Illuminate\Foundation\Application;
10
use Illuminate\Http\RedirectResponse;
11
use Illuminate\Http\Request;
12
use Illuminate\Support\Facades\Auth;
13
use Illuminate\Support\Facades\Hash;
14
15
class PasswordSecurityController extends Controller
16
{
17
    public function show2faForm(Request $request): Application|View|Factory|\Illuminate\Contracts\Foundation\Application
18
    {
19
        $user = $request->user();
20
21
        $google2fa_url = '';
22
        if ($user->passwordSecurity()->exists()) {
23
            $google2fa_url = \Google2FA::getQRCodeInline(
0 ignored issues
show
Bug introduced by
The type Google2FA was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
24
                config('app.name'),
25
                $user->email,
26
                $user->passwordSecurity->google2fa_secret
27
            );
28
        }
29
        $data = [
30
            'user' => $user,
31
            'google2fa_url' => $google2fa_url,
32
        ];
33
34
        return view('auth.2fa')->with('data', $data);
35
    }
36
37
    /**
38
     * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
39
     * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
40
     * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
41
     */
42
    public function generate2faSecret(Request $request): RedirectResponse
43
    {
44
        $user = $request->user();
45
46
        // Add the secret key to the registration data
47
        PasswordSecurity::create(
48
            [
49
                'user_id' => $user->id,
50
                'google2fa_enable' => 0,
51
                'google2fa_secret' => \Google2FA::generateSecretKey(),
52
            ]
53
        );
54
55
        return redirect()->to('2fa')->with('success', 'Secret Key is generated, Please verify Code to Enable 2FA');
56
    }
57
58
    /**
59
     * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
60
     * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
61
     * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
62
     */
63
    public function enable2fa(Request $request): RedirectResponse
64
    {
65
        $user = $request->user();
66
        $secret = $request->input('verify-code');
67
        $valid = \Google2FA::verifyKey($user->passwordSecurity->google2fa_secret, $secret);
68
        if ($valid) {
69
            $user->passwordSecurity->google2fa_enable = 1;
70
            $user->passwordSecurity->save();
71
72
            // Check if we should redirect to profile page
73
            if ($request->has('redirect_to_profile')) {
74
                return redirect()->to('profileedit#security')->with('success_2fa', '2FA is Enabled Successfully.');
75
            }
76
77
            return redirect()->to('2fa')->with('success', '2FA is Enabled Successfully.');
78
        }
79
80
        // Check if we should redirect to profile page on failure as well
81
        if ($request->has('redirect_to_profile')) {
82
            return redirect()->to('profileedit#security')->with('error_2fa', 'Invalid Verification Code, Please try again.');
83
        }
84
85
        return redirect()->to('2fa')->with('error', 'Invalid Verification Code, Please try again.');
86
    }
87
88
    public function disable2fa(Disable2faPasswordSecurityRequest $request): \Illuminate\Routing\Redirector|RedirectResponse|\Illuminate\Contracts\Foundation\Application
89
    {
90
        if (! (Hash::check($request->get('current-password'), $request->user()->password))) {
91
            // Password doesn't match
92
            if ($request->has('redirect_to_profile') || $request->has('from_profile')) {
93
                return redirect()->to('profileedit#security')->with('error_2fa', 'Your password does not match with your account password. Please try again.');
94
            }
95
96
            return redirect()->back()->with('error', 'Your password does not match with your account password. Please try again.');
97
        }
98
99
        $validatedData = $request->validated();
0 ignored issues
show
Unused Code introduced by
The assignment to $validatedData is dead and can be removed.
Loading history...
100
        $user = $request->user();
101
        $user->passwordSecurity->google2fa_enable = 0;
102
        $user->passwordSecurity->save();
103
104
        // Check if this request is from the profile edit page
105
        if ($request->has('redirect_to_profile') || $request->has('from_profile')) {
106
            return redirect()->to('profileedit#security')->with('success_2fa', '2FA is now Disabled.');
107
        }
108
109
        return redirect()->to('2fa')->with('success', '2FA is now Disabled.');
110
    }
111
112
    /**
113
     * Verify the 2FA code provided by the user.
114
     */
115
    public function verify2fa(Request $request): RedirectResponse
116
    {
117
        $request->validate([
118
            'one_time_password' => 'required|numeric',
119
            'trust_device' => 'nullable|boolean',
120
        ]);
121
122
        // Get the user ID from session
123
        if (! $request->session()->has('2fa:user:id')) {
124
            return redirect()->route('login')
125
                ->with('message', 'The two-factor authentication session has expired. Please login again.')
126
                ->with('message_type', 'danger');
127
        }
128
129
        $userId = $request->session()->get('2fa:user:id');
130
        $user = \App\Models\User::find($userId);
131
132
        if (! $user || ! $user->passwordSecurity) {
133
            $request->session()->forget('2fa:user:id');
134
135
            return redirect()->route('login')
136
                ->with('message', 'User not found or 2FA not configured. Please login again.')
137
                ->with('message_type', 'danger');
138
        }
139
140
        // Verify the OTP code
141
        $valid = \Google2FA::verifyKey(
142
            $user->passwordSecurity->google2fa_secret,
143
            $request->input('one_time_password')
144
        );
145
146
        if (! $valid) {
147
            return redirect()->route('2fa.verify')
148
                ->with('message', 'Invalid authentication code. Please try again.')
149
                ->with('message_type', 'danger');
150
        }
151
152
        // Log the user back in
153
        Auth::login($user);
154
155
        // Mark the user as having passed 2FA
156
        session([config('google2fa.session_var') => true]);
157
158
        // Store the timestamp for determining how long the 2FA session is valid
159
        session([config('google2fa.session_var').'.auth.passed_at' => time()]);
160
161
        // If the user has checked "trust this device", create a trust token
162
        if ($request->has('trust_device') && $request->input('trust_device') == 1) {
163
            // Generate a unique token for this device
164
            $token = hash('sha256', $user->id.uniqid().time());
165
166
            // Store the token with an expiry time of 30 days
167
            $expiresAt = now()->addDays(30)->timestamp;
168
169
            // Store the trusted device token in a cookie
170
            cookie()->queue(
171
                '2fa_trusted_device',
172
                json_encode([
173
                    'user_id' => $user->id,
174
                    'token' => $token,
175
                    'expires_at' => $expiresAt,
176
                ]),
177
                60 * 24 * 30 // 30 days in minutes
178
            );
179
180
            // Store the token in the database if you want to manage/revoke trusted devices
181
            // This would require creating a new table for trusted devices
182
            // We're using cookies only for this implementation
183
        }
184
185
        // Clean up the temporary session variable
186
        $request->session()->forget('2fa:user:id');
187
188
        // Determine where to redirect after successful verification
189
        $redirectUrl = $request->session()->pull('url.intended', '/');
190
191
        return redirect()->to($redirectUrl)
192
            ->with('message', 'Two-factor authentication verified successfully.')
193
            ->with('message_type', 'success');
194
    }
195
196
    /**
197
     * Display the 2FA verification form for a user who has already authenticated with username/password
198
     * but needs to enter their 2FA code.
199
     */
200
    public function getVerify2fa(Request $request)
201
    {
202
        // Check if user ID is stored in the session
203
        if (! $request->session()->has('2fa:user:id')) {
204
            return redirect()->route('login')
205
                ->withErrors(['msg' => 'The two-factor authentication session has expired. Please login again.']);
206
        }
207
208
        // Get the user ID from session
209
        $userId = $request->session()->get('2fa:user:id');
210
211
        // Get the user
212
        $user = \App\Models\User::find($userId);
213
        if (! $user) {
214
            $request->session()->forget('2fa:user:id');
215
216
            return redirect()->route('login')
217
                ->withErrors(['msg' => 'User not found. Please login again.']);
218
        }
219
220
        $theme = 'Gentele';
221
        $meta_title = 'Two Factor Authentication';
222
        $meta_keywords = 'Two Factor Authentication, 2FA';
223
        $meta_description = 'Two Factor Authentication Verification';
224
225
        app('smarty.view')->assign(compact('meta_title', 'meta_keywords', 'meta_description', 'user'));
226
227
        return app('smarty.view')->display($theme.'/2fa_verify.tpl');
228
    }
229
230
    /**
231
     * Handle disabling 2FA directly from profile page to avoid form conflicts.
232
     * This route is specifically for the profile page 2FA section.
233
     */
234
    public function profileDisable2fa(Request $request): RedirectResponse
235
    {
236
        $request->validate([
237
            'current-password' => 'required',
238
        ]);
239
240
        if (! (Hash::check($request->get('current-password'), $request->user()->password))) {
241
            return redirect()->to('profileedit#security')->with('error_2fa', 'Your password does not match with your account password. Please try again.');
242
        }
243
244
        $user = $request->user();
245
        if ($user->passwordSecurity) {
246
            $user->passwordSecurity->google2fa_enable = 0;
247
            $user->passwordSecurity->save();
248
        }
249
250
        return redirect()->to('profileedit#security')->with('success_2fa', '2FA is now Disabled.');
251
    }
252
253
    /**
254
     * Show the 2FA enable form on a dedicated page
255
     */
256
    public function showEnable2faForm(Request $request): Application|View|Factory|\Illuminate\Contracts\Foundation\Application
257
    {
258
        $user = $request->user();
259
        $success = $request->session()->get('success');
260
        $error = $request->session()->get('error');
261
262
        $google2fa_url = '';
263
        if ($user->passwordSecurity()->exists()) {
264
            $google2fa_url = \Google2FA::getQRCodeInline(
265
                config('app.name'),
266
                $user->email,
267
                $user->passwordSecurity->google2fa_secret
268
            );
269
        }
270
271
        return view('themes.Gentele.2fa_enable', compact('user', 'google2fa_url', 'success', 'error'));
272
    }
273
274
    /**
275
     * Show the 2FA disable form on a dedicated page
276
     */
277
    public function showDisable2faForm(Request $request): Application|View|Factory|\Illuminate\Contracts\Foundation\Application
278
    {
279
        $user = $request->user();
280
        $success = $request->session()->get('success');
281
        $error = $request->session()->get('error');
282
283
        return view('themes.Gentele.2fa_disable', compact('user', 'success', 'error'));
284
    }
285
}
286