using System; using System.IO; using System.Linq; using System.Threading; using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Models; using BTCPayServer.Abstractions.Services; using BTCPayServer.Common; using BTCPayServer.Client; using BTCPayServer.Configuration; using BTCPayServer.Controllers; using BTCPayServer.Controllers.GreenField; using BTCPayServer.Data; using BTCPayServer.Data.Payouts.LightningLike; using BTCPayServer.HostedServices; using BTCPayServer.Lightning; using BTCPayServer.Logging; using BTCPayServer.PaymentRequest; using BTCPayServer.Payments; using BTCPayServer.Payments.Bitcoin; using BTCPayServer.Payments.Lightning; using BTCPayServer.Payments.PayJoin; using BTCPayServer.Plugins; using BTCPayServer.Security; using BTCPayServer.Security.Bitpay; using BTCPayServer.Security.GreenField; using BTCPayServer.Services; using BTCPayServer.Services.Apps; using BTCPayServer.Services.Fees; using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Labels; using BTCPayServer.Services.Mails; using BTCPayServer.Services.Notifications; using BTCPayServer.Services.Notifications.Blobs; using BTCPayServer.Services.PaymentRequests; using BTCPayServer.Services.Rates; using BTCPayServer.Services.Stores; using BTCPayServer.Services.Wallets; using BundlerMinifier.TagHelpers; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NBitcoin; using NBitpayClient; using NBXplorer.DerivationStrategy; using Newtonsoft.Json; using NicolasDorier.RateLimits; using Serilog; using NBitcoin.RPC; #if ALTCOINS using BTCPayServer.Services.Altcoins.Monero; #endif namespace BTCPayServer.Hosting { public static class BTCPayServerServices { public static IServiceCollection RegisterJsonConverter(this IServiceCollection services, Func create) { services.AddSingleton((s) => new JsonConverterRegistration(create)); return services; } public static IServiceCollection AddBTCPayServer(this IServiceCollection services, IConfiguration configuration, Logs logs) { services.AddSingleton(o => o.GetRequiredService>().Value); services.AddDbContext((provider, o) => { var factory = provider.GetRequiredService(); factory.ConfigureBuilder(o); }); services.AddHttpClient(); services.AddHttpClient(nameof(ExplorerClientProvider), httpClient => { httpClient.Timeout = Timeout.InfiniteTimeSpan; }); services.AddSingleton(logs); services.AddSingleton(); services.AddPayJoinServices(); #if ALTCOINS services.AddMoneroLike(); #endif services.TryAddSingleton(); services.TryAddSingleton(provider => provider.GetService()); services.TryAddSingleton(); services.TryAddSingleton(); services.AddSingleton(provider => provider.GetRequiredService()); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(o => o.GetRequiredService>().Value); // Don't move this StartupTask, we depend on it being right here services.AddStartupTask(); // services.AddStartupTask(); services.TryAddSingleton(); services.AddSingleton(); services.AddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.AddSingleton(); services.AddOptions().Configure( (options) => { options.LoadArgs(configuration, logs); }); services.AddOptions().Configure( (options) => { options.Configure(configuration); }); services.AddOptions().Configure>( (options, datadirs) => { var postgresConnectionString = configuration["postgres"]; var mySQLConnectionString = configuration["mysql"]; var sqliteFileName = configuration["sqlitefile"]; if (!string.IsNullOrEmpty(postgresConnectionString)) { options.DatabaseType = DatabaseType.Postgres; options.ConnectionString = postgresConnectionString; } else if (!string.IsNullOrEmpty(mySQLConnectionString)) { options.DatabaseType = DatabaseType.MySQL; options.ConnectionString = mySQLConnectionString; } else if (!string.IsNullOrEmpty(sqliteFileName)) { var connStr = "Data Source=" + (Path.IsPathRooted(sqliteFileName) ? sqliteFileName : Path.Combine(datadirs.Value.DataDir, sqliteFileName)); options.DatabaseType = DatabaseType.Sqlite; options.ConnectionString = connStr; } else { throw new InvalidOperationException("No database option was configured."); } }); services.AddOptions().Configure( (options, btcPayNetworkProvider) => { foreach (BTCPayNetwork btcPayNetwork in btcPayNetworkProvider.GetAll().OfType()) { NBXplorerConnectionSetting setting = new NBXplorerConnectionSetting { CryptoCode = btcPayNetwork.CryptoCode, ExplorerUri = configuration.GetOrDefault( $"{btcPayNetwork.CryptoCode}.explorer.url", btcPayNetwork.NBXplorerNetwork.DefaultSettings.DefaultUrl), CookieFile = configuration.GetOrDefault( $"{btcPayNetwork.CryptoCode}.explorer.cookiefile", btcPayNetwork.NBXplorerNetwork.DefaultSettings.DefaultCookieFile) }; options.NBXplorerConnectionSettings.Add(setting); } }); services.AddOptions().Configure( (options, btcPayNetworkProvider) => { foreach (var net in btcPayNetworkProvider.GetAll().OfType()) { var lightning = configuration.GetOrDefault($"{net.CryptoCode}.lightning", string.Empty); if (lightning.Length != 0) { if (!LightningConnectionString.TryParse(lightning, true, out var connectionString, out var error)) { logs.Configuration.LogWarning($"Invalid setting {net.CryptoCode}.lightning, " + Environment.NewLine + $"If you have a c-lightning server use: 'type=clightning;server=/root/.lightning/lightning-rpc', " + Environment.NewLine + $"If you have a lightning charge server: 'type=charge;server=https://charge.example.com;api-token=yourapitoken'" + Environment.NewLine + $"If you have a lnd server: 'type=lnd-rest;server=https://lnd:lnd@lnd.example.com;macaroon=abf239...;certthumbprint=2abdf302...'" + Environment.NewLine + $" lnd server: 'type=lnd-rest;server=https://lnd:lnd@lnd.example.com;macaroonfilepath=/root/.lnd/admin.macaroon;certthumbprint=2abdf302...'" + Environment.NewLine + $"If you have an eclair server: 'type=eclair;server=http://eclair.com:4570;password=eclairpassword;bitcoin-host=bitcoind:37393;bitcoin-auth=bitcoinrpcuser:bitcoinrpcpassword" + Environment.NewLine + $" eclair server: 'type=eclair;server=http://eclair.com:4570;password=eclairpassword;bitcoin-host=bitcoind:37393" + Environment.NewLine + $"Error: {error}" + Environment.NewLine + "This service will not be exposed through BTCPay Server"); } else { if (connectionString.IsLegacy) { logs.Configuration.LogWarning( $"Setting {net.CryptoCode}.lightning is a deprecated format, it will work now, but please replace it for future versions with '{connectionString.ToString()}'"); } options.InternalLightningByCryptoCode.Add(net.CryptoCode, connectionString); } } } }); services.AddOptions().Configure( (options, btcPayNetworkProvider) => { foreach (var net in btcPayNetworkProvider.GetAll().OfType()) { options.ExternalServices.Load(net.CryptoCode, configuration); } options.ExternalServices.LoadNonCryptoServices(configuration); var services = configuration.GetOrDefault("externalservices", null); if (services != null) { foreach (var service in services.Split(new[] {';', ','}, StringSplitOptions.RemoveEmptyEntries) .Select(p => (p, SeparatorIndex: p.IndexOf(':', StringComparison.OrdinalIgnoreCase))) .Where(p => p.SeparatorIndex != -1) .Select(p => (Name: p.p.Substring(0, p.SeparatorIndex), Link: p.p.Substring(p.SeparatorIndex + 1)))) { if (Uri.TryCreate(service.Link, UriKind.RelativeOrAbsolute, out var uri)) options.OtherExternalServices.AddOrReplace(service.Name, uri); } } }); services.TryAddSingleton(o => configuration.ConfigureNetworkProvider(logs)); services.TryAddSingleton(); services.AddSingleton(); services.AddSingleton(); services.TryAddTransient(); services.TryAddSingleton(o => { var htmlSanitizer = new Ganss.XSS.HtmlSanitizer(); htmlSanitizer.RemovingAtRule += (sender, args) => { }; htmlSanitizer.RemovingTag += (sender, args) => { if (args.Tag.TagName.Equals("img", StringComparison.InvariantCultureIgnoreCase)) { if (!args.Tag.ClassList.Contains("img-fluid")) { args.Tag.ClassList.Add("img-fluid"); } args.Cancel = true; } }; htmlSanitizer.RemovingAttribute += (sender, args) => { if (args.Tag.TagName.Equals("img", StringComparison.InvariantCultureIgnoreCase) && args.Attribute.Name.Equals("src", StringComparison.InvariantCultureIgnoreCase) && args.Reason == Ganss.XSS.RemoveReason.NotAllowedUrlValue) { args.Cancel = true; } }; htmlSanitizer.RemovingStyle += (sender, args) => { args.Cancel = true; }; htmlSanitizer.AllowedAttributes.Add("class"); htmlSanitizer.AllowedTags.Add("iframe"); htmlSanitizer.AllowedTags.Add("style"); htmlSanitizer.AllowedTags.Remove("img"); htmlSanitizer.AllowedAttributes.Add("webkitallowfullscreen"); htmlSanitizer.AllowedAttributes.Add("allowfullscreen"); return htmlSanitizer; }); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.AddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.AddSingleton( provider => provider.GetService()); services.TryAddSingleton(CurrencyNameTable.Instance); services.TryAddSingleton(o => new NBXplorerFeeProviderFactory(o.GetRequiredService()) { Fallback = new FeeRate(100L, 1) }); services.Configure((o) => { o.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(WalletId))); o.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(DerivationStrategyBase))); }); services.AddSingleton(); services.AddSingleton(o => o.GetRequiredService()); services.AddSingleton(); services.AddSingleton(o => o.GetRequiredService()); services.AddHttpClient(WebhookNotificationManager.OnionNamedClient) .ConfigurePrimaryHttpMessageHandler(); services.AddSingleton(); services.AddSingleton(); services.AddHttpClient(LightningLikePayoutHandler.LightningLikePayoutHandlerOnionNamedClient) .ConfigurePrimaryHttpMessageHandler(); services.AddSingleton(); services.AddSingleton(o => o.GetRequiredService()); services.AddSingleton(); services.AddSingleton(provider => provider.GetService()); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(provider => provider.GetService()); services.AddSingleton(); services.AddSingleton(provider => provider.GetService()); services.AddSingleton(new UIExtension("LNURL/LightningAddressOption", "store-integrations-list")); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddScoped(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddScoped(); services.AddScoped(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); #if DEBUG services.AddSingleton(); #endif services.TryAddSingleton(); services.AddSingleton(x => x.GetRequiredService()); services.TryAddSingleton(o => { if (o.GetRequiredService().NetworkType == ChainName.Mainnet) return new Bitpay(new Key(), new Uri("https://bitpay.com/")); else return new Bitpay(new Key(), new Uri("https://test.bitpay.com/")); }); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddScoped(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); // Add application services. services.AddSingleton(); //create a simple client which hooks up to the http scope services.AddScoped(); //also provide a factory that can impersonate user/store id services.AddSingleton(); services.AddAPIKeyAuthentication(); services.AddBtcPayServerAuthenticationSchemes(); services.AddAuthorization(o => o.AddBTCPayPolicies()); // bundling services.AddSingleton(); services.AddTransient(provider => { var opts = provider.GetRequiredService(); var bundle = new BundleOptions(); bundle.UseBundles = opts.BundleJsCss; bundle.AppendVersion = true; return bundle; }); services.AddCors(options => { options.AddPolicy(CorsPolicies.All, p => p.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin()); }); services.AddSingleton(provider => { var btcPayEnv = provider.GetService(); var rateLimits = new RateLimitService(); if (btcPayEnv.IsDeveloping) { rateLimits.SetZone($"zone={ZoneLimits.Login} rate=1000r/min burst=100 nodelay"); rateLimits.SetZone($"zone={ZoneLimits.Register} rate=1000r/min burst=100 nodelay"); rateLimits.SetZone($"zone={ZoneLimits.PayJoin} rate=1000r/min burst=100 nodelay"); rateLimits.SetZone($"zone={ZoneLimits.Shopify} rate=1000r/min burst=100 nodelay"); rateLimits.SetZone($"zone={ZoneLimits.ForgotPassword} rate=5r/d burst=3 nodelay"); } else { rateLimits.SetZone($"zone={ZoneLimits.Login} rate=5r/min burst=3 nodelay"); rateLimits.SetZone($"zone={ZoneLimits.Register} rate=2r/min burst=2 nodelay"); rateLimits.SetZone($"zone={ZoneLimits.PayJoin} rate=5r/min burst=3 nodelay"); rateLimits.SetZone($"zone={ZoneLimits.Shopify} rate=20r/min burst=3 nodelay"); rateLimits.SetZone($"zone={ZoneLimits.ForgotPassword} rate=5r/d burst=5 nodelay"); } return rateLimits; }); services.AddLogging(logBuilder => { var debugLogFile = BTCPayServerOptions.GetDebugLog(configuration); if (!string.IsNullOrEmpty(debugLogFile)) { Serilog.Log.Logger = new LoggerConfiguration() .Enrich.FromLogContext() .MinimumLevel.Is(BTCPayServerOptions.GetDebugLogLevel(configuration)) .WriteTo.File(debugLogFile, rollingInterval: RollingInterval.Day, fileSizeLimitBytes: MAX_DEBUG_LOG_FILE_SIZE, rollOnFileSizeLimit: true, retainedFileCountLimit: 1) .CreateLogger(); logBuilder.AddProvider(new Serilog.Extensions.Logging.SerilogLoggerProvider(Log.Logger)); } }); services.AddSingleton(); services.SkipModelValidation(); if (configuration.GetOrDefault("cheatmode", false)) { services.AddSingleton(); services.AddSingleton(o => o.GetRequiredService()); } return services; } public static void SkipModelValidation(this IServiceCollection services) { services.AddSingleton>(); } private const long MAX_DEBUG_LOG_FILE_SIZE = 2000000; // If debug log is in use roll it every N MB. private static void AddBtcPayServerAuthenticationSchemes(this IServiceCollection services) { services.AddAuthentication() .AddBitpayAuthentication() .AddAPIKeyAuthentication(); } public static IApplicationBuilder UsePayServer(this IApplicationBuilder app) { app.UseMiddleware(); app.UseMiddleware(); return app; } public static IApplicationBuilder UseHeadersOverride(this IApplicationBuilder app) { app.UseMiddleware(); return app; } } }