Skip to content

fix(ratelimit): apply DB-defined rules at runtime + cover auth endpoints#206

Merged
antosubash merged 2 commits into
mainfrom
ratelimit-fixes
May 18, 2026
Merged

fix(ratelimit): apply DB-defined rules at runtime + cover auth endpoints#206
antosubash merged 2 commits into
mainfrom
ratelimit-fixes

Conversation

@antosubash
Copy link
Copy Markdown
Owner

Summary

Database-backed RateLimitRule rows were CRUD-only — admins could create/edit/disable them in the UI but nothing applied them at request time. Static named policies (auth-strict, etc.) existed but only one endpoint used them.

  • Add IRateLimitRuleSource (Core) + internal RateLimitRuleCache (RateLimiting module) — a singleton IHostedService that loads enabled rules into a volatile snapshot on startup and rebuilds on every admin write.
  • Wire options.GlobalLimiter to resolve the matching rule per request from the cache and partition with the right limiter; falls back to no-limit when nothing matches the path.
  • Pattern matcher supports Exact, Wildcard (/api/users/*), and CatchAll (*); prefix/suffix pre-computed at compile time to avoid per-request slicing. Specificity sort ensures concrete patterns win over catch-alls.
  • RateLimitingService.Create/Update/Delete invalidate the cache so admin edits take effect immediately.
  • .RateLimit("auth-strict") applied to LoginEndpoint, ForgotPasswordEndpoint, ResetPasswordEndpoint, and OpenIddict /connect/token — previously unprotected.
  • Rejection handler now branches to HTML for browser navigation, JSON for API/Inertia; introduced InertiaHttpExtensions.IsInertia() helper that also dedupes 4 pre-existing raw "X-Inertia" callsites.
  • Added missing UpdateRequestValidator and tightened CreateRequestValidator so window/segment/token fields can no longer be silently clamped from 0 by the cache.

Verification

  • Logged in as admin and loaded /rate-limiting/manage via Playwright; Stored Rules + Active Policies tabs render; both auth-strict and fixed-default show in the active list.
  • End-to-end: POSTed a DB rule for /health/live with permitLimit=2, hammered the endpoint 5 times — got [200, 200, 429, 429, 429]. Deleted the rule and got a clean 204. Proves the cache, GlobalLimiter, and invalidation pipeline all work together.
  • All local CI passed: biome check (auto-fixed pre-existing generator-file drift), frontend build, .NET build (0 warnings), 984 .NET tests, 47 Playwright smoke tests.

Test plan

  • CI green on PR
  • Reviewer spot-checks RateLimitRuleCache.Matches and the GlobalLimiter wiring in RateLimitingSetup
  • Reviewer confirms auth-strict on the four auth endpoints is the right policy (10 req/min per IP)

Database-backed RateLimitRule rows were CRUD-only — admins could
create/edit/disable them but nothing applied them at request time, and
only one auth endpoint used the static "auth-strict" policy.

- Add IRateLimitRuleSource (Core) + RateLimitRuleCache (module) as a
  singleton IHostedService that loads enabled rules into a volatile
  snapshot and rebuilds on every admin write
- Wire options.GlobalLimiter to consult the cache per request and
  partition by the matched rule; fall back to no-limit when nothing
  matches the path
- Pattern matcher supports exact, prefix-wildcard, and "*" catch-all,
  pre-computed at compile time to avoid per-request slicing
- Invalidate cache from Create/Update/Delete in RateLimitingService
- Apply .RateLimit("auth-strict") to LoginEndpoint, ForgotPasswordEndpoint,
  ResetPasswordEndpoint, and OpenIddict /connect/token
- OnRejected branches to HTML for browser nav, JSON for API/Inertia,
  using a new InertiaHttpExtensions helper (also dedupes 4 existing
  raw "X-Inertia" header checks)
- Tighten CreateRequestValidator and add missing UpdateRequestValidator
  so windowSeconds / segmentsPerWindow / token fields can no longer be
  silently clamped from 0 by the cache
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 17, 2026

Deploying simplemodule-website with  Cloudflare Pages  Cloudflare Pages

Latest commit: a68b5df
Status: ✅  Deploy successful!
Preview URL: https://012fa78b.simplemodule-website.pages.dev
Branch Preview URL: https://ratelimit-fixes.simplemodule-website.pages.dev

View logs

Add RateLimitPolicies in SimpleModule.Core.RateLimiting with the four
canonical names (FixedDefault, SlidingStrict, TokenBucket, AuthStrict)
and use them everywhere the policy is referenced — registration in
RateLimitingModule.ConfigureRateLimits and the five .RateLimit(...)
call sites across Users and OpenIddict. A rename is now a single edit
instead of seven, and a typo at any call site becomes a compile error.
@antosubash antosubash merged commit ff764ea into main May 18, 2026
6 checks passed
@antosubash antosubash deleted the ratelimit-fixes branch May 18, 2026 21:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant