Skip to content

API: comprehensive rate limiting across the entire API (audit + cover every abuse surface) #815

Description

@radandevist

Scope (expanded 2026-07-23): deliver the TOTAL package — a repo-wide rate-limiting audit and comprehensive coverage across every abuse surface in the API, not only the one endpoint originally named.

Background

There was no rate limiting anywhere in apps/api. POST /auth/request-password-reset (anonymous, takes an email, generates a token) was the flagged example — enumeration is closed (indistinguishable responses) but request-volume abuse (token churn, email-send amplification) was not. That endpoint is just the tip; the real fix is comprehensive.

Goal

Audit every endpoint, classify it, and apply an appropriate rate-limiting policy so nothing is unprotected.

Phase 1 — Audit

Enumerate every endpoint (route + verb, auth scope, operation class, abuse/cost risk) and produce a policy matrix at docs/guides/api-rate-limiting.md (endpoint class → policy → default limit/window/partition → the env var that tunes it). This doc is both the implementation spec and the operator reference.

Phase 2 — Comprehensive coverage

Policy buckets:

  • anon-auth (login, register, request/confirm password reset, resend + verify email, accept invitation): strictest — per-IP and per-email.
  • anon-other (any other unauthenticated endpoint, e.g. invitation lookups, public reads): per-IP.
  • authenticated-default (normal staff/tenant reads & writes): generous per-user / per-session limit tuned NOT to trip legitimate front-2 usage (TanStack Query refetch bursts, prefetch, pagination).
  • expensive / sensitive (CSV/data exports, bulk operations, anything that sends email, heavy search/list, file uploads, fan-out admin actions): tighter per-user (and per-tenant where it protects a shared tenant resource).
  • global baseline: a per-IP safety net so every endpoint is at least minimally protected — excluding health/liveness/readiness probes, SSR document routes, and static/asset serving.

Requirements

  • Correct partition keys: per-IP, per-email, per-user/session, per-tenant as appropriate.
  • Real client IP behind the Traefik/Dokploy reverse proxy — ForwardedHeaders with a trusted-proxy allow-list (env-driven), so per-IP limiting keys on the actual client and X-Forwarded-For can't be spoofed to evade/poison the limiter.
  • RFC-7807 application/problem+json 429 responses with a stable ResponseKey + translation-keyed message + Retry-After. Never 401/403.
  • Every limit configurable via AppEnvironment / env vars (tunable in production without a code change), added to .env.example and the compose files, with conservative-but-usable defaults.
  • Throttle-rejection events logged (policy name + hashed/truncated partition key — no raw email/token/PII, per the API: raw exception.Message leaks into DTOs and RFC-7807 responses (repo-wide) #881 sanitization discipline).
  • Do NOT throttle health/probe endpoints or SSR/static serving.

Acceptance

  • Policy matrix doc committed.
  • Every endpoint covered by at least the global limiter; sensitive/anon classes under their tighter named policy.
  • Real integration tests (*.Spec.cs) per class: at-cap allowed / over-cap → 429 RFC-7807 + Retry-After; per-user and per-email partition independence; an expensive endpoint proven tighter; health endpoints proven un-throttled; per-IP keys on the resolved client IP.
  • just build-api + just test-api + just ci-format green.

Originally filed narrowly from the PR #806 review-fix loop (round 7). Broadened to the full package by owner request 2026-07-23.

Metadata

Metadata

Assignees

No one assigned

    Labels

    backendenhancementNew feature or requestpr806-residueDeferred findings from the PR #806 review-fix loop; handled separately

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions