fix(transport): carry Transport + OAuth2 authority/scope on SmtpConfigDto (OAuth2 fields were silently dropped)

This commit is contained in:
Joseph Doherty
2026-08-10 06:43:43 -04:00
parent 2f217f5742
commit 8df15b34b8
6 changed files with 159 additions and 2 deletions
@@ -285,6 +285,12 @@ public sealed record NotificationRecipientDto(
// WhenWritingNull policy keeps it out of email-only bundles' JSON.
string? PhoneNumber = null);
/// <summary>
/// An email delivery configuration (the <c>SmtpConfiguration</c> entity). Every
/// non-sensitive field is carried directly and only the credential (SMTP password
/// or OAuth2 client secret) rides inside <see cref="Secrets"/>, so a future
/// "share without secrets" export can drop it as a unit.
/// </summary>
public sealed record SmtpConfigDto(
string Host,
int Port,
@@ -295,7 +301,17 @@ public sealed record SmtpConfigDto(
int MaxConcurrentConnections,
int MaxRetries,
TimeSpan RetryDelay,
SecretsBlock? Secrets);
SecretsBlock? Secrets,
// OAuth2 token-endpoint authority and scope: endpoint URLs, not secrets, so
// they belong on the public DTO rather than in the SecretsBlock. Trailing +
// nullable so a bundle written before these were carried deserializes them as
// null (= provider defaults); WhenWritingNull keeps them out of the JSON of
// configurations that don't set them.
string? OAuth2Authority = null,
string? OAuth2Scope = null,
// Delivery transport ("Smtp" or "Ews"); null means Smtp. Trailing + nullable
// for the same additive-evolution reason — no bundleFormatVersion bump.
string? Transport = null);
/// <summary>
/// An SMS provider configuration (the <c>SmsConfiguration</c> entity). Mirrors
@@ -186,7 +186,12 @@ public sealed class EntitySerializer
MaxConcurrentConnections: smtp.MaxConcurrentConnections,
MaxRetries: smtp.MaxRetries,
RetryDelay: smtp.RetryDelay,
Secrets: secrets);
Secrets: secrets,
// Non-sensitive delivery metadata: the OAuth2 endpoint pair and
// the Smtp/Ews transport selector travel as plain DTO fields.
OAuth2Authority: smtp.OAuth2Authority,
OAuth2Scope: smtp.OAuth2Scope,
Transport: smtp.Transport);
}).ToList(),
// Inbound API keys are not transported between environments:
// the bundle carries API methods only. ApiMethod.ApprovedApiKeyIds is also
@@ -486,6 +491,11 @@ public sealed class EntitySerializer
MaxConcurrentConnections = dto.MaxConcurrentConnections,
MaxRetries = dto.MaxRetries,
RetryDelay = dto.RetryDelay,
// Null on a bundle written before these fields were carried, which
// is exactly the entity's "use the provider defaults / Smtp" state.
OAuth2Authority = dto.OAuth2Authority,
OAuth2Scope = dto.OAuth2Scope,
Transport = dto.Transport,
})
.ToList();