Changes
This commit is contained in:
+1
-1
@@ -53,7 +53,7 @@ try
|
|||||||
builder.Services.AddSwaggerWithXmlComments(Assembly.GetExecutingAssembly(), "API");
|
builder.Services.AddSwaggerWithXmlComments(Assembly.GetExecutingAssembly(), "API");
|
||||||
builder.Services.ConfigureCaddyForwardedHeaders();
|
builder.Services.ConfigureCaddyForwardedHeaders();
|
||||||
builder.Services.AddFrontendCorsFromConfiguration(builder.Configuration);
|
builder.Services.AddFrontendCorsFromConfiguration(builder.Configuration);
|
||||||
builder.Services.AddPublicApiRateLimiting();
|
builder.Services.AddPublicApiRateLimiting(builder.Configuration);
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
|||||||
@@ -109,5 +109,27 @@
|
|||||||
"CvMatcherApi": {
|
"CvMatcherApi": {
|
||||||
"BaseUrl": "",
|
"BaseUrl": "",
|
||||||
"InternalApiKey": ""
|
"InternalApiKey": ""
|
||||||
|
},
|
||||||
|
"RateLimiting": {
|
||||||
|
"Global": {
|
||||||
|
"PermitLimit": 120,
|
||||||
|
"Window": "00:01:00",
|
||||||
|
"QueueLimit": 0,
|
||||||
|
"AutoReplenishment": true
|
||||||
|
},
|
||||||
|
"Policies": {
|
||||||
|
"contact": {
|
||||||
|
"PermitLimit": 5,
|
||||||
|
"Window": "00:01:00",
|
||||||
|
"QueueLimit": 0,
|
||||||
|
"AutoReplenishment": true
|
||||||
|
},
|
||||||
|
"cv-matcher": {
|
||||||
|
"PermitLimit": 10,
|
||||||
|
"Window": "00:10:00",
|
||||||
|
"QueueLimit": 0,
|
||||||
|
"AutoReplenishment": true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
namespace Shared.Models.Settings
|
||||||
|
{
|
||||||
|
public class RateLimitingSettings
|
||||||
|
{
|
||||||
|
public RateLimitPolicySettings Global { get; set; } = new();
|
||||||
|
public Dictionary<string, RateLimitPolicySettings> Policies { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RateLimitPolicySettings
|
||||||
|
{
|
||||||
|
public int PermitLimit { get; set; } = 100;
|
||||||
|
|
||||||
|
// Bound from configuration strings like "00:01:00" (1 minute) or "00:10:00" (10 minutes).
|
||||||
|
public TimeSpan Window { get; set; } = TimeSpan.FromMinutes(1);
|
||||||
|
|
||||||
|
public int QueueLimit { get; set; } = 0;
|
||||||
|
|
||||||
|
public bool AutoReplenishment { get; set; } = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,26 @@
|
|||||||
using System.Threading.RateLimiting;
|
using System.Threading.RateLimiting;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Shared.Models.Settings;
|
||||||
|
|
||||||
namespace StartupHelpers;
|
namespace StartupHelpers;
|
||||||
|
|
||||||
public static class RateLimitingExtensions
|
public static class RateLimitingExtensions
|
||||||
{
|
{
|
||||||
public static void AddPublicApiRateLimiting(this IServiceCollection services)
|
public static void AddPublicApiRateLimiting(
|
||||||
|
this IServiceCollection services,
|
||||||
|
IConfiguration configuration,
|
||||||
|
string sectionName = "RateLimiting")
|
||||||
{
|
{
|
||||||
|
var settings = configuration.GetSection(sectionName).Get<RateLimitingSettings>()
|
||||||
|
?? new RateLimitingSettings();
|
||||||
|
|
||||||
services.AddRateLimiter(options =>
|
services.AddRateLimiter(options =>
|
||||||
{
|
{
|
||||||
|
var global = settings.Global ?? new RateLimitPolicySettings();
|
||||||
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
|
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
|
||||||
{
|
{
|
||||||
var ip = httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
|
var ip = httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
|
||||||
@@ -19,40 +28,32 @@ public static class RateLimitingExtensions
|
|||||||
partitionKey: ip,
|
partitionKey: ip,
|
||||||
factory: _ => new FixedWindowRateLimiterOptions
|
factory: _ => new FixedWindowRateLimiterOptions
|
||||||
{
|
{
|
||||||
PermitLimit = 120,
|
PermitLimit = global.PermitLimit,
|
||||||
Window = TimeSpan.FromMinutes(1),
|
Window = global.Window,
|
||||||
QueueLimit = 0,
|
QueueLimit = global.QueueLimit,
|
||||||
AutoReplenishment = true
|
AutoReplenishment = global.AutoReplenishment
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
options.AddPolicy("contact", httpContext =>
|
foreach (var entry in settings.Policies)
|
||||||
{
|
{
|
||||||
var ip = httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
|
var policyName = entry.Key;
|
||||||
return RateLimitPartition.GetFixedWindowLimiter(
|
var policy = entry.Value ?? new RateLimitPolicySettings();
|
||||||
partitionKey: ip,
|
|
||||||
factory: _ => new FixedWindowRateLimiterOptions
|
|
||||||
{
|
|
||||||
PermitLimit = 5,
|
|
||||||
Window = TimeSpan.FromMinutes(1),
|
|
||||||
QueueLimit = 0,
|
|
||||||
AutoReplenishment = true
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
options.AddPolicy("cv-matcher", httpContext =>
|
options.AddPolicy(policyName, httpContext =>
|
||||||
{
|
{
|
||||||
var ip = httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
|
var ip = httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
|
||||||
return RateLimitPartition.GetFixedWindowLimiter(
|
return RateLimitPartition.GetFixedWindowLimiter(
|
||||||
partitionKey: ip,
|
partitionKey: ip,
|
||||||
factory: _ => new FixedWindowRateLimiterOptions
|
factory: _ => new FixedWindowRateLimiterOptions
|
||||||
{
|
{
|
||||||
PermitLimit = 10,
|
PermitLimit = policy.PermitLimit,
|
||||||
Window = TimeSpan.FromMinutes(10),
|
Window = policy.Window,
|
||||||
QueueLimit = 0,
|
QueueLimit = policy.QueueLimit,
|
||||||
AutoReplenishment = true
|
AutoReplenishment = policy.AutoReplenishment
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
|
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
|
||||||
options.OnRejected = async (context, ct) =>
|
options.OnRejected = async (context, ct) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user