diff --git a/BTCPayServer/Controllers/UIManageController.cs b/BTCPayServer/Controllers/UIManageController.cs index 01f70ea8e..b457fcf1c 100644 --- a/BTCPayServer/Controllers/UIManageController.cs +++ b/BTCPayServer/Controllers/UIManageController.cs @@ -200,7 +200,7 @@ namespace BTCPayServer.Controllers { TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["Error updating profile"].Value; } - + return RedirectToAction(nameof(Index)); } @@ -220,7 +220,7 @@ namespace BTCPayServer.Controllers } var callbackUrl = await _callbackGenerator.ForEmailConfirmation(user, Request); - (await _EmailSenderFactory.GetEmailSender()).SendEmailConfirmation(user.GetMailboxAddress(), callbackUrl); + _eventAggregator.Publish(new UserEvent.ConfirmationEmailRequested(user, callbackUrl)); TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Verification email sent. Please check your email."].Value; return RedirectToAction(nameof(Index)); } diff --git a/BTCPayServer/Controllers/UIServerController.Users.cs b/BTCPayServer/Controllers/UIServerController.Users.cs index a54de38eb..538c50437 100644 --- a/BTCPayServer/Controllers/UIServerController.Users.cs +++ b/BTCPayServer/Controllers/UIServerController.Users.cs @@ -405,9 +405,7 @@ namespace BTCPayServer.Controllers } var callbackUrl = await _callbackGenerator.ForEmailConfirmation(user, Request); - - (await _emailSenderFactory.GetEmailSender()).SendEmailConfirmation(user.GetMailboxAddress(), callbackUrl); - + _eventAggregator.Publish(new UserEvent.ConfirmationEmailRequested(user, callbackUrl)); TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Verification email sent"].Value; return RedirectToAction(nameof(ListUsers)); } diff --git a/BTCPayServer/Events/UserEvent.cs b/BTCPayServer/Events/UserEvent.cs index 6e5554663..71d019331 100644 --- a/BTCPayServer/Events/UserEvent.cs +++ b/BTCPayServer/Events/UserEvent.cs @@ -24,6 +24,12 @@ public class UserEvent(ApplicationUser user) { public string ResetLink { get; } = resetLink; } + + public class ConfirmationEmailRequested(ApplicationUser user, string confirmLink) : UserEvent(user) + { + public string ConfirmLink { get; } = confirmLink; + } + public class Registered(ApplicationUser user, string approvalLink, string confirmationEmail) : UserEvent(user) { public string ApprovalLink { get; } = approvalLink; diff --git a/BTCPayServer/Extensions/EmailSenderExtensions.cs b/BTCPayServer/Extensions/EmailSenderExtensions.cs index caa4e9eb6..b8f43cbc6 100644 --- a/BTCPayServer/Extensions/EmailSenderExtensions.cs +++ b/BTCPayServer/Extensions/EmailSenderExtensions.cs @@ -9,56 +9,16 @@ namespace BTCPayServer.Services { private static string BODY_STYLE = "font-family: Open Sans, Helvetica Neue,Arial,sans-serif; font-color: #292929;"; private static string HEADER_HTML = "

BTCPay Server


"; - private static string BUTTON_HTML = "{button_description}"; - - private static string CallToAction(string actionName, string actionLink) - { - var button = $"{BUTTON_HTML}".Replace("{button_description}", actionName, System.StringComparison.InvariantCulture); - return button.Replace("{button_link}", HtmlEncoder.Default.Encode(actionLink), System.StringComparison.InvariantCulture); - } private static string CreateEmailBody(string body) { return $"{HEADER_HTML}{body}"; } - public static void SendEmailConfirmation(this IEmailSender emailSender, MailboxAddress address, string link) - { - emailSender.SendEmail(address, "Confirm your email", CreateEmailBody( - $"Please confirm your account.

{CallToAction("Confirm Email", link)}")); - } - - public static void SendApprovalConfirmation(this IEmailSender emailSender, MailboxAddress address, string link) - { - emailSender.SendEmail(address, "Your account has been approved", CreateEmailBody( - $"Your account has been approved and you can now login here.")); - } - - public static void SendInvitation(this IEmailSender emailSender, MailboxAddress address, string link) - { - emailSender.SendEmail(address, "Invitation", CreateEmailBody( - $"

Please complete your account setup by clicking this link.

You can also use the BTCPay Server app and scan this QR code when connecting:

{GetQrCodeImg(link)}")); - } - - public static void SendNewUserInfo(this IEmailSender emailSender, MailboxAddress address, string newUserInfo, string link) - { - emailSender.SendEmail(address, newUserInfo, CreateEmailBody( - $"{newUserInfo}. You can verify and approve the account here: User details")); - } - public static void SendUserInviteAcceptedInfo(this IEmailSender emailSender, MailboxAddress address, string userInfo, string link) { emailSender.SendEmail(address, userInfo, CreateEmailBody( $"{userInfo}. You can view the store users here: Store users")); } - - private static string GetQrCodeImg(string data) - { - using var qrGenerator = new QRCodeGenerator(); - using var qrCodeData = qrGenerator.CreateQrCode(data, QRCodeGenerator.ECCLevel.Q); - using var qrCode = new Base64QRCode(qrCodeData); - var base64 = qrCode.GetGraphic(20); - return $"{data}"; - } } } diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index 1d000a477..0b8bc05ab 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -436,7 +436,6 @@ o.GetRequiredService>().ToDictionary(o => o.P services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(s => s.GetRequiredService()); diff --git a/BTCPayServer/Plugins/Emails/EmailsPlugin.cs b/BTCPayServer/Plugins/Emails/EmailsPlugin.cs index 73537c7ff..1c9ab0402 100644 --- a/BTCPayServer/Plugins/Emails/EmailsPlugin.cs +++ b/BTCPayServer/Plugins/Emails/EmailsPlugin.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using BTCPayServer.Abstractions.Models; +using BTCPayServer.Plugins.Emails.HostedServices; using BTCPayServer.Plugins.Emails.Views; using BTCPayServer.Plugins.Webhooks; using BTCPayServer.Services; @@ -19,6 +20,7 @@ public class EmailsPlugin : BaseBTCPayServerPlugin { services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); RegisterServerEmailTriggers(services); } private static string BODY_STYLE = "font-family: Open Sans, Helvetica Neue,Arial,sans-serif; font-color: #292929;"; @@ -50,8 +52,67 @@ public class EmailsPlugin : BaseBTCPayServerPlugin Description = "Password Reset Requested", }; vms.Add(vm); + + vm = new EmailTriggerViewModel() + { + Trigger = ServerMailTriggers.EmailConfirm, + RecipientExample = "{User.MailboxAddress}", + SubjectExample = "Confirm your email", + BodyExample = CreateEmailBody($"Please confirm your account.

{CallToAction("Confirm Email", "{ConfirmLink}")}"), + PlaceHolders = new() + { + new ("{ConfirmLink}", "The link used to confirm the user's email address") + }, + Description = "Confirm new user's email", + }; + vms.Add(vm); + + vm = new EmailTriggerViewModel() + { + Trigger = ServerMailTriggers.InvitePending, + RecipientExample = "{User.MailboxAddress}", + SubjectExample = "Invitation to join {Branding.ServerName}", + BodyExample = CreateEmailBody($"

Please complete your account setup by clicking this link.

You can also use the BTCPay Server app and scan this QR code when connecting:

{{InvitationLinkQR}}"), + PlaceHolders = new() + { + new ("{InvitationLink}", "The link where the invited user can set up their account"), + new ("{InvitationLinkQR}", "The QR code representation of the invitation link") + }, + Description = "User invitation email", + }; + vms.Add(vm); + + vm = new EmailTriggerViewModel() + { + Trigger = ServerMailTriggers.ApprovalConfirmed, + RecipientExample = "{User.MailboxAddress}", + SubjectExample = "Your account has been approved", + BodyExample = CreateEmailBody($"Your account has been approved and you can now.

{CallToAction("Login here", "{LoginLink}")}"), + PlaceHolders = new() + { + new ("{LoginLink}", "The link that the user can use to login"), + }, + Description = "User account approved", + }; + vms.Add(vm); + + vm = new EmailTriggerViewModel() + { + Trigger = ServerMailTriggers.ApprovalRequest, + RecipientExample = "{Admin.MailboxAddresses}", + SubjectExample = "Approval request to access the server for {User.Email}", + BodyExample = CreateEmailBody($"A new user ({{User.MailboxAddress}}), is awaiting approval to access the server.

{CallToAction("Approve", "{ApprovalLink}")}"), + PlaceHolders = new() + { + new ("{ApprovalLink}", "The link that the admin needs to use to approve the user"), + }, + Description = "Approval request to administrators", + }; + vms.Add(vm); + var commonPlaceholders = new List() { + new("{Admins.MailboxAddresses}", "The email addresses of the admins separated by a comma (eg. ,)"), new("{User.Name}", "The name of the user (eg. John Doe)"), new("{User.Email}", "The email of the user (eg. john.doe@example.com)"), new("{User.MailboxAddress}", "The formatted mailbox address to use when sending an email. (eg. \"John Doe\" )"), diff --git a/BTCPayServer/Plugins/Emails/EmailRuleProcessorSender.cs b/BTCPayServer/Plugins/Emails/HostedServices/EmailRuleProcessorSender.cs similarity index 75% rename from BTCPayServer/Plugins/Emails/EmailRuleProcessorSender.cs rename to BTCPayServer/Plugins/Emails/HostedServices/EmailRuleProcessorSender.cs index 68dc707f7..18333964b 100644 --- a/BTCPayServer/Plugins/Emails/EmailRuleProcessorSender.cs +++ b/BTCPayServer/Plugins/Emails/HostedServices/EmailRuleProcessorSender.cs @@ -1,6 +1,8 @@ #nullable enable +using System; using System.Collections.Generic; using System.Linq; +using System.Net.Mail; using System.Threading; using System.Threading.Tasks; using BTCPayServer.Data; @@ -10,7 +12,7 @@ using Microsoft.Extensions.Logging; using MimeKit; using Newtonsoft.Json.Linq; -namespace BTCPayServer.Plugins.Emails; +namespace BTCPayServer.Plugins.Emails.HostedServices; public interface ITriggerOwner { @@ -65,13 +67,29 @@ public class StoreEmailRuleProcessorSender( var subject = new TextTemplate(actionableRule.Subject ?? ""); matchedContext.Recipients.AddRange( actionableRule.To - .Select(o => + .SelectMany(o => { - if (!MailboxAddressValidator.TryParse(o, out var oo)) + if (MailboxAddressValidator.TryParse(o, out var oo)) + return new[] { oo }; + + var emails = new TextTemplate(o).Render(triggEvent.Model); + MailAddressCollection mailCollection = new(); + try { - MailboxAddressValidator.TryParse(new TextTemplate(o).Render(triggEvent.Model), out oo); + mailCollection.Add(emails); } - return oo; + catch (FormatException) + { + return Array.Empty(); + } + + return mailCollection.Select(a => + { + MailboxAddressValidator.TryParse(a.ToString(), out oo); + return oo; + }) + .Where(a => a != null) + .ToArray(); }) .Where(o => o != null)!); diff --git a/BTCPayServer/HostedServices/UserEventHostedService.cs b/BTCPayServer/Plugins/Emails/HostedServices/UserEventTriggerHostedService.cs similarity index 61% rename from BTCPayServer/HostedServices/UserEventHostedService.cs rename to BTCPayServer/Plugins/Emails/HostedServices/UserEventTriggerHostedService.cs index 178663e87..eb9a9e9f4 100644 --- a/BTCPayServer/HostedServices/UserEventHostedService.cs +++ b/BTCPayServer/Plugins/Emails/HostedServices/UserEventTriggerHostedService.cs @@ -1,10 +1,12 @@ +#nullable enable +using System.Linq; using System.Text.Encodings.Web; using System.Threading; using System.Threading.Tasks; using BTCPayServer.Data; using BTCPayServer.Events; +using BTCPayServer.HostedServices; using BTCPayServer.Logging; -using BTCPayServer.Plugins.Emails; using BTCPayServer.Services; using BTCPayServer.Services.Mails; using BTCPayServer.Services.Notifications; @@ -13,13 +15,13 @@ using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; +using QRCoder; -namespace BTCPayServer.HostedServices; +namespace BTCPayServer.Plugins.Emails.HostedServices; public class UserEventHostedService( EventAggregator eventAggregator, UserManager userManager, - CallbackGenerator callbackGenerator, ISettingsAccessor serverSettings, EmailSenderFactory emailSenderFactory, NotificationSender notificationSender, @@ -28,62 +30,64 @@ public class UserEventHostedService( : EventHostedServiceBase(eventAggregator, logs) { public UserManager UserManager { get; } = userManager; - public CallbackGenerator CallbackGenerator { get; } = callbackGenerator; protected override void SubscribeToEvents() { - Subscribe(); - Subscribe(); - Subscribe(); - Subscribe(); - Subscribe(); - Subscribe(); + SubscribeAny(); + } + + public static string GetQrCodeImg(string data) + { + using var qrGenerator = new QRCodeGenerator(); + using var qrCodeData = qrGenerator.CreateQrCode(data, QRCodeGenerator.ECCLevel.Q); + using var qrCode = new Base64QRCode(qrCodeData); + var base64 = qrCode.GetGraphic(20); + return $"{data}"; } protected override async Task ProcessEvent(object evt, CancellationToken cancellationToken) { - ApplicationUser user = (evt as UserEvent).User; - IEmailSender emailSender; + var user = (evt as UserEvent)?.User; + if (user is null) return; switch (evt) { case UserEvent.Registered ev: - // can be either a self-registration or by invite from another user - var type = await UserManager.IsInRoleAsync(user, Roles.ServerAdmin) ? "admin" : "user"; - var info = ev switch - { - UserEvent.Invited { InvitedByUser: { } invitedBy } => $"invited by {invitedBy.Email}", - UserEvent.Invited => "invited", - _ => "registered" - }; - var requiresApproval = user.RequiresApproval && !user.Approved; - var requiresEmailConfirmation = user.RequiresEmailConfirmation && !user.EmailConfirmed; - - // log registration info - var newUserInfo = $"New {type} {user.Email} {info}"; - Logs.PayServer.LogInformation(newUserInfo); + var requiresApproval = user is { RequiresApproval: true, Approved: false }; + var requiresEmailConfirmation = user is { RequiresEmailConfirmation: true, EmailConfirmed: false }; // send notification if the user does not require email confirmation. // inform admins only about qualified users and not annoy them with bot registrations. if (requiresApproval && !requiresEmailConfirmation) { - await NotifyAdminsAboutUserRequiringApproval(user, ev.ApprovalLink, newUserInfo); + await NotifyAdminsAboutUserRequiringApproval(user, ev.ApprovalLink); } // set callback result and send email to user - emailSender = await emailSenderFactory.GetEmailSender(); if (ev is UserEvent.Invited invited) { if (invited.SendInvitationEmail) - emailSender.SendInvitation(user.GetMailboxAddress(), invited.InvitationLink); + EventAggregator.Publish(await CreateTriggerEvent(ServerMailTriggers.InvitePending, + new JObject() + { + ["InvitationLink"] = HtmlEncoder.Default.Encode(invited.InvitationLink), + ["InvitationLinkQR"] = GetQrCodeImg(invited.InvitationLink) + }, user)); } else if (requiresEmailConfirmation) { - emailSender.SendEmailConfirmation(user.GetMailboxAddress(), ev.ConfirmationEmailLink); + EventAggregator.Publish(new UserEvent.ConfirmationEmailRequested(user, ev.ConfirmationEmailLink)); } break; + case UserEvent.ConfirmationEmailRequested confReq: + EventAggregator.Publish(await CreateTriggerEvent(ServerMailTriggers.EmailConfirm, + new JObject() + { + ["ConfirmLink"] = HtmlEncoder.Default.Encode(confReq.ConfirmLink) + }, user)); + break; case UserEvent.PasswordResetRequested pwResetEvent: - EventAggregator.Publish(CreateTriggerEvent(ServerMailTriggers.PasswordReset, + EventAggregator.Publish(await CreateTriggerEvent(ServerMailTriggers.PasswordReset, new JObject() { ["ResetLink"] = HtmlEncoder.Default.Encode(pwResetEvent.ResetLink) @@ -92,15 +96,16 @@ public class UserEventHostedService( case UserEvent.Approved approvedEvent: if (!user.Approved) break; - emailSender = await emailSenderFactory.GetEmailSender(); - emailSender.SendApprovalConfirmation(user.GetMailboxAddress(), approvedEvent.LoginLink); + EventAggregator.Publish(await CreateTriggerEvent(ServerMailTriggers.ApprovalConfirmed, + new JObject() + { + ["LoginLink"] = approvedEvent.LoginLink + }, user)); + break; - case UserEvent.ConfirmedEmail confirmedEvent: - if (!user.EmailConfirmed) break; - var confirmedUserInfo = $"User {user.Email} confirmed their email address"; - Logs.PayServer.LogInformation(confirmedUserInfo); - await NotifyAdminsAboutUserRequiringApproval(user, confirmedEvent.ApprovalLink, confirmedUserInfo); + case UserEvent.ConfirmedEmail confirmedEvent when user is { RequiresApproval: true, Approved: false, EmailConfirmed: true }: + await NotifyAdminsAboutUserRequiringApproval(user, confirmedEvent.ApprovalLink); break; case UserEvent.InviteAccepted inviteAcceptedEvent: @@ -110,8 +115,24 @@ public class UserEventHostedService( } } - private TriggerEvent CreateTriggerEvent(string trigger, JObject model, ApplicationUser user) + private async Task NotifyAdminsAboutUserRequiringApproval(ApplicationUser user, string approvalLink) { + await notificationSender.SendNotification(new AdminScope(), new NewUserRequiresApprovalNotification(user)); + EventAggregator.Publish(await CreateTriggerEvent(ServerMailTriggers.ApprovalRequest, + new JObject() + { + ["ApprovalLink"] = approvalLink + }, user)); + } + + private async Task CreateTriggerEvent(string trigger, JObject model, ApplicationUser user) + { + var admins = await UserManager.GetUsersInRoleAsync(Roles.ServerAdmin); + var adminMailboxes = string.Join(", ", admins.Select(a => a.GetMailboxAddress().ToString()).ToArray()); + model["Admins"] = new JObject() + { + ["MailboxAddresses"] = adminMailboxes, + }; model["User"] = new JObject() { ["Name"] = user.UserName, @@ -126,21 +147,6 @@ public class UserEventHostedService( var evt = new TriggerEvent(null, trigger, model, null); return evt; } - - private async Task NotifyAdminsAboutUserRequiringApproval(ApplicationUser user, string approvalLink, string newUserInfo) - { - if (!user.RequiresApproval || user.Approved) return; - // notification - await notificationSender.SendNotification(new AdminScope(), new NewUserRequiresApprovalNotification(user)); - // email - var admins = await UserManager.GetUsersInRoleAsync(Roles.ServerAdmin); - var emailSender = await emailSenderFactory.GetEmailSender(); - foreach (var admin in admins) - { - emailSender.SendNewUserInfo(admin.GetMailboxAddress(), newUserInfo, approvalLink); - } - } - private async Task NotifyAboutUserAcceptingInvite(ApplicationUser user, string storeUsersLink) { var stores = await storeRepository.GetStoresByUserId(user.Id); diff --git a/BTCPayServer/Plugins/Emails/ServerMailTriggers.cs b/BTCPayServer/Plugins/Emails/ServerMailTriggers.cs index c5ee029b3..50a16231f 100644 --- a/BTCPayServer/Plugins/Emails/ServerMailTriggers.cs +++ b/BTCPayServer/Plugins/Emails/ServerMailTriggers.cs @@ -8,4 +8,5 @@ public class ServerMailTriggers public const string ApprovalConfirmed = "SRV-ApprovalConfirmed"; public const string ApprovalPending = "SRV-ApprovalPending"; public const string EmailConfirm = "SRV-EmailConfirmation"; + public const string ApprovalRequest = "SRV-ApprovalRequest"; } diff --git a/BTCPayServer/Plugins/Emails/Views/EmailTriggerViewModel.cs b/BTCPayServer/Plugins/Emails/Views/EmailTriggerViewModel.cs index 66a81e672..933cce160 100644 --- a/BTCPayServer/Plugins/Emails/Views/EmailTriggerViewModel.cs +++ b/BTCPayServer/Plugins/Emails/Views/EmailTriggerViewModel.cs @@ -7,11 +7,18 @@ namespace BTCPayServer.Plugins.Emails.Views; /// public class EmailTriggerViewModel { + public class Default + { + public string SubjectExample { get; set; } + public string BodyExample { get; set; } + public bool CanIncludeCustomerEmail { get; set; } + public string RecipientExample { get; set; } + } + public string Trigger { get; set; } + public string Description { get; set; } - public string SubjectExample { get; set; } - public string BodyExample { get; set; } - public bool CanIncludeCustomerEmail { get; set; } + public class PlaceHolder(string name, string description) { @@ -21,5 +28,4 @@ public class EmailTriggerViewModel public List PlaceHolders { get; set; } = new(); public bool ServerTrigger { get; set; } - public string RecipientExample { get; set; } } diff --git a/BTCPayServer/Plugins/Emails/Views/Shared/EmailRulesManage.cshtml b/BTCPayServer/Plugins/Emails/Views/Shared/EmailRulesManage.cshtml index 605c751be..152a2c9c3 100644 --- a/BTCPayServer/Plugins/Emails/Views/Shared/EmailRulesManage.cshtml +++ b/BTCPayServer/Plugins/Emails/Views/Shared/EmailRulesManage.cshtml @@ -152,31 +152,19 @@ const bodyTextarea = document.querySelector('.email-rule-body'); const placeholdersTd = document.querySelector('#placeholders'); - const isEmptyOrDefault = (value, type) => { - const val = value.replace(/<.*?>/gi, '').trim(); - if (!val) return true; - return Object.values(triggersByType).some(t => t[type] === val); - }; - function applyTemplate() { const selectedTrigger = triggerSelect.value; - console.log(selectedTrigger); if (triggersByType[selectedTrigger]) { - if (isEmptyOrDefault(subjectInput.value, 'subjectExample')) { - subjectInput.value = triggersByType[selectedTrigger].subjectExample; - } - if (isEmptyOrDefault(recipientInput.value, 'recipientExample')) { - recipientInput.value = triggersByType[selectedTrigger].recipientExample; - } + subjectInput.value = triggersByType[selectedTrigger].subjectExample; + recipientInput.value = triggersByType[selectedTrigger].recipientExample; var body = triggersByType[selectedTrigger].bodyExample; - if (isEmptyOrDefault(bodyTextarea.value, 'bodyExample')) { - if ($(bodyTextarea).summernote) { - $(bodyTextarea).summernote('reset'); - $(bodyTextarea).summernote('code', body.replace(/\n/g, '
')); - } else { - bodyTextarea.value = body; - } + if ($(bodyTextarea).summernote) { + console.log(body); + $(bodyTextarea).summernote('reset'); + $(bodyTextarea).summernote('code', body.replace(/\n/g, '
')); + } else { + bodyTextarea.value = body; } placeholdersTd.innerHTML = ''; @@ -213,7 +201,10 @@ // Apply template on page load if a trigger is selected if (triggerSelect.value) { - applyTemplate(); + if (@Safe.Json(!isEdit)) + { + applyTemplate(); + } toggleCustomerEmailVisibility(); } }); diff --git a/BTCPayServer/Plugins/Webhooks/HostedServices/WebhookProviderHostedService.cs b/BTCPayServer/Plugins/Webhooks/HostedServices/WebhookProviderHostedService.cs index af6f15df1..6f94b4ba2 100644 --- a/BTCPayServer/Plugins/Webhooks/HostedServices/WebhookProviderHostedService.cs +++ b/BTCPayServer/Plugins/Webhooks/HostedServices/WebhookProviderHostedService.cs @@ -7,6 +7,7 @@ using BTCPayServer.Client.Models; using BTCPayServer.Data; using BTCPayServer.HostedServices; using BTCPayServer.Plugins.Emails; +using BTCPayServer.Plugins.Emails.HostedServices; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using StoreData = BTCPayServer.Data.StoreData; diff --git a/BTCPayServer/Plugins/Webhooks/TriggerProviders/InvoiceTriggerProvider.cs b/BTCPayServer/Plugins/Webhooks/TriggerProviders/InvoiceTriggerProvider.cs index 1bd661dc4..49eee8c4a 100644 --- a/BTCPayServer/Plugins/Webhooks/TriggerProviders/InvoiceTriggerProvider.cs +++ b/BTCPayServer/Plugins/Webhooks/TriggerProviders/InvoiceTriggerProvider.cs @@ -4,6 +4,7 @@ using BTCPayServer.Client.Models; using BTCPayServer.Controllers.Greenfield; using BTCPayServer.Events; using BTCPayServer.Plugins.Emails; +using BTCPayServer.Plugins.Emails.HostedServices; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using Newtonsoft.Json.Linq; diff --git a/BTCPayServer/Plugins/Webhooks/TriggerProviders/PaymentRequestTriggerProvider.cs b/BTCPayServer/Plugins/Webhooks/TriggerProviders/PaymentRequestTriggerProvider.cs index bf6431c04..39aeb3ab2 100644 --- a/BTCPayServer/Plugins/Webhooks/TriggerProviders/PaymentRequestTriggerProvider.cs +++ b/BTCPayServer/Plugins/Webhooks/TriggerProviders/PaymentRequestTriggerProvider.cs @@ -6,6 +6,7 @@ using BTCPayServer.Client.Models; using BTCPayServer.Data; using BTCPayServer.HostedServices; using BTCPayServer.Plugins.Emails; +using BTCPayServer.Plugins.Emails.HostedServices; using BTCPayServer.Services.PaymentRequests; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; diff --git a/BTCPayServer/Plugins/Webhooks/WebhookTriggerProvider.cs b/BTCPayServer/Plugins/Webhooks/WebhookTriggerProvider.cs index db132c128..559f290d4 100644 --- a/BTCPayServer/Plugins/Webhooks/WebhookTriggerProvider.cs +++ b/BTCPayServer/Plugins/Webhooks/WebhookTriggerProvider.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using BTCPayServer.Client.Models; using BTCPayServer.HostedServices; using BTCPayServer.Plugins.Emails; +using BTCPayServer.Plugins.Emails.HostedServices; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using StoreData = BTCPayServer.Data.StoreData; diff --git a/BTCPayServer/Services/Notifications/NotificationSender.cs b/BTCPayServer/Services/Notifications/NotificationSender.cs index e919d0242..31a7a8861 100644 --- a/BTCPayServer/Services/Notifications/NotificationSender.cs +++ b/BTCPayServer/Services/Notifications/NotificationSender.cs @@ -48,10 +48,7 @@ namespace BTCPayServer.Services.Notifications } await db.SaveChangesAsync(); } - foreach (string user in users) - { - _notificationManager.InvalidateNotificationCache(user); - } + _notificationManager.InvalidateNotificationCache(users); } public BaseNotification GetBaseNotification(NotificationData notificationData) diff --git a/BTCPayServer/Validation/MailboxAddressValidator.cs b/BTCPayServer/Validation/MailboxAddressValidator.cs index 4424be4d6..c7077bf25 100644 --- a/BTCPayServer/Validation/MailboxAddressValidator.cs +++ b/BTCPayServer/Validation/MailboxAddressValidator.cs @@ -8,7 +8,7 @@ using MimeKit; namespace BTCPayServer { /// - /// Validate address in the format "Firstname Lastname " See rfc822 + /// Validate address in the format "Firstname Lastname " See rfc5322 /// public class MailboxAddressValidator { @@ -25,7 +25,7 @@ namespace BTCPayServer public static MailboxAddress Parse(string? str) { if (!TryParse(str, out var mb)) - throw new FormatException("Invalid mailbox address (rfc822)"); + throw new FormatException("Invalid mailbox address (rfc5322)"); return mb; } public static bool TryParse(string? str, [MaybeNullWhen(false)] out MailboxAddress mailboxAddress)