btcpayserver/BTCPayServer/Hosting/Startup.cs

370 lines
17 KiB
C#
Raw Normal View History

2020-06-28 21:44:35 -05:00
using System;
Bootstrap v5 migration (#2490) * Swap bootstrap asset files * Update themes and color definitions * Move general bootstrap customizations * Theme updates Theme updates * Remove BuildBundlerMinifier This lead to an error, because BuildBundlerMinifier and BundlerMinifier.Core seem to conflict here. Details: https://stackoverflow.com/a/61119586 * Rewplace btn-block class with w-100 * Update badge classes * Remove old font family head variable * Update margin classes * Cleanups * Update float classes * Update text classes * Update padding classes * Update border classes * UPdate dropdown classes * Update select classes * Update neutral custom props * Update bootstrap and customizations * Update ChromeDriver; disable smooth scroll https://github.com/SeleniumHQ/selenium/issues/8295 * Improve alert messages * Improve bootstrap customizations * Disable reduced motion See also 7358282f * Update Bootstrap data attributes * Update file inputs * Update input groups * Replace deprecated jumbotron class * Update variables; re-add negative margin util classes * Update cards * Update form labels * Debug alerts * Fix aria-labelledby associations * Dropdown-related test fixes * Fix CanUseWebhooks test * Test fixes * Nav updates * Fix nav usage in wallet send and payouts * Update alert and modal close buttons * Re-add backdrop properties * Upgrade Bootstrap to v5 final * Update screen reader classes * Update font-weight classes * Update monospace font classes * Update accordians * Update close icon usage * Cleanup * Update scripts and style integrations * Update input group texts * Update LN node setup page * Update more form control classes * Update inline forms * Add js specific test * Upgrade Vue.js * Remove unused JS * Upgrade Bootstrap to v5.0.1 * Try container related test updates * Separate jQuery bundle * Remove jQuery from LND seed backup page * Remove unused code * Refactor email autofill js * Refactor camera scanner JS * Re-add tests * Re-add BuildBundlerMinifier * Do not minify bundles containing Bootstrap Details https://github.com/madskristensen/BundlerMinifier/issues/558 * Update bundles * Cleanup JS test * Cleanup tests involving dropdowns * Cleanup tests involving collapses * Cleanup locale additions in ConfigureCore * Cleanup bundles * Remove duplicate status message * Cleanup formatting * Fix missing validation scripts * Remove unused unminified Bootstrap js files * Fix classic theme * Fix Casa theme * Fix PoS validation
2021-05-19 04:39:27 +02:00
using System.Globalization;
2017-09-25 17:18:13 +09:00
using System.IO;
using System.Linq;
2018-02-13 03:27:36 +09:00
using System.Net;
using BTCPayServer.Abstractions.Extensions;
2020-06-28 17:55:27 +09:00
using BTCPayServer.Configuration;
2022-01-14 13:05:23 +09:00
using BTCPayServer.Controllers.Greenfield;
2020-06-28 17:55:27 +09:00
using BTCPayServer.Data;
using BTCPayServer.Fido2;
2020-06-28 17:55:27 +09:00
using BTCPayServer.Filters;
using BTCPayServer.Logging;
2019-02-22 11:37:45 +01:00
using BTCPayServer.PaymentRequest;
using BTCPayServer.Plugins;
2020-06-28 17:55:27 +09:00
using BTCPayServer.Security;
using BTCPayServer.Services.Apps;
using BTCPayServer.Storage;
2021-12-31 16:59:02 +09:00
using Fido2NetLib;
using Microsoft.AspNetCore.Authentication;
2021-03-30 11:41:44 +09:00
using Microsoft.AspNetCore.Authentication.Cookies;
2020-06-28 17:55:27 +09:00
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
2020-06-28 17:55:27 +09:00
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Identity;
2021-12-31 16:59:02 +09:00
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Rewrite;
2020-06-28 17:55:27 +09:00
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.FileProviders;
2020-06-28 17:55:27 +09:00
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Localization;
2020-06-28 17:55:27 +09:00
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
2020-06-28 17:55:27 +09:00
using Microsoft.Net.Http.Headers;
using NBXplorer;
using NicolasDorier.RateLimits;
2017-09-13 15:47:34 +09:00
namespace BTCPayServer.Hosting
{
public class Startup
{
2019-10-03 17:06:49 +09:00
public Startup(IConfiguration conf, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
Configuration = conf;
_Env = env;
LoggerFactory = loggerFactory;
2021-11-22 17:16:08 +09:00
Logs = new Logs();
Logs.Configure(loggerFactory);
}
readonly IWebHostEnvironment _Env;
public IConfiguration Configuration
{
get; set;
}
public ILoggerFactory LoggerFactory { get; }
2021-11-22 17:16:08 +09:00
public Logs Logs { get; }
public static ServiceProvider CreateBootstrap(IConfiguration conf)
{
return CreateBootstrap(conf, new Logs(), new FuncLoggerFactory(n => NullLogger.Instance));
}
public static ServiceProvider CreateBootstrap(IConfiguration conf, Logs logs, ILoggerFactory loggerFactory)
{
ServiceCollection bootstrapServices = new ServiceCollection();
var networkType = DefaultConfiguration.GetNetworkType(conf);
bootstrapServices.AddSingleton(logs);
bootstrapServices.AddSingleton(loggerFactory);
bootstrapServices.AddSingleton<IConfiguration>(conf);
bootstrapServices.AddSingleton<SelectedChains>();
bootstrapServices.AddSingleton<NBXplorerNetworkProvider>(new NBXplorerNetworkProvider(networkType));
return bootstrapServices.BuildServiceProvider();
}
public void ConfigureServices(IServiceCollection services)
{
var bootstrapServiceProvider = CreateBootstrap(Configuration, Logs, LoggerFactory);
services.AddSingleton(bootstrapServiceProvider.GetRequiredService<SelectedChains>());
services.AddSingleton(bootstrapServiceProvider.GetRequiredService<NBXplorerNetworkProvider>());
services.AddMemoryCache();
2020-01-26 15:02:40 +09:00
services.AddDataProtection()
.SetApplicationName("BTCPay Server")
.PersistKeysToFileSystem(new DirectoryInfo(new DataDirectories().Configure(Configuration).DataDir));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders()
.AddInvitationTokenProvider();
services.Configure<AuthenticationOptions>(opts =>
{
opts.DefaultAuthenticateScheme = null;
opts.DefaultChallengeScheme = null;
opts.DefaultForbidScheme = null;
opts.DefaultScheme = IdentityConstants.ApplicationScheme;
opts.DefaultSignInScheme = null;
opts.DefaultSignOutScheme = null;
});
2021-03-30 11:41:44 +09:00
services.PostConfigure<CookieAuthenticationOptions>(IdentityConstants.ApplicationScheme, opt =>
{
opt.LoginPath = "/login";
2022-01-14 12:20:50 +09:00
opt.AccessDeniedPath = "/errors/403";
2022-01-07 21:47:19 +09:00
opt.LogoutPath = "/logout";
2021-03-30 11:41:44 +09:00
});
services.Configure<SecurityStampValidatorOptions>(opts =>
{
opts.ValidationInterval = TimeSpan.FromMinutes(5.0);
});
2019-10-08 15:21:30 +09:00
2021-11-22 17:16:08 +09:00
services.AddBTCPayServer(Configuration, Logs);
services.AddProviderStorage();
services.AddSession();
services.AddSignalR();
services.AddFido2(options =>
{
options.ServerName = "BTCPay Server";
})
.AddCachedMetadataService(config =>
{
//They'll be used in a "first match wins" way in the order registered
config.AddStaticMetadataRepository();
});
2021-12-31 16:59:02 +09:00
var descriptor = services.Single(descriptor => descriptor.ServiceType == typeof(Fido2Configuration));
services.Remove(descriptor);
services.AddScoped(provider =>
{
var httpContext = provider.GetService<IHttpContextAccessor>();
return new Fido2Configuration()
{
ServerName = "BTCPay Server",
Origin = $"{httpContext.HttpContext.Request.Scheme}://{httpContext.HttpContext.Request.Host}",
ServerDomain = httpContext.HttpContext.Request.Host.Host
};
});
services.AddScoped<Fido2Service>();
services.AddSingleton<UserLoginCodeService>();
services.AddSingleton<LnurlAuthService>();
services.AddSingleton<LightningAddressService>();
2024-01-18 09:47:39 +09:00
services.AddMvc(o =>
2021-12-31 16:59:02 +09:00
{
o.Filters.Add(new XFrameOptionsAttribute(XFrameOptionsAttribute.XFrameOptions.Deny));
2021-12-31 16:59:02 +09:00
o.Filters.Add(new XContentTypeOptionsAttribute("nosniff"));
o.Filters.Add(new XXSSProtectionAttribute());
o.Filters.Add(new ReferrerPolicyAttribute("same-origin"));
o.ModelBinderProviders.Insert(0, new ModelBinders.DefaultModelBinderProvider());
if (!Configuration.GetOrDefault<bool>("nocsp", false))
o.Filters.Add(new ContentSecurityPolicyAttribute(CSPTemplate.AntiXSS));
o.Filters.Add(new JsonHttpExceptionFilter());
o.Filters.Add(new JsonObjectExceptionFilter());
2021-12-31 16:59:02 +09:00
})
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
return new UnprocessableEntityObjectResult(context.ModelState.ToGreenfieldValidationError());
};
})
.AddRazorOptions(o =>
{
// /Components/{View Component Name}/{View Name}.cshtml
o.ViewLocationFormats.Add("/{0}.cshtml");
o.PageViewLocationFormats.Add("/{0}.cshtml");
})
2019-10-06 15:54:19 +09:00
.AddNewtonsoftJson()
.AddRazorRuntimeCompilation()
.AddPlugins(services, Configuration, LoggerFactory, bootstrapServiceProvider)
.AddDataAnnotationsLocalization()
2019-10-06 15:54:19 +09:00
.AddControllersAsServices();
2022-01-14 20:16:28 +09:00
services.AddServerSideBlazor();
2022-01-14 20:16:28 +09:00
LowercaseTransformer.Register(services);
ValidateControllerNameTransformer.Register(services);
2018-07-12 17:38:21 +09:00
services.TryAddScoped<ContentSecurityPolicies>();
2018-01-04 22:56:49 +09:00
services.Configure<IdentityOptions>(options =>
{
options.Password.RequireDigit = false;
options.Password.RequiredLength = 6;
2018-01-04 22:56:49 +09:00
options.Password.RequireLowercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
2018-09-12 13:36:44 +02:00
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
2019-10-08 15:21:30 +09:00
options.Password.RequireUppercase = false;
2018-01-04 22:56:49 +09:00
});
// If the HTTPS certificate path is not set this logic will NOT be used and the default Kestrel binding logic will be.
string httpsCertificateFilePath = Configuration.GetOrDefault<string>("HttpsCertificateFilePath", null);
bool useDefaultCertificate = Configuration.GetOrDefault<bool>("HttpsUseDefaultCertificate", false);
bool hasCertPath = !String.IsNullOrEmpty(httpsCertificateFilePath);
2019-07-12 12:23:13 +09:00
services.Configure<KestrelServerOptions>(kestrel =>
{
kestrel.Limits.MaxRequestLineSize = 8_192 * 10 * 5; // Around 500K, transactions passed in URI should not be bigger than this
});
if (hasCertPath || useDefaultCertificate)
{
var bindAddress = Configuration.GetOrDefault<IPAddress>("bind", IPAddress.Any);
int bindPort = Configuration.GetOrDefault<int>("port", 443);
services.Configure<KestrelServerOptions>(kestrel =>
{
if (hasCertPath && !File.Exists(httpsCertificateFilePath))
{
// Note that by design this is a fatal error condition that will cause the process to exit.
throw new ConfigException($"The https certificate file could not be found at {httpsCertificateFilePath}.");
}
2019-10-08 15:21:30 +09:00
if (hasCertPath && useDefaultCertificate)
{
throw new ConfigException($"Conflicting settings: if HttpsUseDefaultCertificate is true, HttpsCertificateFilePath should not be used");
}
kestrel.Listen(bindAddress, bindPort, l =>
{
if (hasCertPath)
{
Logs.Configuration.LogInformation($"Using HTTPS with the certificate located in {httpsCertificateFilePath}.");
l.UseHttps(httpsCertificateFilePath, Configuration.GetOrDefault<string>("HttpsCertificateFilePassword", null));
}
else
{
Logs.Configuration.LogInformation($"Using HTTPS with the default certificate");
l.UseHttps();
}
});
});
}
}
public void Configure(
IApplicationBuilder app,
2019-10-03 17:06:49 +09:00
IWebHostEnvironment env,
IServiceProvider prov,
2018-04-05 15:50:23 +09:00
BTCPayServerOptions options,
IOptions<DataDirectories> dataDirectories,
ILoggerFactory loggerFactory,
IRateLimitService rateLimits)
2018-04-05 15:50:23 +09:00
{
2021-11-22 17:16:08 +09:00
Logs.Configure(loggerFactory);
2018-04-05 15:50:23 +09:00
Logs.Configuration.LogInformation($"Root Path: {options.RootPath}");
if (options.RootPath.Equals("/", StringComparison.OrdinalIgnoreCase))
{
ConfigureCore(app, env, prov, dataDirectories, rateLimits);
2018-04-05 15:50:23 +09:00
}
else
{
app.Map(options.RootPath, appChild =>
{
ConfigureCore(appChild, env, prov, dataDirectories, rateLimits);
2018-04-05 15:50:23 +09:00
});
}
}
private void ConfigureCore(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider prov, IOptions<DataDirectories> dataDirectories, IRateLimitService rateLimits)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
rateLimits.SetZone($"zone={ZoneLimits.Login} rate=1000r/min burst=100 nodelay");
rateLimits.SetZone($"zone={ZoneLimits.PublicInvoices} 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.PublicInvoices} rate=4r/min burst=10 delay=3");
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");
}
// HACK: blazor server js hard code some path, making it works only on root path. This fix it.
// Workaround this bug https://github.com/dotnet/aspnetcore/issues/43191
var rewriteOptions = new RewriteOptions();
rewriteOptions.AddRewrite("_blazor/(negotiate|initializers|disconnect)$", "/_blazor/$1", skipRemainingRules: true);
rewriteOptions.AddRewrite("_blazor$", "/_blazor", skipRemainingRules: true);
// A rewrite rule to support the old API
rewriteOptions.AddRewrite("api/v1/stores/([^/]+)/payment-methods/[Oo]n[Cc]hain/([^/]+)/(preview|generate)", "/api/v1/stores/$1/payment-methods/$2-CHAIN/wallet/$3", skipRemainingRules: true);
rewriteOptions.AddRewrite("api/v1/stores/([^/]+)/payment-methods/[Oo]n[Cc]hain/([^/]+)(.*)", "/api/v1/stores/$1/payment-methods/$2-CHAIN$3", skipRemainingRules: true);
app.UseRewriter(rewriteOptions);
app.UseHeadersOverride();
2019-03-04 22:34:14 +09:00
var forwardingOptions = new ForwardedHeadersOptions()
{
2019-03-04 22:34:14 +09:00
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
};
forwardingOptions.KnownNetworks.Clear();
forwardingOptions.KnownProxies.Clear();
forwardingOptions.ForwardedHeaders = ForwardedHeaders.All;
app.UseForwardedHeaders(forwardingOptions);
2022-01-14 12:20:50 +09:00
app.UseStatusCodePagesWithReExecute("/errors/{0}");
app.UseExceptionHandler("/errors/{0}");
app.UsePayServer();
app.UseRouting();
app.UseCors();
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = LongCache
});
2020-06-28 17:55:27 +09:00
// The framework during publish automatically publish the js files into
// wwwroot, so this shouldn't be needed.
// But somehow during debug the collocated js files, are error 404!
var componentsFolder = Path.Combine(env.ContentRootPath, "Components");
if (Directory.Exists(componentsFolder))
{
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new PhysicalFileProvider(componentsFolder),
RequestPath = "/Components"
});
}
app.UseProviderStorage(dataDirectories);
app.UseAuthentication();
2019-10-06 01:21:00 +09:00
app.UseAuthorization();
app.UseSession();
2020-01-12 13:30:54 +09:00
app.UseWebSockets();
2020-01-12 13:30:54 +09:00
2021-03-19 20:54:30 +09:00
app.UseCookiePolicy(new CookiePolicyOptions()
{
HttpOnly = Microsoft.AspNetCore.CookiePolicy.HttpOnlyPolicy.Always,
Secure = Microsoft.AspNetCore.Http.CookieSecurePolicy.SameAsRequest
});
2019-10-03 18:46:09 +09:00
app.UseEndpoints(endpoints =>
{
AppHub.Register(endpoints);
PaymentRequestHub.Register(endpoints);
endpoints.MapBlazorHub().RequireAuthorization();
endpoints.MapRazorPages();
2019-10-06 01:21:00 +09:00
endpoints.MapControllers();
2022-01-14 20:16:28 +09:00
endpoints.MapControllerRoute("default", "{controller:validate=UIHome}/{action:lowercase=Index}/{id?}");
2019-10-03 18:46:09 +09:00
});
app.UsePlugins();
}
private static void LongCache(Microsoft.AspNetCore.StaticFiles.StaticFileResponseContext ctx)
{
// Cache static assets for one year, set asp-append-version="true" on references to update on change.
// https://andrewlock.net/adding-cache-control-headers-to-static-files-in-asp-net-core/
const int durationInSeconds = 60 * 60 * 24 * 365;
ctx.Context.Response.Headers[HeaderNames.CacheControl] = "public,max-age=" + durationInSeconds;
}
private static Action<Microsoft.AspNetCore.StaticFiles.StaticFileResponseContext> NewMethod()
{
return ctx =>
{
// Cache static assets for one year, set asp-append-version="true" on references to update on change.
// https://andrewlock.net/adding-cache-control-headers-to-static-files-in-asp-net-core/
const int durationInSeconds = 60 * 60 * 24 * 365;
ctx.Context.Response.Headers[HeaderNames.CacheControl] = "public,max-age=" + durationInSeconds;
};
}
}
2017-09-13 15:47:34 +09:00
}