|
|
@@ -0,0 +1,318 @@ |
|
|
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, |
|
|
]), |
|
|
], |