using System.Text.Json; using System.Text.Json.Serialization; namespace MeterVault.Infrastructure.Ingestion; /// /// The parsed JSON for an MQTT broker. Credentials /// are never held here as plaintext (SDD §6.4): / /// name environment variables resolved at runtime, or / /// hold them encrypted under the app's data-protection key ring. /// public sealed record EndpointConfig { private static readonly JsonSerializerOptions Options = new() { PropertyNameCaseInsensitive = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, }; public string Host { get; init; } = "localhost"; public int Port { get; init; } = 1883; public bool Tls { get; init; } /// Extra topic filters to subscribe (beyond the sources' own topics), e.g. tele/+/SENSOR. public IReadOnlyList ExtraTopics { get; init; } = []; public string? UsernameEnv { get; init; } public string? PasswordEnv { get; init; } /// /// Username entered directly in the admin UI. Held as-is: §6.4 covers tokens and passwords, and /// encrypting a username would only blank the field on every edit for no security gain. /// public string? Username { get; init; } /// Password encrypted by (entered in the admin UI). public string? PasswordEnc { get; init; } public static EndpointConfig Parse(string? json) { if (string.IsNullOrWhiteSpace(json)) { return new EndpointConfig(); } try { return JsonSerializer.Deserialize(json, Options) ?? new EndpointConfig(); } catch (JsonException) { return new EndpointConfig(); } } public string ToJson() => JsonSerializer.Serialize(this, Options); public string? ResolveUsername(Security.SecretProtector? protector = null) => !string.IsNullOrWhiteSpace(Username) ? Username : EndpointSecret.Resolve(null, UsernameEnv, protector); public string? ResolvePassword(Security.SecretProtector? protector = null) => EndpointSecret.Resolve(PasswordEnc, PasswordEnv, protector); }