diff --git a/src/Core/Platform/Mail/HandlebarsMailService.cs b/src/Core/Platform/Mail/HandlebarsMailService.cs index 7e97f3648212..903379fa74a2 100644 --- a/src/Core/Platform/Mail/HandlebarsMailService.cs +++ b/src/Core/Platform/Mail/HandlebarsMailService.cs @@ -1194,7 +1194,7 @@ public async Task SendEmergencyAccessInviteEmailAsync(EmergencyAccess emergencyA var message = CreateDefaultMessage($"Emergency Access Contact Invite", emergencyAccess.Email); var model = new EmergencyAccessInvitedViewModel { - Name = CoreHelpers.SanitizeForEmail(name), + Name = CoreHelpers.SanitizeForEmail(name, false), Email = WebUtility.UrlEncode(emergencyAccess.Email), Id = emergencyAccess.Id.ToString(), Token = WebUtility.UrlEncode(token), @@ -1225,7 +1225,7 @@ public async Task SendEmergencyAccessConfirmedEmailAsync(string grantorName, str var message = CreateDefaultMessage($"You Have Been Confirmed as Emergency Access Contact", email); var model = new EmergencyAccessConfirmedViewModel { - Name = CoreHelpers.SanitizeForEmail(grantorName), + Name = CoreHelpers.SanitizeForEmail(grantorName, false), WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash, SiteName = _globalSettings.SiteName }; @@ -1242,7 +1242,7 @@ public async Task SendEmergencyAccessRecoveryInitiated(EmergencyAccess emergency var model = new EmergencyAccessRecoveryViewModel { - Name = CoreHelpers.SanitizeForEmail(initiatingName), + Name = CoreHelpers.SanitizeForEmail(initiatingName, false), Action = emergencyAccess.Type.ToString(), DaysLeft = emergencyAccess.WaitTimeDays - Convert.ToInt32((remainingTime).TotalDays), }; @@ -1256,7 +1256,7 @@ public async Task SendEmergencyAccessRecoveryApproved(EmergencyAccess emergencyA var message = CreateDefaultMessage("Emergency Access Approved", email); var model = new EmergencyAccessApprovedViewModel { - Name = CoreHelpers.SanitizeForEmail(approvingName), + Name = CoreHelpers.SanitizeForEmail(approvingName, false), }; await AddMessageContentAsync(message, "Auth.EmergencyAccessApproved", model); message.Category = "EmergencyAccessApproved"; @@ -1268,7 +1268,7 @@ public async Task SendEmergencyAccessRecoveryRejected(EmergencyAccess emergencyA var message = CreateDefaultMessage("Emergency Access Rejected", email); var model = new EmergencyAccessRejectedViewModel { - Name = CoreHelpers.SanitizeForEmail(rejectingName), + Name = CoreHelpers.SanitizeForEmail(rejectingName, false), }; await AddMessageContentAsync(message, "Auth.EmergencyAccessRejected", model); message.Category = "EmergencyAccessRejected"; @@ -1283,7 +1283,7 @@ public async Task SendEmergencyAccessRecoveryReminder(EmergencyAccess emergencyA var model = new EmergencyAccessRecoveryViewModel { - Name = CoreHelpers.SanitizeForEmail(initiatingName), + Name = CoreHelpers.SanitizeForEmail(initiatingName, false), Action = emergencyAccess.Type.ToString(), DaysLeft = emergencyAccess.WaitTimeDays - Convert.ToInt32((remainingTime).TotalDays), }; @@ -1297,7 +1297,7 @@ public async Task SendEmergencyAccessRecoveryTimedOut(EmergencyAccess emergencyA var message = CreateDefaultMessage("Emergency Access Granted", email); var model = new EmergencyAccessRecoveryTimedOutViewModel { - Name = CoreHelpers.SanitizeForEmail(initiatingName), + Name = CoreHelpers.SanitizeForEmail(initiatingName, false), Action = emergencyAccess.Type.ToString(), }; await AddMessageContentAsync(message, "Auth.EmergencyAccessRecoveryTimedOut", model); diff --git a/test/Core.Test/Services/HandlebarsMailServiceTests.cs b/test/Core.Test/Services/HandlebarsMailServiceTests.cs index 5dae4f5d17d4..7bdc0b9b013f 100644 --- a/test/Core.Test/Services/HandlebarsMailServiceTests.cs +++ b/test/Core.Test/Services/HandlebarsMailServiceTests.cs @@ -380,4 +380,42 @@ public void GetCloudVaultSubscriptionUrl_ResolvesPerRegion(string cloudRegion, s // Assert Assert.StartsWith(expectedVaultBase, result); } + + [Theory] + [InlineData(nameof(HandlebarsMailService.SendEmergencyAccessConfirmedEmailAsync))] + [InlineData(nameof(HandlebarsMailService.SendEmergencyAccessRecoveryApproved))] + [InlineData(nameof(HandlebarsMailService.SendEmergencyAccessRecoveryReminder))] + public async Task EmergencyAccessEmails_ShouldEncodeNamesOnlyOnce(string methodName) + { + // Arrange + const string name = "Alice & Bob"; + const string email = "recipient@example.com"; + var emergencyAccess = new EmergencyAccess + { + Type = EmergencyAccessType.Takeover, + RecoveryInitiatedDate = DateTime.UtcNow.AddHours(-1), + WaitTimeDays = 2, + }; + + // Act + switch (methodName) + { + case nameof(HandlebarsMailService.SendEmergencyAccessConfirmedEmailAsync): + await _sut.SendEmergencyAccessConfirmedEmailAsync(name, email); + break; + case nameof(HandlebarsMailService.SendEmergencyAccessRecoveryApproved): + await _sut.SendEmergencyAccessRecoveryApproved(emergencyAccess, name, email); + break; + case nameof(HandlebarsMailService.SendEmergencyAccessRecoveryReminder): + await _sut.SendEmergencyAccessRecoveryReminder(emergencyAccess, name, email); + break; + default: + throw new ArgumentOutOfRangeException(nameof(methodName), methodName, null); + } + + // Assert + await _mailDeliveryService.Received(1).SendEmailAsync(Arg.Is(m => + m.HtmlContent.Contains("Alice & Bob") && + !m.HtmlContent.Contains("Alice & Bob"))); + } }