Skip to content

Instantly share code, notes, and snippets.

@jleei
Forked from dmandrade/fortify-confirm-2fa.patch
Created December 25, 2021 03:48
Show Gist options
  • Save jleei/8e281eb60cf089b13115487e7fd3401b to your computer and use it in GitHub Desktop.
Save jleei/8e281eb60cf089b13115487e7fd3401b to your computer and use it in GitHub Desktop.

Revisions

  1. @dmandrade dmandrade created this gist Jun 2, 2021.
    318 changes: 318 additions & 0 deletions fortify-confirm-2fa.patch
    Original file line number Diff line number Diff line change
    @@ -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,
    ]),
    ],