diff --git a/config/devdojo/auth/descriptions.php b/config/devdojo/auth/descriptions.php index b13ee64..0e90cf4 100644 --- a/config/devdojo/auth/descriptions.php +++ b/config/devdojo/auth/descriptions.php @@ -10,6 +10,7 @@ 'registration_include_name_field' => 'During registration, include the Name field.', 'registration_require_email_verification' => 'During registration, require users to verify their email.', 'enable_branding' => 'This will toggle on/off the Auth branding at the bottom of each auth screen. Consider leaving on to support and help grow this project.', - 'dev_mode' => 'This is for development mode, when set in Dev Mode Assets will be loaded from Vite' + 'dev_mode' => 'This is for development mode, when set in Dev Mode Assets will be loaded from Vite', + 'enable_2fa' => 'Enable the ability for users to turn on Two Factor Authentication' ] ]; \ No newline at end of file diff --git a/package.json b/package.json index 5f14614..858f918 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@invoate/alpine-query-string": "^1.0.0", "highlightjs": "^9.16.2", "phaser": "^3.80.1", - "split.js": "^1.6.5" + "split.js": "^1.6.5", + "@tailwindcss/container-queries": "^0.1.1" } } diff --git a/resources/views/components/elements/button.blade.php b/resources/views/components/elements/button.blade.php index cf4d1fa..b7bb0f3 100644 --- a/resources/views/components/elements/button.blade.php +++ b/resources/views/components/elements/button.blade.php @@ -17,6 +17,19 @@ }; @endphp +@php + $typeClasses = match ($type) { + 'primary' => '', + 'secondary' => 'bg-zinc-100 border text-gray-500 hover:text-gray-700 border-zinc-100 dark:focus:ring-offset-gray-900 dark:border-gray-400/10 active:bg-white dark:focus:ring-gray-700 focus:bg-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-200/60 dark:bg-gray-800/50 dark:hover:bg-gray-800/70 dark:text-gray-400 focus:shadow-outline', + 'success' => 'bg-green-600 text-white hover:bg-green-600/90 focus:ring-2 focus:ring-offset-2 dark:focus:ring-offset-gray-900 focus:bg-green-700/90 focus:ring-green-700', + 'info' => 'bg-blue-600 text-white hover:bg-blue-600/90 focus:ring-2 focus:ring-offset-2 dark:focus:ring-offset-gray-900 focus:bg-blue-700/90 focus:ring-blue-700', + 'warning' => 'bg-amber-500 text-white hover:bg-amber-500/90 focus:ring-2 focus:ring-offset-2 dark:focus:ring-offset-gray-900 focus:bg-amber-600/90 focus:ring-amber-600', + 'danger' => 'bg-red-600 text-white hover:bg-red-600/90 focus:ring-2 focus:ring-offset-2 dark:focus:ring-offset-gray-900 focus:bg-red-700/90 focus:ring-red-700', + }; + + $loadingTarget = $attributes['wire:target']; +@endphp + @php switch ($tag ?? 'button') { case 'button': @@ -35,7 +48,7 @@ } @endphp -<{!! $tagAttr !!} {!! $attributes->except(['class']) !!} class="{{ $sizeClasses }} auth-component-button opacity-[95%] hover:opacity-100 focus:ring-2 focus:ring-offset-2 cursor-pointer inline-flex items-center w-full justify-center disabled:opacity-50 font-semibold focus:outline-none" style="color:{{ config('devdojo.auth.appearance.color.button_text') }}; background-color:{{ config('devdojo.auth.appearance.color.button') }};"> - +<{!! $tagAttr !!} {!! $attributes->except(['class']) !!} class="@if($type == 'primary'){{ 'auth-component-button' }}@endif {{ $sizeClasses }} {{ $typeClasses }} opacity-[95%] hover:opacity-100 focus:ring-2 focus:ring-offset-2 cursor-pointer inline-flex items-center w-full justify-center disabled:opacity-50 font-semibold focus:outline-none" style="@if($type == 'primary') color:{{ config('devdojo.auth.appearance.color.button_text') }}; background-color:{{ config('devdojo.auth.appearance.color.button') }}; @endif"> + {{ $slot }} \ No newline at end of file diff --git a/resources/views/components/elements/input-code.blade.php b/resources/views/components/elements/input-code.blade.php index 12174f8..420cf32 100644 --- a/resources/views/components/elements/input-code.blade.php +++ b/resources/views/components/elements/input-code.blade.php @@ -31,6 +31,8 @@ this.$refs['input' + (index-1)].focus(); } } + } else { + this.$refs['input' + index].value = ''; } } else { @@ -98,6 +100,7 @@ class="relative" type="number" x-on:paste="pasteValue" x-on:keydown="moveCursorNext({{ $x }}, {{ $digits }}, event)" + x-on:focus="$el.select()" class="w-12 h-12 font-light text-center text-black rounded-md border shadow-sm appearance-none auth-component-code-input dark:text-dark-400 border-zinc-200 focus:border-2" maxlength="1" /> diff --git a/resources/views/pages/auth/login.blade.php b/resources/views/pages/auth/login.blade.php index 266df09..2fcb050 100644 --- a/resources/views/pages/auth/login.blade.php +++ b/resources/views/pages/auth/login.blade.php @@ -41,6 +41,7 @@ public function mount(){ $this->loadConfigs(); + $this->twoFactorEnabled = $this->settings->enable_2fa; } public function editIdentity(){ @@ -49,6 +50,7 @@ public function editIdentity(){ public function authenticate() { + if(!$this->showPasswordField){ $this->validateOnly('email'); $this->showPasswordField = true; diff --git a/resources/views/pages/auth/two-factor-challenge.blade.php b/resources/views/pages/auth/two-factor-challenge.blade.php index c0dcce5..9d2bfea 100644 --- a/resources/views/pages/auth/two-factor-challenge.blade.php +++ b/resources/views/pages/auth/two-factor-challenge.blade.php @@ -5,6 +5,7 @@ use Illuminate\Support\Facades\Route; use Illuminate\Auth\Events\Login; use Livewire\Attributes\On; +use Livewire\Attributes\Validate; use Livewire\Volt\Component; use PragmaRX\Google2FA\Google2FA; use Devdojo\Auth\Traits\HasConfigs; @@ -21,6 +22,7 @@ public $recovery = false; public $google2fa; + #[Validate('required|min:5')] public $auth_code; public $recovery_code; @@ -36,6 +38,7 @@ public function switchToRecovery() if($this->recovery){ $this->js("setTimeout(function(){ console.log('made'); window.dispatchEvent(new CustomEvent('focus-auth-2fa-recovery-code', {})); }, 10);"); } else { + // TODO - this we need to autofocus the first input of the auth code input $this->js("setTimeout(function(){ window.dispatchEvent(new CustomEvent('focus-auth-2fa-auth-code', {})); }, 10);"); } return; @@ -46,23 +49,28 @@ public function switchToRecovery() #[On('submitCode')] public function submitCode($code) { + $this->auth_code = $code; + + $this->validate(); + if(empty($code) || strlen($code) < 5){ dd('show validation error'); return; } - $google2fa = new Google2FA(); - $valid = $google2fa->verifyKey($this->secret, $code); + $user = User::find(session()->get('login.id')); + $secret = decrypt($user->two_factor_secret); + $google2fa = new Google2FA(); + $valid = $google2fa->verifyKey($secret, $code); if($valid){ - $user = User::find(session()->get('login.id')); Auth::login($user); - event(new Login(auth()->guard('web'), User::where('email', $this->email)->first(), true)); + event(new Login(auth()->guard('web'), $user, true)); return redirect()->intended('/'); } else { - + dd('invalid'); } } @@ -141,7 +149,7 @@ public function submit_recovery_code(){ @error('auth_code') -

Incorrect Auth Code

+

{{ $message }}

@enderror Continue @else diff --git a/resources/views/pages/user/two-factor-authentication/index.blade.php b/resources/views/pages/user/two-factor-authentication/index.blade.php index 0de1267..5b6160a 100644 --- a/resources/views/pages/user/two-factor-authentication/index.blade.php +++ b/resources/views/pages/user/two-factor-authentication/index.blade.php @@ -52,6 +52,15 @@ private function generateCodes(){ return $generateCodesFor(auth()->user()); } + public function cancelTwoFactor(){ + auth()->user()->forceFill([ + 'two_factor_secret' => null, + 'two_factor_recovery_codes' => null + ])->save(); + + $this->enabled = false; + } + #[On('submitCode')] public function submitCode($code) { @@ -72,9 +81,9 @@ public function submitCode($code) ])->save(); $this->confirmed = true; - else { + } else { // TODO - implement an invalid message when the user enters an incorrect auth code - dd('show invalide code message') + dd('show invalide code message'); } } @@ -93,19 +102,16 @@ public function disable(){ @volt('user.two-factor-authentication') -
- - - -
+
+
@if($confirmed)

You have enabled two factor authentication.

When two factor authentication is enabled, you will be prompted for a secure, random token during authentication. You may retrieve this token from your phone's Google Authenticator application.

@if($showRecoveryCodes)
-

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.

+

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.

@foreach (json_decode(decrypt(auth()->user()->two_factor_recovery_codes), true) as $code) @@ -114,45 +120,44 @@ public function disable(){
@endif -
- Regenerate Recovery Codes - Disable +
+ Regenerate Codes + Disable 2FA
@else @if(!$enabled)
-

You have not enabled two factor authentication.

-

When two factor authentication is enabled, you will be prompted for a secure, random token during authentication. You may retrieve this token from your phone's Google Authenticator application.

+

Two factor authentication disabled.

+

When you enabled 2FA, you will be prompted for a secure code during authentication. This code can be retrieved from your phone's Google Authenticator application.

- Enable + Enable
@else
-

Finish enabling two factor authentication.

-

When two factor authentication is enabled, you will be prompted for a secure, random token during authentication. You may retrieve this token from your phone's Google Authenticator application.

-

To finish enabling two factor authentication, scan the following QR code using your phone's authenticator application or enter the setup key and provide the generated OTP code.

+

Finish enabling two factor authentication.

+

Enable two-factor authentication to receive a secure token from your phone's Google Authenticator during login.

+

To enable two-factor authentication, scan the QR code or enter the setup key using your phone's authenticator app and provide the OTP code.

-
+
-

+

{{ __('Setup Key') }}: {{ $secret }}

-
- Confirm - Cancel +
+ Cancel + Confirm
-
@endif @endif diff --git a/routes/web.php b/routes/web.php index 58743f1..19b8584 100644 --- a/routes/web.php +++ b/routes/web.php @@ -25,24 +25,6 @@ Route::get('/auth/logout', [LogoutController::class, 'getLogout'])->name('logout.get'); - - Route::get('g2fa', function(){ - $QrCodeAndSecret = new GenerateQrCodeAndSecretKey(); - [$qr, $secret] = $QrCodeAndSecret(auth()->user()); - echo ''; - // $secret should be saved to user database as two_factor_secret, but it should be encrypted like `encrypt($secret)` - - }); - - Route::get('getr', function(){ - $generateCodesFor = new GenerateNewRecoveryCodes(); - $generateCodesFor(auth()->user()); - }); - - Route::get('newr', function(){ - dd(auth()->user()->hasEnabledTwoFactorAuthentication()); - }); - }); diff --git a/src/Http/Controllers/LogoutController.php b/src/Http/Controllers/LogoutController.php index 3299349..d822c5f 100644 --- a/src/Http/Controllers/LogoutController.php +++ b/src/Http/Controllers/LogoutController.php @@ -8,24 +8,10 @@ class LogoutController { - public function __invoke() + public function __invoke(): RedirectResponse { Auth::logout(); - $this->redirectUserAfterLogout(); - } - - public function logout(Request $request) - { - Auth::logout(); - - $request->session()->invalidate(); - $request->session()->regenerateToken(); - - $this->redirectUserAfterLogout(); - } - - private function redirectUserAfterLogout() : RedirectResponse - { return redirect()->route('home'); } + }