99 lines
3.3 KiB
PHP
99 lines
3.3 KiB
PHP
<?php
|
|
|
|
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;
|
|
use Inertia\Inertia;
|
|
use Inertia\Response;
|
|
use Laravel\Fortify\Events\TwoFactorAuthenticationFailed;
|
|
use Laravel\Fortify\Events\ValidTwoFactorAuthenticationCodeProvided;
|
|
use Laravel\Fortify\Http\Requests\TwoFactorLoginRequest;
|
|
|
|
class TwoFactorChallengeController extends Controller
|
|
{
|
|
/**
|
|
* Display the two-factor challenge.
|
|
*/
|
|
public function create(TwoFactorLoginRequest $request): Response|RedirectResponse
|
|
{
|
|
if (! config('auth-ui.features.two_factor')
|
|
|| ! $request->hasChallengedUser()
|
|
|| $this->challengeHasExpired($request)) {
|
|
$this->clearChallenge($request);
|
|
|
|
return redirect()->route('login');
|
|
}
|
|
|
|
return Inertia::render('Auth/TwoFactorChallenge');
|
|
}
|
|
|
|
/**
|
|
* Complete the pending login using a TOTP or recovery code.
|
|
*/
|
|
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');
|
|
}
|
|
|
|
$user = $request->challengedUser();
|
|
|
|
if (! $user->hasEnabledTwoFactorAuthentication()) {
|
|
$this->clearChallenge($request);
|
|
|
|
return redirect()->route('login');
|
|
}
|
|
|
|
if ($recoveryCode = $request->validRecoveryCode()) {
|
|
$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.',
|
|
]);
|
|
}
|
|
|
|
event(new ValidTwoFactorAuthenticationCodeProvided($user));
|
|
|
|
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', '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');
|
|
}
|
|
}
|