-
Notifications
You must be signed in to change notification settings - Fork 164
Adding Two Factor Auth Feature #84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 29 commits
aed4c2d
3265500
ebcd24a
f541ff6
dc4bdae
74c255f
43397be
36f5121
e346f42
07ab1be
00eafe4
fd98bc2
3623e9e
329f6f7
6347ecc
33ae2b1
5f5645a
0f4958e
069b0cc
05cefc1
3273598
2d11b0b
0b97f86
345ab3b
f5b138c
99143ec
039933f
87f4219
d693b5b
43506e1
f0b8a5a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ | |
/storage/*.key | ||
/storage/pail | ||
/vendor | ||
.DS_Store | ||
.env | ||
.env.backup | ||
.env.production | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<?php | ||
|
||
namespace App\Actions\TwoFactorAuth; | ||
|
||
use Illuminate\Support\Facades\Auth; | ||
use Illuminate\Support\Facades\Session; | ||
|
||
class CompleteTwoFactorAuthentication | ||
{ | ||
/** | ||
* Complete the two-factor authentication process. | ||
* | ||
* @param mixed $user The user to authenticate | ||
* @return void | ||
*/ | ||
public function __invoke($user): void | ||
{ | ||
// Get the remember preference from the session (default to false if not set) | ||
$remember = Session::get('login.remember', false); | ||
|
||
// Log the user in with the remember preference | ||
Auth::login($user, $remember); | ||
|
||
// Clear the session variables used for the 2FA challenge | ||
Session::forget(['login.id', 'login.remember']); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<?php | ||
|
||
namespace App\Actions\TwoFactorAuth; | ||
|
||
class ConfirmTwoFactorAuthentication | ||
{ | ||
/** | ||
* Confirm two factor authentication for the user. | ||
* | ||
* @param mixed $user | ||
* @return bool | ||
*/ | ||
public function __invoke($user) | ||
{ | ||
$user->forceFill([ | ||
'two_factor_confirmed_at' => now(), | ||
])->save(); | ||
|
||
return true; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?php | ||
|
||
namespace App\Actions\TwoFactorAuth; | ||
|
||
use App\Models\User; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unused import |
||
|
||
class DisableTwoFactorAuthentication | ||
{ | ||
/** | ||
* Disable two factor authentication for the user. | ||
* | ||
* @return void | ||
*/ | ||
public function __invoke($user) | ||
{ | ||
if (! is_null($user->two_factor_secret) || | ||
! is_null($user->two_factor_recovery_codes) || | ||
! is_null($user->two_factor_confirmed_at)) { | ||
$user->forceFill([ | ||
'two_factor_secret' => null, | ||
'two_factor_recovery_codes' => null, | ||
'two_factor_confirmed_at' => null, | ||
])->save(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<?php | ||
|
||
namespace App\Actions\TwoFactorAuth; | ||
|
||
use Illuminate\Support\Collection; | ||
use Illuminate\Support\Str; | ||
|
||
class GenerateNewRecoveryCodes | ||
{ | ||
/** | ||
* Generate new recovery codes for the user. | ||
* | ||
* @param mixed $user | ||
* @return void | ||
*/ | ||
public function __invoke($user): Collection | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're not using this argument, remove? |
||
{ | ||
return Collection::times(8, function () { | ||
return $this->generate(); | ||
}); | ||
} | ||
|
||
public function generate() | ||
{ | ||
return Str::random(10).'-'.Str::random(10); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,50 @@ | ||||||
<?php | ||||||
|
||||||
namespace App\Actions\TwoFactorAuth; | ||||||
|
||||||
use BaconQrCode\Renderer\Image\SvgImageBackEnd; | ||||||
use BaconQrCode\Renderer\ImageRenderer; | ||||||
use BaconQrCode\Renderer\RendererStyle\RendererStyle; | ||||||
use BaconQrCode\Writer; | ||||||
use App\Models\User; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unused import |
||||||
use PragmaRX\Google2FA\Google2FA; | ||||||
|
||||||
class GenerateQrCodeAndSecretKey | ||||||
{ | ||||||
public string $companyName; | ||||||
|
||||||
/** | ||||||
* Generate new recovery codes for the user. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
* | ||||||
* @return array{string, string} | ||||||
*/ | ||||||
public function __invoke($user): array | ||||||
{ | ||||||
|
||||||
$google2fa = new Google2FA; | ||||||
$secret_key = $google2fa->generateSecretKey(); | ||||||
|
||||||
$this->companyName = 'Auth'; | ||||||
if (is_string(config('app.name'))) { | ||||||
$this->companyName = config('app.name'); | ||||||
} | ||||||
|
||||||
$g2faUrl = $google2fa->getQRCodeUrl( | ||||||
$this->companyName, | ||||||
(string) $user->email, | ||||||
$secret_key | ||||||
); | ||||||
|
||||||
$writer = new Writer( | ||||||
new ImageRenderer( | ||||||
new RendererStyle(800), | ||||||
new SvgImageBackEnd() | ||||||
) | ||||||
); | ||||||
|
||||||
$qrcode_image = base64_encode($writer->writeString($g2faUrl)); | ||||||
|
||||||
return [$qrcode_image, $secret_key]; | ||||||
|
||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<?php | ||
|
||
namespace App\Actions\TwoFactorAuth; | ||
|
||
use Illuminate\Support\Facades\Session; | ||
|
||
class GetTwoFactorAuthenticatableUser | ||
{ | ||
/** | ||
* Get the user that is in the process of two-factor authentication. | ||
* | ||
* @return mixed|null The user model instance or null if not found | ||
*/ | ||
public function __invoke() | ||
{ | ||
$userId = Session::get('login.id'); | ||
|
||
if (!$userId) { | ||
return null; | ||
} | ||
|
||
// Get the user model from auth config | ||
$userModel = app(config('auth.providers.users.model')); | ||
|
||
return $userModel::find($userId); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
<?php | ||
|
||
namespace App\Actions\TwoFactorAuth; | ||
|
||
class ProcessRecoveryCode | ||
{ | ||
/** | ||
* Verify a recovery code and remove it from the list if valid. | ||
* | ||
* @param array $recoveryCodes The array of recovery codes | ||
* @param string $submittedCode The code submitted by the user | ||
* @return array|false Returns the updated array of recovery codes if valid, or false if invalid | ||
*/ | ||
public function __invoke( | ||
#[\SensitiveParameter] array $recoveryCodes, | ||
#[\SensitiveParameter] string $submittedCode | ||
) { | ||
// Clean the submitted code | ||
$submittedCode = trim($submittedCode); | ||
|
||
// If the user has entered multiple codes, only validate the first one | ||
$submittedCode = explode(" ", $submittedCode)[0]; | ||
|
||
// Check if the code is valid | ||
if (!in_array($submittedCode, $recoveryCodes)) { | ||
return false; | ||
} | ||
|
||
// Remove the used recovery code from the list | ||
$updatedCodes = array_values(array_filter($recoveryCodes, function($code) use ($submittedCode) { | ||
return !hash_equals($code, $submittedCode); | ||
})); | ||
|
||
return $updatedCodes; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<?php | ||
|
||
namespace App\Actions\TwoFactorAuth; | ||
|
||
use PragmaRX\Google2FA\Google2FA; | ||
|
||
class VerifyTwoFactorCode | ||
{ | ||
/** | ||
* Verify a two-factor authentication code. | ||
* | ||
* @param string $secret The decrypted secret key | ||
* @param string $code The code to verify | ||
* @return bool | ||
*/ | ||
public function __invoke( | ||
#[\SensitiveParameter] string $secret, | ||
#[\SensitiveParameter] string $code | ||
): bool { | ||
$google2fa = new Google2FA(); | ||
return $google2fa->verifyKey($secret, $code); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason to not use
Session::pull
since you're doing it below anyway?