public async Task <IActionResult> UpdateStoreEmailSettings(string storeId, EmailSettings request) { var store = HttpContext.GetStoreData(); if (store == null) { return(StoreNotFound()); } if (!string.IsNullOrEmpty(request.From) && !MailboxAddressValidator.IsMailboxAddress(request.From)) { request.AddModelError(e => e.From, "Invalid email address", this); return(this.CreateValidationError(ModelState)); } var blob = store.GetStoreBlob(); blob.EmailSettings = request; if (store.SetStoreBlob(blob)) { await _storeRepository.UpdateStore(store); } return(Ok(FromModel(store))); }
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)); }
protected override async Task ProcessEvent(object evt, CancellationToken cancellationToken) { if (evt is InvoiceEvent invoiceEvent) { var type = WebhookSender.GetWebhookEvent(invoiceEvent); if (type is null) { return; } var store = await _storeRepository.FindStore(invoiceEvent.Invoice.StoreId); var blob = store.GetStoreBlob(); if (blob.EmailRules?.Any() is true) { var actionableRules = blob.EmailRules.Where(rule => rule.Trigger == type.Type).ToList(); if (actionableRules.Any()) { var sender = await _emailSenderFactory.GetEmailSender(invoiceEvent.Invoice.StoreId); foreach (UIStoresController.StoreEmailRule actionableRule in actionableRules) { var recipients = (actionableRule.To?.Split(",", StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty <string>()) .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)) { recipients.Add(bmb); } var i = GreenfieldInvoiceController.ToModel(invoiceEvent.Invoice, _linkGenerator, null); sender.SendEmail(recipients.ToArray(), null, null, Interpolator(actionableRule.Subject, i), Interpolator(actionableRule.Body, i)); } } } } }
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, MailboxAddress[] cc, MailboxAddress[] bcc, string subject, string message, bool isHtml) { var bodyBuilder = new BodyBuilder(); if (isHtml) { bodyBuilder.HtmlBody = message; } else { bodyBuilder.TextBody = message; } var mm = new MimeMessage(); mm.Body = bodyBuilder.ToMessageBody(); mm.Subject = subject; 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>()); return(mm); }
public async Task <IActionResult> SendEmailFromStore(string storeId, [FromBody] SendEmailRequest request) { var store = HttpContext.GetStoreData(); if (store == null) { return(this.CreateAPIError(404, "store-not-found", "The store was not found")); } if (!MailboxAddressValidator.TryParse(request.Email, out var to)) { ModelState.AddModelError(nameof(request.Email), "Invalid email"); return(this.CreateValidationError(ModelState)); } var emailSender = await _emailSenderFactory.GetEmailSender(storeId); if (emailSender is null) { return(this.CreateAPIError(404, "smtp-not-configured", "Store does not have an SMTP server configured.")); } emailSender.SendEmail(to, request.Subject, request.Body); return(Ok()); }
async Task Notify(InvoiceEntity invoice, InvoiceEvent invoiceEvent, bool extendedNotification, bool sendMail) { var dto = invoice.EntityToDTO(); var notification = new InvoicePaymentNotificationEventWrapper() { Data = new InvoicePaymentNotification() { Id = dto.Id, Currency = dto.Currency, CurrentTime = dto.CurrentTime, ExceptionStatus = dto.ExceptionStatus, ExpirationTime = dto.ExpirationTime, InvoiceTime = dto.InvoiceTime, PosData = dto.PosData, Price = dto.Price, Status = dto.Status, BuyerFields = invoice.RefundMail == null ? null : new Newtonsoft.Json.Linq.JObject() { new JProperty("buyerEmail", invoice.RefundMail) }, PaymentSubtotals = dto.PaymentSubtotals, PaymentTotals = dto.PaymentTotals, AmountPaid = dto.AmountPaid, ExchangeRates = dto.ExchangeRates, OrderId = dto.OrderId }, Event = new InvoicePaymentNotificationEvent() { Code = (int)invoiceEvent.EventCode, Name = invoiceEvent.Name }, ExtendedNotification = extendedNotification, NotificationURL = invoice.NotificationURL?.AbsoluteUri }; // For lightning network payments, paid, confirmed and completed come all at once. // So despite the event is "paid" or "confirmed" the Status of the invoice is technically complete // This confuse loggers who think their endpoint get duplicated events // So here, we just override the status expressed by the notification if (invoiceEvent.Name == InvoiceEvent.Confirmed) { notification.Data.Status = InvoiceState.ToString(InvoiceStatusLegacy.Confirmed); } if (invoiceEvent.Name == InvoiceEvent.PaidInFull) { notification.Data.Status = InvoiceState.ToString(InvoiceStatusLegacy.Paid); } ////////////////// // We keep backward compatibility with bitpay by passing BTC info to the notification // we don't pass other info, as it is a bad idea to use IPN data for logic processing (can be faked) var btcCryptoInfo = dto.CryptoInfo.FirstOrDefault(c => c.GetpaymentMethodId() == new PaymentMethodId("BTC", Payments.PaymentTypes.BTCLike) && !string.IsNullOrEmpty(c.Address)); if (btcCryptoInfo != null) { #pragma warning disable CS0618 notification.Data.Rate = dto.Rate; notification.Data.Url = dto.Url; notification.Data.BTCDue = dto.BTCDue; notification.Data.BTCPaid = dto.BTCPaid; notification.Data.BTCPrice = dto.BTCPrice; #pragma warning restore CS0618 } if (sendMail && invoice.NotificationEmail is String e && MailboxAddressValidator.TryParse(e, out MailboxAddress notificationEmail)) { var json = NBitcoin.JsonConverters.Serializer.ToString(notification); var store = await _StoreRepository.FindStore(invoice.StoreId); var storeName = store.StoreName ?? "BTCPay Server"; var emailBody = $"Store: {storeName}<br>" + $"Invoice ID: {notification.Data.Id}<br>" + $"Status: {notification.Data.Status}<br>" + $"Amount: {notification.Data.Price} {notification.Data.Currency}<br>" + $"<br><details><summary>Details</summary><pre>{json}</pre></details>"; (await _EmailSenderFactory.GetEmailSender(invoice.StoreId)).SendEmail( notificationEmail, $"{storeName} Invoice Notification - ${invoice.StoreId}", emailBody); } if (invoice.NotificationURL != null) { _Queue.Enqueue(invoice.Id, (cancellationToken) => NotifyHttp(new ScheduledJob() { TryCount = 0, Notification = notification }, cancellationToken)); } }
public bool IsComplete() { return(MailboxAddressValidator.IsMailboxAddress(From) && !string.IsNullOrWhiteSpace(Server) && Port is int); }
public async Task <IActionResult> CreateUser(CreateApplicationUserRequest request, CancellationToken cancellationToken = default) { if (request.Email is null) { ModelState.AddModelError(nameof(request.Email), "Email is missing"); } if (!string.IsNullOrEmpty(request.Email) && !MailboxAddressValidator.IsMailboxAddress(request.Email)) { ModelState.AddModelError(nameof(request.Email), "Invalid email"); } if (request.Password is null) { ModelState.AddModelError(nameof(request.Password), "Password is missing"); } if (!ModelState.IsValid) { return(this.CreateValidationError(ModelState)); } if (User.Identity is null) { throw new JsonHttpException(this.StatusCode(401)); } var anyAdmin = (await _userManager.GetUsersInRoleAsync(Roles.ServerAdmin)).Any(); var policies = await _settingsRepository.GetSettingAsync <PoliciesSettings>() ?? new PoliciesSettings(); var isAuth = User.Identity.AuthenticationType == GreenfieldConstants.AuthenticationType; // If registration are locked and that an admin exists, don't accept unauthenticated connection if (anyAdmin && policies.LockSubscription && !isAuth) { return(this.CreateAPIError(401, "unauthenticated", "New user creation isn't authorized to users who are not admin")); } // Even if subscription are unlocked, it is forbidden to create admin unauthenticated if (anyAdmin && request.IsAdministrator is true && !isAuth) { return(this.CreateAPIError(401, "unauthenticated", "New admin creation isn't authorized to users who are not admin")); } // You are de-facto admin if there is no other admin, else you need to be auth and pass policy requirements bool isAdmin = anyAdmin ? (await _authorizationService.AuthorizeAsync(User, null, new PolicyRequirement(Policies.CanModifyServerSettings))).Succeeded && (await _authorizationService.AuthorizeAsync(User, null, new PolicyRequirement(Policies.Unrestricted))).Succeeded && isAuth : true; // You need to be admin to create an admin if (request.IsAdministrator is true && !isAdmin) { return(this.CreateAPIPermissionError(Policies.Unrestricted, $"Insufficient API Permissions. Please use an API key with permission: {Policies.Unrestricted} and be an admin.")); } if (!isAdmin && (policies.LockSubscription || PoliciesSettings.DisableNonAdminCreateUserApi)) { // If we are not admin and subscriptions are locked, we need to check the Policies.CanCreateUser.Key permission var canCreateUser = (await _authorizationService.AuthorizeAsync(User, null, new PolicyRequirement(Policies.CanCreateUser))).Succeeded; if (!isAuth || !canCreateUser) { return(this.CreateAPIPermissionError(Policies.CanCreateUser)); } } var user = new ApplicationUser { UserName = request.Email, Email = request.Email, RequiresEmailConfirmation = policies.RequiresConfirmedEmail, Created = DateTimeOffset.UtcNow, }; var passwordValidation = await this._passwordValidator.ValidateAsync(_userManager, user, request.Password); if (!passwordValidation.Succeeded) { foreach (var error in passwordValidation.Errors) { ModelState.AddModelError(nameof(request.Password), error.Description); } return(this.CreateValidationError(ModelState)); } if (!isAdmin) { if (!await _throttleService.Throttle(ZoneLimits.Register, this.HttpContext.Connection.RemoteIpAddress, cancellationToken)) { return(new TooManyRequestsResult(ZoneLimits.Register)); } } var identityResult = await _userManager.CreateAsync(user, request.Password); if (!identityResult.Succeeded) { foreach (var error in identityResult.Errors) { if (error.Code == "DuplicateUserName") { ModelState.AddModelError(nameof(request.Email), error.Description); } else { ModelState.AddModelError(string.Empty, error.Description); } } return(this.CreateValidationError(ModelState)); } if (request.IsAdministrator is true) { if (!anyAdmin) { await _roleManager.CreateAsync(new IdentityRole(Roles.ServerAdmin)); } await _userManager.AddToRoleAsync(user, Roles.ServerAdmin); if (!anyAdmin) { var settings = await _settingsRepository.GetSettingAsync <ThemeSettings>(); if (settings != null) { settings.FirstRun = false; await _settingsRepository.UpdateSetting(settings); } await _settingsRepository.FirstAdminRegistered(policies, _options.UpdateUrl != null, _options.DisableRegistration, Logs); } } _eventAggregator.Publish(new UserRegisteredEvent() { RequestUri = Request.GetAbsoluteRootUri(), User = user, Admin = request.IsAdministrator is true });
internal async Task <InvoiceEntity> CreateInvoiceCoreRaw(InvoiceEntity entity, StoreData store, IPaymentFilter?invoicePaymentMethodFilter, string[]?additionalSearchTerms = null, CancellationToken cancellationToken = default) { InvoiceLogs logs = new InvoiceLogs(); logs.Write("Creation of invoice starting", InvoiceEventData.EventSeverity.Info); var storeBlob = store.GetStoreBlob(); if (string.IsNullOrEmpty(entity.Currency)) { entity.Currency = storeBlob.DefaultCurrency; } entity.Currency = entity.Currency.Trim().ToUpperInvariant(); entity.Price = Math.Max(0.0m, entity.Price); var currencyInfo = _CurrencyNameTable.GetNumberFormatInfo(entity.Currency, false); if (currencyInfo != null) { entity.Price = entity.Price.RoundToSignificant(currencyInfo.CurrencyDecimalDigits); } if (entity.Metadata.TaxIncluded is decimal taxIncluded) { if (currencyInfo != null) { taxIncluded = taxIncluded.RoundToSignificant(currencyInfo.CurrencyDecimalDigits); } taxIncluded = Math.Max(0.0m, taxIncluded); taxIncluded = Math.Min(taxIncluded, entity.Price); entity.Metadata.TaxIncluded = taxIncluded; } var getAppsTaggingStore = _InvoiceRepository.GetAppsTaggingStore(store.Id); if (entity.Metadata.BuyerEmail != null) { if (!MailboxAddressValidator.IsMailboxAddress(entity.Metadata.BuyerEmail)) { throw new BitpayHttpException(400, "Invalid email"); } entity.RefundMail = entity.Metadata.BuyerEmail; } entity.Status = InvoiceStatusLegacy.New; HashSet <CurrencyPair> currencyPairsToFetch = new HashSet <CurrencyPair>(); var rules = storeBlob.GetRateRules(_NetworkProvider); var excludeFilter = storeBlob.GetExcludedPaymentMethods(); // Here we can compose filters from other origin with PaymentFilter.Any() if (invoicePaymentMethodFilter != null) { excludeFilter = PaymentFilter.Or(excludeFilter, invoicePaymentMethodFilter); } foreach (var network in store.GetSupportedPaymentMethods(_NetworkProvider) .Where(s => !excludeFilter.Match(s.PaymentId)) .Select(c => _NetworkProvider.GetNetwork <BTCPayNetworkBase>(c.PaymentId.CryptoCode)) .Where(c => c != null)) { currencyPairsToFetch.Add(new CurrencyPair(network.CryptoCode, entity.Currency)); foreach (var paymentMethodCriteria in storeBlob.PaymentMethodCriteria) { if (paymentMethodCriteria.Value != null) { currencyPairsToFetch.Add(new CurrencyPair(network.CryptoCode, paymentMethodCriteria.Value.Currency)); } } } var rateRules = storeBlob.GetRateRules(_NetworkProvider); var fetchingByCurrencyPair = _RateProvider.FetchRates(currencyPairsToFetch, rateRules, cancellationToken); var fetchingAll = WhenAllFetched(logs, fetchingByCurrencyPair); List <ISupportedPaymentMethod> supported = new List <ISupportedPaymentMethod>(); var paymentMethods = new PaymentMethodDictionary(); bool noNeedForMethods = entity.Type != InvoiceType.TopUp && entity.Price == 0m; if (!noNeedForMethods) { // This loop ends with .ToList so we are querying all payment methods at once // instead of sequentially to improve response time var x1 = store.GetSupportedPaymentMethods(_NetworkProvider) .Where(s => !excludeFilter.Match(s.PaymentId) && _paymentMethodHandlerDictionary.Support(s.PaymentId)) .Select(c => (Handler: _paymentMethodHandlerDictionary[c.PaymentId], SupportedPaymentMethod: c, Network: _NetworkProvider.GetNetwork <BTCPayNetworkBase>(c.PaymentId.CryptoCode))) .Where(c => c.Network != null).ToList(); var pmis = x1.Select(tuple => tuple.SupportedPaymentMethod.PaymentId).ToHashSet(); foreach (var o in x1 .Select(o => (SupportedPaymentMethod: o.SupportedPaymentMethod, PaymentMethod: CreatePaymentMethodAsync(fetchingByCurrencyPair, o.Handler, o.SupportedPaymentMethod, o.Network, entity, store, logs, pmis))) .ToList()) { var paymentMethod = await o.PaymentMethod; if (paymentMethod == null) { continue; } supported.Add(o.SupportedPaymentMethod); paymentMethods.Add(paymentMethod); } if (supported.Count == 0) { StringBuilder errors = new StringBuilder(); if (!store.GetSupportedPaymentMethods(_NetworkProvider).Any()) { errors.AppendLine( "Warning: No wallet has been linked to your BTCPay Store. See the following link for more information on how to connect your store and wallet. (https://docs.btcpayserver.org/WalletSetup/)"); } else { errors.AppendLine("Warning: You have payment methods configured but none of them match any of the requested payment methods (e.g., you requested on-chain BTC invoice but don't have an on-chain wallet configured)"); } foreach (var error in logs.ToList()) { errors.AppendLine(error.ToString()); } throw new BitpayHttpException(400, errors.ToString()); } } entity.SetSupportedPaymentMethods(supported); entity.SetPaymentMethods(paymentMethods); foreach (var app in await getAppsTaggingStore) { entity.InternalTags.Add(AppService.GetAppInternalTag(app.Id)); } using (logs.Measure("Saving invoice")) { entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, additionalSearchTerms); } _ = Task.Run(async() => { try { await fetchingAll; } catch (AggregateException ex) { ex.Handle(e => { logs.Write($"Error while fetching rates {ex}", InvoiceEventData.EventSeverity.Error); return(true); }); } await _InvoiceRepository.AddInvoiceLogs(entity.Id, logs); }); _EventAggregator.Publish(new Events.InvoiceEvent(entity, InvoiceEvent.Created)); return(entity); }