Refactor: Update rate limiting to account for reverse proxy deployments
This commit is contained in:
@@ -1,13 +1,11 @@
|
||||
using backend.Models.NswFuelApi;
|
||||
using backend.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
|
||||
namespace backend.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[EnableRateLimiting("sliding")]
|
||||
[Route("api/brands")]
|
||||
public class BrandController : ControllerBase
|
||||
{
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
using backend.Models.NswFuelApi;
|
||||
using backend.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
|
||||
namespace backend.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[EnableRateLimiting("sliding")]
|
||||
[Route("api/fuel-types")]
|
||||
public class FuelTypeController : ControllerBase
|
||||
{
|
||||
|
||||
@@ -3,13 +3,11 @@ using backend.Models.Utilities;
|
||||
using backend.Services;
|
||||
using backend.Validators;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
|
||||
namespace backend.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[EnableRateLimiting("sliding")]
|
||||
[Route("api/prices")]
|
||||
public class PriceController : ControllerBase
|
||||
{
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
using backend.Models.Trends;
|
||||
using backend.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
|
||||
namespace backend.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[EnableRateLimiting("sliding")]
|
||||
[Route("api/trends/daily-averages")]
|
||||
public class PriceTrendsController : ControllerBase
|
||||
{
|
||||
|
||||
@@ -3,13 +3,11 @@ using backend.Models.Utilities;
|
||||
using backend.Services;
|
||||
using backend.Validators;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
|
||||
namespace backend.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[EnableRateLimiting("sliding")]
|
||||
[Route("api/stations")]
|
||||
public class StationController : ControllerBase
|
||||
{
|
||||
|
||||
@@ -3,13 +3,11 @@ using backend.Models.Utilities;
|
||||
using backend.Services;
|
||||
using backend.Validators;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
|
||||
namespace backend.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[EnableRateLimiting("sliding")]
|
||||
[Route("api/stations-with-prices")]
|
||||
public class StationWithPricesController : ControllerBase
|
||||
{
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
using backend.Models.SuburbBoundariesApi;
|
||||
using backend.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
|
||||
namespace backend.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[EnableRateLimiting("sliding")]
|
||||
[Route("api/suburb-boundaries")]
|
||||
public class SuburbBoundaryController : ControllerBase
|
||||
{
|
||||
|
||||
@@ -6,7 +6,6 @@ using backend.Services.Init;
|
||||
using backend.Validators;
|
||||
using Hangfire;
|
||||
using Hangfire.PostgreSql;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
using ServiceStack.Data;
|
||||
using ServiceStack.OrmLite;
|
||||
|
||||
@@ -36,26 +35,50 @@ public static class ServiceCollectionExtensions
|
||||
options.AddSimpleConsole(c =>
|
||||
c.TimestampFormat = "[dd-MM-yyyy HH:mm:ss] "))
|
||||
.AddHttpClient()
|
||||
.AddRateLimiter(rateLimiterOptions =>
|
||||
rateLimiterOptions.AddSlidingWindowLimiter("sliding", options =>
|
||||
.AddRateLimiter(options =>
|
||||
{
|
||||
// apply to all endpoints
|
||||
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(context =>
|
||||
{
|
||||
//TODO: this is something we should grab from config
|
||||
options.PermitLimit = 100;
|
||||
options.Window = TimeSpan.FromSeconds(60);
|
||||
options.SegmentsPerWindow = 60;
|
||||
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
|
||||
//just show the rate limit message immediately, don't queue up any requests
|
||||
options.QueueLimit = 0;
|
||||
}).OnRejected = async (context, token) =>
|
||||
// grab the first IP in X-Forwarded-For, else fall back
|
||||
var forwarded = context.Request.Headers.TryGetValue("X-Forwarded-For", out var vals)
|
||||
? vals.FirstOrDefault()?.Split(',')[0]
|
||||
: null;
|
||||
var clientIp = !string.IsNullOrEmpty(forwarded)
|
||||
? forwarded
|
||||
: context.Connection.RemoteIpAddress?.ToString() ?? "unknown";
|
||||
|
||||
return RateLimitPartition.GetSlidingWindowLimiter(
|
||||
clientIp,
|
||||
_ => new SlidingWindowRateLimiterOptions
|
||||
{
|
||||
PermitLimit = 100,
|
||||
Window = TimeSpan.FromSeconds(60),
|
||||
SegmentsPerWindow = 60,
|
||||
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
|
||||
QueueLimit = 0,
|
||||
AutoReplenishment = true
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
options.OnRejected = async (context, ct) =>
|
||||
{
|
||||
var logger = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>()
|
||||
var logger = context.HttpContext.RequestServices
|
||||
.GetRequiredService<ILoggerFactory>()
|
||||
.CreateLogger("RateLimiter");
|
||||
logger.LogWarning("Client rate limit exceeded for {ConnectionRemoteIpAddress}",
|
||||
context.HttpContext.Connection.RemoteIpAddress);
|
||||
context.HttpContext.Response.StatusCode = 429;
|
||||
await context.HttpContext.Response.WriteAsync("Rate limit exceeded, please try again in one minute",
|
||||
token);
|
||||
})
|
||||
|
||||
var ip = context.HttpContext.Request.Headers["X-Forwarded-For"]
|
||||
.FirstOrDefault()?.Split(',')[0]
|
||||
?? context.HttpContext.Connection.RemoteIpAddress?.ToString();
|
||||
|
||||
logger.LogWarning("Client rate limit exceeded for {ClientIp}", ip);
|
||||
|
||||
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
|
||||
await context.HttpContext.Response
|
||||
.WriteAsync("Rate limit exceeded, please try again in one minute", ct);
|
||||
};
|
||||
})
|
||||
.AddHangfire(config =>
|
||||
config.UsePostgreSqlStorage(c =>
|
||||
c.UseNpgsqlConnection(connectionString)))
|
||||
|
||||
Reference in New Issue
Block a user