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.");
}
}