user()?->can('manage-panel'), 403); $this->enabled = $guard->enabled(); $this->maxretry = $guard->maxretry(); $this->findtime = $guard->findtime(); $this->bantime = $guard->bantime(); $this->whitelist = implode("\n", $guard->whitelist()); } public function save(BruteforceGuard $guard): void { abort_unless(auth()->user()?->can('manage-panel'), 403); $this->validate([ 'maxretry' => ['required', 'integer', 'min:1', 'max:1000'], 'findtime' => ['required', 'integer', 'min:1', 'max:1440'], 'bantime' => ['required', 'integer', 'min:1', 'max:43200'], 'whitelist' => ['nullable', 'string'], ]); $lines = array_values(array_filter(array_map('trim', preg_split('/[\r\n,]+/', $this->whitelist) ?: []))); foreach ($lines as $line) { if (Validator::make(['v' => $line], ['v' => [new ValidIpOrCidr]])->fails()) { $this->addError('whitelist', __('settings.lp_whitelist_invalid', ['value' => $line])); return; } } Setting::put('bruteforce_enabled', $this->enabled ? '1' : '0'); Setting::put('bruteforce_maxretry', (string) $this->maxretry); Setting::put('bruteforce_findtime', (string) $this->findtime); Setting::put('bruteforce_bantime', (string) $this->bantime); Setting::put('bruteforce_whitelist', implode("\n", $lines)); // Widening the whitelist immediately clears any now-exempt active bans. foreach (BannedIp::active()->get() as $ban) { if ($guard->isExempt($ban->ip)) { $guard->unban($ban->ip); } } $this->audit('auth.protection_updated', null); $this->dispatch('notify', message: __('settings.lp_saved')); } public function whitelistMyIp(): void { abort_unless(auth()->user()?->can('manage-panel'), 403); $ip = (string) request()->ip(); $lines = array_values(array_filter(array_map('trim', preg_split('/[\r\n,]+/', $this->whitelist) ?: []))); if (! in_array($ip, $lines, true)) { $lines[] = $ip; } $this->whitelist = implode("\n", $lines); } public function confirmUnban(string $ip): void { abort_unless(auth()->user()?->can('manage-panel'), 403); $this->dispatch('openModal', component: 'modals.confirm-action', arguments: [ 'heading' => __('settings.lp_unban_heading'), 'body' => __('settings.lp_unban_body', ['ip' => $ip]), 'confirmLabel' => __('settings.lp_unban'), 'danger' => false, 'icon' => 'shield', 'notify' => __('settings.lp_unban_notify'), 'token' => ConfirmToken::issue('banCleared', ['ip' => $ip], auditTarget: $ip), ], ); } #[On('banCleared')] public function unban(string $confirmToken, BruteforceGuard $guard): void { abort_unless(auth()->user()?->can('manage-panel'), 403); try { $payload = ConfirmToken::consume($confirmToken, 'banCleared'); } catch (InvalidConfirmToken) { return; } $ip = (string) $payload['params']['ip']; $guard->unban($ip); $this->audit('auth.ip_unbanned', $ip); } public function confirmUnbanAll(): void { abort_unless(auth()->user()?->can('manage-panel'), 403); $this->dispatch('openModal', component: 'modals.confirm-action', arguments: [ 'heading' => __('settings.lp_unban_all_heading'), 'body' => __('settings.lp_unban_all_body'), 'confirmLabel' => __('settings.lp_unban_all'), 'danger' => true, 'icon' => 'shield', 'notify' => __('settings.lp_unban_all_notify'), 'token' => ConfirmToken::issue('bansClearedAll'), ], ); } #[On('bansClearedAll')] public function unbanAll(string $confirmToken, BruteforceGuard $guard): void { abort_unless(auth()->user()?->can('manage-panel'), 403); try { ConfirmToken::consume($confirmToken, 'bansClearedAll'); } catch (InvalidConfirmToken) { return; // forged / replayed / direct-bypass attempt — no-op } $guard->unbanAll(); $this->audit('auth.ip_unbanned', 'all'); } private function audit(string $action, ?string $target): void { AuditEvent::create([ 'user_id' => Auth::id(), 'actor' => Auth::user()?->name ?? 'system', 'action' => $action, 'target' => $target, 'ip' => request()->ip(), ]); } public function render(BruteforceGuard $guard) { $ip = (string) request()->ip(); return view('livewire.settings.login-protection', [ 'bans' => BannedIp::query()->where('banned_until', '>', now())->orderByDesc('banned_until')->limit(200)->get(), 'currentIp' => $ip, 'currentIpExempt' => $guard->isExempt($ip), // Read-only status of the env-driven honeypot deception layer (no toggle here). 'honeypotEnabled' => (bool) config('clusev.honeypot.enabled'), ]); } }