diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index a3e2c1ebb..8fe60cc20 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -1,4 +1,4 @@ - + @@ -71,7 +71,7 @@ - + diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldUsersController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldUsersController.cs index 80f0fd8f6..f0236fa4a 100644 --- a/BTCPayServer/Controllers/GreenField/GreenfieldUsersController.cs +++ b/BTCPayServer/Controllers/GreenField/GreenfieldUsersController.cs @@ -35,7 +35,7 @@ namespace BTCPayServer.Controllers.Greenfield private readonly SettingsRepository _settingsRepository; private readonly EventAggregator _eventAggregator; private readonly IPasswordValidator _passwordValidator; - private readonly RateLimitService _throttleService; + private readonly IRateLimitService _throttleService; private readonly BTCPayServerOptions _options; private readonly IAuthorizationService _authorizationService; private readonly UserService _userService; @@ -46,7 +46,7 @@ namespace BTCPayServer.Controllers.Greenfield PoliciesSettings policiesSettings, EventAggregator eventAggregator, IPasswordValidator passwordValidator, - RateLimitService throttleService, + IRateLimitService throttleService, BTCPayServerOptions options, IAuthorizationService authorizationService, UserService userService, diff --git a/BTCPayServer/Controllers/UIAppsPublicController.cs b/BTCPayServer/Controllers/UIAppsPublicController.cs index 7b96f3a66..6fdce9295 100644 --- a/BTCPayServer/Controllers/UIAppsPublicController.cs +++ b/BTCPayServer/Controllers/UIAppsPublicController.cs @@ -18,6 +18,7 @@ using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using NBitpayClient; +using NicolasDorier.RateLimits; using static BTCPayServer.Controllers.UIAppsController; namespace BTCPayServer.Controllers @@ -116,6 +117,7 @@ namespace BTCPayServer.Controllers [IgnoreAntiforgeryToken] [EnableCors(CorsPolicies.All)] [DomainMappingConstraint(AppType.PointOfSale)] + [RateLimitsFilter(ZoneLimits.PublicInvoices, Scope = RateLimitsScope.RemoteAddress)] public async Task ViewPointOfSale(string appId, PosViewType viewType, [ModelBinder(typeof(InvariantDecimalModelBinder))] decimal? amount, @@ -292,6 +294,7 @@ namespace BTCPayServer.Controllers [IgnoreAntiforgeryToken] [EnableCors(CorsPolicies.All)] [DomainMappingConstraintAttribute(AppType.Crowdfund)] + [RateLimitsFilter(ZoneLimits.PublicInvoices, Scope = RateLimitsScope.RemoteAddress)] public async Task ContributeToCrowdfund(string appId, ContributeToCrowdfund request, CancellationToken cancellationToken) { diff --git a/BTCPayServer/Controllers/UIPublicController.cs b/BTCPayServer/Controllers/UIPublicController.cs index 4c098f028..22d5cb53b 100644 --- a/BTCPayServer/Controllers/UIPublicController.cs +++ b/BTCPayServer/Controllers/UIPublicController.cs @@ -10,6 +10,7 @@ using BTCPayServer.Plugins.PayButton.Models; using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; +using NicolasDorier.RateLimits; namespace BTCPayServer.Controllers { @@ -38,6 +39,7 @@ namespace BTCPayServer.Controllers [Route("api/v1/invoices")] [IgnoreAntiforgeryToken] [EnableCors(CorsPolicies.All)] + [RateLimitsFilter(ZoneLimits.PublicInvoices, Scope = RateLimitsScope.RemoteAddress)] public async Task PayButtonHandle([FromForm] PayButtonViewModel model, CancellationToken cancellationToken) { var store = await _StoreRepository.FindStore(model.StoreId); diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index 0b2dd2c65..3bc391c3a 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -437,28 +437,7 @@ namespace BTCPayServer.Hosting { 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.AddRateLimits(); services.AddLogging(logBuilder => { var debugLogFile = BTCPayServerOptions.GetDebugLog(configuration); diff --git a/BTCPayServer/Hosting/Startup.cs b/BTCPayServer/Hosting/Startup.cs index b3da866b5..c2230ea7b 100644 --- a/BTCPayServer/Hosting/Startup.cs +++ b/BTCPayServer/Hosting/Startup.cs @@ -33,6 +33,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; +using NicolasDorier.RateLimits; namespace BTCPayServer.Hosting { @@ -207,28 +208,45 @@ namespace BTCPayServer.Hosting IServiceProvider prov, BTCPayServerOptions options, IOptions dataDirectories, - ILoggerFactory loggerFactory) + ILoggerFactory loggerFactory, + IRateLimitService rateLimits) { Logs.Configure(loggerFactory); Logs.Configuration.LogInformation($"Root Path: {options.RootPath}"); if (options.RootPath.Equals("/", StringComparison.OrdinalIgnoreCase)) { - ConfigureCore(app, env, prov, dataDirectories); + ConfigureCore(app, env, prov, dataDirectories, rateLimits); } else { app.Map(options.RootPath, appChild => { - ConfigureCore(appChild, env, prov, dataDirectories); + ConfigureCore(appChild, env, prov, dataDirectories, rateLimits); }); } } - private void ConfigureCore(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider prov, IOptions dataDirectories) + private void ConfigureCore(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider prov, IOptions 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"); + } + app.UseHeadersOverride(); var forwardingOptions = new ForwardedHeadersOptions() { diff --git a/BTCPayServer/ZoneLimits.cs b/BTCPayServer/ZoneLimits.cs index fe2adb7f9..42e1faf64 100644 --- a/BTCPayServer/ZoneLimits.cs +++ b/BTCPayServer/ZoneLimits.cs @@ -7,5 +7,6 @@ namespace BTCPayServer public const string PayJoin = "PayJoin"; public const string Shopify = nameof(Shopify); public const string ForgotPassword = "forgotpassword"; + public const string PublicInvoices = "publicinvoices"; } }