dnsZone = ProvisioningSettings::dnsZone(); $this->wgEndpoint = ProvisioningSettings::wgEndpoint(); $this->wgHubPubkey = ProvisioningSettings::wgHubPublicKey(); $this->traefikDynamicPath = ProvisioningSettings::traefikDynamicPath(); $this->sshPublicKey = ProvisioningSettings::sshPublicKey(); $this->monitoringUrl = ProvisioningSettings::monitoringUrl(); } } // ---- Plain settings — App\Support\Settings, hosts.manage, no password. ---- public function saveInfra(): void { $this->guardInfra(); $data = $this->validate([ 'dnsZone' => ['nullable', 'string', 'max:255'], 'wgEndpoint' => ['nullable', 'string', 'max:255'], 'wgHubPubkey' => ['nullable', 'string', 'max:255'], 'traefikDynamicPath' => ['nullable', 'string', 'max:255'], 'sshPublicKey' => ['nullable', 'string', 'max:1000'], 'monitoringUrl' => ['nullable', 'url', 'max:255'], ]); Settings::set('provisioning.dns_zone', trim((string) $data['dnsZone'])); Settings::set('provisioning.wg_endpoint', trim((string) $data['wgEndpoint'])); Settings::set('provisioning.wg_hub_pubkey', trim((string) $data['wgHubPubkey'])); Settings::set('provisioning.traefik_dynamic_path', trim((string) $data['traefikDynamicPath'])); Settings::set('provisioning.ssh_public_key', trim((string) $data['sshPublicKey'])); Settings::set('monitoring.api_url', trim((string) $data['monitoringUrl'])); $this->dispatch('notify', message: __('integrations.settings_saved')); } // ---- Vault entries — SecretVault, secrets.manage + confirmed password. ---- public function save(string $key): void { $this->guardSecrets(); $field = self::field($key); $value = trim((string) ($this->entered[$field] ?? '')); if ($value === '') { $this->addError('entered.'.$field, __('secrets.empty')); return; } try { app(SecretVault::class)->put($key, $value, Auth::guard('operator')->user()); } catch (Throwable $e) { $this->addError('entered.'.$field, $e->getMessage()); return; } $this->entered[$field] = ''; $this->check = null; $this->dispatch('notify', message: __('secrets.saved')); } /** ConfirmSaveSecret dispatches this back (R23) — see that class. */ #[On('secret-save-confirmed')] public function onSaveConfirmed(string $key): void { $this->save($key); } public function forget(string $key): void { $this->guardSecrets(); app(SecretVault::class)->forget($key); $this->check = null; $this->dispatch('notify', message: __('secrets.removed')); } /** ConfirmForgetSecret dispatches this back (R23) — see that class. */ #[On('secret-forget-confirmed')] public function onForgetConfirmed(string $key): void { $this->forget($key); } public function test(string $key): void { $this->guardSecrets(); $checker = SecretVault::REGISTRY[$key]['check'] ?? null; abort_if($checker === null, 404); $candidate = trim((string) ($this->entered[self::field($key)] ?? '')) ?: null; $this->check = app($checker)->run($candidate); } /** The dotless form key for a registry key (a dot means nesting to Livewire). */ public static function field(string $key): string { return str_replace('.', '_', $key); } private function guardInfra(): void { $this->authorize('hosts.manage'); } /** Capability AND a recently confirmed password, on every vault action. */ private function guardSecrets(): void { $this->authorize('secrets.manage'); abort_unless($this->passwordRecentlyConfirmed(), 403); } public function render() { $vault = app(SecretVault::class); $canSecrets = Gate::allows('secrets.manage'); $canInfra = Gate::allows('hosts.manage'); $unlocked = $this->passwordRecentlyConfirmed(); return view('livewire.admin.integrations', [ 'canSecrets' => $canSecrets, 'canInfra' => $canInfra, 'unlocked' => $unlocked, 'usable' => $vault->isUsable(), 'entries' => collect(SecretVault::REGISTRY) ->map(fn (array $meta, string $key) => [ 'key' => $key, 'field' => self::field($key), 'label' => __($meta['label']), 'testable' => isset($meta['check']), 'source' => $vault->source($key), 'outline' => $unlocked ? $vault->outline($key) : null, 'updated_at' => $unlocked ? $vault->updatedAt($key) : null, // The SSH identity is a multi-line PEM key: a single-line // password field would mangle it on paste. 'multiline' => $key === 'ssh.private_key', ]) ->keyBy('key'), ]); } }