btcpayserver/BTCPayServer/Controllers/UIStoresController.Email.cs

269 lines
10 KiB
C#
Raw Normal View History

2020-06-28 21:44:35 -05:00
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
2019-01-06 15:53:37 +01:00
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client;
2019-01-06 15:53:37 +01:00
using BTCPayServer.Data;
using BTCPayServer.Models;
2019-01-06 15:53:37 +01:00
using BTCPayServer.Services.Mails;
using Microsoft.AspNetCore.Authorization;
2019-01-06 15:53:37 +01:00
using Microsoft.AspNetCore.Mvc;
using MimeKit;
2019-01-06 15:53:37 +01:00
2024-04-04 11:00:18 +02:00
namespace BTCPayServer.Controllers;
public partial class UIStoresController
2019-01-06 15:53:37 +01:00
{
2024-04-04 11:00:18 +02:00
[HttpGet("{storeId}/emails")]
public async Task<IActionResult> StoreEmails(string storeId)
2019-01-06 15:53:37 +01:00
{
2024-04-04 11:00:18 +02:00
var store = HttpContext.GetStoreData();
if (store == null)
return NotFound();
2024-04-04 11:00:18 +02:00
var blob = store.GetStoreBlob();
if (blob.EmailSettings?.IsComplete() is not true && !TempData.HasStatusMessage())
{
var emailSender = await _emailSenderFactory.GetEmailSender(store.Id) as StoreEmailSender;
if (!await IsSetupComplete(emailSender?.FallbackSender))
{
2024-04-04 11:00:18 +02:00
TempData.SetStatusMessageModel(new StatusMessageModel
{
2024-04-04 11:00:18 +02:00
Severity = StatusMessageModel.StatusSeverity.Warning,
Html = $"You need to configure email settings before this feature works. <a class='alert-link' href='{Url.Action("StoreEmailSettings", new { storeId })}'>Configure store email settings</a>."
});
}
2024-04-04 11:00:18 +02:00
}
2024-04-04 11:00:18 +02:00
var vm = new StoreEmailRuleViewModel { Rules = blob.EmailRules ?? [] };
return View(vm);
}
[HttpPost("{storeId}/emails")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> StoreEmails(string storeId, StoreEmailRuleViewModel vm, string command)
{
vm.Rules ??= [];
int commandIndex = 0;
var indSep = command.Split(':', StringSplitOptions.RemoveEmptyEntries);
if (indSep.Length > 1)
{
commandIndex = int.Parse(indSep[1], CultureInfo.InvariantCulture);
}
2020-06-28 17:55:27 +09:00
2024-04-04 11:00:18 +02:00
if (command.StartsWith("remove", StringComparison.InvariantCultureIgnoreCase))
{
2024-04-04 11:00:18 +02:00
vm.Rules.RemoveAt(commandIndex);
}
else if (command == "add")
{
vm.Rules.Add(new StoreEmailRule());
2024-04-04 11:00:18 +02:00
return View(vm);
}
2024-04-04 11:00:18 +02:00
for (var i = 0; i < vm.Rules.Count; i++)
{
var rule = vm.Rules[i];
2024-04-04 11:00:18 +02:00
if (!string.IsNullOrEmpty(rule.To) && rule.To.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Any(s => !MailboxAddressValidator.TryParse(s, out _)))
{
2024-04-04 11:00:18 +02:00
ModelState.AddModelError($"{nameof(vm.Rules)}[{i}].{nameof(rule.To)}",
"Invalid mailbox address provided. Valid formats are: 'test@example.com' or 'Firstname Lastname <test@example.com>'");
}
2024-04-04 11:00:18 +02:00
else if (!rule.CustomerEmail && string.IsNullOrEmpty(rule.To))
ModelState.AddModelError($"{nameof(vm.Rules)}[{i}].{nameof(rule.To)}",
"Either recipient or \"Send the email to the buyer\" is required");
}
2024-04-04 11:00:18 +02:00
if (!ModelState.IsValid)
{
return View(vm);
}
2024-04-04 11:00:18 +02:00
var store = HttpContext.GetStoreData();
2024-04-04 11:00:18 +02:00
if (store == null)
return NotFound();
2024-04-04 11:00:18 +02:00
string message = "";
2024-04-04 11:00:18 +02:00
// update rules
var blob = store.GetStoreBlob();
blob.EmailRules = vm.Rules;
if (store.SetStoreBlob(blob))
{
await _storeRepo.UpdateStore(store);
message += "Store email rules saved. ";
}
2024-04-04 11:00:18 +02:00
if (command.StartsWith("test", StringComparison.InvariantCultureIgnoreCase))
{
try
{
2024-04-04 11:00:18 +02:00
var rule = vm.Rules[commandIndex];
var emailSender = await _emailSenderFactory.GetEmailSender(store.Id);
if (await IsSetupComplete(emailSender))
{
2024-04-04 11:00:18 +02:00
var recipients = rule.To.Split(",", StringSplitOptions.RemoveEmptyEntries)
.Select(o =>
{
MailboxAddressValidator.TryParse(o, out var mb);
return mb;
})
.Where(o => o != null)
.ToArray();
2024-04-04 11:00:18 +02:00
emailSender.SendEmail(recipients.ToArray(), null, null, $"[TEST] {rule.Subject}", rule.Body);
message += "Test email sent — please verify you received it.";
}
2024-04-04 11:00:18 +02:00
else
{
2024-04-04 11:00:18 +02:00
message += "Complete the email setup to send test emails.";
}
}
2024-04-04 11:00:18 +02:00
catch (Exception ex)
{
2024-04-04 11:00:18 +02:00
TempData[WellKnownTempData.ErrorMessage] = message + "Error sending test email: " + ex.Message;
return RedirectToAction("StoreEmails", new { storeId });
}
}
2024-04-04 11:00:18 +02:00
if (!string.IsNullOrEmpty(message))
{
2024-04-04 11:00:18 +02:00
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Success,
Message = message
});
}
2024-04-04 11:00:18 +02:00
return RedirectToAction("StoreEmails", new { storeId });
}
public class StoreEmailRuleViewModel
{
public List<StoreEmailRule> Rules { get; set; }
}
public class StoreEmailRule
{
[Required]
public string Trigger { get; set; }
2024-04-04 11:00:18 +02:00
public bool CustomerEmail { get; set; }
2024-04-04 11:00:18 +02:00
public string To { get; set; }
2024-04-04 11:00:18 +02:00
[Required]
public string Subject { get; set; }
2024-04-04 11:00:18 +02:00
[Required]
public string Body { get; set; }
}
2024-04-04 11:00:18 +02:00
[HttpGet("{storeId}/email-settings")]
public async Task<IActionResult> StoreEmailSettings(string storeId)
{
var store = HttpContext.GetStoreData();
if (store == null)
return NotFound();
2024-04-04 11:00:18 +02:00
var blob = store.GetStoreBlob();
var data = blob.EmailSettings ?? new EmailSettings();
var fallbackSettings = await _emailSenderFactory.GetEmailSender(store.Id) is StoreEmailSender { FallbackSender: not null } storeSender
? await storeSender.FallbackSender.GetEmailSettings()
: null;
var vm = new EmailsViewModel(data, fallbackSettings);
2024-04-04 11:00:18 +02:00
return View(vm);
}
2020-06-28 17:55:27 +09:00
2024-04-04 11:00:18 +02:00
[HttpPost("{storeId}/email-settings")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> StoreEmailSettings(string storeId, EmailsViewModel model, string command, [FromForm] bool useCustomSMTP = false)
{
var store = HttpContext.GetStoreData();
if (store == null)
return NotFound();
2024-04-04 11:00:18 +02:00
ViewBag.UseCustomSMTP = useCustomSMTP;
model.FallbackSettings = await _emailSenderFactory.GetEmailSender(store.Id) is StoreEmailSender { FallbackSender: not null } storeSender
? await storeSender.FallbackSender.GetEmailSettings()
: null;
if (useCustomSMTP)
{
model.Settings.Validate("Settings.", ModelState);
}
if (command == "Test")
{
try
2019-01-06 15:53:37 +01:00
{
2024-04-04 11:00:18 +02:00
if (useCustomSMTP)
2019-01-06 15:53:37 +01:00
{
2024-04-04 11:00:18 +02:00
if (model.PasswordSet)
{
2024-04-04 11:00:18 +02:00
model.Settings.Password = store.GetStoreBlob().EmailSettings.Password;
}
2019-01-06 15:53:37 +01:00
}
2024-04-04 11:00:18 +02:00
if (string.IsNullOrEmpty(model.TestEmail))
ModelState.AddModelError(nameof(model.TestEmail), new RequiredAttribute().FormatErrorMessage(nameof(model.TestEmail)));
if (!ModelState.IsValid)
return View(model);
var settings = useCustomSMTP ? model.Settings : model.FallbackSettings;
using var client = await settings.CreateSmtpClient();
var message = settings.CreateMailMessage(MailboxAddress.Parse(model.TestEmail), $"{store.StoreName}: Email test", "You received it, the BTCPay Server SMTP settings work.", false);
await client.SendAsync(message);
await client.DisconnectAsync(true);
TempData[WellKnownTempData.SuccessMessage] = $"Email sent to {model.TestEmail}. Please verify you received it.";
2019-01-06 15:53:37 +01:00
}
2024-04-04 11:00:18 +02:00
catch (Exception ex)
{
2024-04-04 11:00:18 +02:00
TempData[WellKnownTempData.ErrorMessage] = "Error: " + ex.Message;
}
2024-04-04 11:00:18 +02:00
return View(model);
}
if (command == "ResetPassword")
{
var storeBlob = store.GetStoreBlob();
storeBlob.EmailSettings.Password = null;
store.SetStoreBlob(storeBlob);
await _storeRepo.UpdateStore(store);
TempData[WellKnownTempData.SuccessMessage] = "Email server password reset";
}
if (useCustomSMTP)
{
if (model.Settings.From is not null && !MailboxAddressValidator.IsMailboxAddress(model.Settings.From))
2019-01-06 15:53:37 +01:00
{
2024-04-04 11:00:18 +02:00
ModelState.AddModelError("Settings.From", "Invalid email");
}
if (!ModelState.IsValid)
return View(model);
var storeBlob = store.GetStoreBlob();
if (storeBlob.EmailSettings != null && new EmailsViewModel(storeBlob.EmailSettings, model.FallbackSettings).PasswordSet)
{
model.Settings.Password = storeBlob.EmailSettings.Password;
2019-01-06 15:53:37 +01:00
}
2024-04-04 11:00:18 +02:00
storeBlob.EmailSettings = model.Settings;
store.SetStoreBlob(storeBlob);
await _storeRepo.UpdateStore(store);
TempData[WellKnownTempData.SuccessMessage] = "Email settings modified";
2019-01-06 15:53:37 +01:00
}
2024-04-04 11:00:18 +02:00
return RedirectToAction(nameof(StoreEmailSettings), new { storeId });
}
2024-04-04 11:00:18 +02:00
private static async Task<bool> IsSetupComplete(IEmailSender emailSender)
{
return emailSender is not null && (await emailSender.GetEmailSettings())?.IsComplete() == true;
2019-01-06 15:53:37 +01:00
}
}