using LehrerApp.Sync.Crypto; using System.Text.Json; namespace LehrerApp.Desktop.Services; internal class AiSettingsConfig { public bool Enabled { get; set; } public string Username { get; set; } = ""; public string? EncryptedToken { get; set; } } /// /// Einstellungen für die KI-gestützte Planungsunterstützung (TODO 4.5.9). Liegt in /// LehrerApp.Desktop statt LehrerApp.Core, weil die Token-Verschlüsselung /// aus LehrerApp.Sync nutzt — Core bleibt bewusst frei von Abhängigkeiten außerhalb von .NET /// selbst (siehe CLAUDE.md), Sync hängt von Core ab, nicht umgekehrt. /// /// Das Passwort wird nie persistiert, nur das nach erfolgreichem Login vom Backend ausgestellte /// Bearer-Token — und auch das nur verschlüsselt (AES-256-GCM über SyncCrypto, gleicher /// Mechanismus wie beim Sync-Schlüssel). Der Schlüssel selbst liegt dateirechte-geschützt /// (chmod 600 unter Unix) neben der Einstellungsdatei — kein Betriebssystem-Schlüsselbund, aber /// deutlich besser als die bisherige Klartext-Ablage der Sync-Server-URL. /// public class AiSettingsService { private readonly string _configPath; private readonly string _keyPath; private readonly byte[] _tokenKey; private AiSettingsConfig _config; public bool Enabled => _config.Enabled; public string Username => _config.Username; public bool IsLoggedIn => _config.EncryptedToken is not null; public AiSettingsService(string appDataPath) { _configPath = Path.Combine(appDataPath, "ai-settings.json"); _keyPath = Path.Combine(appDataPath, "ai-token.key"); _tokenKey = SyncCrypto.LoadKey(_keyPath) ?? GenerateAndSaveKey(); _config = Load(); } public void SetEnabled(bool enabled) { _config.Enabled = enabled; Save(); } public void SetCredentialsAndToken(string username, string token) { _config.Username = username; _config.EncryptedToken = SyncCrypto.EncryptObject(token, _tokenKey); Save(); } public string? GetToken() => _config.EncryptedToken is null ? null : SyncCrypto.DecryptObject(_config.EncryptedToken, _tokenKey); public void Logout() { _config.EncryptedToken = null; Save(); } private byte[] GenerateAndSaveKey() { var key = SyncCrypto.GenerateKey(); SyncCrypto.SaveKey(key, _keyPath); return key; } private void Save() => File.WriteAllText(_configPath, JsonSerializer.Serialize(_config)); private AiSettingsConfig Load() { try { if (File.Exists(_configPath)) return JsonSerializer.Deserialize(File.ReadAllText(_configPath)) ?? new AiSettingsConfig(); } catch { /* beschädigte Konfiguration -> Standardwert */ } return new AiSettingsConfig(); } }