Umsetzung von #567. Entscheidung Richard, 13.09.2026: Geheimnisse verwalten wir ausschliesslich im Desktop, und selbst dort erst nach zusaetzlicher Passwortabfrage. Bisher sah und aenderte jeder, der die Desktop-Oberflaeche offen vor sich hatte: - Polymarket-Konten mit ApiKey, ApiSecret, ApiPassphrase und PrivateKey - Master-Key, OpenRouter-Key, Watchdog-Token und Lizenzschluessel - Mullvad-Account und Polygon-RPC-URL mit Alchemy-Key, offen im Editor - das WebUI-Token, per "Anzeigen" lesbar Jetzt gesperrt, bis entsperrt: - Tab "Polymarket Accounts": Gesperrt stehen die Konten gar nicht im visuellen Baum. Das Raster bleibt leer, der Editor mit den Private Keys ebenfalls; nichts ist nur verdeckt. - Master-Key erzeugen, OpenRouter-, Watchdog- und Lizenzschluessel setzen. - Mullvad-Account und Polygon-RPC-URL: mit [SensitiveSetting] markiert und im Editor ausgeblendet. - WebUI-Zugang: Gesperrt steht das Token nicht einmal maskiert im Feld. Speichern ist mitgesperrt, weil es den Feldinhalt als Token schreibt; gesperrt und leer haette es das Token geloescht. - Frei bleiben Lizenzpruefung und Test-Heartbeat, weil sie keinen Schluessel zeigen. Kern im Core (PolyTrader.Core.Security), weil nur dort testbar: - SettingsPasswordStore: PBKDF2-HMAC-SHA256, 600 000 Iterationen, Salt, eigene Datei settings-password.json neben master.key, atomar geschrieben und nur fuer den Besitzer lesbar. Nicht in server_settings.xml, die seit #561 das WebUI mitschreibt. Ein vorhandenes Passwort wird nie ueberschrieben. - SettingsLock: - erstes Festlegen mit Wiederholung und mindestens 10 Zeichen - zwei freie Fehlversuche, danach 5 s bis 5 min Wartezeit, in der auch das richtige Passwort nicht geprueft wird - Sperre nach 5 Minuten ohne geschuetzte Aktion und beim Verlassen der Einstellungen - eine unlesbare Passwortdatei entsperrt nie und gilt auch nicht still als "kein Passwort" - Protokoll ohne eingegebene Werte Oberflaeche: PasswordDialog deklarativ in .axaml, Sperr-Leiste ueber den Tabs. Die PBKDF2-Pruefung dauert etwa eine halbe Sekunde und laeuft nicht im UI-Thread. Grenzen: - Die Sperre schuetzt gegen jemanden an einer entsperrten Desktop-Sitzung. Wer Dateizugriff hat, liest master.key selbst. Deshalb gibt es kein "Passwort vergessen": Wer settings-password.json loeschen kann, braucht keins. - Die Wartezeit nach Fehlversuchen beginnt nach einem Neustart von vorn. - "Datenbankeinstellungen": Eine Ansicht dafuer gibt es im Desktop nicht; die Verbindungszeichenfolge steht nur in appsettings.Local.json. Entsteht eine, gehoert sie hinter dieselbe Sperre. Tests: 20 fuer Passwortdatei und Sperre (Festlegen, Fehlversuche, Wartezeit, Leerlauf, unlesbare Datei, Aendern, Iterationszahl aus der Datei, Dateirechte) und 5 fuer die Markierung der Geheimnisfelder. Die Oberflaeche selbst wird auf der VM abgenommen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
156 lines
10 KiB
XML
156 lines
10 KiB
XML
<UserControl xmlns="https://github.com/avaloniaui"
|
||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||
xmlns:controls="using:PolyTrader.App.Avalonia.Controls"
|
||
xmlns:models="using:PolyTraderSharp.Models"
|
||
x:Class="PolyTrader.App.Avalonia.Views.SettingsWindow">
|
||
|
||
<!--
|
||
Einstellungen. Ersetzt die beiden PropertyGrids der WinForms-Fassung durch den
|
||
kategorisierten SettingsEditor (Überschrift, Beschriftung links, Feld rechts,
|
||
Erklärung klein darunter).
|
||
-->
|
||
<DockPanel>
|
||
<Border Classes="statusbar" DockPanel.Dock="Bottom">
|
||
<TextBlock Name="lblStatus" Text="Bereit." />
|
||
</Border>
|
||
|
||
<!-- #567: Sperrzustand der geschuetzten Einstellungen (Konten, Geheimnisse, WebUI-Zugang). -->
|
||
<Border Classes="toolbar" DockPanel.Dock="Top">
|
||
<StackPanel Orientation="Horizontal" Spacing="10">
|
||
<Button Name="btnLockToggle" Content="🔒 Entsperren" />
|
||
<TextBlock Name="lblLockState" VerticalAlignment="Center" Classes="muted"
|
||
Text="Konten, Geheimnisse und WebUI-Zugang sind gesperrt." />
|
||
</StackPanel>
|
||
</Border>
|
||
|
||
<TabControl>
|
||
|
||
<TabItem Header="Allgemeine Einstellungen">
|
||
<DockPanel>
|
||
<Border Classes="toolbar" DockPanel.Dock="Top">
|
||
<StackPanel Orientation="Horizontal">
|
||
<Button Name="btnSave" Content="Speichern" />
|
||
<Button Name="btnReload" Content="Neu laden" />
|
||
<Separator Margin="6,0" />
|
||
<Button Name="btnGenMasterKey" Content="Master-Key erzeugen"
|
||
ToolTip.Tip="Erzeugt einen zufälligen AES-Master-Key (nur wenn noch keiner existiert)." />
|
||
<Button Name="btnOpenRouterKey" Content="OpenRouter-Key setzen …"
|
||
ToolTip.Tip="Speichert den OpenRouter-API-Key für den Supervisor (gitignorierte Datei openrouter.key)." />
|
||
<Button Name="btnWatchdogToken" Content="Watchdog-Token setzen …"
|
||
ToolTip.Tip="Speichert den Watchdog-Agent-Token (maskierte Eingabe, bei gesetztem Master-Key verschlüsselt)." />
|
||
<Button Name="btnTestWatchdog" Content="Test-Heartbeat senden"
|
||
ToolTip.Tip="Sendet sofort einen Heartbeat an den konfigurierten Watchdog-Server." />
|
||
<Separator Margin="6,0" />
|
||
<Button Name="btnLicenseKey" Content="Lizenzschlüssel setzen …"
|
||
ToolTip.Tip="Speichert den Deploymentcenter-Lizenzschlüssel (maskierte Eingabe, bei gesetztem Master-Key verschlüsselt)." />
|
||
<Button Name="btnCheckLicense" Content="Lizenz jetzt prüfen"
|
||
ToolTip.Tip="Prüft den gespeicherten Lizenzschlüssel gegen das Deploymentcenter und zeigt Status sowie Hardware-ID." />
|
||
</StackPanel>
|
||
</Border>
|
||
|
||
<controls:SettingsEditor Name="editorServer" />
|
||
</DockPanel>
|
||
</TabItem>
|
||
|
||
<TabItem Header="Polymarket Accounts">
|
||
<Panel>
|
||
<!-- #567: Gesperrt stehen die Konten gar nicht im visuellen Baum - das Raster bleibt leer,
|
||
nicht nur verdeckt. -->
|
||
<Border Name="pnlAccountsLocked" Padding="32" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||
<StackPanel Spacing="12" MaxWidth="480">
|
||
<TextBlock Text="🔒 Polymarket-Konten sind gesperrt" FontSize="16" FontWeight="SemiBold" />
|
||
<TextBlock TextWrapping="Wrap" Classes="muted"
|
||
Text="Die Konten enthalten Private Keys und API-Schlüssel – also Zugriff auf Geld. Sie sind erst nach Eingabe des Einstellungs-Passworts sichtbar und sperren sich nach 5 Minuten ohne geschützte Aktion wieder." />
|
||
<Button Name="btnAccUnlock" Content="Entsperren …" HorizontalAlignment="Left" Classes="primary" />
|
||
</StackPanel>
|
||
</Border>
|
||
<DockPanel Name="pnlAccounts" IsVisible="False">
|
||
<Border Classes="toolbar" DockPanel.Dock="Top">
|
||
<StackPanel Orientation="Horizontal">
|
||
<Button Name="btnAccNew" Content="Neuer Account" />
|
||
<Button Name="btnAccDelete" Content="Löschen" />
|
||
<Button Name="btnAccSave" Content="Account speichern" />
|
||
</StackPanel>
|
||
</Border>
|
||
|
||
<Grid ColumnDefinitions="380,4,*">
|
||
<DataGrid Name="gridAccounts" Grid.Column="0" x:DataType="models:AccountState">
|
||
<DataGrid.Columns>
|
||
<DataGridTextColumn Header="#" Width="Auto" Binding="{Binding AccountId}" />
|
||
<DataGridTextColumn Header="Name" Width="*" MinWidth="180" Binding="{Binding Name}" />
|
||
<DataGridCheckBoxColumn Header="Demo" Width="Auto" Binding="{Binding IsDemo}" />
|
||
<DataGridCheckBoxColumn Header="Aktiv" Width="Auto" Binding="{Binding IsActive}" />
|
||
</DataGrid.Columns>
|
||
</DataGrid>
|
||
|
||
<GridSplitter Grid.Column="1" Background="{DynamicResource AppBorderBrush}" />
|
||
|
||
<controls:SettingsEditor Name="editorAccount" Grid.Column="2" />
|
||
</Grid>
|
||
</DockPanel>
|
||
</Panel>
|
||
</TabItem>
|
||
|
||
<TabItem Header="WebUI">
|
||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||
<StackPanel Margin="24,20" MaxWidth="680" HorizontalAlignment="Left" Spacing="16">
|
||
<TextBlock Text="WebUI Konfiguration & Fernzugriff" FontSize="18" FontWeight="SemiBold" />
|
||
<TextBlock Text="Das WebUI ermöglicht die Steuerung und Überwachung von PolyTrader über einen Browser – parallel zum Desktop GUI oder im kopflosen Server-Betrieb."
|
||
Foreground="{DynamicResource AppMutedTextBrush}" TextWrapping="Wrap" />
|
||
|
||
<Border Background="{DynamicResource AppSurfaceBrush}" BorderBrush="{DynamicResource AppBorderBrush}" BorderThickness="1" CornerRadius="8" Padding="16">
|
||
<StackPanel Spacing="12">
|
||
<TextBlock Text="Server-Status" FontWeight="SemiBold" FontSize="14" />
|
||
<TextBlock Name="lblWebUiStatus" Text="Status wird ermittelt …" Foreground="{DynamicResource AppAccentBrush}" />
|
||
<TextBlock Name="lblWebUiUrl" Text="http://127.0.0.1:6001" Foreground="{DynamicResource AppMutedTextBrush}" FontSize="12" />
|
||
</StackPanel>
|
||
</Border>
|
||
|
||
<Border Background="{DynamicResource AppSurfaceBrush}" BorderBrush="{DynamicResource AppBorderBrush}" BorderThickness="1" CornerRadius="8" Padding="16">
|
||
<StackPanel Spacing="16">
|
||
<TextBlock Text="Netzwerk & Port" FontWeight="SemiBold" FontSize="14" />
|
||
|
||
<CheckBox Name="chkWebUiEnabled" Content="WebUI Server aktivieren" />
|
||
|
||
<Grid ColumnDefinitions="140,*" Margin="0,4">
|
||
<TextBlock Grid.Column="0" Text="Port:" VerticalAlignment="Center" />
|
||
<NumericUpDown Name="numWebUiPort" Grid.Column="1" Minimum="1024" Maximum="65535" Value="6001" Width="150" HorizontalAlignment="Left" />
|
||
</Grid>
|
||
|
||
<CheckBox Name="chkWebUiLocalOnly" Content="Nur lokaler Zugriff (127.0.0.1)" />
|
||
<TextBlock Text="Wenn deaktiviert, lauscht der Server auf 0.0.0.0 und ist über LAN/VPN erreichbar."
|
||
Foreground="{DynamicResource AppMutedTextBrush}" FontSize="12" Margin="24,-8,0,0" TextWrapping="Wrap" />
|
||
</StackPanel>
|
||
</Border>
|
||
|
||
<Border Background="{DynamicResource AppSurfaceBrush}" BorderBrush="{DynamicResource AppBorderBrush}" BorderThickness="1" CornerRadius="8" Padding="16">
|
||
<StackPanel Spacing="12">
|
||
<TextBlock Text="Zugangssicherung (Token)" FontWeight="SemiBold" FontSize="14" />
|
||
<TextBlock Text="Ohne gültiges Token verweigert das WebUI jeden Zugriff. Das Token wird in appsettings.Local.json gespeichert."
|
||
Foreground="{DynamicResource AppMutedTextBrush}" FontSize="12" TextWrapping="Wrap" />
|
||
|
||
<Grid ColumnDefinitions="*,Auto,Auto" Margin="0,6">
|
||
<TextBox Name="txtWebUiToken" Grid.Column="0" PasswordChar="●" FontFamily="Consolas,monospace" FontSize="12" VerticalAlignment="Center" Margin="0,0,8,0" />
|
||
<Button Name="btnWebUiToggleShowToken" Grid.Column="1" Content="Anzeigen" Margin="0,0,8,0" />
|
||
<Button Name="btnWebUiCopyToken" Grid.Column="2" Content="Kopieren" />
|
||
</Grid>
|
||
|
||
<StackPanel Orientation="Horizontal" Spacing="10" Margin="0,4,0,0">
|
||
<Button Name="btnWebUiGenToken" Content="Neuen Token generieren" />
|
||
</StackPanel>
|
||
</StackPanel>
|
||
</Border>
|
||
|
||
<StackPanel Orientation="Horizontal" Spacing="12" Margin="0,8,0,0">
|
||
<Button Name="btnSaveWebUi" Content="WebUI-Einstellungen speichern" />
|
||
<TextBlock Name="lblWebUiSaveStatus" Text="" VerticalAlignment="Center" Foreground="{DynamicResource AppPositiveBrush}" />
|
||
</StackPanel>
|
||
</StackPanel>
|
||
</ScrollViewer>
|
||
</TabItem>
|
||
|
||
</TabControl>
|
||
</DockPanel>
|
||
|
||
</UserControl>
|