Skip to content

Commit d6ae006

Browse files
committed
Adding more refactor
1 parent 0caff76 commit d6ae006

File tree

4 files changed

+69
-61
lines changed

4 files changed

+69
-61
lines changed

app/Http/Controllers/Settings/TwoFactorAuthController.php

Lines changed: 25 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -20,30 +20,40 @@ class TwoFactorAuthController extends Controller
2020
* @param \Illuminate\Http\Request $request
2121
* @return \Inertia\Response
2222
*/
23-
public function edit(Request $request)
23+
public function show(Request $request)
2424
{
25+
$user = $request->user();
26+
$confirmed = !is_null($user->two_factor_confirmed_at);
27+
2528
return Inertia::render('settings/two-factor', [
26-
'confirmed' => !is_null($request->user()->two_factor_confirmed_at),
27-
'recoveryCodes' => $this->getRecoveryCodes($request->user()),
29+
'confirmed' => $confirmed,
30+
'recoveryCodes' => $this->getRecoveryCodes($user),
2831
]);
2932
}
3033

3134
/**
3235
* Enable two factor authentication for the user.
3336
*
3437
* @param \Illuminate\Http\Request $request
35-
* @return \Illuminate\Http\RedirectResponse
38+
* @return \Illuminate\Http\JsonResponse
3639
*/
3740
public function enable(Request $request)
3841
{
3942
[$qrCode, $secret] = app(GenerateQrCodeAndSecretKey::class)($request->user());
4043

44+
$recoveryCodes = $this->generateRecoveryCodes($request->user());
45+
4146
$request->user()->forceFill([
4247
'two_factor_secret' => encrypt($secret),
43-
'two_factor_recovery_codes' => encrypt(json_encode($this->generateRecoveryCodes($request->user())))
48+
'two_factor_recovery_codes' => encrypt(json_encode($recoveryCodes))
4449
])->save();
4550

46-
return back()->with('status', 'two-factor-authentication-enabled');
51+
return response()->json([
52+
'status' => 'two-factor-authentication-enabled',
53+
'svg' => $qrCode,
54+
'secret' => $secret,
55+
'recovery_codes' => $recoveryCodes
56+
]);
4757
}
4858

4959
/**
@@ -98,45 +108,6 @@ public function confirm(Request $request)
98108
return back()->withErrors(['code' => 'The provided two-factor authentication code was invalid.']);
99109
}
100110

101-
/**
102-
* Get the QR code SVG for the user's two factor authentication.
103-
*
104-
* @param \Illuminate\Http\Request $request
105-
* @return \Illuminate\Http\Response
106-
*/
107-
public function qrCode(Request $request)
108-
{
109-
if (empty($request->user()->two_factor_secret)) {
110-
return response('', 404);
111-
}
112-
113-
// Get the existing secret key instead of generating a new one
114-
$secret = decrypt($request->user()->two_factor_secret);
115-
116-
// Generate QR code based on the existing secret
117-
$google2fa = new \PragmaRX\Google2FA\Google2FA();
118-
$companyName = config('app.name', 'Laravel');
119-
120-
$g2faUrl = $google2fa->getQRCodeUrl(
121-
$companyName,
122-
$request->user()->email,
123-
$secret
124-
);
125-
126-
$writer = new \BaconQrCode\Writer(
127-
new \BaconQrCode\Renderer\ImageRenderer(
128-
new \BaconQrCode\Renderer\RendererStyle\RendererStyle(400),
129-
new \BaconQrCode\Renderer\Image\SvgImageBackEnd()
130-
)
131-
);
132-
133-
$qrCode = base64_encode($writer->writeString($g2faUrl));
134-
135-
return response()->json([
136-
'svg' => $qrCode,
137-
'secret' => $secret
138-
]);
139-
}
140111

141112
/**
142113
* Get the recovery codes for the user.
@@ -177,7 +148,7 @@ private function getRecoveryCodes($user)
177148
* Generate new recovery codes for the user.
178149
*
179150
* @param \Illuminate\Http\Request $request
180-
* @return \Illuminate\Http\RedirectResponse
151+
* @return \Illuminate\Http\Response
181152
*/
182153
public function regenerateRecoveryCodes(Request $request)
183154
{
@@ -187,6 +158,14 @@ public function regenerateRecoveryCodes(Request $request)
187158
'two_factor_recovery_codes' => encrypt(json_encode($codes))
188159
])->save();
189160

161+
// Check if this is an AJAX request
162+
if ($request->wantsJson() || $request->ajax()) {
163+
return response()->json([
164+
'status' => 'recovery-codes-generated',
165+
'recovery_codes' => $codes
166+
]);
167+
}
168+
190169
return back()->with('status', 'recovery-codes-generated');
191170
}
192171
}

resources/js/pages/settings/two-factor.tsx

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useState, useEffect } from 'react';
22
import { Head, useForm } from '@inertiajs/react';
3+
import axios from 'axios';
34
import AppLayout from '@/layouts/app-layout';
45
import SettingsLayout from '@/layouts/settings/layout';
56
import HeadingSmall from '@/components/heading-small';
@@ -59,12 +60,17 @@ export default function TwoFactor({ confirmed: initialConfirmed, recoveryCodes }
5960
}
6061
};
6162

62-
const fetchRecoveryCodes = async () => {
63-
const response = await fetch(route('two-factor.recovery-codes'));
64-
const data = await response.json();
65-
setRecoveryCodesList(data.recoveryCodes);
63+
// Use recovery codes that are already in the props
64+
const showRecoveryCodes = () => {
6665
setShowingRecoveryCodes(true);
6766
};
67+
68+
// Only fetch new recovery codes when needed (after confirmation)
69+
const fetchRecoveryCodes = async () => {
70+
// After confirming 2FA, we need to get the latest recovery codes
71+
// The codes are already included in the props, so we just need to show them
72+
showRecoveryCodes();
73+
};
6874

6975
const verifyTwoFactorCode = () => {
7076
if (!data.code || data.code.length !== 6) {
@@ -84,7 +90,7 @@ export default function TwoFactor({ confirmed: initialConfirmed, recoveryCodes }
8490
setShowModal(false);
8591
setVerifyStep(false);
8692
reset();
87-
fetchRecoveryCodes();
93+
showRecoveryCodes();
8894
},
8995
onError: (errors) => {
9096
if (errors.code) {
@@ -100,12 +106,27 @@ export default function TwoFactor({ confirmed: initialConfirmed, recoveryCodes }
100106
setTimeout(() => setCopied(false), 1500);
101107
};
102108

109+
interface RecoveryCodesResponse {
110+
status: string;
111+
recovery_codes: string[];
112+
}
113+
103114
const regenerateRecoveryCodes = () => {
104-
post(route('two-factor.regenerate-recovery-codes'), {
105-
preserveScroll: true,
106-
onSuccess: () => {
107-
fetchRecoveryCodes();
108-
},
115+
// Use Axios directly which will handle CSRF tokens automatically
116+
// Laravel sets up Axios with CSRF protection in resources/js/bootstrap.js
117+
axios.post<RecoveryCodesResponse>(route('two-factor.regenerate-recovery-codes'), {}, {
118+
headers: {
119+
'X-Requested-With': 'XMLHttpRequest',
120+
'Accept': 'application/json'
121+
}
122+
})
123+
.then((response) => {
124+
if (response.data && response.data.recovery_codes) {
125+
setRecoveryCodesList(response.data.recovery_codes);
126+
}
127+
})
128+
.catch((error) => {
129+
console.error('Error regenerating recovery codes:', error);
109130
});
110131
};
111132

routes/settings.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,11 @@
1616
Route::get('settings/password', [PasswordController::class, 'edit'])->name('password.edit');
1717
Route::put('settings/password', [PasswordController::class, 'update'])->name('password.update');
1818

19-
Route::get('settings/two-factor', [TwoFactorAuthController::class, 'edit'])->name('two-factor.edit');
19+
Route::get('settings/two-factor', [TwoFactorAuthController::class, 'show'])->name('two-factor.show');
2020
Route::post('settings/two-factor', [TwoFactorAuthController::class, 'enable'])->name('two-factor.enable');
2121
Route::post('settings/two-factor/confirm', [TwoFactorAuthController::class, 'confirm'])->name('two-factor.confirm');
22-
Route::delete('settings/two-factor', [TwoFactorAuthController::class, 'disable'])->name('two-factor.disable');
23-
Route::get('settings/two-factor/qr-code', [TwoFactorAuthController::class, 'qrCode'])->name('two-factor.qr-code');
24-
Route::get('settings/two-factor/recovery-codes', [TwoFactorAuthController::class, 'recoveryCodes'])->name('two-factor.recovery-codes');
2522
Route::post('settings/two-factor/recovery-codes', [TwoFactorAuthController::class, 'regenerateRecoveryCodes'])->name('two-factor.regenerate-recovery-codes');
23+
Route::delete('settings/two-factor', [TwoFactorAuthController::class, 'disable'])->name('two-factor.disable');
2624

2725
Route::get('settings/appearance', function () {
2826
return Inertia::render('settings/appearance');

tests/Feature/Auth/TwoFactorAuthTest.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,17 @@ public function test_can_enable_two_factor_authentication()
4444

4545
// Simulate enabling 2FA via POST
4646
$response = $this->post('/settings/two-factor');
47-
$response->assertRedirect(); // Should redirect after enabling
47+
48+
// Assert JSON response with expected structure
49+
$response->assertStatus(200)
50+
->assertJson([
51+
'status' => 'two-factor-authentication-enabled'
52+
])
53+
->assertJsonStructure([
54+
'svg',
55+
'secret',
56+
'recovery_codes'
57+
]);
4858

4959
$user->refresh();
5060
$this->assertNotNull($user->two_factor_secret);

0 commit comments

Comments
 (0)