- Settings: neuer Toolbar-Button 'Master-Key erzeugen' (Tab General Settings, via Designer). Erzeugt zufaelligen 32-Byte-AES-Key -> master.key (gitignored), nur aktiv wenn KEIN Key existiert (Env-Var oder Datei), Lockout-Schutz + Backup-Warnung, danach deaktiviert. - Einbezogen: laufende Designer-Umstrukturierung (SettingsView/LauncherForm: Button-Bilder aus Properties.Resources statt eingebettet; dgv_accountlist im Launcher; DashboardView.resx). - docs/steuer: US-CPA-Fragebogen als PDF. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
187 lines
7.5 KiB
C#
187 lines
7.5 KiB
C#
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
|
||
{
|
||
/// <summary>
|
||
/// 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).
|
||
/// </summary>
|
||
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<AccountState> _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<AccountState>(_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) =====
|
||
|
||
/// <summary>Pfad der Master-Key-Datei – identisch zu Program.cs (App-Ordner, gitignored).</summary>
|
||
private static string MasterKeyFilePath => Path.Combine(AppContext.BaseDirectory, "master.key");
|
||
|
||
/// <summary>Existiert bereits ein Master-Key (Umgebungsvariable ODER Datei)?</summary>
|
||
private static bool MasterKeyExists() =>
|
||
!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("POLYTRADER_MASTER_KEY"))
|
||
|| File.Exists(MasterKeyFilePath);
|
||
|
||
/// <summary>Button nur aktiv, solange KEIN Master-Key existiert (Überschreiben = Lockout-Gefahr).</summary>
|
||
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
|
||
}
|
||
}
|
||
}
|