diff --git a/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Checker/OperationRateLimitingContext.cs b/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Checker/OperationRateLimitingContext.cs index 3f408b8240..f75c06b05b 100644 --- a/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Checker/OperationRateLimitingContext.cs +++ b/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Checker/OperationRateLimitingContext.cs @@ -8,8 +8,9 @@ public class OperationRateLimitingContext { /// /// Optional parameter passed by the caller. - /// Used by rules configured with PartitionByParameter(). - /// Can be email, phone number, resource id, or any string. + /// Used as the partition key by PartitionByParameter() (required), + /// and as a fallback by PartitionByEmail() and PartitionByPhoneNumber(). + /// Can be email, phone number, user id, resource id, or any string. /// public string? Parameter { get; set; } diff --git a/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Policies/OperationRateLimitingRuleBuilder.cs b/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Policies/OperationRateLimitingRuleBuilder.cs index 82ed356f01..b9f2eacf9a 100644 --- a/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Policies/OperationRateLimitingRuleBuilder.cs +++ b/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Policies/OperationRateLimitingRuleBuilder.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; namespace Volo.Abp.OperationRateLimiting; @@ -8,7 +9,7 @@ public class OperationRateLimitingRuleBuilder private TimeSpan _duration; private int _maxCount; private OperationRateLimitingPartitionType? _partitionType; - private Func? _customPartitionKeyResolver; + private Func>? _customPartitionKeyResolver; private bool _isMultiTenant; internal bool IsCommitted { get; private set; } @@ -43,7 +44,8 @@ public class OperationRateLimitingRuleBuilder } /// - /// Auto resolve from ICurrentUser.Id. + /// Partition by the current authenticated user (ICurrentUser.Id). + /// Use PartitionByParameter() if you need to specify the user ID explicitly. /// public OperationRateLimitingPolicyBuilder PartitionByCurrentUser() { @@ -53,7 +55,7 @@ public class OperationRateLimitingRuleBuilder } /// - /// Auto resolve from ICurrentTenant.Id. + /// Partition by the current tenant (ICurrentTenant.Id). Uses "host" when no tenant is active. /// public OperationRateLimitingPolicyBuilder PartitionByCurrentTenant() { @@ -63,7 +65,8 @@ public class OperationRateLimitingRuleBuilder } /// - /// Auto resolve from IWebClientInfoProvider.ClientIpAddress. + /// Partition by the client IP address (IWebClientInfoProvider.ClientIpAddress). + /// Use PartitionByParameter() if you need to specify the IP explicitly. /// public OperationRateLimitingPolicyBuilder PartitionByClientIp() { @@ -95,10 +98,10 @@ public class OperationRateLimitingRuleBuilder } /// - /// Custom partition key resolver from context. + /// Custom async partition key resolver from context. /// public OperationRateLimitingPolicyBuilder PartitionBy( - Func keyResolver) + Func> keyResolver) { _partitionType = OperationRateLimitingPartitionType.Custom; _customPartitionKeyResolver = Check.NotNull(keyResolver, nameof(keyResolver)); diff --git a/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Policies/OperationRateLimitingRuleDefinition.cs b/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Policies/OperationRateLimitingRuleDefinition.cs index a3415105d9..f8d1bcf9e7 100644 --- a/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Policies/OperationRateLimitingRuleDefinition.cs +++ b/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Policies/OperationRateLimitingRuleDefinition.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; namespace Volo.Abp.OperationRateLimiting; @@ -10,7 +11,7 @@ public class OperationRateLimitingRuleDefinition public OperationRateLimitingPartitionType PartitionType { get; set; } - public Func? CustomPartitionKeyResolver { get; set; } + public Func>? CustomPartitionKeyResolver { get; set; } public bool IsMultiTenant { get; set; } } diff --git a/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Rules/FixedWindowOperationRateLimitingRule.cs b/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Rules/FixedWindowOperationRateLimitingRule.cs index af072bc8c4..7df79a0052 100644 --- a/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Rules/FixedWindowOperationRateLimitingRule.cs +++ b/framework/src/Volo.Abp.OperationRateLimiting/Volo/Abp/OperationRateLimiting/Rules/FixedWindowOperationRateLimitingRule.cs @@ -38,7 +38,7 @@ public class FixedWindowOperationRateLimitingRule : IOperationRateLimitingRule public virtual async Task AcquireAsync( OperationRateLimitingContext context) { - var partitionKey = ResolvePartitionKey(context); + var partitionKey = await ResolvePartitionKeyAsync(context); var storeKey = BuildStoreKey(partitionKey); var storeResult = await Store.IncrementAsync(storeKey, Definition.Duration, Definition.MaxCount); @@ -48,7 +48,7 @@ public class FixedWindowOperationRateLimitingRule : IOperationRateLimitingRule public virtual async Task CheckAsync( OperationRateLimitingContext context) { - var partitionKey = ResolvePartitionKey(context); + var partitionKey = await ResolvePartitionKeyAsync(context); var storeKey = BuildStoreKey(partitionKey); var storeResult = await Store.GetAsync(storeKey, Definition.Duration, Definition.MaxCount); @@ -57,12 +57,12 @@ public class FixedWindowOperationRateLimitingRule : IOperationRateLimitingRule public virtual async Task ResetAsync(OperationRateLimitingContext context) { - var partitionKey = ResolvePartitionKey(context); + var partitionKey = await ResolvePartitionKeyAsync(context); var storeKey = BuildStoreKey(partitionKey); await Store.ResetAsync(storeKey); } - protected virtual string ResolvePartitionKey(OperationRateLimitingContext context) + protected virtual async Task ResolvePartitionKeyAsync(OperationRateLimitingContext context) { return Definition.PartitionType switch { @@ -71,17 +71,20 @@ public class FixedWindowOperationRateLimitingRule : IOperationRateLimitingRule $"OperationRateLimitingContext.Parameter is required for policy '{PolicyName}' (PartitionByParameter)."), OperationRateLimitingPartitionType.CurrentUser => - CurrentUser.Id?.ToString() ?? throw new AbpException( - $"Current user is not authenticated. Policy '{PolicyName}' requires PartitionByCurrentUser."), + CurrentUser.Id?.ToString() + ?? throw new AbpException( + $"Current user is not authenticated. Policy '{PolicyName}' requires PartitionByCurrentUser. " + + "Use PartitionByParameter() if you need to specify the user ID explicitly."), OperationRateLimitingPartitionType.CurrentTenant => - CurrentTenant.Id?.ToString() ?? HostTenantKey, + CurrentTenant.Id?.ToString() + ?? HostTenantKey, OperationRateLimitingPartitionType.ClientIp => WebClientInfoProvider.ClientIpAddress ?? throw new AbpException( $"Client IP address could not be determined. Policy '{PolicyName}' requires PartitionByClientIp. " + - "Ensure IWebClientInfoProvider is properly configured."), + "Ensure IWebClientInfoProvider is properly configured or use PartitionByParameter() to pass the IP explicitly."), OperationRateLimitingPartitionType.Email => context.Parameter @@ -96,7 +99,7 @@ public class FixedWindowOperationRateLimitingRule : IOperationRateLimitingRule $"Phone number is required for policy '{PolicyName}' (PartitionByPhoneNumber). Provide it via context.Parameter or ensure the user has a phone number."), OperationRateLimitingPartitionType.Custom => - Definition.CustomPartitionKeyResolver!(context), + await Definition.CustomPartitionKeyResolver!(context), _ => throw new AbpException($"Unknown partition type: {Definition.PartitionType}") }; diff --git a/framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/AbpOperationRateLimitingTestModule.cs b/framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/AbpOperationRateLimitingTestModule.cs index 6bee2b7d83..7a43716927 100644 --- a/framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/AbpOperationRateLimitingTestModule.cs +++ b/framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/AbpOperationRateLimitingTestModule.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using NSubstitute; using Volo.Abp.AspNetCore.WebClientInfo; @@ -157,7 +158,7 @@ public class AbpOperationRateLimitingTestModule : AbpModule options.AddPolicy("TestCustomResolver", policy => { policy.WithFixedWindow(TimeSpan.FromHours(1), maxCount: 2) - .PartitionBy(ctx => $"action:{ctx.Parameter}"); + .PartitionBy(ctx => Task.FromResult($"action:{ctx.Parameter}")); }); // Multi-tenant: ByParameter with tenant isolation - same param, different tenants = different counters diff --git a/framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/OperationRateLimitingPolicyBuilder_Tests.cs b/framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/OperationRateLimitingPolicyBuilder_Tests.cs index 56347cf53b..6a503a6191 100644 --- a/framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/OperationRateLimitingPolicyBuilder_Tests.cs +++ b/framework/test/Volo.Abp.OperationRateLimiting.Tests/Volo/Abp/OperationRateLimiting/OperationRateLimitingPolicyBuilder_Tests.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Shouldly; using Xunit; @@ -74,7 +75,7 @@ public class OperationRateLimitingPolicyBuilder_Tests { policy.AddRule(rule => rule .WithFixedWindow(TimeSpan.FromMinutes(30), maxCount: 5) - .PartitionBy(ctx => $"custom:{ctx.Parameter}")); + .PartitionBy(ctx => Task.FromResult($"custom:{ctx.Parameter}"))); }); var policy = options.Policies["CustomPolicy"]; @@ -242,11 +243,11 @@ public class OperationRateLimitingPolicyBuilder_Tests { policy.AddRule(rule => rule .WithFixedWindow(TimeSpan.FromHours(1), maxCount: 5) - .PartitionBy(ctx => $"by-ip:{ctx.Parameter}")); + .PartitionBy(ctx => Task.FromResult($"by-ip:{ctx.Parameter}"))); policy.AddRule(rule => rule .WithFixedWindow(TimeSpan.FromHours(1), maxCount: 5) - .PartitionBy(ctx => $"by-device:{ctx.Parameter}")); + .PartitionBy(ctx => Task.FromResult($"by-device:{ctx.Parameter}"))); }); var policy = options.Policies["MultiCustomPolicy"];