feat: 2fa reset notifs

Signed-off-by: miguel456 <me@nogueira.codes>
This commit is contained in:
Miguel Nogueira 2022-09-21 05:43:11 +01:00
parent 3122c23eb4
commit f3996bb68c
No known key found for this signature in database
GPG Key ID: 3C6A7E29AF26D370
9 changed files with 141 additions and 10 deletions

View File

@ -30,11 +30,13 @@ use App\Http\Requests\ChangePasswordRequest;
use App\Http\Requests\DeleteUserRequest; use App\Http\Requests\DeleteUserRequest;
use App\Http\Requests\FlushSessionsRequest; use App\Http\Requests\FlushSessionsRequest;
use App\Http\Requests\Remove2FASecretRequest; use App\Http\Requests\Remove2FASecretRequest;
use App\Http\Requests\Reset2FASecretRequest;
use App\Http\Requests\SearchPlayerRequest; use App\Http\Requests\SearchPlayerRequest;
use App\Http\Requests\UpdateUserRequest; use App\Http\Requests\UpdateUserRequest;
use App\Notifications\ChangedPassword; use App\Notifications\ChangedPassword;
use App\Notifications\EmailChanged; use App\Notifications\EmailChanged;
use App\Notifications\PasswordAdminResetNotification; use App\Notifications\PasswordAdminResetNotification;
use App\Notifications\TwoFactorResetNotification;
use App\Services\AccountSuspensionService; use App\Services\AccountSuspensionService;
use App\Traits\DisablesFeatures; use App\Traits\DisablesFeatures;
use App\Traits\HandlesAccountDeletion; use App\Traits\HandlesAccountDeletion;
@ -446,6 +448,39 @@ class UserController extends Controller
} }
/**
* Remove the given user's two factor secret key
*
* @param Reset2FASecretRequest $request
* @param User $user
* @return \Illuminate\Http\RedirectResponse
*/
public function reset2FASecret(Reset2FASecretRequest $request, User $user) {
if ($user->has2FA()) {
Log::warning('SECURITY: Disabling two factor authentication (admin initiated)', [
'initiator' => $request->user()->email,
'target' => $user->email,
'ip' => $request->ip(),
]);
$user->twofa_secret = null;
$user->password = null;
$user->save();
$user->notify(new TwoFactorResetNotification());
return redirect()
->back()
->with('success', __('Two factor removed & user notified.'));
}
return redirect()
->back()
->with('error', 'This user does not have two-factor authentication enabled.');
}
/** /**
* Demote the given user's privileges * Demote the given user's privileges
* *

View File

@ -44,7 +44,6 @@ class Remove2FASecretRequest extends FormRequest
{ {
return [ return [
'currentPassword' => 'required|current_password', 'currentPassword' => 'required|current_password',
'consent' => 'required|accepted',
]; ];
} }
} }

View File

@ -0,0 +1,20 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class Reset2FASecretRequest extends FormRequest
{
public function rules(): array
{
return [
'currentPassword' => 'required|current_password',
];
}
public function authorize(): bool
{
return true;
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class TwoFactorResetNotification extends Notification implements ShouldQueue
{
use Queueable;
public function __construct()
{
}
public function via($notifiable): array
{
return ['mail'];
}
public function toMail($notifiable): MailMessage
{
return (new MailMessage)
->from(config('notification.sender.address'), config('notification.sender.name'))
->subject(config('app.name').' - your second factor has been reset')
->markdown('mail.two-factor-reset', ['name' => $notifiable->name]);
}
public function toArray($notifiable): array
{
return [];
}
}

View File

@ -59,9 +59,6 @@
<script src="https://www.google.com/recaptcha/api.js" async defer></script> <script src="https://www.google.com/recaptcha/api.js" async defer></script>
<script id="help-ukraine-win" async="true" src="https://helpukrainewinwidget.org/cdn/widget.js" data-type="four" data-position="bottom-left"></script>
</head> </head>
<!--Main Navigation--> <!--Main Navigation-->
@ -113,7 +110,7 @@
<div class="container text-center white-text"> <div class="container text-center white-text">
<div class="white-text text-center wow fadeInUp"> <div class="white-text text-center wow fadeInUp">
<h2>{{config('app.name')}}</h2> <h2>{{config('app.name')}}</h2>
<h5>{{ __('Welcome to the Games Club Recruitment Portal!') }}</h5> <h5>{{ __('Welcome to the :appName Recruitment Portal!', ['appName' => config('app.name')]) }}</h5>
<br> <br>
<p>{{ __('We process applications for our Discord server\'s management team here. If you have any questions, don\'t hesistate to contact our support team! Take a look at the open jobs below.') }}</p> <p>{{ __('We process applications for our Discord server\'s management team here. If you have any questions, don\'t hesistate to contact our support team! Take a look at the open jobs below.') }}</p>
<p>{!! __('If you\'d like to learn more about our community, make sure to visit our <a href=":mainWebsiteUrlConfigValue" target="_blank">main website</a>!', ['mainWebsiteUrlConfigValue' => config('app.sitehomepage')]) !!}</p> <p>{!! __('If you\'d like to learn more about our community, make sure to visit our <a href=":mainWebsiteUrlConfigValue" target="_blank">main website</a>!', ['mainWebsiteUrlConfigValue' => config('app.sitehomepage')]) !!}</p>

View File

@ -3,7 +3,7 @@
<label for="otp">{{ __('Two-factor authentication code') }}</label> <label for="otp">{{ __('Two-factor authentication code') }}</label>
<input type="text" id="otp" name="otp" class="form-control"> <input type="text" id="otp" name="otp" class="form-control">
<p class="text-muted text-sm"><i class="fas fa-info-circle"></i> $slot</p> <p class="text-muted text-sm"><i class="fas fa-info-circle"></i> {{ $slot }}</p>
</div> </div>
@endif @endif

View File

@ -15,9 +15,37 @@
@section('content') @section('content')
<x-modal id="resetAccountPasswordModal" modal-label="resetAccountPassword" modal-title="{{ __('Confirm your password') }}" include-close-button="true"> @if($user->has2FA())
<p>{{ __('Please confirm that you want to invalidate this account\'s password. Since this is a sensitive operation, you\'ll need to confirm your own password and provide a 2FA code, if enabled.') }}</p> <x-modal id="resetTwoFactorModal" modal-label="resetTwoFactor" modal-title="{{ __('Verify your identity') }}" include-close-button="true">
<p>{{ __('Resetting an account\'s two-factor authentication secret will automatically notify the account holder. Additionally, the user\'s password will also be forcefully reset during this process. Please confirm this action by verifying your identity below.') }}</p>
<form id="resetAccountTwofaForm" method="POST" action="{{ route('reset-twofa', ['user' => $user]) }}">
@csrf
@method('PATCH')
<x-confirm-password>
{{ __('Please re-enter your password.') }}
</x-confirm-password>
<x-confirm-second-factor>
{{ __('Please enter your two-factor authentication code.') }}
</x-confirm-second-factor>
</form>
<x-slot:modalFooter>
<button onclick="$('#resetAccountTwofaForm').submit()" type="button" class="btn btn-warning"><i class="fas fa-check"></i> {{ __('Re-authenticate and verify') }}</button>
</x-slot:modalFooter>
</x-modal>
@endif
<x-modal id="resetAccountPasswordModal" modal-label="resetAccountPassword" modal-title="{{ __('Verify your identity') }}" include-close-button="true">
<p>{{ __('Forcing a password reset will automatically notify the account holder and send them a password reset link. Please confirm this action by verifying your identity below.') }}</p>
<form id="resetAccountPasswordForm" method="POST" action="{{ route('force-reset-user', ['user' => $user]) }}"> <form id="resetAccountPasswordForm" method="POST" action="{{ route('force-reset-user', ['user' => $user]) }}">
@csrf @csrf
@ -34,7 +62,7 @@
</form> </form>
<x-slot name="modalFooter"> <x-slot name="modalFooter">
<button onclick="$('#resetAccountPasswordForm').submit()" type="button" class="btn btn-warning"><i class="fas fa-check"></i> {{ __('Re-authenticate and confirm') }}</button> <button onclick="$('#resetAccountPasswordForm').submit()" type="button" class="btn btn-warning"><i class="fas fa-check"></i> {{ __('Re-authenticate and verify') }}</button>
</x-slot> </x-slot>
</x-modal> </x-modal>
@ -388,7 +416,9 @@
</form> </form>
@endif @endif
<button onclick="$('#resetAccountPasswordModal').modal('show')" class="btn-danger btn mr-3" type="button"><i class="fas fa-key"></i> {{ __('Force password reset') }}</button> <button onclick="$('#resetAccountPasswordModal').modal('show')" class="btn-danger btn mr-3" type="button"><i class="fas fa-key"></i> {{ __('Force password reset') }}</button>
<button class="btn-danger btn mr-3" type="button"><i class="fas fa-unlock"></i> {{ __('Reset MFA') }}</button> @if($user->has2FA())
<button onclick="$('#resetTwoFactorModal').modal('show')" class="btn-danger btn mr-3" type="button"><i class="fas fa-unlock"></i> {{ __('Reset MFA') }}</button>
@endif
<button onclick="$('#deleteAccount').modal('show')" type="button" class="btn btn-danger"><i class="fas fa-trash"></i> {{ __('Delete account') }}</button> <button onclick="$('#deleteAccount').modal('show')" type="button" class="btn btn-danger"><i class="fas fa-trash"></i> {{ __('Delete account') }}</button>
</div> </div>
</div> </div>

View File

@ -0,0 +1,12 @@
@component('mail::message')
# Hi {{ $name }},
Important security notification regarding your account at {{ config('app.name') }}:
Your account was previously secured with two-factor authentication. This is no longer the case. An administrator has disabled two-factor authentication for your account. Admins only reset two-factor authentication after an identity verification is complete.
As a result of this action and as an additional security measure, your password has also been voided, which means you'll need to [reset it]({{ route('password.email') }}) if you want to keep using the app.
Thank you,<br>
The team at {{ config('app.name') }}
@endcomponent

View File

@ -302,6 +302,9 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => ['lo
Route::patch('accounts/force-reset/{user}', [UserController::class, 'forcePasswordReset']) Route::patch('accounts/force-reset/{user}', [UserController::class, 'forcePasswordReset'])
->name('force-reset-user'); ->name('force-reset-user');
Route::patch('accounts/reset-twofa/{user}', [UserController::class, 'reset2FASecret'])
->name('reset-twofa');
Route::patch('accounts/update/{user}', [UserController::class, 'update']) Route::patch('accounts/update/{user}', [UserController::class, 'update'])
->name('updateUser'); ->name('updateUser');