Files
PolyTraderSharp/src/PolyTrader.App.Avalonia/Views/SettingsWindow.axaml
T
RichardandClaude Opus 5 86b497bdbf
CI / Build & Tests (Linux) (push) Canceled after 0s
CI / Plattformneutralität & Hygiene (push) Canceled after 0s
Desktop: Passwortabfrage vor Konten, Geheimnissen und WebUI-Zugang
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>
2026-09-13 14:34:32 +02:00

156 lines
10 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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 &amp; 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 &amp; 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>