Skip to content

Commit

Permalink
Rate Limiter First Part
Browse files Browse the repository at this point in the history
  • Loading branch information
eilyushenko committed Nov 25, 2024
1 parent 7b3bbe9 commit 9ea2723
Show file tree
Hide file tree
Showing 22 changed files with 245 additions and 64 deletions.
18 changes: 11 additions & 7 deletions RateLimiter.Api/Controllers/TargetController.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using RateLimiter.Api.Infrastructure.Attributes;
using RateLimiter.Api.Infrastructure.Filters;
using RateLimiter.Core.Domain.Enums;

namespace RateLimiter.Api.Controllers
{
[Authorize]
[ApiController]
[Route("api/target")]
public class TargetController : ControllerBase
public class TargetController
{
[HttpGet]
[Route("test-endpoint")]
public Task<string> TestEndpoint()
{
var a = User.Claims.ToList();
[Route("limited-info")]
[AppliedRule(RuleType.RequestPerTimeSpan, RuleType.TimeSpanPassedSinceLastCall)]
[ServiceFilter(typeof(RuleFilter))]
public Task GetLimitedInfo() => Task.CompletedTask;

return Task.FromResult("Ok");
}
[HttpGet]
[Route("full-info")]
public Task GetFullInfo() => Task.CompletedTask;
}
}
15 changes: 15 additions & 0 deletions RateLimiter.Api/Infrastructure/Attributes/AppliedRuleAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using RateLimiter.Core.Domain.Enums;

namespace RateLimiter.Api.Infrastructure.Attributes
{
[AttributeUsage(AttributeTargets.Method)]
public class AppliedRuleAttribute : Attribute
{
public RuleType[] RuleTypes { get; }

public AppliedRuleAttribute(params RuleType[] ruleTypes)
{
RuleTypes = ruleTypes;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using RateLimiter.BusinessLogic.Services;
using RateLimiter.Core.Domain.Enums;
using RateLimiter.Core.Helpers;

namespace RateLimiter.Api.Infrastructure.Authentication
{
Expand Down Expand Up @@ -31,9 +33,42 @@ public void Configure(JwtBearerOptions options)
RequireExpirationTime = false,
ValidIssuer = tokenService.TokenSettings.Issuer,
ValidAudience = tokenService.TokenSettings.Audience,
IssuerSigningKey = tokenService.KeyGenerator.GetKey(),
IssuerSigningKey = tokenService.KeyGeneratorService.GetKey(),
};

options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var claimsPrincipal = context.Principal;

var regionClaim = claimsPrincipal.FindFirst(nameof(CustomClaimTypes.Region));
var uniqueIdentifierClaim = claimsPrincipal.FindFirst(nameof(CustomClaimTypes.UniqueIdentifier));

if (regionClaim == null || uniqueIdentifierClaim == null)
{
context.Fail($"Required custom claims have been missed.");
return Task.CompletedTask;
}

var regionType = regionClaim.Value;
var uniqueIdentifier = uniqueIdentifierClaim.Value;

if (string.IsNullOrEmpty(regionType) || string.IsNullOrEmpty(uniqueIdentifier))
{
context.Fail("Custom claim values are invalid.");
return Task.CompletedTask;
}

if (!Enum.TryParse(regionType, out RegionType type))
{
context.Fail($"Invalid value of claim: {nameof(CustomClaimTypes.Region)}.");
}

return Task.CompletedTask;
}
};
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Microsoft.AspNetCore.Diagnostics;
using RateLimiter.Api.Infrastructure.ExeptionHandling.Model;
using RateLimiter.Core.Exceptions;
using System.Net;

namespace RateLimiter.Api.Infrastructure.ExeptionHandling
Expand Down Expand Up @@ -35,7 +34,6 @@ private static async Task HandleExceptionAsync(HttpResponse response, Exception?
response.StatusCode = exception switch
{
ArgumentException => (int)HttpStatusCode.BadRequest,
UnauthorizedException => (int)HttpStatusCode.Unauthorized,
_ => (int)HttpStatusCode.InternalServerError,
};

Expand Down
46 changes: 46 additions & 0 deletions RateLimiter.Api/Infrastructure/Filters/RuleFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Microsoft.AspNetCore.Mvc.Filters;
using RateLimiter.Api.Infrastructure.Attributes;
using RateLimiter.BusinessLogic.Services;
using RateLimiter.Core.Domain.Enums;
using RateLimiter.Core.Helpers;
using System.Security.Claims;

namespace RateLimiter.Api.Infrastructure.Filters
{
public class RuleFilter : IAsyncActionFilter
{
private readonly IRuleFactory _ruleFactory;

public RuleFilter(IRuleFactory ruleFactory)
{
_ruleFactory = ruleFactory;
}

public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var appliedRuleAttribute = context.ActionDescriptor.EndpointMetadata
.OfType<AppliedRuleAttribute>()
.FirstOrDefault();

if (appliedRuleAttribute == null || !appliedRuleAttribute.RuleTypes.Any())
{
throw new Exception($"{nameof(AppliedRuleAttribute)} and {nameof(RuleFilter)} have been applied incorecctly.");
}

var ruleTypes = appliedRuleAttribute.RuleTypes.ToList();
var (regionType, uniqueId) = GetClaimValues(context.HttpContext.User?.Claims);

// TO DO

await next();
}

private (RegionType regionType, string uniqueId) GetClaimValues(IEnumerable<Claim> claims)
{
var region = claims.FirstOrDefault(c => c.Type == nameof(CustomClaimTypes.Region)).Value;
var identifierId = claims.FirstOrDefault(c => c.Type == nameof(CustomClaimTypes.UniqueIdentifier)).Value;

return ((RegionType)Enum.Parse(typeof(RegionType), region), identifierId);
}
}
}
37 changes: 24 additions & 13 deletions RateLimiter.Api/Infrastructure/IocConfig.cs
Original file line number Diff line number Diff line change
@@ -1,35 +1,46 @@
using Microsoft.Extensions.Configuration;
using RateLimiter.Api.Infrastructure.Filters;
using RateLimiter.BusinessLogic.Services;
using RateLimiter.BusinessLogic.Services.Implementation;
using RateLimiter.Core.Helpers;
using RateLimiter.BusinessLogic.Services.Implementation.RateLimiter.Factory;
using RateLimiter.BusinessLogic.Services.Implementation.RateLimiter.Rules.EU;
using RateLimiter.BusinessLogic.Services.Implementation.RateLimiter.Rules.USA;
using RateLimiter.Core.Settings;

namespace RateLimiter.Api.Infrastructure
{
public static class IocConfig
{
public static void AddRateLimiterServices(this IServiceCollection services)
public static void AddFilters(this IServiceCollection services)
{
AddBussinessLogicServices(services);
AddDataAccessLayerRepositories(services);
services.AddScoped<RuleFilter>();
}

private static void AddBussinessLogicServices(this IServiceCollection services)
{ }

private static void AddDataAccessLayerRepositories(this IServiceCollection services)
{ }

public static void AddTokenConfigurationServices(this IServiceCollection services, IConfiguration configuration)
{
services.Configure<TokenSettings>(configuration.GetSection(nameof(TokenSettings)));
services.AddSingleton(provider =>
services.AddSingleton<IKeyGeneratorService>(provider =>
{
var secretKey = configuration.GetValue<string>("GeneratorSecretKey");
return new KeyGenerator(secretKey);
return new KeyGeneratorService(secretKey);
});

services.AddScoped<ITokenService, TokenService>();
}

public static void AddRateLimiterServices(this IServiceCollection services)
{
AddBussinessLogicServices(services);
AddDataAccessLayerRepositories(services);
}

private static void AddBussinessLogicServices(this IServiceCollection services)
{
services.AddScoped<IRuleFactory, RuleFactory>();
services.AddScoped<IRuleService, LastCallTimeRule>();
services.AddScoped<IRuleService, RequestPerTimeRule>();
}

private static void AddDataAccessLayerRepositories(this IServiceCollection services)
{ }
}
}
2 changes: 1 addition & 1 deletion RateLimiter.Api/Models/Request/CreateTokenRequestModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ namespace RateLimiter.Api.Models.Request
{
public class CreateTokenRequestModel
{
public RegionTokenType Type { get; set; }
public RegionType Type { get; set; }
}
}
5 changes: 0 additions & 5 deletions RateLimiter.Api/RateLimiter.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
Expand All @@ -17,8 +16,4 @@
<ProjectReference Include="..\RateLimiter.BusinessLogic\RateLimiter.BusinessLogic.csproj" />
</ItemGroup>

<ItemGroup>
<Folder Include="Models\Response\" />
</ItemGroup>

</Project>
3 changes: 2 additions & 1 deletion RateLimiter.Api/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public void ConfigureServices(IServiceCollection services)
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer();
services.AddJwtBearerConfiguration();

services.AddFilters();
services.AddRateLimiterServices();
services.AddSwaggerServices();
}
Expand All @@ -44,13 +45,13 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
}

app.UseHttpsRedirection();

app.UseRouting();

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

app.UseErrorHandler();

app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
Expand Down
10 changes: 10 additions & 0 deletions RateLimiter.BusinessLogic/Services/IKeyGeneratorService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Microsoft.IdentityModel.Tokens;

namespace RateLimiter.BusinessLogic.Services
{
public interface IKeyGeneratorService
{
string SigningAlgorithm { get; }
SecurityKey GetKey();
}
}
9 changes: 9 additions & 0 deletions RateLimiter.BusinessLogic/Services/IRuleFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using RateLimiter.Core.Domain.Enums;

namespace RateLimiter.BusinessLogic.Services
{
public interface IRuleFactory
{
IRuleService GetRule(RegionType regionType, RuleType ruleType);
}
}
11 changes: 11 additions & 0 deletions RateLimiter.BusinessLogic/Services/IRuleService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using RateLimiter.Core.Domain.Enums;

namespace RateLimiter.BusinessLogic.Services
{
public interface IRuleService
{
public RegionType RegionType { get; }
public RuleType RuleType { get; }
Task ApplyRule();
}
}
5 changes: 2 additions & 3 deletions RateLimiter.BusinessLogic/Services/ITokenService.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
using RateLimiter.Core.Domain.Enums;
using RateLimiter.Core.Helpers;
using RateLimiter.Core.Settings;

namespace RateLimiter.BusinessLogic.Services
{
public interface ITokenService
{
KeyGenerator KeyGenerator { get; }
IKeyGeneratorService KeyGeneratorService { get; }
TokenSettings TokenSettings { get; }
Task<string> GenerateTokenAsync(RegionTokenType type);
Task<string> GenerateTokenAsync(RegionType type);
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
using Microsoft.IdentityModel.Tokens;
using System.Text;

namespace RateLimiter.Core.Helpers
namespace RateLimiter.BusinessLogic.Services.Implementation
{
public class KeyGenerator
public class KeyGeneratorService : IKeyGeneratorService
{
private readonly SymmetricSecurityKey _secretKey;
public string SigningAlgorithm => SecurityAlgorithms.HmacSha256;

public KeyGenerator(string key)
public KeyGeneratorService(string key)
{
var secretBytes = Encoding.UTF8.GetBytes(key);
_secretKey = new SymmetricSecurityKey(secretBytes);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using RateLimiter.Core.Domain.Enums;

namespace RateLimiter.BusinessLogic.Services.Implementation.RateLimiter.Factory
{
public class RuleFactory : IRuleFactory
{
private readonly IEnumerable<IRuleService> _rules;

public RuleFactory(IEnumerable<IRuleService> rules)
{
_rules = rules;
}

public IRuleService GetRule(RegionType regionType, RuleType ruleType)
=> _rules.Single(x => x.RegionType == regionType && x.RuleType == ruleType);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using RateLimiter.Core.Domain.Enums;

namespace RateLimiter.BusinessLogic.Services.Implementation.RateLimiter.Rules.EU
{
public class LastCallTimeRule : IRuleService
{
public RegionType RegionType => RegionType.EU;
public RuleType RuleType => RuleType.TimeSpanPassedSinceLastCall;

public Task ApplyRule()
{
throw new NotImplementedException();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using RateLimiter.Core.Domain.Enums;

namespace RateLimiter.BusinessLogic.Services.Implementation.RateLimiter.Rules.USA
{
public class RequestPerTimeRule : IRuleService
{
public RegionType RegionType => RegionType.US;
public RuleType RuleType => RuleType.RequestPerTimeSpan;

public Task ApplyRule()
{
throw new NotImplementedException();
}
}
}
Loading

0 comments on commit 9ea2723

Please sign in to comment.