From ecbf2f07d6891ed41f3a07f530317858144629bf Mon Sep 17 00:00:00 2001 From: Jose Henrique Date: Thu, 19 Jun 2025 19:56:17 -0300 Subject: [PATCH] rate limiting e cpf masking --- OpenCand.API/Config/RateLimitingConfig.cs | 10 +++++++++ .../Controllers/CandidatoController.cs | 12 +++++++++-- .../Controllers/EstatisticaController.cs | 2 +- OpenCand.Core/Utils/CpfMasking.cs | 21 +++++++++++++++++++ 4 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 OpenCand.Core/Utils/CpfMasking.cs diff --git a/OpenCand.API/Config/RateLimitingConfig.cs b/OpenCand.API/Config/RateLimitingConfig.cs index 0c9cbf0..732db8d 100644 --- a/OpenCand.API/Config/RateLimitingConfig.cs +++ b/OpenCand.API/Config/RateLimitingConfig.cs @@ -8,6 +8,7 @@ namespace OpenCand.API.Config public const string DefaultPolicy = "DefaultPolicy"; public const string CandidatoSearchPolicy = "CandidatoSearchPolicy"; public const string CpfRevealPolicy = "CpfRevealPolicy"; + public const string EstatisticaPolicy = "EstatisticaPolicy"; public static void ConfigureRateLimiting(this IServiceCollection services) { @@ -50,6 +51,15 @@ namespace OpenCand.API.Config options.QueueLimit = 0; // No burst }); + // CPF Reveal policy: 25 requests per minute with 10 burst + options.AddFixedWindowLimiter(policyName: EstatisticaPolicy, options => + { + options.PermitLimit = 25; + options.Window = TimeSpan.FromMinutes(1); + options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; + options.QueueLimit = 10; // No burst + }); + options.OnRejected = async (context, token) => { context.HttpContext.Response.StatusCode = 429; diff --git a/OpenCand.API/Controllers/CandidatoController.cs b/OpenCand.API/Controllers/CandidatoController.cs index a328578..d6e1b5c 100644 --- a/OpenCand.API/Controllers/CandidatoController.cs +++ b/OpenCand.API/Controllers/CandidatoController.cs @@ -5,6 +5,7 @@ using OpenCand.API.Config; using OpenCand.API.Model; using OpenCand.API.Services; using OpenCand.Core.Models; +using OpenCand.Core.Utils; namespace OpenCand.API.Controllers { @@ -27,7 +28,11 @@ namespace OpenCand.API.Controllers throw new ArgumentException("Query parameter 'q' cannot be null/empty.", nameof(q)); } - return await openCandService.SearchCandidatosAsync(q); + var result = await openCandService.SearchCandidatosAsync(q); + + result.Candidatos.ForEach(c => c.Cpf = CpfMasking.MaskCpf(c.Cpf)); + + return result; } [HttpGet("random")] @@ -42,7 +47,10 @@ namespace OpenCand.API.Controllers [HttpGet("{id}")] public async Task GetCandidatoById([FromRoute] Guid id) { - return await openCandService.GetCandidatoAsync(id); + var result = await openCandService.GetCandidatoAsync(id); + result.Cpf = CpfMasking.MaskCpf(result.Cpf); + + return result; } [HttpGet("{id}/bens")] diff --git a/OpenCand.API/Controllers/EstatisticaController.cs b/OpenCand.API/Controllers/EstatisticaController.cs index d0a27f9..a14166e 100644 --- a/OpenCand.API/Controllers/EstatisticaController.cs +++ b/OpenCand.API/Controllers/EstatisticaController.cs @@ -6,7 +6,7 @@ using OpenCand.API.Services; namespace OpenCand.API.Controllers { - [EnableRateLimiting(RateLimitingConfig.DefaultPolicy)] + [EnableRateLimiting(RateLimitingConfig.EstatisticaPolicy)] public class EstatisticaController : BaseController { private readonly EstatisticaService estatisticaService; diff --git a/OpenCand.Core/Utils/CpfMasking.cs b/OpenCand.Core/Utils/CpfMasking.cs new file mode 100644 index 0000000..b981cfe --- /dev/null +++ b/OpenCand.Core/Utils/CpfMasking.cs @@ -0,0 +1,21 @@ +namespace OpenCand.Core.Utils +{ + public static class CpfMasking + { + /// + /// Masks a CPF number by replacing the middle 3 digits with '*' + /// + /// The CPF number to mask. + /// The masked CPF number. + public static string MaskCpf(string cpf) + { + if (string.IsNullOrEmpty(cpf) || cpf.Length != 11) + { + return cpf; + } + + // Mask the middle 3 digits + return $"{cpf.Substring(0, 3)}***{cpf.Substring(6)}"; + } + } +}