feat(auth): harden authentication and add configurable two-factor support
This commit is contained in:
@@ -4,6 +4,8 @@ namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use App\Notifications\TwoFactorSecurityNotification;
|
||||
use App\Services\Auth\SecurityEventRecorder;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
@@ -21,19 +23,19 @@ class TwoFactorSettingsController extends Controller
|
||||
*/
|
||||
public function enable(Request $request, EnableTwoFactorAuthentication $enable): RedirectResponse
|
||||
{
|
||||
$this->ensureFeatureIsEnabled();
|
||||
$this->ensureFeatureIsEnabled($request);
|
||||
|
||||
/** @var User $user */
|
||||
$user = $request->user();
|
||||
|
||||
if ($user->hasEnabledTwoFactorAuthentication()) {
|
||||
return redirect()->route('profile.show');
|
||||
return $this->enrollmentRedirect($request);
|
||||
}
|
||||
|
||||
$this->validatePasswordForPasswordUser($request, $user);
|
||||
$enable($user, force: true);
|
||||
|
||||
return redirect()->route('profile.show')
|
||||
return $this->enrollmentRedirect($request)
|
||||
->with('success', 'Scan the QR code, then enter a code to finish enabling two-factor authentication.');
|
||||
}
|
||||
|
||||
@@ -42,9 +44,10 @@ class TwoFactorSettingsController extends Controller
|
||||
*/
|
||||
public function confirm(
|
||||
Request $request,
|
||||
ConfirmTwoFactorAuthentication $confirm
|
||||
ConfirmTwoFactorAuthentication $confirm,
|
||||
SecurityEventRecorder $securityEvents
|
||||
): RedirectResponse {
|
||||
$this->ensureFeatureIsEnabled();
|
||||
$this->ensureFeatureIsEnabled($request);
|
||||
|
||||
$validated = $request->validate([
|
||||
'code' => ['required', 'string', 'regex:/^\d{6}$/'],
|
||||
@@ -53,8 +56,18 @@ class TwoFactorSettingsController extends Controller
|
||||
/** @var User $user */
|
||||
$user = $request->user();
|
||||
$confirm($user, $validated['code']);
|
||||
$user->notify(new TwoFactorSecurityNotification(
|
||||
'Two-factor authentication was enabled for your account.',
|
||||
(string) $request->ip(),
|
||||
now()->toIso8601String()
|
||||
));
|
||||
$securityEvents->record('two_factor.enabled', $user, $request);
|
||||
|
||||
return redirect()->route('profile.show')
|
||||
$redirect = $request->routeIs('two-factor.setup.confirm')
|
||||
? redirect()->route('two-factor.setup')
|
||||
: redirect()->route('profile.show');
|
||||
|
||||
return $redirect
|
||||
->with('success', 'Two-factor authentication is now enabled.')
|
||||
->with('recoveryCodes', $user->fresh()->recoveryCodes());
|
||||
}
|
||||
@@ -64,9 +77,15 @@ class TwoFactorSettingsController extends Controller
|
||||
*/
|
||||
public function disable(
|
||||
Request $request,
|
||||
DisableTwoFactorAuthentication $disable
|
||||
DisableTwoFactorAuthentication $disable,
|
||||
SecurityEventRecorder $securityEvents
|
||||
): RedirectResponse {
|
||||
$this->ensureFeatureIsEnabled();
|
||||
$this->ensureFeatureIsEnabled($request);
|
||||
abort_if(
|
||||
config('auth-ui.features.two_factor_required'),
|
||||
403,
|
||||
'Two-factor authentication is required for this application.'
|
||||
);
|
||||
|
||||
/** @var User $user */
|
||||
$user = $request->user();
|
||||
@@ -76,6 +95,12 @@ class TwoFactorSettingsController extends Controller
|
||||
}
|
||||
|
||||
$disable($user);
|
||||
$user->notify(new TwoFactorSecurityNotification(
|
||||
'Two-factor authentication was disabled for your account.',
|
||||
(string) $request->ip(),
|
||||
now()->toIso8601String()
|
||||
));
|
||||
$securityEvents->record('two_factor.disabled', $user, $request);
|
||||
|
||||
return redirect()->route('profile.show')
|
||||
->with('success', 'Two-factor authentication has been disabled.');
|
||||
@@ -86,9 +111,10 @@ class TwoFactorSettingsController extends Controller
|
||||
*/
|
||||
public function recoveryCodes(
|
||||
Request $request,
|
||||
GenerateNewRecoveryCodes $generate
|
||||
GenerateNewRecoveryCodes $generate,
|
||||
SecurityEventRecorder $securityEvents
|
||||
): RedirectResponse {
|
||||
$this->ensureFeatureIsEnabled();
|
||||
$this->ensureFeatureIsEnabled($request);
|
||||
|
||||
/** @var User $user */
|
||||
$user = $request->user();
|
||||
@@ -102,6 +128,14 @@ class TwoFactorSettingsController extends Controller
|
||||
if ($request->boolean('regenerate')) {
|
||||
$generate($user);
|
||||
$user->refresh();
|
||||
$user->notify(new TwoFactorSecurityNotification(
|
||||
'New two-factor authentication recovery codes were generated for your account.',
|
||||
(string) $request->ip(),
|
||||
now()->toIso8601String()
|
||||
));
|
||||
$securityEvents->record('two_factor.recovery_codes_regenerated', $user, $request);
|
||||
} else {
|
||||
$securityEvents->record('two_factor.recovery_codes_revealed', $user, $request);
|
||||
}
|
||||
|
||||
return redirect()->route('profile.show')
|
||||
@@ -111,9 +145,20 @@ class TwoFactorSettingsController extends Controller
|
||||
->with('recoveryCodes', $user->recoveryCodes());
|
||||
}
|
||||
|
||||
private function ensureFeatureIsEnabled(): void
|
||||
private function ensureFeatureIsEnabled(Request $request): void
|
||||
{
|
||||
abort_unless(config('auth-ui.features.two_factor'), 404);
|
||||
|
||||
if ($request->routeIs('two-factor.setup.*')) {
|
||||
abort_unless(config('auth-ui.features.two_factor_required'), 404);
|
||||
}
|
||||
}
|
||||
|
||||
private function enrollmentRedirect(Request $request): RedirectResponse
|
||||
{
|
||||
return $request->routeIs('two-factor.setup.*')
|
||||
? redirect()->route('two-factor.setup')
|
||||
: redirect()->route('profile.show');
|
||||
}
|
||||
|
||||
private function validatePasswordForPasswordUser(Request $request, User $user): void
|
||||
@@ -131,8 +176,6 @@ class TwoFactorSettingsController extends Controller
|
||||
{
|
||||
if ($user->hasPassword()) {
|
||||
$this->validatePasswordForPasswordUser($request, $user);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
|
||||
Reference in New Issue
Block a user