Skip to content

Commit

Permalink
PAS-392 | Add rate limit bypass (#509)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tyrrrz authored Apr 3, 2024
1 parent 83019a2 commit 53ee04c
Show file tree
Hide file tree
Showing 18 changed files with 411 additions and 226 deletions.
14 changes: 10 additions & 4 deletions src/Api/Endpoints/Magic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.AspNetCore.RateLimiting;
using Passwordless.Api.Authorization;
using Passwordless.Api.OpenApi;
using Passwordless.Api.Overrides;
using Passwordless.Common.MagicLinks.Models;
using Passwordless.Service.Features;
using Passwordless.Service.Helpers;
Expand All @@ -26,12 +27,20 @@ public static class MagicEndpoints
/// <summary>
/// Adds a rate limiter policy for the MagicEndpoints. Each tenant will have its own partition.
/// </summary>
/// <param name="builder">The RateLimiterOptions builder.</param>
public static void AddMagicRateLimiterPolicy(this RateLimiterOptions builder) =>
builder.AddPolicy(RateLimiterPolicy, context =>
{
var tenant = context.User.FindFirstValue(CustomClaimTypes.AccountName) ?? "<global>";

var isRateLimitBypassed = context.RequestServices
.GetRequiredService<IConfiguration>()
.GetSection("ApplicationOverrides")
.GetApplicationOverrides(tenant)
.IsRateLimitBypassEnabled;

if (isRateLimitBypassed)
return RateLimitPartition.GetNoLimiter(tenant);

return RateLimitPartition.GetFixedWindowLimiter(tenant, _ =>
new FixedWindowRateLimiterOptions { Window = TimeSpan.FromMinutes(5), PermitLimit = 10, QueueLimit = 0, AutoReplenishment = true }
);
Expand All @@ -40,7 +49,6 @@ public static void AddMagicRateLimiterPolicy(this RateLimiterOptions builder) =>
/// <summary>
/// Maps the magic link endpoints.
/// </summary>
/// <param name="app">The <see cref="IEndpointRouteBuilder"/> object.</param>
public static void MapMagicEndpoints(this IEndpointRouteBuilder app)
{
var group = app.MapGroup("/magic-links")
Expand All @@ -55,8 +63,6 @@ public static void MapMagicEndpoints(this IEndpointRouteBuilder app)

/// <summary>
/// Sends an e-mail containing a magic link template allowing users to login.
///
/// Warning: Verify the e-mail address matches the user identifier in your backend.
/// </summary>
[ProducesResponseType((int)HttpStatusCode.NoContent)]
[ProducesResponseType(typeof(ValidationProblemDetails), (int)HttpStatusCode.BadRequest, MediaTypeNames.Application.ProblemJson)]
Expand Down
12 changes: 12 additions & 0 deletions src/Api/Overrides/ApplicationOverrides.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Passwordless.Api.Overrides;

/// <summary>
/// Configures behavior overrides for an application.
/// </summary>
public class ApplicationOverrides
{
/// <summary>
/// Whether actions on behalf of this application bypass rate limiting.
/// </summary>
public bool IsRateLimitBypassEnabled { get; init; }
}
17 changes: 17 additions & 0 deletions src/Api/Overrides/ApplicationOverridesExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Passwordless.Api.Overrides;

/// <summary>
/// Extensions for <see cref="ApplicationOverrides" />.
/// </summary>
public static class ApplicationOverridesExtensions
{
/// <summary>
/// Gets overrides for the specified application from the configuration.
/// </summary>
public static ApplicationOverrides GetApplicationOverrides(
this IConfiguration configuration,
string applicationId) =>
configuration
.GetSection(applicationId)
.Get<ApplicationOverrides>() ?? new ApplicationOverrides();
}
30 changes: 25 additions & 5 deletions tests/Api.IntegrationTests/AuthorizationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ public class AuthorizationTests(ITestOutputHelper testOutput, PasswordlessApiFix
public async Task ValidateThatEndpointsHaveProtectionAsync()
{
// Arrange
await using var api = await apiFixture.CreateApiAsync(testOutput);
await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions
{

TestOutput = testOutput
});
using var client = api.CreateClient().AddAcceptApplicationJson();

// Act
Expand Down Expand Up @@ -64,7 +68,11 @@ public async Task ValidateThatEndpointsHaveProtectionAsync()
public async Task ValidateThatMissingApiSecretThrowsAsync()
{
// Arrange
await using var api = await apiFixture.CreateApiAsync(testOutput);
await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions
{

TestOutput = testOutput
});
using var client = api.CreateClient().AddAcceptApplicationJson();

// Act
Expand All @@ -88,7 +96,11 @@ public async Task ValidateThatMissingApiSecretThrowsAsync()
public async Task ValidateThatInvalidApiSecretThrowsAsync()
{
// Arrange
await using var api = await apiFixture.CreateApiAsync(testOutput);
await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions
{

TestOutput = testOutput
});
using var client = api.CreateClient().AddAcceptApplicationJson();

// Act
Expand Down Expand Up @@ -121,7 +133,11 @@ public async Task ValidateThatInvalidApiSecretThrowsAsync()
public async Task ApiSecretGivesHelpfulAdviceAsync(string input, string details)
{
// Arrange
await using var api = await apiFixture.CreateApiAsync(testOutput);
await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions
{

TestOutput = testOutput
});
using var client = api.CreateClient().AddAcceptApplicationJson();

using var request = new HttpRequestMessage(HttpMethod.Get, "/credentials/list?userId=1");
Expand Down Expand Up @@ -163,7 +179,11 @@ public async Task ApiSecretGivesHelpfulAdviceAsync(string input, string details)
public async Task ApiPublicGivesHelpfulAdviceAsync(string input, string details)
{
// Arrange
await using var api = await apiFixture.CreateApiAsync(testOutput);
await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions
{

TestOutput = testOutput
});
using var client = api.CreateClient().AddAcceptApplicationJson();

using var request = new HttpRequestMessage(HttpMethod.Post, "/signin/begin");
Expand Down
Loading

0 comments on commit 53ee04c

Please sign in to comment.