fix(sms): code-review fixes — Admin-gate provider-config updates, guard secret-clear/data-loss, type-aware UI
Findings from the per-module code review of the SMS feature (code-reviews/):
- ManagementService (High): UpdateSmsConfig + UpdateSmtpConfig were Designer-gated
while both /notifications/{sms,smtp} pages enforce RequireAdmin — a Designer
blocked in the UI could still rotate a production credential via CLI. Moved both
to the Administrator arm so the actor gate matches the UI.
- ManagementService (Medium): UpdateSmsConfig treated --auth-token "" as a value,
silently clearing the stored Twilio token. Guard on IsNullOrWhiteSpace so empty ==
omitted (SMTP Credentials keeps its null-only guard — empty is valid for no-auth).
- CentralUI (Medium): NotificationLists recipient badge rendered Name <Email>
unconditionally, showing "Name <>" for SMS lists. Now type-aware (phone for SMS).
- ConfigurationDatabase (Medium): AddSmsNotifications.Down() backfilled NULL emails
to '' — silent data loss for SMS-only recipients. Added a pre-drop guard that
refuses rollback while such rows exist.
- NotificationOutbox (Low): SMS body truncation could split a surrogate pair at the
cap boundary; back off one code unit to stay well-formed.
- Commons (Low): NotificationRecipient public ctor name-guard now matches the
ForEmail factory (IsNullOrWhiteSpace). Documented SmsConfiguration.MaxRetries/
RetryDelay as RESERVED (dispatcher reuses the shared SMTP-derived retry policy).
This commit is contained in:
@@ -174,7 +174,14 @@ public class ManagementActor : ReceiveActor
|
||||
// should use the REST endpoint; this command is retained for
|
||||
// backward compatibility with the CentralUI Configuration Audit
|
||||
// Log page (Management-018).
|
||||
or QueryAuditLogCommand => Roles.Administrator,
|
||||
or QueryAuditLogCommand
|
||||
// MgmtSvc-021: SMTP/SMS provider-config updates rotate stored secrets
|
||||
// (SMTP Credentials, Twilio AuthToken). Both the /notifications/smtp and
|
||||
// /notifications/sms Central UI pages enforce RequireAdmin, so the actor
|
||||
// gate must match — otherwise a Designer blocked in the UI could still
|
||||
// rotate a production credential via the CLI/Management API.
|
||||
or UpdateSmtpConfigCommand
|
||||
or UpdateSmsConfigCommand => Roles.Administrator,
|
||||
|
||||
// Designer operations
|
||||
CreateAreaCommand or DeleteAreaCommand
|
||||
@@ -186,8 +193,6 @@ public class ManagementActor : ReceiveActor
|
||||
or DeleteExternalSystemMethodCommand
|
||||
or CreateNotificationListCommand or UpdateNotificationListCommand
|
||||
or DeleteNotificationListCommand
|
||||
or UpdateSmtpConfigCommand
|
||||
or UpdateSmsConfigCommand
|
||||
or CreateDataConnectionCommand or UpdateDataConnectionCommand
|
||||
or DeleteDataConnectionCommand or MoveDataConnectionCommand
|
||||
or AddTemplateAttributeCommand or UpdateTemplateAttributeCommand or DeleteTemplateAttributeCommand
|
||||
@@ -1838,7 +1843,7 @@ public class ManagementActor : ReceiveActor
|
||||
var repo = sp.GetRequiredService<INotificationRepository>();
|
||||
var configs = await repo.GetAllSmsConfigurationsAsync();
|
||||
// MgmtSvc-020: project away the AuthToken field — read access to this
|
||||
// list is broader than the Designer-gated UpdateSmsConfig path that owns
|
||||
// list is broader than the Admin-only UpdateSmsConfig path that owns
|
||||
// the secret.
|
||||
return configs.Select(SmsConfigPublicShape).ToList();
|
||||
}
|
||||
@@ -1856,7 +1861,13 @@ public class ManagementActor : ReceiveActor
|
||||
// existing values intact (non-breaking for callers that do not send them,
|
||||
// and so the secret AuthToken survives a config edit that does not rotate it).
|
||||
if (cmd.ApiBaseUrl is not null) config.ApiBaseUrl = cmd.ApiBaseUrl;
|
||||
if (cmd.AuthToken is not null) config.AuthToken = cmd.AuthToken;
|
||||
// MgmtSvc-021: treat an empty/whitespace AuthToken as "omitted", not "clear".
|
||||
// A Twilio Auth Token is always required, so an empty value is never a valid
|
||||
// rotation — guarding against IsNullOrEmpty (not just null) stops an accidental
|
||||
// `--auth-token ""` from silently wiping the stored secret and 401-ing every
|
||||
// subsequent send. (Contrast SmtpConfiguration.Credentials, which MAY be cleared
|
||||
// when switching to anonymous SMTP, so that path keeps its null-only guard.)
|
||||
if (!string.IsNullOrWhiteSpace(cmd.AuthToken)) config.AuthToken = cmd.AuthToken;
|
||||
await repo.UpdateSmsConfigurationAsync(config);
|
||||
await repo.SaveChangesAsync();
|
||||
// MgmtSvc-020: audit the credential-free shape — the AuthToken secret is
|
||||
|
||||
Reference in New Issue
Block a user