mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-20 13:34:37 +01:00
Improve email settings validation and UX (#3891)
This commit is contained in:
parent
c2d72e71aa
commit
c89f7aaaed
26 changed files with 161 additions and 93 deletions
|
@ -1,4 +1,4 @@
|
|||
namespace BTCPayServer.Client.Models;
|
||||
namespace BTCPayServer.Client.Models;
|
||||
|
||||
public class EmailSettingsData
|
||||
{
|
||||
|
@ -21,11 +21,6 @@ public class EmailSettingsData
|
|||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string FromDisplay
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string From
|
||||
{
|
||||
get; set;
|
||||
|
|
|
@ -1940,6 +1940,7 @@ retry:
|
|||
s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit();
|
||||
s.FindAlertMessage();
|
||||
s.Driver.FindElement(By.Id("Settings_Password")).SendKeys("mypassword");
|
||||
s.Driver.FindElement(By.Id("Settings_From")).SendKeys("Firstname Lastname <email@example.com>");
|
||||
s.Driver.FindElement(By.Id("Save")).SendKeys(Keys.Enter);
|
||||
Assert.Contains("Configured", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("test_fix@gmail.com");
|
||||
|
|
|
@ -39,7 +39,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
{
|
||||
return this.CreateAPIError(404, "store-not-found", "The store was not found");
|
||||
}
|
||||
if (!MailboxAddress.TryParse(request.Email, out MailboxAddress to))
|
||||
if (!MailboxAddressValidator.TryParse(request.Email, out var to))
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Email), "Invalid email");
|
||||
return this.CreateValidationError(ModelState);
|
||||
|
@ -72,7 +72,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
return StoreNotFound();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(request.From) && !EmailValidator.IsEmail(request.From))
|
||||
if (!string.IsNullOrEmpty(request.From) && !MailboxAddressValidator.IsMailboxAddress(request.From))
|
||||
{
|
||||
request.AddModelError(e => e.From,
|
||||
"Invalid email address", this);
|
||||
|
|
|
@ -118,7 +118,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
{
|
||||
if (request.Email is null)
|
||||
ModelState.AddModelError(nameof(request.Email), "Email is missing");
|
||||
if (!string.IsNullOrEmpty(request.Email) && !Validation.EmailValidator.IsEmail(request.Email))
|
||||
if (!string.IsNullOrEmpty(request.Email) && !MailboxAddressValidator.IsMailboxAddress(request.Email))
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Email), "Invalid email");
|
||||
}
|
||||
|
|
|
@ -233,7 +233,7 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
if (entity.Metadata.BuyerEmail != null)
|
||||
{
|
||||
if (!EmailValidator.IsEmail(entity.Metadata.BuyerEmail))
|
||||
if (!MailboxAddressValidator.IsMailboxAddress(entity.Metadata.BuyerEmail))
|
||||
throw new BitpayHttpException(400, "Invalid email");
|
||||
entity.RefundMail = entity.Metadata.BuyerEmail;
|
||||
}
|
||||
|
|
|
@ -165,8 +165,7 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
||||
var callbackUrl = _linkGenerator.EmailConfirmationLink(user.Id, code, Request.Scheme, Request.Host, Request.PathBase);
|
||||
var address = new MailboxAddress(user.UserName, user.Email);
|
||||
(await _EmailSenderFactory.GetEmailSender()).SendEmailConfirmation(address, callbackUrl);
|
||||
(await _EmailSenderFactory.GetEmailSender()).SendEmailConfirmation(user.GetMailboxAddress(), callbackUrl);
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Verification email sent. Please check your email.";
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
|
|
@ -303,8 +303,7 @@ namespace BTCPayServer.Controllers
|
|||
var code = await _UserManager.GenerateEmailConfirmationTokenAsync(user);
|
||||
var callbackUrl = _linkGenerator.EmailConfirmationLink(user.Id, code, Request.Scheme, Request.Host, Request.PathBase);
|
||||
|
||||
var address = new MailboxAddress(user.UserName, user.Email);
|
||||
(await _emailSenderFactory.GetEmailSender()).SendEmailConfirmation(address, callbackUrl);
|
||||
(await _emailSenderFactory.GetEmailSender()).SendEmailConfirmation(user.GetMailboxAddress(), callbackUrl);
|
||||
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Verification email sent";
|
||||
return RedirectToAction(nameof(ListUsers));
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
@ -25,6 +26,7 @@ using BTCPayServer.Services.Stores;
|
|||
using BTCPayServer.Storage.Models;
|
||||
using BTCPayServer.Storage.Services;
|
||||
using BTCPayServer.Storage.Services.Providers;
|
||||
using BTCPayServer.Validation;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
@ -1015,18 +1017,13 @@ namespace BTCPayServer.Controllers
|
|||
var settings = await _SettingsRepository.GetSettingAsync<EmailSettings>() ?? new EmailSettings();
|
||||
model.Settings.Password = settings.Password;
|
||||
}
|
||||
if (!model.Settings.IsComplete())
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] = "Required fields missing";
|
||||
model.Settings.Validate("Settings.", ModelState);
|
||||
if (string.IsNullOrEmpty(model.TestEmail))
|
||||
ModelState.AddModelError(nameof(model.TestEmail), new RequiredAttribute().FormatErrorMessage(nameof(model.TestEmail)));
|
||||
if (!ModelState.IsValid)
|
||||
return View(model);
|
||||
}
|
||||
if (!MailboxAddress.TryParse(model.TestEmail, out MailboxAddress testEmail))
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] = "Invalid test email";
|
||||
return View(model);
|
||||
}
|
||||
using (var client = await model.Settings.CreateSmtpClient())
|
||||
using (var message = model.Settings.CreateMailMessage(testEmail, "BTCPay test", "BTCPay test", false))
|
||||
using (var message = model.Settings.CreateMailMessage(MailboxAddress.Parse(model.TestEmail), "BTCPay test", "BTCPay test", false))
|
||||
{
|
||||
await client.SendAsync(message);
|
||||
await client.DisconnectAsync(true);
|
||||
|
@ -1043,12 +1040,17 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
var settings = await _SettingsRepository.GetSettingAsync<EmailSettings>() ?? new EmailSettings();
|
||||
settings.Password = null;
|
||||
await _SettingsRepository.UpdateSetting(model.Settings);
|
||||
await _SettingsRepository.UpdateSetting(settings);
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Email server password reset";
|
||||
return RedirectToAction(nameof(Emails));
|
||||
}
|
||||
else // if (command == "Save")
|
||||
{
|
||||
if (model.Settings.From is not null && !MailboxAddressValidator.IsMailboxAddress(model.Settings.From))
|
||||
{
|
||||
ModelState.AddModelError("Settings.From", "Invalid email");
|
||||
return View(model);
|
||||
}
|
||||
var oldSettings = await _SettingsRepository.GetSettingAsync<EmailSettings>() ?? new EmailSettings();
|
||||
if (new EmailsViewModel(oldSettings).PasswordSet)
|
||||
{
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
|
@ -45,7 +46,7 @@ namespace BTCPayServer.Controllers
|
|||
if (command.StartsWith("remove", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
var item = command[(command.IndexOf(":", StringComparison.InvariantCultureIgnoreCase) + 1)..];
|
||||
var index = int.Parse(item);
|
||||
var index = int.Parse(item, CultureInfo.InvariantCulture);
|
||||
vm.Rules.RemoveAt(index);
|
||||
|
||||
return View(vm);
|
||||
|
@ -117,18 +118,13 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
model.Settings.Password = store.GetStoreBlob().EmailSettings.Password;
|
||||
}
|
||||
if (!model.Settings.IsComplete())
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] = "Required fields missing";
|
||||
model.Settings.Validate("Settings.", ModelState);
|
||||
if (string.IsNullOrEmpty(model.TestEmail))
|
||||
ModelState.AddModelError(nameof(model.TestEmail), new RequiredAttribute().FormatErrorMessage(nameof(model.TestEmail)));
|
||||
if (!ModelState.IsValid)
|
||||
return View(model);
|
||||
}
|
||||
if (!MailboxAddress.TryParse(model.TestEmail, out MailboxAddress testEmail))
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] = "Invalid test email";
|
||||
return View(model);
|
||||
}
|
||||
using var client = await model.Settings.CreateSmtpClient();
|
||||
var message = model.Settings.CreateMailMessage(testEmail, "BTCPay test", "BTCPay test", false);
|
||||
var message = model.Settings.CreateMailMessage(MailboxAddress.Parse(model.TestEmail), "BTCPay test", "BTCPay test", false);
|
||||
await client.SendAsync(message);
|
||||
await client.DisconnectAsync(true);
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"Email sent to {model.TestEmail}. Please verify you received it.";
|
||||
|
@ -150,6 +146,11 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
else // if (command == "Save")
|
||||
{
|
||||
if (model.Settings.From is not null && !MailboxAddressValidator.IsMailboxAddress(model.Settings.From))
|
||||
{
|
||||
ModelState.AddModelError("Settings.From", "Invalid email");
|
||||
return View(model);
|
||||
}
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
if (new EmailsViewModel(storeBlob.EmailSettings).PasswordSet && storeBlob.EmailSettings != null)
|
||||
{
|
||||
|
|
|
@ -64,7 +64,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
|||
try
|
||||
{
|
||||
string lnurlTag = null;
|
||||
var lnurl = EmailValidator.IsEmail(destination)
|
||||
var lnurl = MailboxAddressValidator.IsMailboxAddress(destination)
|
||||
? LNURL.LNURL.ExtractUriFromInternetIdentifier(destination)
|
||||
: LNURL.LNURL.Parse(destination, out lnurlTag);
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ namespace BTCPayServer
|
|||
{
|
||||
public static class ModelStateExtensions
|
||||
{
|
||||
|
||||
public static void AddModelError<TModel, TProperty>(this TModel source,
|
||||
Expression<Func<TModel, TProperty>> ex,
|
||||
string message,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
|
@ -7,6 +8,15 @@ namespace BTCPayServer
|
|||
{
|
||||
public static class UserExtensions
|
||||
{
|
||||
public static MimeKit.MailboxAddress GetMailboxAddress(this ApplicationUser user)
|
||||
{
|
||||
if (user is null)
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
var name = user.UserName ?? String.Empty;
|
||||
if (user.Email == user.UserName)
|
||||
name = String.Empty;
|
||||
return new MimeKit.MailboxAddress(name, user.Email);
|
||||
}
|
||||
public static UserBlob GetBlob(this ApplicationUser user)
|
||||
{
|
||||
var result = user.Blob == null
|
||||
|
|
|
@ -122,7 +122,7 @@ namespace BTCPayServer.HostedServices
|
|||
|
||||
if (sendMail &&
|
||||
invoice.NotificationEmail is String e &&
|
||||
MailboxAddress.TryParse(e, out MailboxAddress notificationEmail))
|
||||
MailboxAddressValidator.TryParse(e, out MailboxAddress notificationEmail))
|
||||
{
|
||||
var json = NBitcoin.JsonConverters.Serializer.ToString(notification);
|
||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -53,20 +53,18 @@ public class StoreEmailRuleProcessorSender : EventHostedServiceBase
|
|||
var sender = await _emailSenderFactory.GetEmailSender(invoiceEvent.Invoice.StoreId);
|
||||
foreach (UIStoresController.StoreEmailRule actionableRule in actionableRules)
|
||||
{
|
||||
var dest = actionableRule.To.Split(",", StringSplitOptions.RemoveEmptyEntries).Where(IsValidEmailAddress);
|
||||
if (actionableRule.CustomerEmail && IsValidEmailAddress(invoiceEvent.Invoice.Metadata.BuyerEmail))
|
||||
var recipients = actionableRule.To.Split(",", StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(o => { MailboxAddressValidator.TryParse(o, out var mb); return mb; })
|
||||
.Where(o => o != null)
|
||||
.ToList();
|
||||
if (actionableRule.CustomerEmail && MailboxAddressValidator.TryParse(invoiceEvent.Invoice.Metadata.BuyerEmail, out var bmb))
|
||||
{
|
||||
dest = dest.Append(invoiceEvent.Invoice.Metadata.BuyerEmail);
|
||||
recipients.Add(bmb);
|
||||
}
|
||||
|
||||
var recipients = dest.Select(address => new MailboxAddress(address, address)).ToArray();
|
||||
sender.SendEmail(recipients, null, null, actionableRule.Subject, actionableRule.Body);
|
||||
sender.SendEmail(recipients.ToArray(), null, null, actionableRule.Subject, actionableRule.Body);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValidEmailAddress(string address) =>
|
||||
!string.IsNullOrEmpty(address) && MailboxAddress.TryParse(address, out _);
|
||||
}
|
||||
|
|
|
@ -55,8 +55,7 @@ namespace BTCPayServer.HostedServices
|
|||
new HostString(userRegisteredEvent.RequestUri.Host, userRegisteredEvent.RequestUri.Port),
|
||||
userRegisteredEvent.RequestUri.PathAndQuery);
|
||||
userRegisteredEvent.CallbackUrlGenerated?.SetResult(new Uri(callbackUrl));
|
||||
address = new MailboxAddress(userRegisteredEvent.User.Email,
|
||||
userRegisteredEvent.User.Email);
|
||||
address = userRegisteredEvent.User.GetMailboxAddress();
|
||||
(await _emailSenderFactory.GetEmailSender()).SendEmailConfirmation(address, callbackUrl);
|
||||
}
|
||||
else if (!await _userManager.HasPasswordAsync(userRegisteredEvent.User))
|
||||
|
@ -86,8 +85,7 @@ passwordSetter:
|
|||
userPasswordResetRequestedEvent.RequestUri.Port),
|
||||
userPasswordResetRequestedEvent.RequestUri.PathAndQuery);
|
||||
userPasswordResetRequestedEvent.CallbackUrlGenerated?.SetResult(new Uri(callbackUrl));
|
||||
address = new MailboxAddress(userPasswordResetRequestedEvent.User.Email,
|
||||
userPasswordResetRequestedEvent.User.Email);
|
||||
address = userPasswordResetRequestedEvent.User.GetMailboxAddress();
|
||||
(await _emailSenderFactory.GetEmailSender())
|
||||
.SendSetPasswordConfirmation(address, callbackUrl, newPassword);
|
||||
break;
|
||||
|
|
|
@ -50,7 +50,7 @@ namespace BTCPayServer.Models.InvoicingModels
|
|||
get; set;
|
||||
}
|
||||
|
||||
[EmailAddress]
|
||||
[MailboxAddress]
|
||||
[DisplayName("Buyer Email")]
|
||||
public string BuyerEmail
|
||||
{
|
||||
|
@ -76,7 +76,7 @@ namespace BTCPayServer.Models.InvoicingModels
|
|||
get; set;
|
||||
}
|
||||
|
||||
[EmailAddress]
|
||||
[MailboxAddress]
|
||||
[DisplayName("Notification Email")]
|
||||
public string NotificationEmail
|
||||
{
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using BTCPayServer.Validation;
|
||||
|
||||
namespace BTCPayServer.Models.InvoicingModels
|
||||
{
|
||||
public class UpdateCustomerModel
|
||||
{
|
||||
[EmailAddress]
|
||||
[MailboxAddress]
|
||||
[Required]
|
||||
public string Email
|
||||
{
|
||||
|
|
|
@ -5,6 +5,7 @@ using BTCPayServer.Client.Models;
|
|||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Validation;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using PaymentRequestData = BTCPayServer.Data.PaymentRequestData;
|
||||
|
||||
|
@ -64,7 +65,7 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
|
|||
[Display(Name = "Store")]
|
||||
public SelectList Stores { get; set; }
|
||||
|
||||
[EmailAddress]
|
||||
[MailboxAddress]
|
||||
public string Email { get; set; }
|
||||
|
||||
[MaxLength(500)]
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using BTCPayServer.Services.Mails;
|
||||
using BTCPayServer.Validation;
|
||||
|
||||
namespace BTCPayServer.Models.ServerViewModels
|
||||
{
|
||||
|
@ -19,7 +20,7 @@ namespace BTCPayServer.Models.ServerViewModels
|
|||
get; set;
|
||||
}
|
||||
public bool PasswordSet { get; set; }
|
||||
[EmailAddress]
|
||||
[MailboxAddressAttribute]
|
||||
[Display(Name = "Test Email")]
|
||||
public string TestEmail
|
||||
{
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.ComponentModel.DataAnnotations;
|
|||
using BTCPayServer.ModelBinders;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Validation;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Plugins.PayButton.Models
|
||||
|
@ -34,7 +35,7 @@ namespace BTCPayServer.Plugins.PayButton.Models
|
|||
public string ServerIpn { get; set; }
|
||||
[Url]
|
||||
public string BrowserRedirect { get; set; }
|
||||
[EmailAddress]
|
||||
[MailboxAddress]
|
||||
public string NotifyEmail { get; set; }
|
||||
|
||||
public string StoreId { get; set; }
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Validation;
|
||||
using MailKit.Net.Smtp;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using MimeKit;
|
||||
|
||||
namespace BTCPayServer.Services.Mails
|
||||
|
@ -10,7 +13,29 @@ namespace BTCPayServer.Services.Mails
|
|||
{
|
||||
public bool IsComplete()
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(Server) && Port is int;
|
||||
return MailboxAddressValidator.IsMailboxAddress(From)
|
||||
&& !string.IsNullOrWhiteSpace(Server)
|
||||
&& Port is int;
|
||||
}
|
||||
|
||||
public void Validate(string prefixKey, ModelStateDictionary modelState)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(From))
|
||||
{
|
||||
modelState.AddModelError($"{prefixKey}{nameof(From)}", new RequiredAttribute().FormatErrorMessage(nameof(From)));
|
||||
}
|
||||
if (!MailboxAddressValidator.IsMailboxAddress(From))
|
||||
{
|
||||
modelState.AddModelError($"{prefixKey}{nameof(From)}", MailboxAddressAttribute.ErrorMessageConst);
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(Server))
|
||||
{
|
||||
modelState.AddModelError($"{prefixKey}{nameof(Server)}", new RequiredAttribute().FormatErrorMessage(nameof(Server)));
|
||||
}
|
||||
if (Port is null)
|
||||
{
|
||||
modelState.AddModelError($"{prefixKey}{nameof(Port)}", new RequiredAttribute().FormatErrorMessage(nameof(Port)));
|
||||
}
|
||||
}
|
||||
|
||||
public MimeMessage CreateMailMessage(MailboxAddress to, string subject, string message, bool isHtml) =>
|
||||
|
@ -30,12 +55,11 @@ namespace BTCPayServer.Services.Mails
|
|||
var mm = new MimeMessage();
|
||||
mm.Body = bodyBuilder.ToMessageBody();
|
||||
mm.Subject = subject;
|
||||
mm.From.Add(new MailboxAddress(From, !string.IsNullOrWhiteSpace(FromDisplay) ? From : FromDisplay));
|
||||
mm.From.Add(MailboxAddressValidator.Parse(From));
|
||||
mm.To.AddRange(to);
|
||||
mm.Cc.AddRange(cc?? System.Array.Empty<InternetAddress>());
|
||||
mm.Bcc.AddRange(bcc?? System.Array.Empty<InternetAddress>());
|
||||
mm.Cc.AddRange(cc ?? System.Array.Empty<InternetAddress>());
|
||||
mm.Bcc.AddRange(bcc ?? System.Array.Empty<InternetAddress>());
|
||||
return mm;
|
||||
|
||||
}
|
||||
|
||||
public async Task<SmtpClient> CreateSmtpClient()
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace BTCPayServer.Validation
|
||||
{
|
||||
public class EmailValidator
|
||||
{
|
||||
static Regex _Email;
|
||||
public static bool IsEmail(string str)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(str))
|
||||
return false;
|
||||
if (_Email == null)
|
||||
_Email = new Regex("^((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.?$", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture | RegexOptions.Compiled, TimeSpan.FromSeconds(2.0));
|
||||
return _Email.IsMatch(str);
|
||||
}
|
||||
}
|
||||
}
|
25
BTCPayServer/Validation/MailboxAddressAttribute.cs
Normal file
25
BTCPayServer/Validation/MailboxAddressAttribute.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace BTCPayServer.Validation
|
||||
{
|
||||
/// <summary>
|
||||
/// Validate address in the format "Firstname Lastname <blah@example.com>" See rfc822
|
||||
/// </summary>
|
||||
public class MailboxAddressAttribute : ValidationAttribute
|
||||
{
|
||||
public MailboxAddressAttribute()
|
||||
{
|
||||
ErrorMessage = ErrorMessageConst;
|
||||
}
|
||||
public const string ErrorMessageConst = "Invalid mailbox address. Some valid examples are: 'test@example.com' or 'Firstname Lastname <test@example.com>'";
|
||||
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
|
||||
{
|
||||
if (value is null)
|
||||
return ValidationResult.Success;
|
||||
var str = value as string;
|
||||
if (MailboxAddressValidator.IsMailboxAddress(str))
|
||||
return ValidationResult.Success;
|
||||
return new ValidationResult(ErrorMessage);
|
||||
}
|
||||
}
|
||||
}
|
39
BTCPayServer/Validation/MailboxAddressValidator.cs
Normal file
39
BTCPayServer/Validation/MailboxAddressValidator.cs
Normal file
|
@ -0,0 +1,39 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.RegularExpressions;
|
||||
using MimeKit;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Validate address in the format "Firstname Lastname <blah@example.com>" See rfc822
|
||||
/// </summary>
|
||||
public class MailboxAddressValidator
|
||||
{
|
||||
static ParserOptions _options;
|
||||
static MailboxAddressValidator()
|
||||
{
|
||||
_options = ParserOptions.Default.Clone();
|
||||
_options.AllowAddressesWithoutDomain = false;
|
||||
}
|
||||
public static bool IsMailboxAddress(string? str)
|
||||
{
|
||||
return TryParse(str, out _);
|
||||
}
|
||||
public static MailboxAddress Parse(string? str)
|
||||
{
|
||||
if (!TryParse(str, out var mb))
|
||||
throw new FormatException("Invalid mailbox address (rfc822)");
|
||||
return mb;
|
||||
}
|
||||
public static bool TryParse(string? str, [MaybeNullWhen(false)] out MailboxAddress mailboxAddress)
|
||||
{
|
||||
mailboxAddress = null;
|
||||
if (String.IsNullOrWhiteSpace(str))
|
||||
return false;
|
||||
return MailboxAddress.TryParse(_options, str, out mailboxAddress) && mailboxAddress is not null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -39,17 +39,9 @@
|
|||
<input asp-for="Settings.Port" data-fill="port" class="form-control"/>
|
||||
<span asp-validation-for="Settings.Port" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Settings.FromDisplay" class="form-label">Sender's display name</label>
|
||||
<input asp-for="Settings.FromDisplay" class="form-control"/>
|
||||
<small class="form-text text-muted">
|
||||
Some email providers (like Gmail) don't allow you to set your display name, so this setting may not have any effect.
|
||||
</small>
|
||||
<span asp-validation-for="Settings.FromDisplay" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Settings.From" class="form-label">Sender's Email Address</label>
|
||||
<input asp-for="Settings.From" class="form-control"/>
|
||||
<input asp-for="Settings.From" class="form-control" placeholder="Firstname Lastname <email@example.com>" />
|
||||
<span asp-validation-for="Settings.From" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
@ -89,7 +81,7 @@
|
|||
To test your settings, enter an email address below.
|
||||
</p>
|
||||
<label asp-for="TestEmail" class="form-label"></label>
|
||||
<input asp-for="TestEmail" class="form-control" />
|
||||
<input asp-for="TestEmail" placeholder="Firstname Lastname <email@example.com>" class="form-control" />
|
||||
<span asp-validation-for="TestEmail" class="text-danger"></span>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-secondary mt-2" name="command" value="Test" id="Test">Send Test Email</button>
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Email" class="form-label"></label>
|
||||
<input type="email" asp-for="Email" class="form-control" />
|
||||
<input type="email" asp-for="Email" placeholder="Firstname Lastname <email@example.com>" class="form-control" />
|
||||
<span asp-validation-for="Email" class="text-danger"></span>
|
||||
<p id="PaymentRequestEmailHelpBlock" class="form-text text-muted">
|
||||
Receive updates for this payment request.
|
||||
|
|
Loading…
Add table
Reference in a new issue