rate limiting e cpf masking

This commit is contained in:
Jose Henrique 2025-06-19 19:56:17 -03:00
parent 68d91b8151
commit ecbf2f07d6
4 changed files with 42 additions and 3 deletions

View File

@ -8,6 +8,7 @@ namespace OpenCand.API.Config
public const string DefaultPolicy = "DefaultPolicy"; public const string DefaultPolicy = "DefaultPolicy";
public const string CandidatoSearchPolicy = "CandidatoSearchPolicy"; public const string CandidatoSearchPolicy = "CandidatoSearchPolicy";
public const string CpfRevealPolicy = "CpfRevealPolicy"; public const string CpfRevealPolicy = "CpfRevealPolicy";
public const string EstatisticaPolicy = "EstatisticaPolicy";
public static void ConfigureRateLimiting(this IServiceCollection services) public static void ConfigureRateLimiting(this IServiceCollection services)
{ {
@ -50,6 +51,15 @@ namespace OpenCand.API.Config
options.QueueLimit = 0; // No burst 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) => options.OnRejected = async (context, token) =>
{ {
context.HttpContext.Response.StatusCode = 429; context.HttpContext.Response.StatusCode = 429;

View File

@ -5,6 +5,7 @@ using OpenCand.API.Config;
using OpenCand.API.Model; using OpenCand.API.Model;
using OpenCand.API.Services; using OpenCand.API.Services;
using OpenCand.Core.Models; using OpenCand.Core.Models;
using OpenCand.Core.Utils;
namespace OpenCand.API.Controllers namespace OpenCand.API.Controllers
{ {
@ -27,7 +28,11 @@ namespace OpenCand.API.Controllers
throw new ArgumentException("Query parameter 'q' cannot be null/empty.", nameof(q)); 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")] [HttpGet("random")]
@ -42,7 +47,10 @@ namespace OpenCand.API.Controllers
[HttpGet("{id}")] [HttpGet("{id}")]
public async Task<Candidato> GetCandidatoById([FromRoute] Guid id) public async Task<Candidato> 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")] [HttpGet("{id}/bens")]

View File

@ -6,7 +6,7 @@ using OpenCand.API.Services;
namespace OpenCand.API.Controllers namespace OpenCand.API.Controllers
{ {
[EnableRateLimiting(RateLimitingConfig.DefaultPolicy)] [EnableRateLimiting(RateLimitingConfig.EstatisticaPolicy)]
public class EstatisticaController : BaseController public class EstatisticaController : BaseController
{ {
private readonly EstatisticaService estatisticaService; private readonly EstatisticaService estatisticaService;

View File

@ -0,0 +1,21 @@
namespace OpenCand.Core.Utils
{
public static class CpfMasking
{
/// <summary>
/// Masks a CPF number by replacing the middle 3 digits with '*'
/// </summary>
/// <param name="cpf">The CPF number to mask.</param>
/// <returns>The masked CPF number.</returns>
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)}";
}
}
}