87 lines
2.4 KiB
PHP
87 lines
2.4 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Requests\Auth;
|
||
|
||
use Illuminate\Auth\Events\Lockout;
|
||
use Illuminate\Foundation\Http\FormRequest;
|
||
use Illuminate\Support\Facades\Auth;
|
||
use Illuminate\Support\Facades\RateLimiter;
|
||
use Illuminate\Support\Str;
|
||
use Illuminate\Validation\ValidationException;
|
||
|
||
/**
|
||
* Валидация и обработка попытки входа (меры ИАФ.1, ИАФ.6, УПД.6).
|
||
*
|
||
* Реализует блокировку после N неудачных попыток на T минут согласно
|
||
* config/security.php → lockout.
|
||
*/
|
||
class LoginRequest extends FormRequest
|
||
{
|
||
public function authorize(): bool
|
||
{
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @return array<string, mixed>
|
||
*/
|
||
public function rules(): array
|
||
{
|
||
return [
|
||
'email' => ['required', 'string', 'email'],
|
||
'password' => ['required', 'string'],
|
||
];
|
||
}
|
||
|
||
/**
|
||
* Попытка аутентификации с проверкой ограничения скорости и блокировки.
|
||
*/
|
||
public function authenticate(): void
|
||
{
|
||
$this->ensureIsNotRateLimited();
|
||
|
||
if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
|
||
RateLimiter::hit($this->throttleKey(), $this->decaySeconds());
|
||
|
||
throw ValidationException::withMessages([
|
||
'email' => __('auth.failed'),
|
||
]);
|
||
}
|
||
|
||
RateLimiter::clear($this->throttleKey());
|
||
}
|
||
|
||
/**
|
||
* Проверка лимита неудачных попыток (ИАФ.6).
|
||
*/
|
||
public function ensureIsNotRateLimited(): void
|
||
{
|
||
$maxAttempts = (int) config('security.lockout.max_attempts');
|
||
|
||
if (! RateLimiter::tooManyAttempts($this->throttleKey(), $maxAttempts)) {
|
||
return;
|
||
}
|
||
|
||
event(new Lockout($this));
|
||
|
||
$seconds = RateLimiter::availableIn($this->throttleKey());
|
||
|
||
throw ValidationException::withMessages([
|
||
'email' => __('auth.throttle', [
|
||
'seconds' => $seconds,
|
||
'minutes' => ceil($seconds / 60),
|
||
]),
|
||
]);
|
||
}
|
||
|
||
public function throttleKey(): string
|
||
{
|
||
return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip());
|
||
}
|
||
|
||
private function decaySeconds(): int
|
||
{
|
||
return (int) config('security.lockout.decay_minutes') * 60;
|
||
}
|
||
}
|