| | | 1 | | using System.Threading.RateLimiting; |
| | | 2 | | using Microsoft.AspNetCore.Http; |
| | | 3 | | using Microsoft.AspNetCore.RateLimiting; |
| | | 4 | | using Microsoft.Extensions.Options; |
| | | 5 | | using Services.Authorization; |
| | | 6 | | |
| | | 7 | | namespace OzonOrdersWeb.Authorization; |
| | | 8 | | |
| | | 9 | | /// <summary> |
| | | 10 | | /// Политика ограничения частоты запросов для страницы логина и эндпоинтов аутентификации. |
| | | 11 | | /// Разделяет учет попыток отправки форм (POST) для защиты от брут-форса паролей и |
| | | 12 | | /// комфортный лимит открытия/чтения страниц (GET/HEAD/OPTIONS), исключая блокировку легитимных пользователей (регрессия |
| | | 13 | | /// </summary> |
| | 2 | 14 | | public sealed class AuthRateLimiterPolicy(IOptions<AppAuthorizationOptions> options) : IRateLimiterPolicy<string> |
| | | 15 | | { |
| | 0 | 16 | | public Func<OnRejectedContext, CancellationToken, ValueTask>? OnRejected => null; |
| | | 17 | | |
| | | 18 | | public RateLimitPartition<string> GetPartition(HttpContext httpContext) |
| | | 19 | | { |
| | 46 | 20 | | var opts = options.Value; |
| | 46 | 21 | | var ip = httpContext.Connection.RemoteIpAddress?.ToString() ?? "anonymous"; |
| | 46 | 22 | | var isMutating = HttpMethods.IsPost(httpContext.Request.Method) |
| | 46 | 23 | | || HttpMethods.IsPut(httpContext.Request.Method) |
| | 46 | 24 | | || HttpMethods.IsPatch(httpContext.Request.Method) |
| | 46 | 25 | | || HttpMethods.IsDelete(httpContext.Request.Method); |
| | | 26 | | |
| | 46 | 27 | | var limit = isMutating |
| | 46 | 28 | | ? Math.Clamp(opts.AuthPostRateLimitPerMinute, 5, 1_000) |
| | 46 | 29 | | : Math.Clamp(opts.AuthGetRateLimitPerMinute, 30, 10_000); |
| | | 30 | | |
| | 46 | 31 | | var partitionKey = $"{ip}:{(isMutating ? "post" : "get")}:{limit}"; |
| | | 32 | | |
| | 46 | 33 | | return RateLimitPartition.GetFixedWindowLimiter(partitionKey, static key => |
| | 46 | 34 | | { |
| | 3 | 35 | | var lastColon = key.LastIndexOf(':'); |
| | 3 | 36 | | var parsedLimit = lastColon >= 0 && int.TryParse(key.AsSpan(lastColon + 1), out var parsed) |
| | 3 | 37 | | ? parsed |
| | 3 | 38 | | : 20; |
| | 46 | 39 | | |
| | 3 | 40 | | return new FixedWindowRateLimiterOptions |
| | 3 | 41 | | { |
| | 3 | 42 | | PermitLimit = parsedLimit, |
| | 3 | 43 | | Window = TimeSpan.FromMinutes(1), |
| | 3 | 44 | | AutoReplenishment = true, |
| | 3 | 45 | | QueueLimit = 0, |
| | 3 | 46 | | }; |
| | 46 | 47 | | }); |
| | | 48 | | } |
| | | 49 | | } |