Skip to content

Commit

Permalink
Adding updates to the latest 2fa functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
tnylea committed May 11, 2024
1 parent 8851130 commit e215726
Show file tree
Hide file tree
Showing 9 changed files with 68 additions and 67 deletions.
3 changes: 2 additions & 1 deletion config/devdojo/auth/descriptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'
]
];
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
17 changes: 15 additions & 2 deletions resources/views/components/elements/button.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand All @@ -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') }};">
<svg xmlns="http://www.w3.org/2000/svg" wire:loading viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-1.5 w-4 h-4 animate-spin"><path d="M21 12a9 9 0 1 1-6.219-8.56"></path></svg>
<{!! $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">
<svg xmlns="http://www.w3.org/2000/svg" wire:loading @if(isset($loadingTarget)) wire:target="{{ $loadingTarget }}" @endif viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-1.5 w-4 h-4 animate-spin"><path d="M21 12a9 9 0 1 1-6.219-8.56"></path></svg>
{{ $slot }}
</{{ $tagClose }}>
3 changes: 3 additions & 0 deletions resources/views/components/elements/input-code.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
this.$refs['input' + (index-1)].focus();
}
}
} else {
this.$refs['input' + index].value = '';
}
} else {
Expand Down Expand Up @@ -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"
/>
Expand Down
2 changes: 2 additions & 0 deletions resources/views/pages/auth/login.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
public function mount(){
$this->loadConfigs();
$this->twoFactorEnabled = $this->settings->enable_2fa;
}
public function editIdentity(){
Expand All @@ -49,6 +50,7 @@ public function editIdentity(){
public function authenticate()
{
if(!$this->showPasswordField){
$this->validateOnly('email');
$this->showPasswordField = true;
Expand Down
20 changes: 14 additions & 6 deletions resources/views/pages/auth/two-factor-challenge.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -21,6 +22,7 @@
public $recovery = false;
public $google2fa;
#[Validate('required|min:5')]
public $auth_code;
public $recovery_code;
Expand All @@ -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;
Expand All @@ -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');
}
}
Expand Down Expand Up @@ -141,7 +149,7 @@ public function submit_recovery_code(){
<x-auth::elements.input-code wire:model="auth_code" id="auth-input-code" digits="6" eventCallback="code-input-complete" type="text" label="Code" />
</div>
@error('auth_code')
<p>Incorrect Auth Code</p>
<p class="my-2 text-sm text-red-600">{{ $message }}</p>
@enderror
<x-auth::elements.button rounded="md" submit="true" wire:click="submitCode(document.getElementById('auth-input-code').value)">Continue</x-auth::elements.button>
@else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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');
}
}
Expand All @@ -93,19 +102,16 @@ public function disable(){

<x-auth::layouts.empty title="Two Factor Authentication">
@volt('user.two-factor-authentication')
<section class="flex justify-center items-center w-screen h-screen">

<svg xmlns="http://www.w3.org/2000/svg" data-name="Two-Factor Authentication" viewBox="0 0 64 64"><g fill="#0a0f26"><path d="M58 2H6C3.79 2 2 3.79 2 6v38c0 2.21 1.79 4 4 4h9c.55 0 1-.45 1-1s-.45-1-1-1H6c-1.1 0-2-.9-2-2V9h34.67l3.73 2.8c.17.13.38.2.6.2h17v32c0 1.1-.9 2-2 2h-9c-.55 0-1 .45-1 1s.45 1 1 1h9c2.21 0 4-1.79 4-4V6c0-2.21-1.79-4-4-4zm-14.67 8L39.6 7.2A.984.984 0 0 0 39 7H4V6c0-1.1.9-2 2-2h52c1.1 0 2 .9 2 2v4z"/><path d="M55 8h-2c-.55 0-1-.45-1-1s.45-1 1-1h2c.55 0 1 .45 1 1s-.45 1-1 1zM48 8h-2c-.55 0-1-.45-1-1s.45-1 1-1h2c.55 0 1 .45 1 1s-.45 1-1 1z"/></g><path fill="#6b71f2" d="M42 62H22c-2.21 0-4-1.79-4-4V24c0-2.21 1.79-4 4-4h20c2.21 0 4 1.79 4 4v34c0 2.21-1.79 4-4 4zM22 22c-1.1 0-2 .9-2 2v34c0 1.1.9 2 2 2h20c1.1 0 2-.9 2-2V24c0-1.1-.9-2-2-2z"/><path fill="#6b71f2" d="M34 56h-4c-.55 0-1-.45-1-1s.45-1 1-1h4c.55 0 1 .45 1 1s-.45 1-1 1z"/><path fill="#0a0f26" d="M32 49c-.1 0-.19-.01-.29-.04C25.85 47.2 23 44.02 23 39.24v-6.5a1 1 0 0 1 .68-.95l8-2.74c.21-.07.44-.07.65 0l8 2.74c.4.14.68.52.68.95v6.5c0 4.78-2.85 7.96-8.71 9.72-.09.03-.19.04-.29.04zm-7-15.55v5.79c0 3.81 2.16 6.2 7 7.71 4.84-1.52 7-3.91 7-7.71v-5.79l-7-2.4z"/><path fill="#6b71f2" d="M31 42c-.26 0-.51-.1-.71-.29l-2-2a.996.996 0 1 1 1.41-1.41l1.29 1.29 3.29-3.29a.996.996 0 1 1 1.41 1.41l-4 4c-.2.2-.45.29-.71.29z"/></svg>

<div x-data x-on:code-input-complete.window="$dispatch('submitCode', [event.detail.code])" class="flex flex-col mx-auto w-full max-w-md text-sm">
<section class="flex @container justify-center items-center w-screen h-screen">

<div x-data x-on:code-input-complete.window="$dispatch('submitCode', [event.detail.code])" class="flex flex-col mx-auto w-full max-w-sm text-sm">
@if($confirmed)
<div class="flex flex-col space-y-5">
<h2 class="text-xl">You have enabled two factor authentication.</h2>
<p>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.</p>
@if($showRecoveryCodes)
<div class="relative">
<p class="font-bold">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>
<p class="font-medium">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 class="grid gap-1 px-4 py-4 mt-4 max-w-xl font-mono text-sm bg-gray-100 rounded-lg dark:bg-gray-900 dark:text-gray-100">

@foreach (json_decode(decrypt(auth()->user()->two_factor_recovery_codes), true) as $code)
Expand All @@ -114,45 +120,44 @@ public function disable(){
</div>
</div>
@endif
<div class="flex items-center">
<x-auth::elements.button type="primary" wire:click="regenerateCodes">Regenerate Recovery Codes</x-auto::elements.button>
<x-auth::elements.button type="danger" wire:click="disable">Disable</x-auto::elements.button>
<div class="flex items-center space-x-5">
<x-auth::elements.button type="primary" wire:click="regenerateCodes" rounded="md" size="md">Regenerate Codes</x-auto::elements.button>
<x-auth::elements.button type="danger" wire:click="disable" size="md" rounded="md">Disable 2FA</x-auto::elements.button>
</div>
</div>

@else
@if(!$enabled)
<div class="flex relative flex-col justify-start items-start space-y-5">
<h2 class="text-xl">You have not enabled two factor authentication.</h2>
<p>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.</p>
<h2 class="text-lg font-semibold">Two factor authentication disabled.</h2>
<p class="-translate-y-1">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.</p>
<div class="relative w-auto">
<x-auth::elements.button type="primary" rounded="md" size="md" wire:click="enable">Enable</x-auth>
<x-auth::elements.button type="primary" rounded="md" size="md" wire:click="enable" wire:target="enable">Enable</x-auth>
</div>
</div>
@else
<div class="relative space-y-5 w-full">
<div class="space-y-5">
<h2 class="text-xl">Finish enabling two factor authentication.</h2>
<p>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.</p>
<p class="font-bold">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.</p>
<h2 class="text-lg font-semibold">Finish enabling two factor authentication.</h2>
<p>Enable two-factor authentication to receive a secure token from your phone's Google Authenticator during login.</p>
<p class="font-bold">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.</p>
</div>

<div class="relative mx-auto max-w-64">
<div class="overflow-hidden relative mx-auto max-w-full rounded-lg border border-zinc-200">
<img src="data:image/png;base64, {{ $qr }}" style="width:400px; height:auto" />
</div>

<p class="font-semibold">
<p class="font-semibold text-center">
{{ __('Setup Key') }}: {{ $secret }}
</p>

<x-auth::elements.input-code id="auth-input-code" digits="6" eventCallback="code-input-complete" type="text" label="Code" />

<div class="flex items-center">
<x-auth::elements.button type="primary" wire:click="submitCode(document.getElementById('auth-input-code').value)">Confirm</x-auto::elements.button>
<x-auth::elements.button type="secondary">Cancel</x-auto::elements.button>
<div class="flex items-center space-x-5">
<x-auth::elements.button type="secondary" size="md" rounded="md" wire:click="cancelTwoFactor" wire:target="cancelTwoFactor">Cancel</x-auto::elements.button>
<x-auth::elements.button type="primary" size="md" wire:click="submitCode(document.getElementById('auth-input-code').value)" wire:target="submitCode" rounded="md">Confirm</x-auto::elements.button>
</div>


</div>
@endif
@endif
Expand Down
18 changes: 0 additions & 18 deletions routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 '<img src="data:image/png;base64, ' . $qr . ' " style="width:400px; height:auto" />';
// $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());
});

});


Expand Down
18 changes: 2 additions & 16 deletions src/Http/Controllers/LogoutController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}

}

0 comments on commit e215726

Please sign in to comment.