using System.Security.Cryptography; using System.Text; using System.Text.Json; namespace CareFix.Agent; /// /// Agent settings in %ProgramData%\CareFix\agent.json. Secrets are encrypted with Windows DPAPI (machine scope), /// so the file is useless if copied to another computer. /// public sealed class AgentConfig { public string ServerUrl { get; set; } = ""; public string HospitalCode { get; set; } = ""; public string AgentKeyProtected { get; set; } = ""; public string SqlServer { get; set; } = "localhost"; public int SqlPort { get; set; } = 1433; public string Database { get; set; } = ""; public string ReadUser { get; set; } = "carefix_ro"; public string ReadPasswordProtected { get; set; } = ""; public string WriteUser { get; set; } = "carefix_rw"; public string WritePasswordProtected { get; set; } = ""; public bool Encrypt { get; set; } = true; public bool TrustServerCertificate { get; set; } = true; public int MaxRowsPerSelect { get; set; } = 200; public int SelectTimeoutSeconds { get; set; } = 30; public int LockTimeoutMs { get; set; } = 5000; public int MaxParallelJobs { get; set; } = 3; public bool AllowInsecureHttp { get; set; } public static string Folder => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "CareFix"); public static string FilePath => Path.Combine(Folder, "agent.json"); private static readonly byte[] Entropy = Encoding.UTF8.GetBytes("CareFix.Agent.v1"); private static readonly JsonSerializerOptions Json = new() { WriteIndented = true }; public static string Protect(string plain) => Convert.ToBase64String(ProtectedData.Protect(Encoding.UTF8.GetBytes(plain), Entropy, DataProtectionScope.LocalMachine)); public static string Unprotect(string protectedValue) => Encoding.UTF8.GetString(ProtectedData.Unprotect(Convert.FromBase64String(protectedValue), Entropy, DataProtectionScope.LocalMachine)); public string AgentKey => Unprotect(AgentKeyProtected); public string ReadPassword => Unprotect(ReadPasswordProtected); public string WritePassword => Unprotect(WritePasswordProtected); public static AgentConfig Load() { if (!File.Exists(FilePath)) throw new InvalidOperationException($"Agent is not configured. Run: CareFix.Agent.exe configure (expected {FilePath})"); var c = JsonSerializer.Deserialize(File.ReadAllText(FilePath)) ?? throw new InvalidOperationException("agent.json is empty."); c.Validate(); return c; } public void Save() { Validate(); Directory.CreateDirectory(Folder); File.WriteAllText(FilePath, JsonSerializer.Serialize(this, Json)); } public void Validate() { if (!Uri.TryCreate(ServerUrl, UriKind.Absolute, out var u)) throw new InvalidOperationException("ServerUrl is not a valid URL."); if (u.Scheme != Uri.UriSchemeHttps && !AllowInsecureHttp) throw new InvalidOperationException("ServerUrl must use https://."); if (string.IsNullOrWhiteSpace(HospitalCode) || string.IsNullOrWhiteSpace(Database)) throw new InvalidOperationException("HospitalCode and Database are required."); if (MaxRowsPerSelect is < 1 or > 200) throw new InvalidOperationException("MaxRowsPerSelect must be 1 to 200."); if (SelectTimeoutSeconds is < 5 or > 60) throw new InvalidOperationException("SelectTimeoutSeconds must be 5 to 60."); if (MaxParallelJobs is < 1 or > 8) throw new InvalidOperationException("MaxParallelJobs must be 1 to 8."); } }