using System; using System.ComponentModel; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Windows.Forms; using PolyTrader.Core.Persistence; using PolyTraderSharp.Models; using PolyTraderSharp.Services; namespace PolyTraderSharp.Ui.Views { /// /// Core-Settings-Fenster: allgemeine Server-Einstellungen (PropertyGrid) und die /// allgemeine Verwaltung der Polymarket-Accounts (Wallets, API-Keys …). /// Die copytrading-spezifischen Detail-Limits werden NICHT hier, sondern im /// Copytrading-Modul-View bearbeitet (AccountState ist bewusst general-only). /// public partial class SettingsView : Form { private const string SettingsPath = "server_settings.xml"; private ServerSettings _settings = new(); private ThreemaService? _threema; private MullvadVpnService? _vpn; private TerminalLogger? _logger; private IAccountRepository? _accountRepo; private TradingState? _state; private BindingList _accounts = new(); public SettingsView() { InitializeComponent(); btn_save.Click += (_, _) => Save(); btn_loadsettings.Click += (_, _) => Reload(); btnGenMasterKey.Click += (_, _) => GenerateMasterKey(); UpdateMasterKeyButtonState(); btnAccNew.Click += (_, _) => AddAccount(); btnAccDelete.Click += (_, _) => DeleteAccount(); dgvAccounts.SelectionChanged += (_, _) => { pgAccount.SelectedObject = dgvAccounts.CurrentRow?.DataBoundItem as AccountState; }; pgAccount.PropertyValueChanged += (_, _) => { if (pgAccount.SelectedObject is AccountState acc) SaveAccount(acc); }; } public void Initialize(ThreemaService threema, MullvadVpnService vpn, TerminalLogger logger, IAccountRepository accountRepo, TradingState state) { _threema = threema; _vpn = vpn; _logger = logger; _accountRepo = accountRepo; _state = state; Reload(); LoadAccounts(); } // ===== Server-Settings ===== private void Reload() { _settings = ServerSettings.Load(SettingsPath); propertyGrid.SelectedObject = _settings; } private void Save() { try { _settings.Save(SettingsPath); _threema?.ReloadSettings(); _vpn?.ReloadSettings(); _logger?.Info("Server-Einstellungen gespeichert und Services neu geladen."); MessageBox.Show("Server-Einstellungen gespeichert.", "Erfolg", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { MessageBox.Show($"Fehler beim Speichern: {ex.Message}", "Fehler", MessageBoxButtons.OK, MessageBoxIcon.Error); } } // ===== Polymarket Accounts (allgemeine Einstellungen) ===== private void LoadAccounts() { if (_state == null) return; _accounts = new BindingList(_state.Accounts.Values.OrderBy(a => a.AccountId).ToList()); dgvAccounts.DataSource = _accounts; pgAccount.SelectedObject = dgvAccounts.CurrentRow?.DataBoundItem as AccountState; } private void AddAccount() { if (_state == null || _accountRepo == null) return; int newId = _state.Accounts.Count > 0 ? _state.Accounts.Keys.Max() + 1 : 1; var acc = new AccountState { AccountId = newId, Name = "Neuer Account" }; _state.Accounts[acc.AccountId] = acc; _accountRepo.Upsert(acc); _accounts.Add(acc); dgvAccounts.CurrentCell = dgvAccounts.Rows[dgvAccounts.Rows.Count - 1].Cells[0]; } private void DeleteAccount() { if (_state == null || _accountRepo == null) return; if (dgvAccounts.CurrentRow?.DataBoundItem is not AccountState acc) return; if (MessageBox.Show($"Account '{acc.Name}' (ID {acc.AccountId}) wirklich löschen?", "Löschen bestätigen", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes) return; _state.Accounts.TryRemove(acc.AccountId, out _); _accountRepo.Delete(acc.AccountId); _accounts.Remove(acc); } private void SaveAccount(AccountState acc) { _accountRepo?.Upsert(acc); _state?.Accounts.AddOrUpdate(acc.AccountId, acc, (_, _) => acc); dgvAccounts.Refresh(); } // ===== Master-Key (at-rest-Verschlüsselung, F1) ===== /// Pfad der Master-Key-Datei – identisch zu Program.cs (App-Ordner, gitignored). private static string MasterKeyFilePath => Path.Combine(AppContext.BaseDirectory, "master.key"); /// Existiert bereits ein Master-Key (Umgebungsvariable ODER Datei)? private static bool MasterKeyExists() => !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("POLYTRADER_MASTER_KEY")) || File.Exists(MasterKeyFilePath); /// Button nur aktiv, solange KEIN Master-Key existiert (Überschreiben = Lockout-Gefahr). private void UpdateMasterKeyButtonState() => btnGenMasterKey.Enabled = !MasterKeyExists(); private void GenerateMasterKey() { // Sicherheitsnetz gegen Race/Doppelklick: einen bestehenden Key NIEMALS überschreiben. if (MasterKeyExists()) { MessageBox.Show( "Es existiert bereits ein Master-Key – Erzeugung abgebrochen. Ein Überschreiben würde den " + "Zugriff auf bereits verschlüsselte Wallet-Keys unwiederbringlich zerstören.", "Master-Key vorhanden", MessageBoxButtons.OK, MessageBoxIcon.Warning); UpdateMasterKeyButtonState(); return; } try { byte[] keyBytes = RandomNumberGenerator.GetBytes(32); // 256-Bit-Schlüssel File.WriteAllText(MasterKeyFilePath, Convert.ToBase64String(keyBytes)); _logger?.Info("🔐 Master-Key erzeugt und in master.key gespeichert. At-rest-Verschlüsselung wird beim nächsten Start aktiv."); MessageBox.Show( "Ein zufälliger 32-Byte-Master-Key wurde erzeugt und in der Datei 'master.key' (App-Ordner, gitignored) gespeichert.\n\n" + "WICHTIG:\n" + "• Sichere diese Datei SOFORT separat und sicher (z. B. Passwort-Manager / Offline-Backup).\n" + "• Master-Key-Verlust = KEIN Zugriff mehr auf die verschlüsselten Wallet-Keys!\n" + "• Die Verschlüsselung der Account-Credentials wird beim nächsten Programmstart aktiv.", "Master-Key erzeugt", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { MessageBox.Show($"Fehler beim Erzeugen des Master-Keys: {ex.Message}", "Fehler", MessageBoxButtons.OK, MessageBoxIcon.Error); } UpdateMasterKeyButtonState(); // nach Erzeugung deaktivieren } } }