Created
June 2, 2021 14:22
Adds option to confirm activation of 2FA
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Adds option to confirm activation of 2FA | |
@package laravel/fortify | |
--- /dev/null | |
+++ database/migrations/2021_06_01_145800_add_two_factor_confirmed_column_to_users_table.php | |
@@ -0,0 +1,34 @@ | |
+<?php | |
+ | |
+use Illuminate\Database\Migrations\Migration; | |
+use Illuminate\Database\Schema\Blueprint; | |
+use Illuminate\Support\Facades\Schema; | |
+ | |
+class AddTwoFactorConfirmedColumnToUsersTable extends Migration | |
+{ | |
+ /** | |
+ * Run the migrations. | |
+ * | |
+ * @return void | |
+ */ | |
+ public function up() | |
+ { | |
+ Schema::table('users', function (Blueprint $table) { | |
+ $table->boolean('two_factor_confirmed') | |
+ ->after('two_factor_recovery_codes') | |
+ ->default(false); | |
+ }); | |
+ } | |
+ | |
+ /** | |
+ * Reverse the migrations. | |
+ * | |
+ * @return void | |
+ */ | |
+ public function down() | |
+ { | |
+ Schema::table('users', function (Blueprint $table) { | |
+ $table->dropColumn('two_factor_confirmed'); | |
+ }); | |
+ } | |
+} | |
--- routes/routes.php | |
+++ routes/routes.php | |
@@ -140,6 +140,10 @@ | |
->middleware($twoFactorMiddleware) | |
->name('two-factor.enable'); | |
+ Route::post('/user/two-factor-authentication/confirm', [TwoFactorAuthenticationController::class, 'confirm']) | |
+ ->middleware($twoFactorMiddleware) | |
+ ->name('two-factor.confirm'); | |
+ | |
Route::delete('/user/two-factor-authentication', [TwoFactorAuthenticationController::class, 'destroy']) | |
->middleware($twoFactorMiddleware) | |
->name('two-factor.disable'); | |
--- /dev/null | |
+++ src/Actions/ConfirmEnableTwoFactorAuthentication.php | |
@@ -0,0 +1,45 @@ | |
+<?php | |
+ | |
+namespace Laravel\Fortify\Actions; | |
+ | |
+use Laravel\Fortify\Contracts\TwoFactorAuthenticationProvider; | |
+ | |
+class ConfirmEnableTwoFactorAuthentication | |
+{ | |
+ /** | |
+ * The two factor authentication provider. | |
+ * | |
+ * @var \Laravel\Fortify\Contracts\TwoFactorAuthenticationProvider | |
+ */ | |
+ protected $provider; | |
+ | |
+ /** | |
+ * Create a new action instance. | |
+ * | |
+ * @param \Laravel\Fortify\Contracts\TwoFactorAuthenticationProvider $provider | |
+ * @return void | |
+ */ | |
+ public function __construct(TwoFactorAuthenticationProvider $provider) | |
+ { | |
+ $this->provider = $provider; | |
+ } | |
+ | |
+ /** | |
+ * Enable two factor authentication for the user. | |
+ * | |
+ * @param \Illuminate\Foundation\Auth\User $user | |
+ * @param string $code | |
+ * @return bool | |
+ */ | |
+ public function __invoke($user, $code) | |
+ { | |
+ if (! $this->provider->verify(decrypt($user->two_factor_secret), $code)) { | |
+ return false; | |
+ } | |
+ | |
+ $user->two_factor_confirmed = true; | |
+ $user->save(); | |
+ | |
+ return true; | |
+ } | |
+} | |
--- src/Actions/DisableTwoFactorAuthentication.php | |
+++ src/Actions/DisableTwoFactorAuthentication.php | |
@@ -13,6 +13,7 @@ class DisableTwoFactorAuthentication | |
public function __invoke($user) | |
{ | |
$user->forceFill([ | |
+ 'two_factor_confirmed' => false, | |
'two_factor_secret' => null, | |
'two_factor_recovery_codes' => null, | |
])->save(); | |
--- src/Actions/EnableTwoFactorAuthentication.php | |
+++ src/Actions/EnableTwoFactorAuthentication.php | |
@@ -2,43 +2,9 @@ | |
namespace Laravel\Fortify\Actions; | |
-use Illuminate\Support\Collection; | |
-use Laravel\Fortify\Contracts\TwoFactorAuthenticationProvider; | |
-use Laravel\Fortify\RecoveryCode; | |
- | |
-class EnableTwoFactorAuthentication | |
+/** | |
+ * @deprecated | |
+ */ | |
+class EnableTwoFactorAuthentication extends GenerateTwoFactorAuthenticationSecret | |
{ | |
- /** | |
- * The two factor authentication provider. | |
- * | |
- * @var \Laravel\Fortify\Contracts\TwoFactorAuthenticationProvider | |
- */ | |
- protected $provider; | |
- | |
- /** | |
- * Create a new action instance. | |
- * | |
- * @param \Laravel\Fortify\Contracts\TwoFactorAuthenticationProvider $provider | |
- * @return void | |
- */ | |
- public function __construct(TwoFactorAuthenticationProvider $provider) | |
- { | |
- $this->provider = $provider; | |
- } | |
- | |
- /** | |
- * Enable two factor authentication for the user. | |
- * | |
- * @param mixed $user | |
- * @return void | |
- */ | |
- public function __invoke($user) | |
- { | |
- $user->forceFill([ | |
- 'two_factor_secret' => encrypt($this->provider->generateSecretKey()), | |
- 'two_factor_recovery_codes' => encrypt(json_encode(Collection::times(8, function () { | |
- return RecoveryCode::generate(); | |
- })->all())), | |
- ])->save(); | |
- } | |
} | |
--- /dev/null | |
+++ src/Actions/GenerateTwoFactorAuthenticationSecret.php | |
@@ -0,0 +1,44 @@ | |
+<?php | |
+ | |
+namespace Laravel\Fortify\Actions; | |
+ | |
+use Illuminate\Support\Collection; | |
+use Laravel\Fortify\Contracts\TwoFactorAuthenticationProvider; | |
+use Laravel\Fortify\RecoveryCode; | |
+ | |
+class GenerateTwoFactorAuthenticationSecret | |
+{ | |
+ /** | |
+ * The two factor authentication provider. | |
+ * | |
+ * @var \Laravel\Fortify\Contracts\TwoFactorAuthenticationProvider | |
+ */ | |
+ protected $provider; | |
+ | |
+ /** | |
+ * Create a new action instance. | |
+ * | |
+ * @param \Laravel\Fortify\Contracts\TwoFactorAuthenticationProvider $provider | |
+ * @return void | |
+ */ | |
+ public function __construct(TwoFactorAuthenticationProvider $provider) | |
+ { | |
+ $this->provider = $provider; | |
+ } | |
+ | |
+ /** | |
+ * Generate two factor authentication secret and recovery codes for the user. | |
+ * | |
+ * @param mixed $user | |
+ * @return void | |
+ */ | |
+ public function __invoke($user) | |
+ { | |
+ $user->forceFill([ | |
+ 'two_factor_secret' => encrypt($this->provider->generateSecretKey()), | |
+ 'two_factor_recovery_codes' => encrypt(json_encode(Collection::times(8, function () { | |
+ return RecoveryCode::generate(); | |
+ })->all())), | |
+ ])->save(); | |
+ } | |
+} | |
--- src/Actions/RedirectIfTwoFactorAuthenticatable.php | |
+++ src/Actions/RedirectIfTwoFactorAuthenticatable.php | |
@@ -49,7 +49,7 @@ public function handle($request, $next) | |
{ | |
$user = $this->validateCredentials($request); | |
- if (optional($user)->two_factor_secret && | |
+ if (optional($user)->is_two_factor_enabled && | |
in_array(TwoFactorAuthenticatable::class, class_uses_recursive($user))) { | |
return $this->twoFactorChallengeResponse($request, $user); | |
} | |
--- src/Http/Controllers/TwoFactorAuthenticationController.php | |
+++ src/Http/Controllers/TwoFactorAuthenticationController.php | |
@@ -5,8 +5,9 @@ | |
use Illuminate\Http\JsonResponse; | |
use Illuminate\Http\Request; | |
use Illuminate\Routing\Controller; | |
+use Laravel\Fortify\Actions\ConfirmEnableTwoFactorAuthentication; | |
use Laravel\Fortify\Actions\DisableTwoFactorAuthentication; | |
-use Laravel\Fortify\Actions\EnableTwoFactorAuthentication; | |
+use Laravel\Fortify\Actions\GenerateTwoFactorAuthenticationSecret; | |
class TwoFactorAuthenticationController extends Controller | |
{ | |
@@ -14,18 +15,40 @@ class TwoFactorAuthenticationController extends Controller | |
* Enable two factor authentication for the user. | |
* | |
* @param \Illuminate\Http\Request $request | |
- * @param \Laravel\Fortify\Actions\EnableTwoFactorAuthentication $enable | |
+ * @param \Laravel\Fortify\Actions\GenerateTwoFactorAuthenticationSecret $enable | |
* @return \Symfony\Component\HttpFoundation\Response | |
*/ | |
- public function store(Request $request, EnableTwoFactorAuthentication $enable) | |
+ public function store(Request $request, GenerateTwoFactorAuthenticationSecret $generate) | |
{ | |
- $enable($request->user()); | |
+ $generate($request->user()); | |
return $request->wantsJson() | |
? new JsonResponse('', 200) | |
: back()->with('status', 'two-factor-authentication-enabled'); | |
} | |
+ /** | |
+ * Confirms activation of two-factor authentication by validating a TOTP code. | |
+ * | |
+ * @param \Illuminate\Http\Request $request | |
+ * @param \Laravel\Fortify\Actions\ConfirmEnableTwoFactorAuthentication $confirm | |
+ * @return \Symfony\Component\HttpFoundation\Response | |
+ */ | |
+ public function confirm(Request $request, ConfirmEnableTwoFactorAuthentication $enable) | |
+ { | |
+ if (! $enable($request->user(), $request->code)) { | |
+ $error = __('The provided two factor authentication code was invalid.'); | |
+ | |
+ return $request->wantsJson() | |
+ ? new JsonResponse($error, 422) | |
+ : back()->withErrors($error); | |
+ } | |
+ | |
+ return $request->wantsJson() | |
+ ? new JsonResponse('', 200) | |
+ : back(); | |
+ } | |
+ | |
/** | |
* Disable two factor authentication for the user. | |
* | |
--- src/TwoFactorAuthenticatable.php | |
+++ src/TwoFactorAuthenticatable.php | |
@@ -22,6 +22,20 @@ public function recoveryCodes() | |
return json_decode(decrypt($this->two_factor_recovery_codes), true); | |
} | |
+ /** | |
+ * Check if two factor authentication is enabled for user. | |
+ * | |
+ * @return string|bool | |
+ */ | |
+ public function getIsTwoFactorEnabledAttribute() | |
+ { | |
+ if (Features::optionEnabled(Features::twoFactorAuthentication(), 'confirmTwoFactor')) { | |
+ return ! empty($this->two_factor_secret) && $this->two_factor_confirmed; | |
+ } | |
+ | |
+ return ! empty($this->two_factor_secret); | |
+ } | |
+ | |
/** | |
* Replace the given recovery code with a new one in the user's stored codes. | |
* | |
--- stubs/fortify.php | |
+++ stubs/fortify.php | |
@@ -139,6 +139,7 @@ | |
Features::updatePasswords(), | |
Features::twoFactorAuthentication([ | |
'confirmPassword' => true, | |
+ 'confirmTwoFactor' => true, | |
]), | |
], |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey,
I've tried this out in a demo project. However, I have run into a problem with applying the patch.
Steps I have taken: