Last active
February 27, 2022 06:40
-
-
Save dmandrade/ae0c0d3cb41d55ac475a0110f20b46e0 to your computer and use it in GitHub Desktop.
Add views to confirm 2FA enable using TOTP code
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
Add 2FA enable confirmation using TOTP code | |
@package laravel/jetstream | |
--- src/Http/Livewire/TwoFactorAuthenticationForm.php | |
+++ src/Http/Livewire/TwoFactorAuthenticationForm.php | |
@@ -3,8 +3,9 @@ | |
namespace Laravel\Jetstream\Http\Livewire; | |
use Illuminate\Support\Facades\Auth; | |
+use Laravel\Fortify\Actions\ConfirmEnableTwoFactorAuthentication; | |
use Laravel\Fortify\Actions\DisableTwoFactorAuthentication; | |
-use Laravel\Fortify\Actions\EnableTwoFactorAuthentication; | |
+use Laravel\Fortify\Actions\GenerateTwoFactorAuthenticationSecret; | |
use Laravel\Fortify\Actions\GenerateNewRecoveryCodes; | |
use Laravel\Fortify\Features; | |
use Laravel\Jetstream\ConfirmsPasswords; | |
@@ -29,23 +30,47 @@ class TwoFactorAuthenticationForm extends Component | |
public $showingRecoveryCodes = false; | |
/** | |
- * Enable two factor authentication for the user. | |
+ * Code to confirm activation of two-factor authentication | |
+ * @var string | |
+ */ | |
+ public $confirmationCode = null; | |
+ | |
+ /** | |
+ * Generate two factor authentication secret for user. | |
* | |
- * @param \Laravel\Fortify\Actions\EnableTwoFactorAuthentication $enable | |
+ * @param \Laravel\Fortify\Actions\GenerateTwoFactorAuthenticationSecret $generate | |
* @return void | |
*/ | |
- public function enableTwoFactorAuthentication(EnableTwoFactorAuthentication $enable) | |
+ public function generateTwoFactorAuthenticationSecret(GenerateTwoFactorAuthenticationSecret $generate) | |
{ | |
if (Features::optionEnabled(Features::twoFactorAuthentication(), 'confirmPassword')) { | |
$this->ensurePasswordIsConfirmed(); | |
} | |
- $enable(Auth::user()); | |
+ $generate(Auth::user()); | |
$this->showingQrCode = true; | |
$this->showingRecoveryCodes = true; | |
} | |
+ /** | |
+ * Enable two factor authentication for the user. | |
+ * | |
+ * @param \Laravel\Fortify\Actions\ConfirmEnableTwoFactorAuthentication $enable | |
+ * @return void | |
+ */ | |
+ public function confirmEnableTwoFactorAuthentication(ConfirmEnableTwoFactorAuthentication $enable) | |
+ { | |
+ $enabled = $enable(Auth::user(), $this->confirmationCode); | |
+ | |
+ if (!$enabled) { | |
+ $this->addError('confirmationCode', __('The provided two factor authentication code was invalid.')); | |
+ return; | |
+ } | |
+ | |
+ $this->hideSetup(); | |
+ } | |
+ | |
/** | |
* Display the user's recovery codes. | |
* | |
@@ -89,9 +114,17 @@ public function disableTwoFactorAuthentication(DisableTwoFactorAuthentication $d | |
$this->ensurePasswordIsConfirmed(); | |
} | |
+ $this->hideSetup(); | |
$disable(Auth::user()); | |
} | |
+ protected function hideSetup() | |
+ { | |
+ $this->showingQrCode = false; | |
+ $this->showingRecoveryCodes = false; | |
+ $this->confirmationCode = null; | |
+ } | |
+ | |
/** | |
* Get the current user of the application. | |
* | |
@@ -102,6 +135,16 @@ public function getUserProperty() | |
return Auth::user(); | |
} | |
+ /** | |
+ * Determine if two-factor authentication is pending configuration. | |
+ * | |
+ * @return bool | |
+ */ | |
+ public function getSetupProperty() | |
+ { | |
+ return ! empty($this->user->two_factor_secret) && !$this->user->is_two_factor_enabled; | |
+ } | |
+ | |
/** | |
* Determine if two factor authentication is enabled. | |
* | |
@@ -109,7 +152,7 @@ public function getUserProperty() | |
*/ | |
public function getEnabledProperty() | |
{ | |
- return ! empty($this->user->two_factor_secret); | |
+ return ! empty($this->user->two_factor_secret) && $this->user->is_two_factor_enabled; | |
} | |
/** | |
--- src/Http/Middleware/ShareInertiaData.php | |
+++ src/Http/Middleware/ShareInertiaData.php | |
@@ -48,7 +48,8 @@ public function handle($request, $next) | |
return array_merge($request->user()->toArray(), array_filter([ | |
'all_teams' => Jetstream::hasTeamFeatures() ? $request->user()->allTeams() : null, | |
]), [ | |
- 'two_factor_enabled' => ! is_null($request->user()->two_factor_secret), | |
+ 'two_factor_enabled' => $request->user()->is_two_factor_enabled, | |
+ 'two_factor_setup' => ! is_null($request->user()->two_factor_secret), | |
]); | |
}, | |
'errorBags' => function () { | |
--- stubs/inertia/resources/js/Pages/Profile/TwoFactorAuthenticationForm.vue | |
+++ stubs/inertia/resources/js/Pages/Profile/TwoFactorAuthenticationForm.vue | |
@@ -23,37 +23,44 @@ | |
</p> | |
</div> | |
- <div v-if="twoFactorEnabled"> | |
- <div v-if="qrCode"> | |
- <div class="mt-4 max-w-xl text-sm text-gray-600"> | |
- <p class="font-semibold"> | |
- Two factor authentication is now enabled. Scan the following QR code using your phone's authenticator application. | |
- </p> | |
- </div> | |
+ <div v-if="recoveryCodes.length > 0"> | |
+ <div class="mt-4 max-w-xl text-sm text-gray-600"> | |
+ <p class="font-semibold"> | |
+ Store these recovery codes in a secure password manager. They can be used to recover access to your account if your two factor authentication device is lost. | |
+ </p> | |
+ </div> | |
- <div class="mt-4" v-html="qrCode"> | |
+ <div class="grid gap-1 max-w-xl mt-4 px-4 py-4 font-mono text-sm bg-gray-100 rounded-lg"> | |
+ <div v-for="code in recoveryCodes" :key="code"> | |
+ {{ code }} | |
</div> | |
</div> | |
+ </div> | |
- <div v-if="recoveryCodes.length > 0"> | |
- <div class="mt-4 max-w-xl text-sm text-gray-600"> | |
- <p class="font-semibold"> | |
- Store these recovery codes in a secure password manager. They can be used to recover access to your account if your two factor authentication device is lost. | |
- </p> | |
- </div> | |
+ <div v-if="twoFactorSetup || displayQrCord"> | |
+ <div class="mt-4 max-w-xl text-sm text-gray-600"> | |
+ <p class="font-semibold"> | |
+ Scan the following QR code using your phone\'s authenticator application to setup two factor authentication. | |
+ </p> | |
+ </div> | |
- <div class="grid gap-1 max-w-xl mt-4 px-4 py-4 font-mono text-sm bg-gray-100 rounded-lg"> | |
- <div v-for="code in recoveryCodes" :key="code"> | |
- {{ code }} | |
- </div> | |
+ <div class="mt-4" v-html="qrCode"> | |
+ </div> | |
+ </div> | |
+ | |
+ <div v-if="twoFactorSetup"> | |
+ <div class="mt-4 max-w-xl text-sm text-gray-600"> | |
+ <div class="col-span-6 sm:col-span-4"> | |
+ <jet-label for="confirmationCode" value="After configuring the authenticator application, enter the code to validate the two-factor authentication." /> | |
+ <jet-input id="confirmationCode" type="text" class="mt-1 block w-full" v-model="confirmationCode" /> | |
</div> | |
</div> | |
</div> | |
<div class="mt-5"> | |
- <div v-if="! twoFactorEnabled"> | |
- <jet-confirms-password @confirmed="enableTwoFactorAuthentication"> | |
- <jet-button type="button" :class="{ 'opacity-25': enabling }" :disabled="enabling"> | |
+ <div v-if="! twoFactorEnabled && ! twoFactorSetup"> | |
+ <jet-confirms-password @confirmed="setupTwoFactorAuthentication"> | |
+ <jet-button type="button" :class="{ 'opacity-25': setuping }" :disabled="setuping"> | |
Enable | |
</jet-button> | |
</jet-confirms-password> | |
@@ -73,13 +80,22 @@ | |
</jet-secondary-button> | |
</jet-confirms-password> | |
- <jet-confirms-password @confirmed="disableTwoFactorAuthentication"> | |
- <jet-danger-button | |
- :class="{ 'opacity-25': disabling }" | |
- :disabled="disabling"> | |
- Disable | |
- </jet-danger-button> | |
- </jet-confirms-password> | |
+ <div v-if="twoFactorEnabled"> | |
+ <jet-button | |
+ :class="{ 'opacity-25': enabling }" | |
+ :disabled="enabling" | |
+ @click="enableTwoFactorAuthentication"> | |
+ Confirm | |
+ </jet-button> | |
+ <div v-else> | |
+ <jet-confirms-password @confirmed="disableTwoFactorAuthentication"> | |
+ <jet-danger-button | |
+ :class="{ 'opacity-25': disabling }" | |
+ :disabled="disabling"> | |
+ Disable | |
+ </jet-danger-button> | |
+ </jet-confirms-password> | |
+ <div> | |
</div> | |
</div> | |
</template> | |
@@ -105,16 +121,18 @@ | |
data() { | |
return { | |
enabling: false, | |
+ setuping: false, | |
disabling: false, | |
qrCode: null, | |
recoveryCodes: [], | |
+ confirmationCode: null, | |
} | |
}, | |
methods: { | |
- enableTwoFactorAuthentication() { | |
- this.enabling = true | |
+ setupTwoFactorAuthentication() { | |
+ this.setuping = true | |
this.$inertia.post('/user/two-factor-authentication', {}, { | |
preserveScroll: true, | |
@@ -122,6 +140,19 @@ | |
this.showQrCode(), | |
this.showRecoveryCodes(), | |
]), | |
+ onFinish: () => (this.setuping = false), | |
+ }) | |
+ }, | |
+ enableTwoFactorAuthentication() { | |
+ this.setuping = false | |
+ this.enabling = true | |
+ | |
+ this.$inertia.post('/user/two-factor-authentication/confirm', {code: this.confirmationCode}, { | |
+ preserveScroll: true, | |
+ onSuccess: () => { | |
+ this.qrCode = null; | |
+ this.recoveryCodes = []; | |
+ }, | |
onFinish: () => (this.enabling = false), | |
}) | |
}, | |
@@ -160,6 +191,9 @@ | |
computed: { | |
twoFactorEnabled() { | |
return ! this.enabling && this.$page.props.user.two_factor_enabled | |
+ }, | |
+ twoFactorSetup() { | |
+ return this.setuping || this.$page.props.user.two_factor_setup | |
} | |
} | |
} | |
--- stubs/livewire/resources/views/profile/two-factor-authentication-form.blade.php | |
+++ stubs/livewire/resources/views/profile/two-factor-authentication-form.blade.php | |
@@ -22,37 +22,45 @@ | |
</p> | |
</div> | |
- @if ($this->enabled) | |
- @if ($showingQrCode) | |
- <div class="mt-4 max-w-xl text-sm text-gray-600"> | |
- <p class="font-semibold"> | |
- {{ __('Two factor authentication is now enabled. Scan the following QR code using your phone\'s authenticator application.') }} | |
- </p> | |
- </div> | |
+ @if ($showingRecoveryCodes) | |
+ <div class="mt-4 max-w-xl text-sm text-gray-600"> | |
+ <p class="font-semibold"> | |
+ {{ __('Store these recovery codes in a secure password manager. They can be used to recover access to your account if your two factor authentication device is lost.') }} | |
+ </p> | |
+ </div> | |
- <div class="mt-4"> | |
- {!! $this->user->twoFactorQrCodeSvg() !!} | |
- </div> | |
- @endif | |
+ <div class="grid gap-1 max-w-xl mt-4 px-4 py-4 font-mono text-sm bg-gray-100 rounded-lg"> | |
+ @foreach (json_decode(decrypt($this->user->two_factor_recovery_codes), true) as $code) | |
+ <div>{{ $code }}</div> | |
+ @endforeach | |
+ </div> | |
+ @endif | |
- @if ($showingRecoveryCodes) | |
- <div class="mt-4 max-w-xl text-sm text-gray-600"> | |
- <p class="font-semibold"> | |
- {{ __('Store these recovery codes in a secure password manager. They can be used to recover access to your account if your two factor authentication device is lost.') }} | |
- </p> | |
- </div> | |
+ @if ($this->setup || $showingQrCode) | |
+ <div class="mt-4 max-w-xl text-sm text-gray-600"> | |
+ <p class="font-semibold"> | |
+ {{ __('Scan the following QR code using your phone\'s authenticator application to setup two factor authentication.') }} | |
+ </p> | |
+ </div> | |
+ | |
+ <div class="mt-4 dark:p-4 dark:w-56 dark:bg-white"> | |
+ {!! $this->user->twoFactorQrCodeSvg() !!} | |
+ </div> | |
+ @endif | |
- <div class="grid gap-1 max-w-xl mt-4 px-4 py-4 font-mono text-sm bg-gray-100 rounded-lg"> | |
- @foreach (json_decode(decrypt($this->user->two_factor_recovery_codes), true) as $code) | |
- <div>{{ $code }}</div> | |
- @endforeach | |
+ @if ($this->setup) | |
+ <div class="mt-4 max-w-xl text-sm text-gray-600"> | |
+ <div class="col-span-6 sm:col-span-4"> | |
+ <x-jet-label for="confirmationCode" value="{{ __('After configuring the authenticator application, enter the code to validate the two-factor authentication.') }}" /> | |
+ <x-jet-input id="confirmationCode" type="text" class="mt-1 block w-full" wire:model.defer="confirmationCode" /> | |
+ <x-jet-input-error for="confirmationCode" class="mt-2" /> | |
</div> | |
- @endif | |
+ </div> | |
@endif | |
<div class="mt-5"> | |
- @if (! $this->enabled) | |
- <x-jet-confirms-password wire:then="enableTwoFactorAuthentication"> | |
+ @if (! $this->setup && !$this->enabled) | |
+ <x-jet-confirms-password wire:then="generateTwoFactorAuthenticationSecret"> | |
<x-jet-button type="button" wire:loading.attr="disabled"> | |
{{ __('Enable') }} | |
</x-jet-button> | |
@@ -72,11 +80,17 @@ | |
</x-jet-confirms-password> | |
@endif | |
- <x-jet-confirms-password wire:then="disableTwoFactorAuthentication"> | |
- <x-jet-danger-button wire:loading.attr="disabled"> | |
- {{ __('Disable') }} | |
- </x-jet-danger-button> | |
- </x-jet-confirms-password> | |
+ @if($this->enabled) | |
+ <x-jet-confirms-password wire:then="disableTwoFactorAuthentication"> | |
+ <x-jet-danger-button wire:loading.attr="disabled"> | |
+ {{ __('Disable') }} | |
+ </x-jet-danger-button> | |
+ </x-jet-confirms-password> | |
+ @else | |
+ <x-jet-button wire:click="confirmEnableTwoFactorAuthentication" wire:loading.attr="disabled"> | |
+ {{ __('Confirm') }} | |
+ </x-jet-button> | |
+ @endif | |
@endif | |
</div> | |
</x-slot> |
Updating to latest version caused an error.
Changed the
--- stubs/inertia/resources/js/Pages/Profile/TwoFactorAuthenticationForm.vue
+++ stubs/inertia/resources/js/Pages/Profile/TwoFactorAuthenticationForm.vue
to
--- stubs/inertia/resources/js/Pages/Profile/Partials/TwoFactorAuthenticationForm.vue
+++ stubs/inertia/resources/js/Pages/Profile/Partials/TwoFactorAuthenticationForm.vue
and got it working, but i unfortunately did not check if patch still works as intended otherwise
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
To apply it automatically first install the vaimo/composer-patches package.
Save this gist file in PROJECT_ROOT/patches folder.
In composer.json add:
After adding the patch file and update composer.json run composer patch:apply
To use it, just install this patch with another one specific for fortify
After installing both patches update your resources according to the stack used using as a reference the files present in vendor/laravel/jetstream/stubs
Livewire - update file:
Inertia - update file:
OBS: I didn't get to test the solution with jetstream + inertia, but with livewire works well