feat(auth): harden authentication and add configurable two-factor support
This commit is contained in:
@@ -5,6 +5,8 @@ namespace App\Http\Controllers\Auth;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Auth\CompleteProfileRequest;
|
||||
use App\Models\User;
|
||||
use App\Services\Auth\SecurityEventRecorder;
|
||||
use App\Services\Auth\TwoFactorChallenge;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -33,8 +35,11 @@ class CompleteProfileController extends Controller
|
||||
/**
|
||||
* Handle the complete profile request.
|
||||
*/
|
||||
public function store(CompleteProfileRequest $request): RedirectResponse
|
||||
{
|
||||
public function store(
|
||||
CompleteProfileRequest $request,
|
||||
TwoFactorChallenge $twoFactor,
|
||||
SecurityEventRecorder $securityEvents
|
||||
): RedirectResponse {
|
||||
$socialiteUser = session('socialite_user');
|
||||
$validated = $request->validated();
|
||||
|
||||
@@ -58,6 +63,16 @@ class CompleteProfileController extends Controller
|
||||
|
||||
Auth::login($user, remember: true);
|
||||
$request->session()->regenerate();
|
||||
$securityEvents->record('login.social_succeeded', $user, $request);
|
||||
|
||||
if ($twoFactor->enrollmentRequiredFor($user)) {
|
||||
$request->session()->put(
|
||||
'url.intended',
|
||||
config('auth-ui.redirects.login', '/')
|
||||
);
|
||||
|
||||
return redirect()->route('two-factor.setup');
|
||||
}
|
||||
|
||||
return redirect()->intended(config('auth-ui.redirects.login', '/'));
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Auth\ForgotPasswordRequest;
|
||||
use App\Services\Auth\SecurityEventRecorder;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Inertia\Inertia;
|
||||
@@ -26,16 +27,20 @@ class ForgotPasswordController extends Controller
|
||||
/**
|
||||
* Handle an incoming password reset link request.
|
||||
*/
|
||||
public function store(ForgotPasswordRequest $request): RedirectResponse
|
||||
{
|
||||
$status = Password::sendResetLink(
|
||||
public function store(
|
||||
ForgotPasswordRequest $request,
|
||||
SecurityEventRecorder $securityEvents
|
||||
): RedirectResponse {
|
||||
Password::sendResetLink(
|
||||
$request->validated()
|
||||
);
|
||||
$securityEvents->record('password_reset.requested', null, $request, [
|
||||
'email_hash' => hash('sha256', strtolower($request->validated('email'))),
|
||||
]);
|
||||
|
||||
if ($status === Password::RESET_LINK_SENT) {
|
||||
return back()->with('status', __($status));
|
||||
}
|
||||
|
||||
return back()->withErrors(['email' => __($status)]);
|
||||
return back()->with(
|
||||
'status',
|
||||
'If an account exists for that email address, a password reset link has been sent.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,17 @@ namespace App\Http\Controllers\Auth;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Auth\LoginRequest;
|
||||
use App\Models\User;
|
||||
use App\Services\Auth\SecurityEventRecorder;
|
||||
use App\Services\Auth\TwoFactorChallenge;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
use RuntimeException;
|
||||
|
||||
class LoginController extends Controller
|
||||
{
|
||||
@@ -26,8 +30,11 @@ class LoginController extends Controller
|
||||
/**
|
||||
* Handle an incoming authentication request.
|
||||
*/
|
||||
public function store(LoginRequest $request, TwoFactorChallenge $twoFactor): RedirectResponse
|
||||
{
|
||||
public function store(
|
||||
LoginRequest $request,
|
||||
TwoFactorChallenge $twoFactor,
|
||||
SecurityEventRecorder $securityEvents
|
||||
): RedirectResponse {
|
||||
$login = $request->validated('login');
|
||||
$password = $request->validated('password');
|
||||
|
||||
@@ -46,7 +53,19 @@ class LoginController extends Controller
|
||||
? $provider->retrieveByCredentials($credentials)
|
||||
: null;
|
||||
|
||||
if (! $user || ! $provider->validateCredentials($user, $credentials)) {
|
||||
try {
|
||||
$credentialsAreValid = $user
|
||||
? $provider->validateCredentials($user, $credentials)
|
||||
: Hash::check($password, $this->dummyPasswordHash());
|
||||
} catch (RuntimeException) {
|
||||
$credentialsAreValid = false;
|
||||
}
|
||||
|
||||
if (! $user || ! $credentialsAreValid) {
|
||||
$securityEvents->record('login.password_failed', $user, $request, [
|
||||
'login_hash' => hash('sha256', strtolower($login)),
|
||||
]);
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'login' => __('auth.failed'),
|
||||
]);
|
||||
@@ -58,11 +77,23 @@ class LoginController extends Controller
|
||||
}
|
||||
|
||||
if ($twoFactor->requiredFor($user)) {
|
||||
$securityEvents->record('login.password_succeeded_pending_two_factor', $user, $request);
|
||||
|
||||
return $twoFactor->begin($request, $user, $remember);
|
||||
}
|
||||
|
||||
Auth::login($user, $remember);
|
||||
$request->session()->regenerate();
|
||||
$securityEvents->record('login.succeeded', $user, $request);
|
||||
|
||||
if ($twoFactor->enrollmentRequiredFor($user)) {
|
||||
$request->session()->put(
|
||||
'url.intended',
|
||||
config('auth-ui.redirects.login', '/')
|
||||
);
|
||||
|
||||
return redirect()->route('two-factor.setup');
|
||||
}
|
||||
|
||||
return redirect()->intended(config('auth-ui.redirects.login', '/'));
|
||||
}
|
||||
@@ -72,6 +103,10 @@ class LoginController extends Controller
|
||||
*/
|
||||
public function destroy(Request $request): RedirectResponse
|
||||
{
|
||||
/** @var User|null $user */
|
||||
$user = $request->user();
|
||||
app(SecurityEventRecorder::class)->record('logout.succeeded', $user, $request);
|
||||
|
||||
Auth::logout();
|
||||
|
||||
$request->session()->invalidate();
|
||||
@@ -79,4 +114,11 @@ class LoginController extends Controller
|
||||
|
||||
return redirect(config('auth-ui.redirects.logout', '/'));
|
||||
}
|
||||
|
||||
private function dummyPasswordHash(): string
|
||||
{
|
||||
static $hash;
|
||||
|
||||
return $hash ??= Hash::make(Str::random(64));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace App\Http\Controllers\Auth;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Auth\RegisterRequest;
|
||||
use App\Models\User;
|
||||
use App\Services\Auth\SecurityEventRecorder;
|
||||
use App\Services\Auth\TwoFactorChallenge;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
@@ -28,8 +30,11 @@ class RegisterController extends Controller
|
||||
/**
|
||||
* Handle an incoming registration request.
|
||||
*/
|
||||
public function store(RegisterRequest $request): RedirectResponse
|
||||
{
|
||||
public function store(
|
||||
RegisterRequest $request,
|
||||
TwoFactorChallenge $twoFactor,
|
||||
SecurityEventRecorder $securityEvents
|
||||
): RedirectResponse {
|
||||
$validated = $request->validated();
|
||||
|
||||
$user = User::create([
|
||||
@@ -43,11 +48,22 @@ class RegisterController extends Controller
|
||||
event(new Registered($user));
|
||||
|
||||
Auth::login($user);
|
||||
$request->session()->regenerate();
|
||||
$securityEvents->record('registration.completed', $user, $request);
|
||||
|
||||
if (config('auth-ui.features.email_verification')) {
|
||||
return redirect()->route('verification.notice');
|
||||
}
|
||||
|
||||
if ($twoFactor->enrollmentRequiredFor($user)) {
|
||||
$request->session()->put(
|
||||
'url.intended',
|
||||
config('auth-ui.redirects.register', '/')
|
||||
);
|
||||
|
||||
return redirect()->route('two-factor.setup');
|
||||
}
|
||||
|
||||
return redirect(config('auth-ui.redirects.register', '/'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,13 @@ namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Auth\ResetPasswordRequest;
|
||||
use App\Models\User;
|
||||
use App\Notifications\PasswordChangedNotification;
|
||||
use App\Services\Auth\SecurityEventRecorder;
|
||||
use Illuminate\Auth\Events\PasswordReset;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Illuminate\Support\Str;
|
||||
use Inertia\Inertia;
|
||||
@@ -32,21 +36,39 @@ class ResetPasswordController extends Controller
|
||||
/**
|
||||
* Handle an incoming new password request.
|
||||
*/
|
||||
public function store(ResetPasswordRequest $request): RedirectResponse
|
||||
{
|
||||
public function store(
|
||||
ResetPasswordRequest $request,
|
||||
SecurityEventRecorder $securityEvents
|
||||
): RedirectResponse {
|
||||
$resetUser = null;
|
||||
$status = Password::reset(
|
||||
$request->validated(),
|
||||
function ($user, string $password): void {
|
||||
function (User $user, string $password) use ($request, &$resetUser): void {
|
||||
$user->forceFill([
|
||||
'password' => $password,
|
||||
'remember_token' => Str::random(60),
|
||||
'auth_session_version' => $user->auth_session_version + 1,
|
||||
])->save();
|
||||
|
||||
if (config('session.driver') === 'database') {
|
||||
DB::table(config('session.table'))
|
||||
->where('user_id', $user->getKey())
|
||||
->delete();
|
||||
}
|
||||
|
||||
$user->notify(new PasswordChangedNotification(
|
||||
(string) $request->ip(),
|
||||
now()->toIso8601String()
|
||||
));
|
||||
$resetUser = $user;
|
||||
|
||||
event(new PasswordReset($user));
|
||||
}
|
||||
);
|
||||
|
||||
if ($status === Password::PASSWORD_RESET) {
|
||||
$securityEvents->record('password_reset.completed', $resetUser, $request);
|
||||
|
||||
return redirect()->route('login')->with('status', __($status));
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Auth;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\SocialAccount;
|
||||
use App\Models\User;
|
||||
use App\Services\Auth\SecurityEventRecorder;
|
||||
use App\Services\Auth\TwoFactorChallenge;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -54,7 +55,8 @@ class SocialiteController extends Controller
|
||||
public function callback(
|
||||
Request $request,
|
||||
string $provider,
|
||||
TwoFactorChallenge $twoFactor
|
||||
TwoFactorChallenge $twoFactor,
|
||||
SecurityEventRecorder $securityEvents
|
||||
): RedirectResponse {
|
||||
if (! in_array($provider, $this->getEnabledProviders())) {
|
||||
abort(404, 'Provider not enabled');
|
||||
@@ -74,7 +76,12 @@ class SocialiteController extends Controller
|
||||
->first();
|
||||
|
||||
if ($socialAccount) {
|
||||
return $this->finishAuthentication($request, $socialAccount->user, $twoFactor);
|
||||
return $this->finishAuthentication(
|
||||
$request,
|
||||
$socialAccount->user,
|
||||
$twoFactor,
|
||||
$securityEvents
|
||||
);
|
||||
}
|
||||
|
||||
// Check if a user with this email already exists
|
||||
@@ -92,7 +99,12 @@ class SocialiteController extends Controller
|
||||
'provider_id' => $socialUser->getId(),
|
||||
]);
|
||||
|
||||
return $this->finishAuthentication($request, $existingUser, $twoFactor);
|
||||
return $this->finishAuthentication(
|
||||
$request,
|
||||
$existingUser,
|
||||
$twoFactor,
|
||||
$securityEvents
|
||||
);
|
||||
}
|
||||
|
||||
// New user — check if registration is enabled
|
||||
@@ -134,20 +146,33 @@ class SocialiteController extends Controller
|
||||
'provider_id' => $socialUser->getId(),
|
||||
]);
|
||||
|
||||
return $this->finishAuthentication($request, $user, $twoFactor);
|
||||
return $this->finishAuthentication($request, $user, $twoFactor, $securityEvents);
|
||||
}
|
||||
|
||||
private function finishAuthentication(
|
||||
Request $request,
|
||||
User $user,
|
||||
TwoFactorChallenge $twoFactor
|
||||
TwoFactorChallenge $twoFactor,
|
||||
SecurityEventRecorder $securityEvents
|
||||
): RedirectResponse {
|
||||
if ($twoFactor->requiredFor($user)) {
|
||||
$securityEvents->record('login.social_succeeded_pending_two_factor', $user, $request);
|
||||
|
||||
return $twoFactor->begin($request, $user, remember: true);
|
||||
}
|
||||
|
||||
Auth::login($user, remember: true);
|
||||
$request->session()->regenerate();
|
||||
$securityEvents->record('login.social_succeeded', $user, $request);
|
||||
|
||||
if ($twoFactor->enrollmentRequiredFor($user)) {
|
||||
$request->session()->put(
|
||||
'url.intended',
|
||||
config('auth-ui.redirects.login', '/')
|
||||
);
|
||||
|
||||
return redirect()->route('two-factor.setup');
|
||||
}
|
||||
|
||||
return redirect()->intended(config('auth-ui.redirects.login', '/'));
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Notifications\TwoFactorFailedNotification;
|
||||
use App\Services\Auth\SecurityEventRecorder;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
@@ -19,7 +21,9 @@ class TwoFactorChallengeController extends Controller
|
||||
*/
|
||||
public function create(TwoFactorLoginRequest $request): Response|RedirectResponse
|
||||
{
|
||||
if (! config('auth-ui.features.two_factor') || ! $request->hasChallengedUser()) {
|
||||
if (! config('auth-ui.features.two_factor')
|
||||
|| ! $request->hasChallengedUser()
|
||||
|| $this->challengeHasExpired($request)) {
|
||||
$this->clearChallenge($request);
|
||||
|
||||
return redirect()->route('login');
|
||||
@@ -31,9 +35,13 @@ class TwoFactorChallengeController extends Controller
|
||||
/**
|
||||
* Complete the pending login using a TOTP or recovery code.
|
||||
*/
|
||||
public function store(TwoFactorLoginRequest $request): RedirectResponse
|
||||
{
|
||||
if (! config('auth-ui.features.two_factor') || ! $request->hasChallengedUser()) {
|
||||
public function store(
|
||||
TwoFactorLoginRequest $request,
|
||||
SecurityEventRecorder $securityEvents
|
||||
): RedirectResponse {
|
||||
if (! config('auth-ui.features.two_factor')
|
||||
|| ! $request->hasChallengedUser()
|
||||
|| $this->challengeHasExpired($request)) {
|
||||
$this->clearChallenge($request);
|
||||
|
||||
return redirect()->route('login');
|
||||
@@ -51,6 +59,12 @@ class TwoFactorChallengeController extends Controller
|
||||
$user->replaceRecoveryCode($recoveryCode);
|
||||
} elseif (! $request->hasValidCode()) {
|
||||
event(new TwoFactorAuthenticationFailed($user));
|
||||
$user->notify(new TwoFactorFailedNotification(
|
||||
(string) $request->ip(),
|
||||
(string) $request->userAgent(),
|
||||
now()->toIso8601String()
|
||||
));
|
||||
$securityEvents->record('login.two_factor_failed', $user, $request);
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
$request->filled('recovery_code') ? 'recovery_code' : 'code' => 'The provided authentication code was invalid.',
|
||||
@@ -61,12 +75,24 @@ class TwoFactorChallengeController extends Controller
|
||||
|
||||
Auth::login($user, $request->remember());
|
||||
$request->session()->regenerate();
|
||||
$securityEvents->record('login.two_factor_succeeded', $user, $request, [
|
||||
'used_recovery_code' => (bool) $recoveryCode,
|
||||
]);
|
||||
|
||||
return redirect()->intended(config('auth-ui.redirects.login', '/dashboard'));
|
||||
}
|
||||
|
||||
private function clearChallenge(TwoFactorLoginRequest $request): void
|
||||
{
|
||||
$request->session()->forget(['login.id', 'login.remember']);
|
||||
$request->session()->forget(['login.id', 'login.remember', 'login.started_at']);
|
||||
}
|
||||
|
||||
private function challengeHasExpired(TwoFactorLoginRequest $request): bool
|
||||
{
|
||||
$startedAt = $request->session()->get('login.started_at');
|
||||
|
||||
return ! is_int($startedAt)
|
||||
|| now()->timestamp - $startedAt
|
||||
> config('auth-ui.security.two_factor_challenge_timeout');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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([
|
||||
|
||||
54
app/Http/Controllers/Auth/TwoFactorSetupController.php
Normal file
54
app/Http/Controllers/Auth/TwoFactorSetupController.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use App\Services\Auth\TwoFactorEnrollmentState;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class TwoFactorSetupController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the mandatory two-factor enrollment flow.
|
||||
*/
|
||||
public function __invoke(
|
||||
Request $request,
|
||||
TwoFactorEnrollmentState $enrollmentState
|
||||
): Response|RedirectResponse {
|
||||
abort_unless(
|
||||
config('auth-ui.features.two_factor')
|
||||
&& config('auth-ui.features.two_factor_required'),
|
||||
404
|
||||
);
|
||||
|
||||
/** @var User $user */
|
||||
$user = $request->user();
|
||||
|
||||
if ($user->hasEnabledTwoFactorAuthentication()
|
||||
&& ! $request->session()->has('recoveryCodes')) {
|
||||
return redirect()->intended(config('auth-ui.redirects.login', '/'));
|
||||
}
|
||||
|
||||
return Inertia::render('Auth/TwoFactorSetup', [
|
||||
'twoFactor' => $enrollmentState->for($user),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Continue to the page that was requested before enrollment.
|
||||
*/
|
||||
public function complete(Request $request): RedirectResponse
|
||||
{
|
||||
abort_unless(
|
||||
config('auth-ui.features.two_factor')
|
||||
&& $request->user()?->hasEnabledTwoFactorAuthentication(),
|
||||
404
|
||||
);
|
||||
|
||||
return redirect()->intended(config('auth-ui.redirects.login', '/'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user