mirror of
https://github.com/aljazceru/BTCPayServerPlugins.git
synced 2025-12-17 07:34:24 +01:00
Dynamic rate limit plugin
This commit is contained in:
@@ -51,6 +51,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.Tests"
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.DataErasure", "Plugins\BTCPayServer.Plugins.DataErasure\BTCPayServer.Plugins.DataErasure.csproj", "{034D1487-81C2-4250-A26E-162579C43C18}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.DataErasure", "Plugins\BTCPayServer.Plugins.DataErasure\BTCPayServer.Plugins.DataErasure.csproj", "{034D1487-81C2-4250-A26E-162579C43C18}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.DynamicRateLimits", "Plugins\BTCPayServer.Plugins.DynamicRateLimits\BTCPayServer.Plugins.DynamicRateLimits.csproj", "{C6033B0A-1070-4908-8A4E-F7B32C5007DB}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -243,6 +245,14 @@ Global
|
|||||||
{034D1487-81C2-4250-A26E-162579C43C18}.Altcoins-Debug|Any CPU.Build.0 = Debug|Any CPU
|
{034D1487-81C2-4250-A26E-162579C43C18}.Altcoins-Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{034D1487-81C2-4250-A26E-162579C43C18}.Altcoins-Release|Any CPU.ActiveCfg = Debug|Any CPU
|
{034D1487-81C2-4250-A26E-162579C43C18}.Altcoins-Release|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{034D1487-81C2-4250-A26E-162579C43C18}.Altcoins-Release|Any CPU.Build.0 = Debug|Any CPU
|
{034D1487-81C2-4250-A26E-162579C43C18}.Altcoins-Release|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C6033B0A-1070-4908-8A4E-F7B32C5007DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C6033B0A-1070-4908-8A4E-F7B32C5007DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C6033B0A-1070-4908-8A4E-F7B32C5007DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C6033B0A-1070-4908-8A4E-F7B32C5007DB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C6033B0A-1070-4908-8A4E-F7B32C5007DB}.Altcoins-Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C6033B0A-1070-4908-8A4E-F7B32C5007DB}.Altcoins-Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C6033B0A-1070-4908-8A4E-F7B32C5007DB}.Altcoins-Release|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C6033B0A-1070-4908-8A4E-F7B32C5007DB}.Altcoins-Release|Any CPU.Build.0 = Debug|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{B19C9F52-DC47-466D-8B5C-2D202B7B003F} = {9E04ECE9-E304-4FF2-9CBC-83256E6C6962}
|
{B19C9F52-DC47-466D-8B5C-2D202B7B003F} = {9E04ECE9-E304-4FF2-9CBC-83256E6C6962}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<LangVersion>10</LangVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<!-- -->
|
||||||
|
<!-- Plugin specific properties -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<Product>Dynamic Rate Limit</Product>
|
||||||
|
<Description>Allows you to override the default rate limiting.</Description>
|
||||||
|
<Version>1.0.0</Version>
|
||||||
|
</PropertyGroup>
|
||||||
|
<!-- Plugin development properties -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
|
||||||
|
<PreserveCompilationContext>false</PreserveCompilationContext>
|
||||||
|
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<!-- This will make sure that referencing BTCPayServer doesn't put any artifact in the published directory -->
|
||||||
|
<ItemDefinitionGroup>
|
||||||
|
<ProjectReference>
|
||||||
|
<Properties>StaticWebAssetsEnabled=false</Properties>
|
||||||
|
<Private>false</Private>
|
||||||
|
<ExcludeAssets>runtime;native;build;buildTransitive;contentFiles</ExcludeAssets>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="Resources\**" />
|
||||||
|
<ProjectReference Include="..\..\submodules\btcpayserver\BTCPayServer\BTCPayServer.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace BTCPayServer.Plugins.DynamicRateLimits
|
||||||
|
{
|
||||||
|
public class DynamicRateLimitSettings
|
||||||
|
{
|
||||||
|
public string[] RateLimits { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using BTCPayServer.Abstractions.Contracts;
|
||||||
|
using BTCPayServer.Abstractions.Models;
|
||||||
|
using BTCPayServer.Abstractions.Services;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Plugins.DynamicRateLimits;
|
||||||
|
|
||||||
|
public class DynamicRateLimitsPlugin : BaseBTCPayServerPlugin
|
||||||
|
{
|
||||||
|
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
|
||||||
|
{
|
||||||
|
new() { Identifier = nameof(BTCPayServer), Condition = ">=1.8.0" }
|
||||||
|
};
|
||||||
|
public override void Execute(IServiceCollection applicationBuilder)
|
||||||
|
{
|
||||||
|
applicationBuilder.AddSingleton<DynamicRateLimitsService>();
|
||||||
|
applicationBuilder.AddSingleton<IHostedService>(provider => provider.GetRequiredService<DynamicRateLimitsService>());
|
||||||
|
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("DynamicRateLimitsPlugin/Nav",
|
||||||
|
"server-nav"));
|
||||||
|
base.Execute(applicationBuilder);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Abstractions.Contracts;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using NicolasDorier.RateLimits;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Plugins.DynamicRateLimits;
|
||||||
|
|
||||||
|
public class DynamicRateLimitsService : IHostedService
|
||||||
|
{
|
||||||
|
private readonly ISettingsRepository _settingsRepository;
|
||||||
|
private readonly RateLimitService _rateLimitService;
|
||||||
|
public IEnumerable<string> OriginalLimits { get; private set; }
|
||||||
|
|
||||||
|
public DynamicRateLimitsService(ISettingsRepository settingsRepository, RateLimitService rateLimitService)
|
||||||
|
{
|
||||||
|
_settingsRepository = settingsRepository;
|
||||||
|
_rateLimitService = rateLimitService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
OriginalLimits =
|
||||||
|
((ConcurrentDictionary<string, LimitRequestZone>) _rateLimitService.GetType()
|
||||||
|
.GetField("_Zones", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(_rateLimitService))
|
||||||
|
.Values.Select(zone => zone.ToString());
|
||||||
|
|
||||||
|
var settings = await _settingsRepository.GetSettingAsync<DynamicRateLimitSettings>();
|
||||||
|
if (settings?.RateLimits is not null)
|
||||||
|
{
|
||||||
|
foreach (var limit in settings.RateLimits)
|
||||||
|
{
|
||||||
|
_rateLimitService.SetZone(limit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<DynamicRateLimitSettings> Get()
|
||||||
|
{
|
||||||
|
|
||||||
|
return (await _settingsRepository.GetSettingAsync<DynamicRateLimitSettings>())?? new DynamicRateLimitSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UseDefaults()
|
||||||
|
{
|
||||||
|
foreach (var originalLimit in OriginalLimits)
|
||||||
|
{
|
||||||
|
_rateLimitService.SetZone(originalLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _settingsRepository.UpdateSetting(new DynamicRateLimitSettings());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Update(string[] limits)
|
||||||
|
{
|
||||||
|
foreach (var originalLimit in OriginalLimits)
|
||||||
|
{
|
||||||
|
_rateLimitService.SetZone(originalLimit);
|
||||||
|
}
|
||||||
|
foreach (var limit in limits)
|
||||||
|
{
|
||||||
|
_rateLimitService.SetZone(limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _settingsRepository.UpdateSetting(new DynamicRateLimitSettings()
|
||||||
|
{
|
||||||
|
RateLimits = limits
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
using System;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Abstractions.Constants;
|
||||||
|
using BTCPayServer.Abstractions.Extensions;
|
||||||
|
using BTCPayServer.Client;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using NicolasDorier.RateLimits;
|
||||||
|
using Org.BouncyCastle.Security.Certificates;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Plugins.DynamicRateLimits
|
||||||
|
{
|
||||||
|
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
|
[Authorize(Policy = Policies.CanModifyServerSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
|
[Route("~/plugins/dynamicrateslimiter")]
|
||||||
|
public class DynamicRatesLimiterController : Controller
|
||||||
|
{
|
||||||
|
private readonly DynamicRateLimitsService _dynamicRateLimitsService;
|
||||||
|
|
||||||
|
public DynamicRatesLimiterController(DynamicRateLimitsService dynamicRateLimitsService)
|
||||||
|
{
|
||||||
|
_dynamicRateLimitsService = dynamicRateLimitsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("")]
|
||||||
|
public async Task<IActionResult> Update()
|
||||||
|
{
|
||||||
|
return View(await _dynamicRateLimitsService.Get());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpPost("")]
|
||||||
|
public async Task<IActionResult> Update(DynamicRateLimitSettings vm, string command)
|
||||||
|
{
|
||||||
|
switch (command)
|
||||||
|
{
|
||||||
|
case "save":
|
||||||
|
if (vm.RateLimits is not null)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < vm.RateLimits.Length; i++)
|
||||||
|
{
|
||||||
|
if (!LimitRequestZone.TryParse(vm.RateLimits[i], out var zone))
|
||||||
|
{
|
||||||
|
vm.AddModelError(s => s.RateLimits[i], "Invalid rate limit", this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
return View(vm);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _dynamicRateLimitsService.Update(vm.RateLimits);
|
||||||
|
TempData["SuccessMessage"] = "Dynamic rate limits modified";
|
||||||
|
return RedirectToAction(nameof(Update));
|
||||||
|
case "use-defaults":
|
||||||
|
await _dynamicRateLimitsService.UseDefaults();
|
||||||
|
TempData["SuccessMessage"] = "Dynamic rate limits modified";
|
||||||
|
return RedirectToAction(nameof(Update));
|
||||||
|
default:
|
||||||
|
return View(await _dynamicRateLimitsService.Get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
@using BTCPayServer.Plugins.DynamicRateLimits
|
||||||
|
@model DynamicRateLimitSettings
|
||||||
|
|
||||||
|
@{
|
||||||
|
Layout = "../Shared/_NavLayout.cshtml";
|
||||||
|
ViewData["NavPartialName"] = "../UIServer/_Nav";
|
||||||
|
}
|
||||||
|
|
||||||
|
<h2 class="mb-4">Rate limit configuration</h2>
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<table class="table table-responsive col-12">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
Rate Limit
|
||||||
|
</th>
|
||||||
|
<th class="text-end">
|
||||||
|
Actions
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="limit-list">
|
||||||
|
@if (Model.RateLimits is not null)
|
||||||
|
{
|
||||||
|
@for (var index = 0; index < Model.RateLimits.Length; index++)
|
||||||
|
{
|
||||||
|
<tr data-index="@index">
|
||||||
|
<td>
|
||||||
|
<input class="form-control" type="text" asp-for="RateLimits[index]">
|
||||||
|
</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<button class="btn btn-link" type="button" data-remove>Remove</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<button type="button" id="add-limit" class="btn btn-outline-secondary mx-2">Add rate limit</button>
|
||||||
|
<button name="command" type="submit" value="save" class="btn btn-primary mt-2">Save</button>
|
||||||
|
<button name="command" type="submit" value="use-defaults" class="btn btn-primary mt-2">Use defaults</button>
|
||||||
|
|
||||||
|
<button class="btn btn-link" type="button"data-bs-toggle="collapse" data-bs-target="#defaults">View defaults</button>
|
||||||
|
<div class="collapse" id="defaults">
|
||||||
|
@inject DynamicRateLimitsService DynamicRateLimitsService
|
||||||
|
<div class="card card-body">
|
||||||
|
<ul>
|
||||||
|
@foreach (var rateLimit in DynamicRateLimitsService.OriginalLimits)
|
||||||
|
{
|
||||||
|
<li>@rateLimit</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@section PageFootContent {
|
||||||
|
<partial name="_ValidationScriptsPartial"/>
|
||||||
|
}
|
||||||
|
<template id="row">
|
||||||
|
<tr data-index="-1">
|
||||||
|
<td>
|
||||||
|
<input type="text" class="form-control">
|
||||||
|
</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<button class="btn btn-link" type="button" data-remove>Remove</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script >
|
||||||
|
document.addEventListener("DOMContentLoaded", ()=>{
|
||||||
|
|
||||||
|
setupRemoveBtn();
|
||||||
|
|
||||||
|
document.getElementById("add-limit").addEventListener("click", ()=>{
|
||||||
|
const template = document.querySelector('#row');
|
||||||
|
const clone = template.content.cloneNode(true);
|
||||||
|
document.getElementById("limit-list").appendChild(clone);
|
||||||
|
setIndex();
|
||||||
|
setupRemoveBtn();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function setupRemoveBtn(){
|
||||||
|
document.querySelectorAll("[data-remove]").forEach(value =>{
|
||||||
|
value.removeEventListener("click",onRemove )
|
||||||
|
value.addEventListener("click",onRemove );
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function onRemove(evt){
|
||||||
|
evt.target.parentElement.parentElement.remove();
|
||||||
|
setIndex();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function setIndex(){
|
||||||
|
document.querySelectorAll("[data-index]").forEach((value, key) => {
|
||||||
|
value.setAttribute("data-index", key);
|
||||||
|
value.querySelector("input").name = `RateLimits[${key}]`;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
@using BTCPayServer.Plugins.DynamicRateLimits
|
||||||
|
@{
|
||||||
|
var isActive = ViewContext.RouteData.Values.TryGetValue("Controller", out var controller) && controller is not null &&
|
||||||
|
nameof(DynamicRatesLimiterController).StartsWith(controller?.ToString(), StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
<a class="nav-link @(isActive ? "active" : string.Empty)" asp-action="Update" asp-controller="DynamicRatesLimiter">Rate Limits</a>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
@using BTCPayServer.Abstractions.Services
|
||||||
|
@inject Safe Safe
|
||||||
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
|
@addTagHelper *, BTCPayServer
|
||||||
|
@addTagHelper *, BTCPayServer.Abstractions
|
||||||
Reference in New Issue
Block a user