configured = true; $this->host = $hc->host; $this->port = $hc->port; $this->username = $hc->username; $this->authType = $hc->auth_type; // secret/passphrase stay blank — never reveal the stored login back into the form. } } /** * @return array> */ protected function rules(): array { return [ 'host' => ['required', 'string', 'max:255'], 'port' => ['required', 'integer', 'min:1', 'max:65535'], 'username' => ['required', 'string', 'max:120'], 'authType' => ['required', Rule::in(['password', 'key'])], // On first setup the secret is required; when editing an existing login a blank // secret means "keep the stored one". 'secret' => [$this->configured ? 'nullable' : 'required', 'string'], 'passphrase' => ['nullable', 'string'], ]; } /** * @return array */ protected function validationAttributes(): array { return [ 'host' => __('terminal.host_field_host'), 'port' => __('terminal.host_field_port'), 'username' => __('terminal.host_field_user'), 'authType' => __('terminal.host_field_auth'), 'secret' => $this->authType === 'key' ? __('terminal.host_field_key') : __('terminal.host_field_password'), ]; } public function save(): void { $data = $this->validate(); $existing = HostCredential::current(); // Switching the auth method (password ↔ key) requires a fresh secret: keeping the stored one // would feed e.g. an old password into key auth (or vice versa) and silently break the login. if ($this->secret === '' && $existing !== null && $this->authType !== $existing->auth_type) { throw ValidationException::withMessages([ 'secret' => __('terminal.host_secret_required_for_auth_change'), ]); } $hc = $existing ?? new HostCredential; $hc->host = trim($data['host']); $hc->port = (int) $data['port']; $hc->username = trim($data['username']); $hc->auth_type = $data['authType'] === 'key' ? 'key' : 'password'; // Only overwrite the secret when a new one was entered (blank = keep the stored one). if ($this->secret !== '') { $hc->secret = $this->secret; $hc->passphrase = $this->authType === 'key' && $this->passphrase !== '' ? $this->passphrase : null; } $hc->save(); AuditEvent::create([ 'user_id' => Auth::id(), 'actor' => Auth::user()?->name ?? 'system', 'action' => 'host.terminal.configure', 'target' => $hc->username.'@'.$hc->host.':'.$hc->port, 'ip' => request()->ip(), ]); $this->dispatch('hostShellSaved'); $this->dispatch('notify', message: __('terminal.host_saved')); $this->closeModal(); } public function remove(): void { $hc = HostCredential::current(); if ($hc !== null) { $target = $hc->username.'@'.$hc->host.':'.$hc->port; HostCredential::query()->delete(); // singleton — clear any/all rows, not just the first AuditEvent::create([ 'user_id' => Auth::id(), 'actor' => Auth::user()?->name ?? 'system', 'action' => 'host.terminal.remove', 'target' => $target, 'ip' => request()->ip(), ]); } $this->dispatch('hostShellSaved'); $this->dispatch('notify', message: __('terminal.host_removed')); $this->closeModal(); } public function render() { return view('livewire.modals.host-shell'); } }