Claude-skill-registry add-rate-limiting
Configure rate limiting policies for API endpoints with sliding window limits
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/add-rate-limiting" ~/.claude/skills/majiayu000-claude-skill-registry-add-rate-limiting && rm -rf "$T"
manifest:
skills/data/add-rate-limiting/SKILL.mdsource content
Add Rate Limiting Skill
Configure rate limiting for NovaTune API endpoints.
Project Context
- Configuration:
src/NovaTuneApp/NovaTuneApp.ApiService/appsettings.json - Rate limit setup:
src/NovaTuneApp/NovaTuneApp.ApiService/Program.cs - Policies:
src/NovaTuneApp/NovaTuneApp.ApiService/Infrastructure/RateLimiting/
Steps
1. Add Configuration Section
Location:
appsettings.json
{ "RateLimiting": { "Auth": { "LoginPerIp": { "PermitLimit": 10, "WindowMinutes": 1 }, "LoginPerAccount": { "PermitLimit": 5, "WindowMinutes": 1 }, "RegisterPerIp": { "PermitLimit": 10, "WindowMinutes": 1 }, "RefreshPerIp": { "PermitLimit": 20, "WindowMinutes": 1 } }, "Upload": { "PerUser": { "PermitLimit": 20, "WindowMinutes": 60 }, "BurstPerUser": { "PermitLimit": 5, "WindowMinutes": 1 } }, "Streaming": { "PerUser": { "PermitLimit": 60, "WindowMinutes": 1 }, "PerTrack": { "PermitLimit": 10, "WindowMinutes": 1 } }, "Telemetry": { "PerDevice": { "PermitLimit": 120, "WindowMinutes": 1 } } } }
2. Create Rate Limit Settings Classes
Location:
src/NovaTuneApp/NovaTuneApp.ApiService/Configuration/RateLimitSettings.cs
public class RateLimitSettings { public const string SectionName = "RateLimiting"; public AuthRateLimits Auth { get; set; } = new(); public UploadRateLimits Upload { get; set; } = new(); public StreamingRateLimits Streaming { get; set; } = new(); public TelemetryRateLimits Telemetry { get; set; } = new(); } public class AuthRateLimits { public RateLimitPolicy LoginPerIp { get; set; } = new(10, 1); public RateLimitPolicy LoginPerAccount { get; set; } = new(5, 1); public RateLimitPolicy RegisterPerIp { get; set; } = new(10, 1); public RateLimitPolicy RefreshPerIp { get; set; } = new(20, 1); } public class UploadRateLimits { public RateLimitPolicy PerUser { get; set; } = new(20, 60); public RateLimitPolicy BurstPerUser { get; set; } = new(5, 1); } public class StreamingRateLimits { public RateLimitPolicy PerUser { get; set; } = new(60, 1); public RateLimitPolicy PerTrack { get; set; } = new(10, 1); } public class TelemetryRateLimits { public RateLimitPolicy PerDevice { get; set; } = new(120, 1); } public record RateLimitPolicy(int PermitLimit, int WindowMinutes);
3. Configure Rate Limiting in Program.cs
using System.Threading.RateLimiting; var rateLimitSettings = builder.Configuration .GetSection(RateLimitSettings.SectionName) .Get<RateLimitSettings>() ?? new(); builder.Services.AddRateLimiter(options => { options.RejectionStatusCode = StatusCodes.Status429TooManyRequests; // Global policy for unmatched endpoints options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(context => RateLimitPartition.GetSlidingWindowLimiter( partitionKey: context.Connection.RemoteIpAddress?.ToString() ?? "unknown", factory: _ => new SlidingWindowRateLimiterOptions { PermitLimit = 100, Window = TimeSpan.FromMinutes(1), SegmentsPerWindow = 4 })); // Auth: Login per IP options.AddPolicy("auth-login-ip", context => RateLimitPartition.GetSlidingWindowLimiter( partitionKey: context.Connection.RemoteIpAddress?.ToString() ?? "unknown", factory: _ => new SlidingWindowRateLimiterOptions { PermitLimit = rateLimitSettings.Auth.LoginPerIp.PermitLimit, Window = TimeSpan.FromMinutes(rateLimitSettings.Auth.LoginPerIp.WindowMinutes), SegmentsPerWindow = 4 })); // Auth: Login per account (extracted from request body - needs custom logic) options.AddPolicy("auth-login-account", context => RateLimitPartition.GetSlidingWindowLimiter( partitionKey: GetAccountKeyFromRequest(context), factory: _ => new SlidingWindowRateLimiterOptions { PermitLimit = rateLimitSettings.Auth.LoginPerAccount.PermitLimit, Window = TimeSpan.FromMinutes(rateLimitSettings.Auth.LoginPerAccount.WindowMinutes), SegmentsPerWindow = 4 })); // Auth: Register per IP options.AddPolicy("auth-register", context => RateLimitPartition.GetSlidingWindowLimiter( partitionKey: context.Connection.RemoteIpAddress?.ToString() ?? "unknown", factory: _ => new SlidingWindowRateLimiterOptions { PermitLimit = rateLimitSettings.Auth.RegisterPerIp.PermitLimit, Window = TimeSpan.FromMinutes(rateLimitSettings.Auth.RegisterPerIp.WindowMinutes), SegmentsPerWindow = 4 })); // Upload per user options.AddPolicy("upload-user", context => RateLimitPartition.GetSlidingWindowLimiter( partitionKey: context.User.FindFirstValue(ClaimTypes.NameIdentifier) ?? "anonymous", factory: _ => new SlidingWindowRateLimiterOptions { PermitLimit = rateLimitSettings.Upload.PerUser.PermitLimit, Window = TimeSpan.FromMinutes(rateLimitSettings.Upload.PerUser.WindowMinutes), SegmentsPerWindow = 6 })); // Streaming per user options.AddPolicy("streaming-user", context => RateLimitPartition.GetSlidingWindowLimiter( partitionKey: context.User.FindFirstValue(ClaimTypes.NameIdentifier) ?? "anonymous", factory: _ => new SlidingWindowRateLimiterOptions { PermitLimit = rateLimitSettings.Streaming.PerUser.PermitLimit, Window = TimeSpan.FromMinutes(rateLimitSettings.Streaming.PerUser.WindowMinutes), SegmentsPerWindow = 4 })); // On rejected: add Retry-After header options.OnRejected = async (context, token) => { context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests; if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter)) { context.HttpContext.Response.Headers.RetryAfter = ((int)retryAfter.TotalSeconds).ToString(); } // Return Problem Details await context.HttpContext.Response.WriteAsJsonAsync(new ProblemDetails { Type = "https://novatune.example/errors/rate-limit-exceeded", Title = "Rate Limit Exceeded", Status = StatusCodes.Status429TooManyRequests, Detail = "Too many requests. Please try again later." }, token); // Log the violation var logger = context.HttpContext.RequestServices .GetRequiredService<ILogger<Program>>(); logger.LogWarning( "Rate limit exceeded for {Endpoint} from {IP}", context.HttpContext.Request.Path, context.HttpContext.Connection.RemoteIpAddress); }; }); // Helper to extract email from login request static string GetAccountKeyFromRequest(HttpContext context) { // For login endpoint, extract email from request // This requires request body buffering if (context.Items.TryGetValue("login-email", out var email) && email is string emailStr) return emailStr.ToLowerInvariant(); return context.Connection.RemoteIpAddress?.ToString() ?? "unknown"; }
4. Apply Rate Limiting Middleware
// After app.Build() app.UseRateLimiter(); // Apply to auth endpoints app.MapPost("/auth/login", Login) .RequireRateLimiting("auth-login-ip"); app.MapPost("/auth/register", Register) .RequireRateLimiting("auth-register"); // Apply to upload endpoints app.MapPost("/tracks/upload/initiate", InitiateUpload) .RequireRateLimiting("upload-user"); // Apply to streaming endpoints app.MapPost("/tracks/{id}/stream", GetStreamUrl) .RequireRateLimiting("streaming-user");
5. Create Middleware for Account-Based Rate Limiting
For login endpoint, extract email before rate limiting:
public class LoginRateLimitMiddleware { private readonly RequestDelegate _next; public LoginRateLimitMiddleware(RequestDelegate next) => _next = next; public async Task InvokeAsync(HttpContext context) { if (context.Request.Path.StartsWithSegments("/auth/login") && context.Request.Method == "POST") { context.Request.EnableBuffering(); using var reader = new StreamReader(context.Request.Body, leaveOpen: true); var body = await reader.ReadToEndAsync(); context.Request.Body.Position = 0; try { var login = JsonSerializer.Deserialize<LoginRequest>(body); if (login?.Email != null) { context.Items["login-email"] = login.Email; } } catch { /* Ignore parse errors */ } } await _next(context); } } // Register before rate limiter app.UseMiddleware<LoginRateLimitMiddleware>(); app.UseRateLimiter();
Default Rate Limits (NF-2.5)
| Endpoint | Per IP | Per Account/User | Window |
|---|---|---|---|
| 10 | 5 | 1 min |
| 10 | N/A | 1 min |
| 20 | N/A | 1 min |
| N/A | 20 (burst: 5/min) | 1 hour |
| N/A | 60 | 1 min |
| N/A | 120/device | 1 min |
Response Format
When rate limited, return HTTP 429 with:
HTTP/1.1 429 Too Many Requests Retry-After: 30 Content-Type: application/problem+json { "type": "https://novatune.example/errors/rate-limit-exceeded", "title": "Rate Limit Exceeded", "status": 429, "detail": "Too many requests. Please try again later." }
Testing
[Fact] public async Task Login_Returns429_WhenRateLimitExceeded() { // Make 11 requests (limit is 10) for (int i = 0; i < 11; i++) { var response = await _client.PostAsJsonAsync("/auth/login", new { Email = "test@example.com", Password = "wrong" }); if (i < 10) { response.StatusCode.Should().NotBe(HttpStatusCode.TooManyRequests); } else { response.StatusCode.Should().Be(HttpStatusCode.TooManyRequests); response.Headers.Should().ContainKey("Retry-After"); } } }
Observability
Log rate limit violations for monitoring:
logger.LogWarning( "Rate limit exceeded: Endpoint={Endpoint}, IP={IP}, Policy={Policy}", context.Request.Path, context.Connection.RemoteIpAddress, policyName);
Add metrics:
_meter.CreateCounter<long>("rate_limit_exceeded_total") .Add(1, new KeyValuePair<string, object?>("endpoint", endpoint));