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);
        }