Skip to content

Commit

Permalink
working version
Browse files Browse the repository at this point in the history
  • Loading branch information
jstallm committed Nov 24, 2024
1 parent d0a5741 commit 956c885
Show file tree
Hide file tree
Showing 25 changed files with 649 additions and 18 deletions.
22 changes: 22 additions & 0 deletions Crexi.Auctions.API/Controllers/AuctionsController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Mvc;

namespace Crexi.Auctions.API.Controllers
{
[ApiController]
[Route("[controller]")]
public class AuctionsController : ControllerBase
{
private readonly ILogger<AuctionsController> _logger;

public AuctionsController(ILogger<AuctionsController> logger)
{
_logger = logger;
}

[HttpGet]
public int Get()
{
return 1;
}
}
}
22 changes: 22 additions & 0 deletions Crexi.Auctions.API/Crexi.Auctions.API.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\RateLimiter\Crexi.API.Common.RateLimiter.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.2.1" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using Crexi.API.Common.RateLimiter.Interfaces;
using Crexi.API.Common.RateLimiter.Rules;

namespace Crexi.Auctions.API.Middleware.RateLimiter
{
public static class RateLimiterConfigurator
{
public static void ConfigureRateLimitingRules(IRateLimiter rateLimiter)
{
var usRateLimitRule = new FixedWindowRateLimitRule(
maxRequestsPerClient: 10,
timeWindow: TimeSpan.FromSeconds(10)
);

var euRateLimitRule = new TimeSinceLastCallRule(
requiredInterval: TimeSpan.FromSeconds(2)
);

var usConditionalRule = new ConditionalRateLimitRule(
condition: client => client.Location == "US",
rule: usRateLimitRule
);

var euConditionalRule = new ConditionalRateLimitRule(
condition: client => client.Location == "EU",
rule: euRateLimitRule
);

var compositeRule = new CompositeRateLimitRule(new[]
{
usConditionalRule,
euConditionalRule
});

rateLimiter.ConfigureResource("/Auctions", compositeRule);

var anotherRule = new FixedWindowRateLimitRule(50, TimeSpan.FromMinutes(10));
rateLimiter.ConfigureResource("/AuctionSettings", anotherRule);
}
}
}
42 changes: 42 additions & 0 deletions Crexi.Auctions.API/Middleware/RateLimiter/RateLimiterMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Crexi.API.Common.RateLimiter.Interfaces;

public class RateLimiterMiddleware
{
private readonly RequestDelegate _next;
private readonly IRateLimiter _rateLimiter;
private readonly ITokenToClientConverter _tokenToClientConverter;
private readonly ITokenExtractor _tokenExtractor;

public RateLimiterMiddleware(RequestDelegate next, IRateLimiter rateLimiter, ITokenToClientConverter tokenToClientConverter, ITokenExtractor tokenExtractor)
{
_next = next;
_rateLimiter = rateLimiter;
_tokenToClientConverter = tokenToClientConverter;
_tokenExtractor = tokenExtractor;
}

public async Task InvokeAsync(HttpContext context)
{
var accessToken = _tokenExtractor.ExtractToken(context);

var client = _tokenToClientConverter.ConvertTokenToClient(accessToken);

if (client == null)
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
await context.Response.WriteAsync("Invalid or malformed access token.");
return;
}

var resource = context.Request.Path.Value;

if (!_rateLimiter.IsRequestAllowed(client, resource))
{
context.Response.StatusCode = StatusCodes.Status429TooManyRequests;
await context.Response.WriteAsync("Rate limit exceeded.");
return;
}

await _next(context);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

public interface ITokenExtractor
{
string ExtractToken(HttpContext context);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using Crexi.API.Common.RateLimiter.Models;

public interface ITokenToClientConverter
{
Client ConvertTokenToClient(string token);
}
13 changes: 13 additions & 0 deletions Crexi.Auctions.API/Middleware/RateLimiter/Util/TokenExtractor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
public class TokenExtractor : ITokenExtractor
{
public string ExtractToken(HttpContext context)
{
if (context.Request.Headers.TryGetValue("Authorization", out var authorizationHeader))
{
var token = authorizationHeader.ToString().Split(' ').Last();
return token;
}

return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Crexi.API.Common.RateLimiter.Models;
using System.IdentityModel.Tokens.Jwt;

public class TokenToClientConverter : ITokenToClientConverter
{
public Client ConvertTokenToClient(string token)
{
try
{
var handler = new JwtSecurityTokenHandler();
var jwtToken = handler.ReadJwtToken(token);

// Extract client ID and location from token claims
var clientId = jwtToken.Claims.FirstOrDefault(c => c.Type == "client_id")?.Value;
var clientLocation = jwtToken.Claims.FirstOrDefault(c => c.Type == "location")?.Value;

if (string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(clientLocation))
{
return null;
}

return new Client(clientId, clientLocation);
}
catch (Exception ex)
{
// TODO logging
return null;
}
}
}
67 changes: 67 additions & 0 deletions Crexi.Auctions.API/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using Crexi.API.Common.RateLimiter;
using Crexi.API.Common.RateLimiter.Interfaces;
using Crexi.Auctions.API.Middleware.RateLimiter;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder(args);

// Register services
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Crexi Auction API", Version = "v1" });

// Add the security definition for Bearer tokens
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Bearer {token}\"",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});

// Add the security requirement to include the Bearer token globally
c.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
Scheme = "oauth2",
Name = "Bearer",
In = ParameterLocation.Header,
},
new List<string>()
}
});
});

builder.Services.AddSingleton<IRateLimiter, RateLimiter>();
builder.Services.AddTransient<ITokenToClientConverter, TokenToClientConverter>();
builder.Services.AddTransient<ITokenExtractor, TokenExtractor>();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthentication();
app.UseAuthorization();

RateLimiterConfigurator.ConfigureRateLimitingRules(app.Services.GetRequiredService<IRateLimiter>());
app.UseMiddleware<RateLimiterMiddleware>();

app.MapControllers();

app.Run();
31 changes: 31 additions & 0 deletions Crexi.Auctions.API/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:42884",
"sslPort": 44366
}
},
"profiles": {
"Crexi.Auctions.API": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7145;http://localhost:5071",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
8 changes: 8 additions & 0 deletions Crexi.Auctions.API/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
9 changes: 9 additions & 0 deletions Crexi.Auctions.API/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\RateLimiter\RateLimiter.csproj" />
<ProjectReference Include="..\RateLimiter\Crexi.API.Common.RateLimiter.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
Expand Down
13 changes: 0 additions & 13 deletions RateLimiter.Tests/RateLimiterTest.cs

This file was deleted.

Loading

0 comments on commit 956c885

Please sign in to comment.