using System; using Deploymentcenter.Client; using PolyTrader.Core; using PolyTrader.Core.Configuration; using PolyTrader.Core.Security; using PolyTraderSharp.Models; using PolyTraderSharp.Services; namespace PolyTrader.App.Avalonia.Licensing { /// /// Lizenz-Torwächter für den Programmstart (D-2). Ersetzt den früheren, gegen /// LicenseLabrador.Client gebauten Licensing/LicenseGate.cs im WinForms-Projekt /// (siehe docs/archiv/umsetzungsplaene/UMSETZUNGSPLAN-Deploymentcenter-Integration.md) – dort läuft /// ohnehin keine Lizenzprüfung, weil Avalonia die tatsächlich benutzte Anwendung ist. /// /// **Grundsatz unverändert:** kein bei ungültiger Lizenz. Ein /// Trading-Bot mit offenen Positionen darf nicht mitten im Lauf abgeschossen werden. Bei /// fehlender/ungültiger Lizenz startet die Core-Shell (Terminal, Einstellungen) ohne die /// Trading-/Analyse-Module; der Schlüssel lässt sich jederzeit über den Button /// „Lizenzschlüssel setzen …" im Einstellungsfenster nachtragen (wirkt ab dem nächsten Start – /// eine Laufzeit-Revalidierung gibt es bewusst nicht, siehe Entscheidung vom 31.07.2026). /// /// **Bewusst kein eigenes :** Der interaktive Weg läuft über die /// bereits vorhandene, erprobte Settings-UI (derselbe maskierte Prompt-Button wie beim /// Watchdog-Token); ein zusätzlicher Dialog nur für die Lizenz wäre eine zweite, redundante /// Eingabe-Oberfläche. Für den Kopflos-Betrieb (--headless, CLI-Schalter) genügt die vom /// SDK mitgelieferte – eine eigene Fassung böte keinen Mehrwert. /// public static class LicenseGate { /// /// Prüft still (ohne Dialog), ob die gespeicherte Lizenz aktuell nutzbar ist. Entscheidet /// darüber, ob die Trading-/Analyse-Module überhaupt registriert werden – muss deshalb vor /// dem Host-Aufbau laufen. Wirft nie: jeder Fehler in der Prüfung führt zum eingeschränkten /// Modus, nicht zum Absturz. /// public static bool CheckUsable(DeploymentcenterOptions options, ServerSettings settings, TerminalLogger logger) { try { if (string.IsNullOrWhiteSpace(options.BaseUrl)) { logger.Warning("⚠️ Keine Deploymentcenter-URL konfiguriert. Start im eingeschränkten Modus (nur Core-Shell)."); return false; } // Befund D-3 aus dem Umsetzungsplan: das SDK erzwingt selbst kein TLS. Ohne diese // Prüfung ginge der Lizenzschlüssel im Klartext übers Netz, wenn die URL versehentlich // auf http:// steht. if (!Uri.TryCreate(options.BaseUrl, UriKind.Absolute, out var parsed) || !(parsed.Scheme == Uri.UriSchemeHttps || (parsed.Scheme == Uri.UriSchemeHttp && parsed.IsLoopback))) { logger.Warning($"⚠️ Deploymentcenter-URL muss HTTPS verwenden (http:// nur für localhost): '{options.BaseUrl}'. " + "Start im eingeschränkten Modus."); return false; } string key; try { // Erlaubt einen mit dem Master-Key verschlüsselten Schlüssel (enc:v1:…); // Klartext wird unverändert durchgereicht. key = SecretProtection.Unprotect(settings.LicenseKey); } catch (Exception ex) { logger.Warning($"⚠️ Gespeicherter Lizenzschlüssel nicht lesbar ({ex.Message}) – behandle als 'keine Lizenz'."); return false; } if (string.IsNullOrWhiteSpace(key)) { logger.Warning("⚠️ Kein Lizenzschlüssel gesetzt. Start im eingeschränkten Modus (nur Core-Shell). " + "Schlüssel über „Lizenzschlüssel setzen …\" im Einstellungsfenster eintragen."); return false; } LicenseClient.DefaultAppVersion = BuildInfo.Version; var client = new LicenseClient(); LicenseValidationResult result = client .ValidateAsync(options.ProjectSlug, key, options.BaseUrl, BuildInfo.Version) .GetAwaiter().GetResult(); if (result.IsValid) { logger.Info(result.IsCached ? $"🔑 Lizenz im Offline-Kulanzmodus gültig ({result.Status})." : "🔑 Lizenz gültig (online geprüft)."); return true; } logger.Warning($"⚠️ Keine gültige Lizenz ({result.Status}: {result.Message}). " + "Start im eingeschränkten Modus (nur Core-Shell, keine Module). " + "Schlüssel über „Lizenzschlüssel setzen …\" im Einstellungsfenster prüfen."); return false; } catch (Exception ex) { logger.Error($"❌ Lizenzprüfung unerwartet fehlgeschlagen: {ex.Message}. Start im eingeschränkten Modus."); return false; } } } }