Skip to content

Commit ef8a672

Browse files
committed
Adding 2fa initial commit
1 parent 7e160e0 commit ef8a672

26 files changed

+10149
-6
lines changed

.DS_Store

6 KB
Binary file not shown.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace App\Actions\TwoFactorAuth;
4+
5+
use Illuminate\Support\Facades\Auth;
6+
use Illuminate\Support\Facades\Session;
7+
8+
class CompleteTwoFactorAuthentication
9+
{
10+
/**
11+
* Complete the two-factor authentication process.
12+
*
13+
* @param mixed $user The user to authenticate
14+
* @return void
15+
*/
16+
public function __invoke($user): void
17+
{
18+
// Log the user in
19+
Auth::login($user);
20+
21+
// Clear the session that is used to determine if the user can visit the 2fa challenge page
22+
Session::forget('login.id');
23+
}
24+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace App\Actions\TwoFactorAuth;
4+
5+
class ConfirmTwoFactorAuthentication
6+
{
7+
/**
8+
* Confirm two factor authentication for the user.
9+
*
10+
* @param mixed $user
11+
* @return bool
12+
*/
13+
public function __invoke($user)
14+
{
15+
$user->forceFill([
16+
'two_factor_confirmed_at' => now(),
17+
])->save();
18+
19+
return true;
20+
}
21+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace App\Actions\TwoFactorAuth;
4+
5+
use App\Models\User;
6+
7+
class DisableTwoFactorAuthentication
8+
{
9+
/**
10+
* Disable two factor authentication for the user.
11+
*
12+
* @return void
13+
*/
14+
public function __invoke($user)
15+
{
16+
if (! is_null($user->two_factor_secret) ||
17+
! is_null($user->two_factor_recovery_codes) ||
18+
! is_null($user->two_factor_confirmed_at)) {
19+
$user->forceFill([
20+
'two_factor_secret' => null,
21+
'two_factor_recovery_codes' => null,
22+
'two_factor_confirmed_at' => null,
23+
])->save();
24+
}
25+
}
26+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace App\Actions\TwoFactorAuth;
4+
5+
use Illuminate\Support\Collection;
6+
use Illuminate\Support\Str;
7+
8+
class GenerateNewRecoveryCodes
9+
{
10+
/**
11+
* Generate new recovery codes for the user.
12+
*
13+
* @param mixed $user
14+
* @return void
15+
*/
16+
public function __invoke($user): Collection
17+
{
18+
return Collection::times(8, function () {
19+
return $this->generate();
20+
});
21+
}
22+
23+
public function generate()
24+
{
25+
return Str::random(10).'-'.Str::random(10);
26+
}
27+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace App\Actions\TwoFactorAuth;
4+
5+
use BaconQrCode\Renderer\Image\SvgImageBackEnd;
6+
use BaconQrCode\Renderer\ImageRenderer;
7+
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
8+
use BaconQrCode\Writer;
9+
use App\Models\User;
10+
use PragmaRX\Google2FA\Google2FA;
11+
12+
class GenerateQrCodeAndSecretKey
13+
{
14+
public string $companyName;
15+
16+
/**
17+
* Generate new recovery codes for the user.
18+
*
19+
* @return array{string, string}
20+
*/
21+
public function __invoke($user): array
22+
{
23+
24+
$google2fa = new Google2FA;
25+
$secret_key = $google2fa->generateSecretKey();
26+
27+
$this->companyName = 'Auth';
28+
if (is_string(config('app.name'))) {
29+
$this->companyName = config('app.name');
30+
}
31+
32+
$g2faUrl = $google2fa->getQRCodeUrl(
33+
$this->companyName,
34+
(string) $user->email,
35+
$secret_key
36+
);
37+
38+
$writer = new Writer(
39+
new ImageRenderer(
40+
new RendererStyle(800),
41+
new SvgImageBackEnd()
42+
)
43+
);
44+
45+
$qrcode_image = base64_encode($writer->writeString($g2faUrl));
46+
47+
return [$qrcode_image, $secret_key];
48+
49+
}
50+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace App\Actions\TwoFactorAuth;
4+
5+
use Illuminate\Support\Facades\Session;
6+
7+
class GetTwoFactorAuthenticatableUser
8+
{
9+
/**
10+
* Get the user that is in the process of two-factor authentication.
11+
*
12+
* @return mixed|null The user model instance or null if not found
13+
*/
14+
public function __invoke()
15+
{
16+
$userId = Session::get('login.id');
17+
18+
if (!$userId) {
19+
return null;
20+
}
21+
22+
// Get the user model from auth config
23+
$userModel = app(config('auth.providers.users.model'));
24+
25+
return $userModel::find($userId);
26+
}
27+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace App\Actions\TwoFactorAuth;
4+
5+
class ProcessRecoveryCode
6+
{
7+
/**
8+
* Verify a recovery code and remove it from the list if valid.
9+
*
10+
* @param array $recoveryCodes The array of recovery codes
11+
* @param string $submittedCode The code submitted by the user
12+
* @return array|false Returns the updated array of recovery codes if valid, or false if invalid
13+
*/
14+
public function __invoke(array $recoveryCodes, string $submittedCode)
15+
{
16+
// Clean the submitted code
17+
$submittedCode = trim($submittedCode);
18+
19+
// If the user has entered multiple codes, only validate the first one
20+
$submittedCode = explode(" ", $submittedCode)[0];
21+
22+
// Check if the code is valid
23+
if (!in_array($submittedCode, $recoveryCodes)) {
24+
return false;
25+
}
26+
27+
// Remove the used recovery code from the list
28+
$updatedCodes = array_values(array_filter($recoveryCodes, function($code) use ($submittedCode) {
29+
return $code !== $submittedCode;
30+
}));
31+
32+
return $updatedCodes;
33+
}
34+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace App\Actions\TwoFactorAuth;
4+
5+
use PragmaRX\Google2FA\Google2FA;
6+
7+
class VerifyTwoFactorCode
8+
{
9+
/**
10+
* Verify a two-factor authentication code.
11+
*
12+
* @param string $secret The decrypted secret key
13+
* @param string $code The code to verify
14+
* @return bool
15+
*/
16+
public function __invoke(string $secret, string $code): bool
17+
{
18+
$google2fa = new Google2FA();
19+
return $google2fa->verifyKey($secret, $code);
20+
}
21+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Auth;
4+
5+
use App\Http\Controllers\Controller;
6+
use Illuminate\Http\Request;
7+
use Illuminate\Support\Facades\Auth;
8+
use Inertia\Inertia;
9+
use Laravel\Fortify\Actions\ConfirmTwoFactorAuthentication;
10+
use Laravel\Fortify\Actions\DisableTwoFactorAuthentication;
11+
use Laravel\Fortify\Actions\EnableTwoFactorAuthentication;
12+
use Laravel\Fortify\Actions\GenerateNewRecoveryCodes;
13+
use Laravel\Fortify\Features;
14+
15+
class TwoFactorAuthenticatedSessionController extends Controller
16+
{
17+
/**
18+
* Display the two factor authentication challenge view.
19+
*
20+
* @param \Illuminate\Http\Request $request
21+
* @return \Inertia\Response
22+
*/
23+
public function create(Request $request)
24+
{
25+
if (! $request->session()->has('auth.two_factor_user_id')) {
26+
return redirect()->route('login');
27+
}
28+
29+
return Inertia::render('auth/two-factor-challenge');
30+
}
31+
32+
/**
33+
* Attempt to authenticate a new session using the two factor authentication code.
34+
*
35+
* @param \Illuminate\Http\Request $request
36+
* @return mixed
37+
*/
38+
public function store(Request $request)
39+
{
40+
$request->validate([
41+
'code' => 'nullable|string',
42+
'recovery_code' => 'nullable|string',
43+
]);
44+
45+
$user = Auth::loginUsingId($request->session()->pull('auth.two_factor_user_id'));
46+
47+
if ($request->filled('code')) {
48+
$request->session()->put([
49+
'login.id' => $user->getKey(),
50+
'login.remember' => $request->session()->pull('auth.two_factor_remember'),
51+
]);
52+
53+
$result = app(ConfirmTwoFactorAuthentication::class)(
54+
$request->user(),
55+
$request->code
56+
);
57+
58+
if ($result) {
59+
$request->session()->forget('login');
60+
return redirect()->intended(route('dashboard'));
61+
}
62+
63+
return back()->withErrors(['code' => __('The provided two factor authentication code was invalid.')]);
64+
}
65+
66+
if ($request->filled('recovery_code')) {
67+
$recovery = collect($user->recoveryCodes())->first(function ($code) use ($request) {
68+
return hash_equals($code, $request->recovery_code);
69+
});
70+
71+
if (! $recovery) {
72+
return back()->withErrors(['recovery_code' => __('The provided two factor authentication recovery code was invalid.')]);
73+
}
74+
75+
$user->replaceRecoveryCode($recovery);
76+
77+
$request->session()->forget('login');
78+
79+
return redirect()->intended(route('dashboard'));
80+
}
81+
82+
return back()->withErrors(['code' => __('Please provide a valid two factor authentication code.')]);
83+
}
84+
}

0 commit comments

Comments
 (0)