Skip to content

Conversation

@seapagan
Copy link
Owner

@seapagan seapagan commented Jan 6, 2026

Summary

Implements comprehensive rate limiting for all authentication endpoints to protect against brute force attacks, spam registration, and abuse. Addresses security review item #3 from SECURITY-REVIEW.md.

Key Features

  • Optional rate limiting (disabled by default) with zero overhead when disabled
  • Dual backend support: In-memory (single-instance) or Redis (multi-instance)
  • Conservative limits applied to 7 authentication endpoints:
    • Registration: 3/hour (spam prevention)
    • Login: 5/15min (brute force protection)
    • Forgot password: 3/hour (email flooding prevention)
    • Email verification: 10/minute
    • Token refresh: 20/minute
    • Password reset: 5-10/hour
  • Automatic fallback to in-memory storage if Redis connection fails
  • HTTP 429 responses with Retry-After header indicating reset time
  • Prometheus metrics integration tracking rate limit violations
  • Category-based logging (AUTH category) for monitoring

Implementation Details

New Modules

  • app/rate_limit/config.py - Conservative rate limit definitions
  • app/rate_limit/__init__.py - Limiter initialization with backend selection
  • app/rate_limit/decorators.py - @rate_limited() decorator with logging/metrics
  • app/rate_limit/handlers.py - Exception handler and Retry-After calculation

Modified Files

  • app/main.py - Register rate limit exception handler and middleware
  • app/resources/auth.py - Apply rate limiting to all 7 auth endpoints
  • app/config/settings.py - Add RATE_LIMIT_ENABLED configuration
  • app/metrics/custom.py - Add rate_limit_exceeded_total metric
  • .env.example - Document RATE_LIMIT_ENABLED setting

Architecture

  • Uses slowapi library (FastAPI-native rate limiting)
  • IP-based rate limiting via get_remote_address
  • Reuses existing Redis connection when available
  • Graceful degradation when disabled or Redis unavailable
  • Follows existing codebase patterns (decorators, dependency injection, no globals)

Documentation

  • ✅ Comprehensive documentation in docs/usage/rate-limiting.md (541 lines)
    • Overview and quick start guides
    • Configuration for both backends
    • Usage examples and best practices
    • Security considerations
    • Performance impact analysis
    • Troubleshooting guide
  • ✅ Updated README.md with feature description
  • ✅ Updated docs/index.md to mirror README
  • ✅ Added to mkdocs navigation
  • ✅ Cross-referenced with caching docs about decorator order
  • ✅ Updated .env.example

Breaking Changes

None. Rate limiting is disabled by default and completely opt-in.

Configuration

# Enable rate limiting (disabled by default)
RATE_LIMIT_ENABLED=true

# Use Redis for multi-instance deployments (optional)
REDIS_ENABLED=true
REDIS_HOST=localhost
REDIS_PORT=6379

Testing the PR

  1. Enable rate limiting:

    echo "RATE_LIMIT_ENABLED=true" >> .env
  2. Start the server:

    api-admin serve
  3. Test rate limiting on login endpoint:

    # Make 5 requests (should succeed)
    for i in {1..5}; do
      curl -X POST http://localhost:8000/login/ \
        -H "Content-Type: application/json" \
        -d '{"email":"[email protected]","password":"wrong"}'
    done
    
    # 6th request should return HTTP 429
    curl -i -X POST http://localhost:8000/login/ \
      -H "Content-Type: application/json" \
      -d '{"email":"[email protected]","password":"wrong"}'
  4. Check for Retry-After header in 429 response

  5. View metrics (if enabled): curl http://localhost:8000/metrics | grep rate_limit

Migration Notes

No migration needed. Feature is opt-in and backward compatible.

Summary by CodeRabbit

  • New Features

    • Opt-in rate limiting for authentication endpoints with conservative per-endpoint defaults, HTTP 429 responses including Retry-After, logging, and Prometheus metrics when enabled.
  • Configuration

    • New RATE_LIMIT_ENABLED flag (default: disabled) with choice of in-memory or Redis-backed storage and example .env entries.
  • Documentation

    • Expanded docs and usage guidance covering enabling, backends, decorator ordering (rate limit before cache), limits, monitoring, and troubleshooting.
  • Tests

    • New unit and integration tests covering limits, Retry-After parsing, Redis behavior, logging, and metrics.

✏️ Tip: You can customize this high-level summary in your review settings.

…ge notes

- Create complete rate limiting documentation (docs/usage/rate-limiting.md)
  - Overview of rate limiting features and backends
  - Configuration guide for in-memory and Redis backends
  - Usage examples and best practices
  - Security considerations and troubleshooting
  - 541 lines of comprehensive documentation

- Add rate limiting feature to README.md and docs/index.md
  - Describe conservative limits for auth endpoints
  - Mention both in-memory and Redis backend support
  - Note HTTP 429 responses with Retry-After header
  - Highlight Prometheus metrics integration

- Add decorator ordering notes to both caching and rate limiting docs
  - Document correct order when using both decorators
  - Explain security rationale (rate limit before cache)
  - Provide code examples with both decorators
  - Cross-reference between documentation pages

- Update mkdocs.yml navigation
  - Add "Rate Limiting and Security" before Metrics section
  - Position logically in Usage section

All documentation follows existing style and format.
@coderabbitai
Copy link

coderabbitai bot commented Jan 6, 2026

📝 Walkthrough

Walkthrough

Adds an opt-in rate-limiting subsystem using slowapi with in-memory or Redis backends, per-endpoint limits on authentication routes, a 429 Retry-After handler, Prometheus metric for violations, settings/configuration entries, documentation, and tests; no existing public signatures removed.

Changes

Cohort / File(s) Summary
Env & Dependencies
\.env.example, pyproject.toml, requirements.txt, requirements-dev.txt
Adds RATE_LIMIT_ENABLED to example env; adds slowapi/limits and supporting deps; adds pytest warning filter and test env flag.
Settings
app/config/settings.py
Adds rate_limit_enabled: bool = False and rate_limit_storage_url(self) -> str to expose Redis URL when applicable.
Rate limit core
app/rate_limit/__init__.py, app/rate_limit/config.py, app/rate_limit/decorators.py, app/rate_limit/handlers.py
New limiter initializer selecting Redis vs memory, RateLimits constants, rate_limited(limit) decorator (logs, metrics, extracts request/IP), and rate_limit_handler with parse_retry_after.
App integration
app/main.py, app/resources/auth.py
Registers SlowAPI middleware and RateLimitExceeded handler; applies @rate_limited(...) to auth endpoints and injects request: Request into route signatures.
Metrics
app/metrics/__init__.py, app/metrics/custom.py
Exposes increment_rate_limit_exceeded and adds rate_limit_exceeded_total Prometheus counter (labels: endpoint, limit).
Docs & Site
README.md, SECURITY-REVIEW.md, TODO.md, docs/index.md, docs/usage/caching.md, docs/usage/rate-limiting.md, mkdocs.yml
Adds comprehensive rate-limiting documentation, usage guidance, caching ordering note, security considerations, troubleshooting, and mkdocs navigation entry.
Tests — integration
tests/integration/test_rate_limiting.py, tests/integration/test_rate_limit_redis.py, tests/integration/test_rate_limit_metrics.py
Integration tests for enforcement across auth endpoints, Retry-After header, Redis vs memory selection, and metrics exposure.
Tests — unit / helpers
tests/unit/test_rate_limit_config.py, tests/unit/test_rate_limit_helpers.py, tests/unit/test_rate_limit_init.py, tests/unit/test_logging_helpers.py, tests/unit/test_logging.py
Unit tests for RateLimits formatting/values, parse_retry_after, limiter init logging, logging helper redaction; two logging tests removed and replaced by helper-focused tests.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant App as FastAPI
    participant Decorator as rate_limited
    participant Storage as Redis/Memory
    participant Auth as Auth Handler
    participant Metrics as Prometheus

    Client->>App: HTTP request (e.g., POST /register)
    App->>Decorator: invoke rate_limited (key = get_remote_address)
    Decorator->>Storage: read/update counter for key+endpoint
    alt Within limit
        Storage-->>Decorator: count < quota
        Decorator-->>App: allow
        App->>Auth: call endpoint handler (request injected)
        Auth-->>App: response (200/201/...)
        App-->>Client: return response
    else Limit exceeded
        Storage-->>Decorator: count >= quota
        Decorator->>Metrics: increment_rate_limit_exceeded(endpoint, limit)
        Decorator-->>App: raise RateLimitExceeded
        App->>App: rate_limit_handler(parse_retry_after)
        App-->>Client: 429 with Retry-After header and JSON detail
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I counted hops to guard the gate,
Redis or memory, set the state,
When too many knock, I softly chide,
"Retry-After" on the side,
Metrics hum — the warren's safe.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately reflects the main change: adding rate limiting for authentication endpoints. It is concise, specific, and directly related to the primary objective documented in the PR summary.
Docstring Coverage ✅ Passed Docstring coverage is 96.08% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between af464de and 555bc79.

📒 Files selected for processing (1)
  • pyproject.toml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.14)
🔇 Additional comments (3)
pyproject.toml (3)

244-244: LGTM! The filterwarning is appropriately scoped.

The deprecation warning is correctly limited to slowapi.extension, preventing suppression of similar warnings from other modules.


259-259: LGTM! Good test isolation practice.

Disabling rate limiting by default in tests prevents local .env configurations from affecting test behavior. Integration tests can explicitly enable it when needed via monkeypatch.


37-37: The dependency specification is appropriate. Version 0.1.9 is the current release on PyPI (released Feb 5, 2024) with no known security vulnerabilities. The >=0.1.9 constraint correctly allows future updates while maintaining a known stable baseline.


Comment @coderabbitai help to get the list of available commands and usage tips.

@codacy-production
Copy link

codacy-production bot commented Jan 6, 2026

Coverage summary from Codacy

See diff coverage on Codacy

Coverage variation Diff coverage
+0.00% (target: -1.00%) 100.00%
Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (b631964) 2246 2246 100.00%
Head commit (555bc79) 2353 (+107) 2353 (+107) 100.00% (+0.00%)

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#807) 108 108 100.00%

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

See your quality gate settings    Change summary preferences

@seapagan seapagan self-assigned this Jan 6, 2026
@seapagan seapagan added the enhancement New feature or request label Jan 6, 2026
Signed-off-by: Grant Ramsay <[email protected]>
…ancement plan

- Mark critical item #3 as complete with implementation details
- Update summary to show 4 of 5 critical issues closed
- Add checkmarks to all 7 implemented rate limits
- Mark rate limiting complete in Sprint 1 implementation order
- Add comprehensive user-based rate limiting enhancement plan to future features
- Document implementation approach, benefits, and technical considerations
@seapagan seapagan marked this pull request as ready for review January 7, 2026 20:37
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @app/rate_limit/decorators.py:
- Around line 61-63: The code sets client_ip = "unknown" and then assumes
request.client is non-None when doing request.client.host, which can raise
AttributeError; update the logic in the decorator where client_ip is determined
to safely handle a None request.client (e.g., check request is truthy and
request.client is not None and hasattr(request.client, "host") or use
getattr(request.client, "host", None)) and fall back to "unknown" if host is
missing; make this change around the client_ip assignment to avoid accessing
.host on None.

In @tests/integration/test_rate_limit_metrics.py:
- Around line 44-53: The assertion in the test uses a weak fallback ("or '#
HELP' in metrics_text") that always passes; update the check in
tests/integration/test_rate_limit_metrics.py to assert presence of
"rate_limit_exceeded_total" only (i.e., replace the current compound assert with
assert "rate_limit_exceeded_total" in metrics_text) or, if metric timing is
genuinely unreliable, mark the test with @pytest.mark.skip and add a short skip
reason; locate the assertion around variables metrics_response and metrics_text
to apply the change.

In @tests/unit/test_rate_limit_config.py:
- Around line 21-32: The period validation is inconsistent; update the check in
the loop over limits in tests/unit/test_rate_limit_config.py (variables: limits,
limit, count, period) to use a single pattern match instead of the current list
+ endswith logic—validate that period matches an optional numeric prefix
followed by one of the base units with optional plural (e.g. regex like:
optional digits + (second|minute|hour|day) with optional trailing "s"), and
replace the existing asserts with a regex match assertion so inputs like
"15minutes" or "2hours" are handled correctly.
🧹 Nitpick comments (6)
tests/unit/test_logging_helpers.py (1)

10-20: Suggest additional test cases for comprehensive coverage.

The current tests only cover empty queries and queries without parameters. Consider adding tests for:

  • Queries with sensitive keys that should be redacted (e.g., "token=secret123""token=REDACTED")
  • Queries with mixed sensitive and non-sensitive keys (e.g., "name=test&token=secret""name=test&token=REDACTED")
  • Queries with multiple sensitive keys
  • Edge cases like queries with blank values

This would ensure the redaction logic is fully validated.

🧪 Proposed additional test cases

Add these test methods to the TestRedactQuery class:

def test_redact_query_redacts_sensitive_keys(self) -> None:
    """Test redaction of sensitive query parameters."""
    assert "token=REDACTED" in LoggingMiddleware._redact_query("token=secret123")
    assert "api_key=REDACTED" in LoggingMiddleware._redact_query("api_key=mykey")

def test_redact_query_preserves_non_sensitive_keys(self) -> None:
    """Test non-sensitive parameters are preserved."""
    result = LoggingMiddleware._redact_query("name=test&page=1")
    assert "name=test" in result
    assert "page=1" in result

def test_redact_query_handles_mixed_keys(self) -> None:
    """Test redaction with both sensitive and non-sensitive parameters."""
    result = LoggingMiddleware._redact_query("name=test&token=secret&page=1")
    assert "name=test" in result
    assert "token=REDACTED" in result
    assert "page=1" in result
pyproject.toml (1)

244-244: Consider advocating for slowapi to switch to inspect.iscoroutinefunction to silence the deprecation warning.

Slowapi (latest release 0.1.9 in Feb 2024) is not actively maintained with infrequent releases and sporadic development activity. The deprecation warning for asyncio.iscoroutinefunction is a known issue—slowapi has a PR to switch to inspect.iscoroutinefunction (the CPython-recommended replacement), as other projects like Starlette have already done. While filtering the warning is a valid interim workaround, consider opening an issue or reaching out to maintainers to prioritize this fix, since the slow release cycle means it may remain unresolved for some time.

tests/unit/test_rate_limit_helpers.py (1)

6-41: Good test coverage for the parsing helper.

The tests cover the main formats and edge cases well. Consider adding a test for empty string input to ensure robustness:

💡 Optional: Add edge case for empty string
def test_parse_empty_string(self) -> None:
    """Test empty string returns default."""
    assert parse_retry_after("") == "3600"
docs/usage/rate-limiting.md (1)

531-548: Dynamic rate limits example is incomplete.

The example shows the decorator structure but doesn't actually apply rate limiting—the comment # Apply limit dynamically has no implementation. This could mislead users.

Consider either completing the example with actual dynamic limit application (which would require custom slowapi integration) or adding a note that this is a conceptual pattern requiring additional implementation.

app/rate_limit/handlers.py (2)

10-50: Extract default retry value as a constant.

The hardcoded value "3600" appears four times (lines 25, 33, 48, 62). Consider extracting it as a module-level constant for better maintainability.

♻️ Proposed refactor

Add at the top of the file after imports:

# Default retry-after period (1 hour in seconds)
DEFAULT_RETRY_AFTER = "3600"

Then replace the hardcoded values:

 def parse_retry_after(limit_str: str) -> str:
     """Parse rate limit string and return Retry-After seconds.
 
     Args:
         limit_str: Rate limit string like "3 per 1 hour" or "5/15minutes"
 
     Returns:
         Retry-After value in seconds as string
     """
     # Handle both "X per Y period" and "X/Y period" formats
     limit_str = limit_str.replace(" per ", "/")
 
     # Extract the time period
     parts = limit_str.split("/")
     if len(parts) < 2:  # noqa: PLR2004
-        return "3600"  # Default to 1 hour
+        return DEFAULT_RETRY_AFTER
 
     period_str = parts[1].strip().lower()
 
     # Parse period to seconds
     # Extract number prefix if present (e.g., "15minutes" -> 15)
     match = re.match(r"(\d+)?\s*(\w+)", period_str)
     if not match:
-        return "3600"
+        return DEFAULT_RETRY_AFTER
 
     multiplier_str, unit = match.groups()
     multiplier = int(multiplier_str) if multiplier_str else 1
 
     # Convert unit to seconds
     if unit.startswith("second"):
         seconds = 1
     elif unit.startswith("minute"):
         seconds = 60
     elif unit.startswith("hour"):
         seconds = 3600
     elif unit.startswith("day"):
         seconds = 86400
     else:
-        seconds = 3600  # Default to hour
+        seconds = 3600  # Default to hour (fallback within conversion)
 
     return str(multiplier * seconds)

And in the handler:

 async def rate_limit_handler(
     request: Request,  # noqa: ARG001 - Required by FastAPI
     exc: RateLimitExceeded,
 ) -> JSONResponse:
     """Handle rate limit exceeded exceptions.
 
     Returns a 429 status with Retry-After header.
     """
     # Calculate Retry-After from limit string
-    retry_after = "3600"  # Default to 1 hour
+    retry_after = DEFAULT_RETRY_AFTER
     if exc.limit is not None:
         limit_str = str(exc.limit.limit)
         retry_after = parse_retry_after(limit_str)

38-49: Consider using a dict for unit-to-seconds mapping.

The if-elif chain for unit conversion could be replaced with a dictionary lookup for better maintainability and readability.

♻️ Proposed refactor
+    # Unit to seconds mapping
+    UNIT_TO_SECONDS = {
+        "second": 1,
+        "minute": 60,
+        "hour": 3600,
+        "day": 86400,
+    }
+
     multiplier_str, unit = match.groups()
     multiplier = int(multiplier_str) if multiplier_str else 1
 
-    # Convert unit to seconds
-    if unit.startswith("second"):
-        seconds = 1
-    elif unit.startswith("minute"):
-        seconds = 60
-    elif unit.startswith("hour"):
-        seconds = 3600
-    elif unit.startswith("day"):
-        seconds = 86400
-    else:
-        seconds = 3600  # Default to hour
+    # Find matching unit (using startswith for flexibility)
+    seconds = next(
+        (v for k, v in UNIT_TO_SECONDS.items() if unit.startswith(k)),
+        3600  # Default to hour if no match
+    )
 
     return str(multiplier * seconds)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b631964 and 8d4e7f1.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (28)
  • .env.example
  • README.md
  • SECURITY-REVIEW.md
  • TODO.md
  • app/config/settings.py
  • app/main.py
  • app/metrics/__init__.py
  • app/metrics/custom.py
  • app/rate_limit/__init__.py
  • app/rate_limit/config.py
  • app/rate_limit/decorators.py
  • app/rate_limit/handlers.py
  • app/resources/auth.py
  • docs/index.md
  • docs/usage/caching.md
  • docs/usage/rate-limiting.md
  • mkdocs.yml
  • pyproject.toml
  • requirements-dev.txt
  • requirements.txt
  • tests/integration/test_rate_limit_metrics.py
  • tests/integration/test_rate_limit_redis.py
  • tests/integration/test_rate_limiting.py
  • tests/unit/test_logging.py
  • tests/unit/test_logging_helpers.py
  • tests/unit/test_rate_limit_config.py
  • tests/unit/test_rate_limit_helpers.py
  • tests/unit/test_rate_limit_init.py
💤 Files with no reviewable changes (1)
  • tests/unit/test_logging.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-01T19:57:45.447Z
Learnt from: seapagan
Repo: seapagan/fastapi-template PR: 803
File: requirements.txt:164-165
Timestamp: 2026-01-01T19:57:45.447Z
Learning: When using fastapi-cache2 (versions <= 0.2.2), ensure Redis is constrained to <5.0.0. Do not upgrade Redis to 5.x or 7.x as it is incompatible. In this repository, pin Redis to a compatible 4.x version (e.g., redis==4.6.0 or redis<5.0.0) in requirements.txt to maintain compatibility with fastapi-cache2.

Applied to files:

  • requirements.txt
🧬 Code graph analysis (11)
app/rate_limit/decorators.py (4)
app/config/log_config.py (1)
  • LogCategory (13-28)
app/metrics/custom.py (1)
  • increment_rate_limit_exceeded (60-66)
app/rate_limit/__init__.py (1)
  • get_limiter (19-66)
app/logs.py (1)
  • warning (36-39)
app/metrics/custom.py (2)
app/config/settings.py (1)
  • get_settings (267-269)
tests/integration/test_metrics_endpoint.py (1)
  • test_auth_failure_metrics (354-380)
tests/integration/test_rate_limit_redis.py (2)
app/config/settings.py (2)
  • Settings (30-263)
  • rate_limit_storage_url (169-181)
tests/unit/test_validation.py (1)
  • TestRedisUrlProperty (199-269)
tests/unit/test_rate_limit_init.py (2)
app/config/settings.py (1)
  • redis_url (151-166)
app/rate_limit/__init__.py (1)
  • get_limiter (19-66)
app/main.py (1)
app/rate_limit/handlers.py (1)
  • rate_limit_handler (53-71)
tests/unit/test_rate_limit_config.py (1)
app/rate_limit/config.py (1)
  • RateLimits (6-33)
tests/unit/test_logging_helpers.py (1)
app/middleware/logging_middleware.py (2)
  • LoggingMiddleware (21-101)
  • _redact_query (48-69)
tests/unit/test_rate_limit_helpers.py (1)
app/rate_limit/handlers.py (1)
  • parse_retry_after (10-50)
app/metrics/__init__.py (1)
app/metrics/custom.py (1)
  • increment_rate_limit_exceeded (60-66)
app/rate_limit/__init__.py (3)
app/config/settings.py (2)
  • get_settings (267-269)
  • redis_url (151-166)
app/config/log_config.py (1)
  • LogCategory (13-28)
app/logs.py (1)
  • info (26-29)
app/resources/auth.py (3)
app/rate_limit/config.py (1)
  • RateLimits (6-33)
app/rate_limit/decorators.py (1)
  • rate_limited (14-81)
app/database/db.py (1)
  • get_database (78-81)
🪛 LanguageTool
docs/usage/caching.md

[uncategorized] ~217-~217: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...mited()` decorators together, place rate limiting first for security. Rate limits should ...

(EN_COMPOUND_ADJECTIVE_INTERNAL)

SECURITY-REVIEW.md

[grammar] ~518-~518: Use a hyphen to join words.
Context: ...ints. Next enhancement: add user/API-key based limiting for authenticated API o...

(QB_NEW_EN_HYPHEN)


[uncategorized] ~652-~652: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...condition (#11) - ✅ app/rate_limit/ - Rate limiting implementation (#3) High Priority:...

(EN_COMPOUND_ADJECTIVE_INTERNAL)

docs/usage/rate-limiting.md

[grammar] ~7-~7: Use a hyphen to join words.
Context: ...rute force attacks, abuse, and denial of service attempts. Rate limiting is **dis...

(QB_NEW_EN_HYPHEN)


[grammar] ~12-~12: Use a hyphen to join words.
Context: ...ntaining good user experience. The rate limiting system supports two backends: ...

(QB_NEW_EN_HYPHEN)


[grammar] ~76-~76: Use a hyphen to join words.
Context: ...ate limiting. ## How It Works ### Rate Limiting Behavior The rate limiting sys...

(QB_NEW_EN_HYPHEN)


[grammar] ~78-~78: Use a hyphen to join words.
Context: ...ks ### Rate Limiting Behavior The rate limiting system has three operating mode...

(QB_NEW_EN_HYPHEN)


[uncategorized] ~95-~95: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: .... Automatically falls back to in-memory rate limiting 3. Application continues functioning no...

(EN_COMPOUND_ADJECTIVE_INTERNAL)


[grammar] ~149-~149: Use a hyphen to join words.
Context: ...s whether to use Redis or in-memory rate limiting backend. Only relevant when `RA...

(QB_NEW_EN_HYPHEN)


[uncategorized] ~224-~224: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...ached()` decorators together, place rate limiting first for security. Rate limits should ...

(EN_COMPOUND_ADJECTIVE_INTERNAL)


[grammar] ~281-~281: Use a hyphen to join words.
Context: ...0/day"` - 1000 requests per day ## Rate Limiting Strategy ### IP-Based Limiting...

(QB_NEW_EN_HYPHEN)


[uncategorized] ~430-~430: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...k) - When Disabled: Zero overhead - rate limiting code is not executed ### Redis Memor...

(EN_COMPOUND_ADJECTIVE_INTERNAL)


[grammar] ~529-~529: Use a hyphen to join words.
Context: ... ### Dynamic Rate Limits For user-tier based limits, consider creating a custom...

(QB_NEW_EN_HYPHEN)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.14)
🔇 Additional comments (44)
pyproject.toml (1)

37-37: LGTM - slowapi dependency added.

The addition of slowapi for rate limiting functionality is appropriate.

requirements-dev.txt (1)

184-185: LGTM - Dependencies correctly added.

The slowapi (0.1.9) and limits (5.6.0) dependencies have been properly added to the auto-generated requirements file. The pinned versions ensure reproducible builds.

Also applies to: 399-400

README.md (1)

131-136: LGTM - Clear and comprehensive documentation.

The rate limiting feature documentation is well-written and covers all essential aspects:

  • Opt-in nature (disabled by default)
  • Backend options (in-memory for single-instance, Redis for multi-instance)
  • Conservative default limits with specific examples
  • HTTP 429 responses with Retry-After header
  • Prometheus metrics integration

This provides users with a clear understanding of the feature's capabilities and behavior.

app/config/settings.py (2)

141-144: LGTM - Rate limiting configuration follows established patterns.

The rate_limit_enabled setting is appropriately:

  • Disabled by default (opt-in)
  • Documented with clear comments
  • Consistent with other opt-in features (cache_enabled, metrics_enabled)
  • Designed to work in conjunction with redis_enabled

168-181: LGTM - Storage URL property implements correct backend selection.

The rate_limit_storage_url property correctly:

  • Returns Redis URL when both rate_limit_enabled and redis_enabled are True
  • Returns empty string for in-memory storage (when either flag is False)
  • Includes comprehensive docstring explaining the behavior
  • Follows the same pattern as the existing redis_url property

This design cleanly separates Redis-backed rate limiting from in-memory rate limiting.

.env.example (1)

153-160: LGTM! Clear and well-documented rate limiting configuration.

The opt-in design with sensible defaults and clear documentation of Redis integration is excellent. The reuse of existing Redis configuration keeps setup simple.

app/metrics/__init__.py (1)

7-7: LGTM! Correct export of new metric function.

The import and all entry properly expose the new rate limit metric function, following the established pattern for other metrics.

Also applies to: 18-18

requirements.txt (2)

171-172: Redis version correctly maintained for fastapi-cache2 compatibility.

Good—redis==4.6.0 remains within the required constraint (redis<5.0.0) for fastapi-cache2<=0.2.2 compatibility. Based on learnings, this is correct.


53-54: No action needed—slowapi version is current and secure.

The slowapi version 0.1.9 used in the project is the latest stable release (Feb 5, 2024) and has no known security vulnerabilities, security advisories, or CVEs recorded in major vulnerability databases (Safety DB, Snyk).

Likely an incorrect or invalid review comment.

app/metrics/custom.py (2)

32-38: LGTM! Rate limit counter follows established metrics pattern.

The Counter definition is well-structured with appropriate labels (endpoint, limit) for granular tracking of rate limit violations.


59-66: LGTM! Increment function correctly implements metrics guard pattern.

The function properly follows the existing pattern of checking metrics_enabled before incrementing, ensuring zero overhead when metrics are disabled.

app/rate_limit/config.py (1)

6-33: Rate limit string formats are intentionally supported and properly validated.

The format validation in tests/unit/test_rate_limit_config.py (lines 21-32) explicitly accepts "5/15minutes" format. The test validator accepts period names in ["second", "minute", "hour", "day", "minutes", "hours"] OR any period ending with "minutes" (line 32), which intentionally supports custom time periods like "15minutes". The test test_conservative_limits (line 37) confirms LOGIN == "5/15minutes" is the expected value. The decorator passes all rate limit strings directly to slowapi's limiter.limit() method, which handles these formats correctly. Mixed singular/plural period names are intentionally supported.

Likely an incorrect or invalid review comment.

SECURITY-REVIEW.md (2)

54-76: LGTM! Thorough documentation of rate limiting implementation.

The documentation accurately reflects the implementation details: slowapi integration, dual backend support (in-memory and Redis), conservative per-endpoint limits, HTTP 429 responses with Retry-After headers, and Prometheus metrics tracking. The checkmarks on each specific endpoint provide clear visibility into coverage.


517-547: Excellent future enhancement planning.

The detailed feature idea for user-based rate limiting (in addition to the IP-based implementation) is well thought out. The implementation approach, code examples, and technical considerations provide a clear roadmap for when this enhancement is prioritized.

TODO.md (1)

7-8: LGTM!

The TODO item accurately reflects the current implementation (IP-based rate limiting) and documents the next enhancement (user-based quotas).

mkdocs.yml (1)

82-82: LGTM!

The navigation entry is correctly added and properly formatted.

docs/index.md (1)

80-85: LGTM!

The feature description accurately documents the rate limiting implementation with all key details: opt-in nature, dual backend support, specific limits, and HTTP 429 responses with Retry-After headers.

docs/usage/caching.md (1)

215-231: Excellent security guidance on decorator order.

This tip provides important security advice: rate limiting should be checked before caching to prevent users from bypassing rate limits via cached responses. The code example clearly demonstrates the correct pattern with rate_limited applied first.

app/main.py (2)

167-171: LGTM! Exception handler registration looks correct.

The cast("Any", rate_limit_handler) works around FastAPI's exception handler type expectations. The handler signature in app/rate_limit/handlers.py correctly accepts Request and RateLimitExceeded and returns JSONResponse.


204-208: LGTM! Middleware and state setup is correctly unconditional.

The comment clearly explains why app.state.limiter and SlowAPIMiddleware are always registered—the limiter itself handles the disabled state internally via enabled=False. This avoids conditional middleware registration complexity.

tests/integration/test_rate_limit_redis.py (1)

8-45: LGTM! Good coverage of the storage URL property logic.

The tests correctly verify all three branches of rate_limit_storage_url:

  1. Both flags enabled → Redis URL
  2. Rate limiting enabled, Redis disabled → empty string
  3. Rate limiting disabled → empty string

Direct Settings instantiation is a clean approach for testing computed properties.

tests/integration/test_rate_limit_metrics.py (1)

22-24: Monkeypatching cached settings may not affect already-initialized components.

The limiter singleton in app/rate_limit/__init__.py is created at import time. Patching settings.rate_limit_enabled after import won't reinitialize the limiter. This may explain why the test uses a soft assertion.

Consider whether the test needs to force limiter reinitialization, or if a dedicated test fixture that patches settings before app initialization would be more reliable.

app/rate_limit/decorators.py (1)

41-45: Limiter is resolved at decoration time, not request time.

get_limiter() is called when the decorator is applied (module import), which means the limiter configuration is locked in at startup. This is likely intentional for performance, but worth noting that runtime setting changes won't affect already-decorated endpoints.

app/rate_limit/__init__.py (2)

19-66: LGTM! Clean singleton implementation with appropriate backend selection.

The get_limiter() function correctly:

  • Uses singleton pattern to avoid re-initialization
  • Selects Redis backend only when both rate_limit_enabled and redis_enabled are true
  • Falls back to memory:// for in-memory storage
  • Logs initialization state under the AUTH category
  • Respects the enabled flag to allow zero-overhead when disabled

69-70: Module-level initialization locks configuration at import time.

limiter = get_limiter() executes at module import, so settings are read once at startup. This is appropriate for the use case but means configuration changes require a restart.

docs/usage/rate-limiting.md (1)

1-560: Comprehensive and well-structured documentation.

The documentation covers all aspects of the rate limiting feature: configuration options, usage patterns, security considerations, troubleshooting, and advanced usage. The examples are practical and the explanations are clear.

tests/unit/test_rate_limit_init.py (2)

12-34: LGTM! Good test isolation.

The test properly resets the cached limiter to force re-initialization and verifies the expected log message. The use of mocker.ANY for the LogCategory parameter is acceptable here.


36-57: LGTM! Consistent test pattern.

The test follows the same pattern as the Redis test, ensuring consistent coverage of both initialization paths.

app/rate_limit/handlers.py (1)

53-71: LGTM! Proper exception handling and response structure.

The handler correctly returns a 429 response with appropriate headers and falls back gracefully when limit information is unavailable.

tests/unit/test_rate_limit_config.py (1)

34-42: LGTM! Exact value validation ensures security requirements.

The test properly validates that the rate limits match the conservative values specified in SECURITY-REVIEW.md.

tests/integration/test_rate_limiting.py (6)

14-26: LGTM! Proper test isolation.

The autouse fixture correctly resets rate limiter storage between tests to ensure test independence.


33-72: LGTM! Comprehensive rate limit testing.

The test properly validates the 3/hour limit on registration and verifies both successful and rate-limited responses.


74-114: LGTM! Handles authentication failure scenarios.

The test correctly accepts both authentication failures (400, 401) and success (200) as valid non-rate-limited responses, ensuring the rate limiter is tested independently of authentication logic.


116-144: LGTM! Tests forgot-password rate limiting.

The test validates the 3/hour limit on password recovery to prevent email flooding.


146-185: LGTM! Validates Retry-After header presence.

The test ensures rate-limited responses include the required Retry-After header using case-insensitive lookup.


187-226: LGTM! Verifies user-friendly error messages.

The test confirms that rate limit errors include appropriate user-facing messaging.

app/resources/auth.py (8)

29-30: LGTM! Proper imports for rate limiting.

The imports are correctly added for the rate limiting functionality.


58-64: LGTM! Rate limiting applied to registration.

The @rate_limited decorator is correctly applied with the REGISTER limit, and the Request parameter is properly added with ARG001 suppression since slowapi requires it but the endpoint doesn't use it directly.


90-95: LGTM! Rate limiting applied to login.

The decorator and Request parameter are correctly added to protect against brute-force attacks.


114-119: LGTM! Rate limiting applied to token refresh.

The decorator properly protects the refresh endpoint from token harvesting attempts.


130-135: LGTM! Rate limiting applied to verification.

The decorator correctly limits email verification attempts to prevent abuse.


152-158: LGTM! Rate limiting applied to forgot-password.

The decorator properly prevents email flooding through the password recovery endpoint.


179-184: LGTM! Rate limiting applied to password reset form.

The decorator is correctly applied. Note that this endpoint already had a Request parameter (line 181), so no ARG001 suppression is added since the Request is used in the endpoint logic.


252-256: LGTM! Rate limiting applied to password reset POST.

The decorator protects the critical password reset operation. The Request parameter (line 254) was already present and is used throughout the endpoint, so no ARG001 suppression is needed.

- Add null check for request.client to prevent AttributeError
- Remove weak assertion fallback that always passed in metrics test
- Fix period validation regex to properly handle compound periods
- Extract DEFAULT_RETRY_AFTER constant to eliminate hardcoded values
- Refactor unit conversion from if-elif chain to dictionary lookup
- Add comprehensive query redaction tests for all edge cases
- Add empty string edge case test for retry-after parsing
- Clarify dynamic rate limits documentation with implementation notes
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
tests/unit/test_rate_limit_config.py (1)

11-30: Solid format validation with correct regex for compound periods.

The regex pattern correctly handles both simple periods ("hour") and compound periods ("15minutes"), which aligns with the commit message about strengthening period validation.

💡 Optional: Add semantic validation for count > 0

While not critical for config tests, you could optionally add a check that the parsed count is greater than zero to catch semantically invalid values like "0/hour":

         for limit in limits:
             assert "/" in limit
             count, period = limit.split("/")
             assert count.isdigit()
+            assert int(count) > 0, f"Count must be positive: {limit}"
             # Period: [number]unit (second/minute/hour/day + optional 's')
             assert re.match(
                 r"^\d*\s*(seconds?|minutes?|hours?|days?)$", period
             ), f"Invalid period format: {period}"
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8d4e7f1 and a4176b5.

📒 Files selected for processing (7)
  • app/rate_limit/decorators.py
  • app/rate_limit/handlers.py
  • docs/usage/rate-limiting.md
  • tests/integration/test_rate_limit_metrics.py
  • tests/unit/test_logging_helpers.py
  • tests/unit/test_rate_limit_config.py
  • tests/unit/test_rate_limit_helpers.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • tests/unit/test_rate_limit_helpers.py
  • tests/integration/test_rate_limit_metrics.py
  • tests/unit/test_logging_helpers.py
  • app/rate_limit/decorators.py
🧰 Additional context used
🧬 Code graph analysis (1)
tests/unit/test_rate_limit_config.py (1)
app/rate_limit/config.py (1)
  • RateLimits (6-33)
🪛 LanguageTool
docs/usage/rate-limiting.md

[grammar] ~7-~7: Use a hyphen to join words.
Context: ...rute force attacks, abuse, and denial of service attempts. Rate limiting is **dis...

(QB_NEW_EN_HYPHEN)


[grammar] ~12-~12: Use a hyphen to join words.
Context: ...ntaining good user experience. The rate limiting system supports two backends: ...

(QB_NEW_EN_HYPHEN)


[grammar] ~76-~76: Use a hyphen to join words.
Context: ...ate limiting. ## How It Works ### Rate Limiting Behavior The rate limiting sys...

(QB_NEW_EN_HYPHEN)


[grammar] ~78-~78: Use a hyphen to join words.
Context: ...ks ### Rate Limiting Behavior The rate limiting system has three operating mode...

(QB_NEW_EN_HYPHEN)


[uncategorized] ~95-~95: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: .... Automatically falls back to in-memory rate limiting 3. Application continues functioning no...

(EN_COMPOUND_ADJECTIVE_INTERNAL)


[grammar] ~149-~149: Use a hyphen to join words.
Context: ...s whether to use Redis or in-memory rate limiting backend. Only relevant when `RA...

(QB_NEW_EN_HYPHEN)


[uncategorized] ~224-~224: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...ached()` decorators together, place rate limiting first for security. Rate limits should ...

(EN_COMPOUND_ADJECTIVE_INTERNAL)


[grammar] ~281-~281: Use a hyphen to join words.
Context: ...0/day"` - 1000 requests per day ## Rate Limiting Strategy ### IP-Based Limiting...

(QB_NEW_EN_HYPHEN)


[uncategorized] ~431-~431: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...k) - When Disabled: Zero overhead - rate limiting code is not executed ### Redis Memor...

(EN_COMPOUND_ADJECTIVE_INTERNAL)


[grammar] ~536-~536: Use a hyphen to join words.
Context: ...omplex dynamic scenarios. For user-tier based limits, you could structure a cust...

(QB_NEW_EN_HYPHEN)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.14)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.10)
🔇 Additional comments (6)
tests/unit/test_rate_limit_config.py (2)

1-10: LGTM! Clean test structure.

The imports and test class setup are well-organized with appropriate dependencies for validating rate limit configuration.


32-40: Excellent! Enforces security requirements.

These exact-value assertions ensure rate limits remain aligned with the security review requirements and prevent unintended configuration drift. The noqa: S105 comments appropriately suppress false positives from bandit.

app/rate_limit/handlers.py (3)

1-19: LGTM! Clean module structure.

The imports, constants, and unit mapping are well-organized. The 1-hour default is a reasonable fallback.


21-56: LGTM! Robust parsing with appropriate fallbacks.

The function correctly handles multiple format variations and gracefully falls back to the default on invalid input. The use of startswith at line 51 provides flexibility for plural forms (e.g., "minute" matches "minutes").


58-76: LGTM! Proper exception handling with security-conscious response.

The handler correctly extracts the limit, parses it for Retry-After, and returns a generic error message (avoiding information leakage about specific limits).

docs/usage/rate-limiting.md (1)

1-575: LGTM! Comprehensive and technically accurate documentation.

The documentation is well-structured, provides clear examples, and accurately describes the rate limiting implementation. Key strengths:

  • Clear instructions for both in-memory and Redis setups
  • Code examples are correct and include necessary parameters (e.g., request: Request)
  • Security section is balanced, listing both protections and limitations
  • Honest about conceptual examples (lines 530-534) that require additional implementation
  • Comprehensive troubleshooting guidance

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
tests/unit/test_rate_limit_config.py (2)

11-31: Consider adding a completeness check to catch missing constants.

The format validation logic is sound and correctly handles slowapi's format (e.g., "15minutes", "hour"). However, if a new rate limit constant is added to RateLimits but forgotten here, the test will still pass.

♻️ Optional: Add a completeness test

You could add a test method that uses introspection to ensure all RateLimits constants are covered:

def test_all_rate_limits_tested(self) -> None:
    """Ensure all RateLimits constants are tested."""
    from typing import get_type_hints
    
    # Get all ClassVar attributes from RateLimits
    rate_limit_attrs = [
        attr for attr in dir(RateLimits)
        if not attr.startswith('_') and isinstance(getattr(RateLimits, attr), str)
    ]
    
    tested_limits = [
        RateLimits.REGISTER,
        RateLimits.LOGIN,
        RateLimits.FORGOT_PASSWORD,
        RateLimits.VERIFY,
        RateLimits.REFRESH,
        RateLimits.RESET_PASSWORD_GET,
        RateLimits.RESET_PASSWORD_POST,
    ]
    
    assert len(rate_limit_attrs) == len(tested_limits), (
        f"Mismatch: {len(rate_limit_attrs)} constants defined but {len(tested_limits)} tested"
    )

33-41: LGTM! All assertions correctly validate the documented limits.

The exact value assertions match the constants in app/rate_limit/config.py and align with SECURITY-REVIEW.md requirements.

Minor note: The # noqa: S105 comments (lines 37, 40-41) suppress hardcoded password warnings, but these are just string literals—not actual secrets. They're harmless but possibly unnecessary.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a4176b5 and cd5d31e.

📒 Files selected for processing (1)
  • tests/unit/test_rate_limit_config.py
🧰 Additional context used
🧬 Code graph analysis (1)
tests/unit/test_rate_limit_config.py (1)
app/rate_limit/config.py (1)
  • RateLimits (6-33)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.14)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.10)
🔇 Additional comments (1)
tests/unit/test_rate_limit_config.py (1)

1-10: LGTM! Clean test file structure.

The imports and test class setup follow best practices with clear documentation.

Replace hardcoded list with dynamic discovery of RateLimits constants.
This ensures new rate limit constants are automatically tested without
manual updates to the test file.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
docs/usage/rate-limiting.md (2)

7-7: Address hyphenation in compound adjectives and nouns.

Several compound adjectives and nouns require hyphens for proper English grammar when modifying following nouns or used in specific contexts. This improves readability and professionalism.

📝 Proposed grammar fixes
- denial of service attempts
+ denial-of-service attempts

- The rate limiting system supports two backends:
+ The rate-limiting system supports two backends:

- ### Rate Limiting Behavior
+ ### Rate-Limiting Behavior

- The rate limiting system has three operating modes:
+ The rate-limiting system has three operating modes:

- Automatically falls back to in-memory rate limiting
+ Automatically falls back to in-memory rate-limiting

- Controls whether to use Redis or in-memory rate limiting backend
+ Controls whether to use Redis or in-memory rate-limiting backend

- place rate limiting first for security
+ place rate-limiting first for security

- ## Rate Limiting Strategy
+ ## Rate-Limiting Strategy

- **When Disabled**: Zero overhead - rate limiting code is not
+ **When Disabled**: Zero overhead - rate-limiting code is not

- For user-tier based limits, you could structure a custom
+ For user-tier-based limits, you could structure a custom

Also applies to: 12-12, 76-78, 95-95, 149-149, 224-224, 281-281, 431-431, 536-536


282-292: Clarify IP-based limiting caveats for documentation clarity.

The IP-based limiting explanation (lines 285-291) correctly identifies shared network issues, but consider adding a note about X-Forwarded-For headers for deployments behind trusted proxies. While line 499 mentions this briefly, it could be more prominent in this core section for users diagnosing why shared-network users hit limits quickly.

Consider expanding line 289 with:

Note: If your application is behind a reverse proxy (nginx, CloudFlare, 
AWS ALB), configure slowapi to respect X-Forwarded-For headers to track 
individual clients correctly rather than all users' shared proxy IP.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 47faee4 and af464de.

📒 Files selected for processing (1)
  • docs/usage/rate-limiting.md
🧰 Additional context used
🪛 LanguageTool
docs/usage/rate-limiting.md

[grammar] ~7-~7: Use a hyphen to join words.
Context: ...rute force attacks, abuse, and denial of service attempts. Rate limiting is **dis...

(QB_NEW_EN_HYPHEN)


[grammar] ~12-~12: Use a hyphen to join words.
Context: ...ntaining good user experience. The rate limiting system supports two backends: ...

(QB_NEW_EN_HYPHEN)


[grammar] ~76-~76: Use a hyphen to join words.
Context: ...ate limiting. ## How It Works ### Rate Limiting Behavior The rate limiting sys...

(QB_NEW_EN_HYPHEN)


[grammar] ~78-~78: Use a hyphen to join words.
Context: ...ks ### Rate Limiting Behavior The rate limiting system has three operating mode...

(QB_NEW_EN_HYPHEN)


[uncategorized] ~95-~95: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: .... Automatically falls back to in-memory rate limiting 3. Application continues functioning no...

(EN_COMPOUND_ADJECTIVE_INTERNAL)


[grammar] ~149-~149: Use a hyphen to join words.
Context: ...s whether to use Redis or in-memory rate limiting backend. Only relevant when `RA...

(QB_NEW_EN_HYPHEN)


[uncategorized] ~224-~224: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...ached()` decorators together, place rate limiting first for security. Rate limits should ...

(EN_COMPOUND_ADJECTIVE_INTERNAL)


[grammar] ~281-~281: Use a hyphen to join words.
Context: ...0/day"` - 1000 requests per day ## Rate Limiting Strategy ### IP-Based Limiting...

(QB_NEW_EN_HYPHEN)


[uncategorized] ~431-~431: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...k) - When Disabled: Zero overhead - rate limiting code is not executed ### Redis Memor...

(EN_COMPOUND_ADJECTIVE_INTERNAL)


[grammar] ~536-~536: Use a hyphen to join words.
Context: ...omplex dynamic scenarios. For user-tier based limits, you could structure a cust...

(QB_NEW_EN_HYPHEN)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.14)
🔇 Additional comments (2)
docs/usage/rate-limiting.md (2)

189-192: Clarify rate limiting is not automatically enabled in tests.

The documentation states tests "automatically enable rate limiting," but the actual test implementation shows rate limiting is disabled by default (rate_limit_enabled=False) and must be explicitly enabled per test using monkeypatch.setattr(reset_rate_limiter, "enabled", True). The in-memory storage backend claim is correct—when enabled, tests use memory:// storage and do not require Redis. Update the note to reflect that rate limiting requires manual per-test enabling rather than automatic enablement.


196-220: Document that the decorator gracefully degrades without the request parameter rather than failing with clear error messages.

The implementation applies a fallback mechanism when the request: Request parameter is missing—it attempts to extract the request from kwargs or args, and defaults the client IP to "unknown" if not found. This means violations of the "MUST include a request: Request parameter" requirement won't be caught with validation errors or helpful error messages. While all production endpoints correctly include the parameter, developers won't receive immediate feedback if they omit it, leading to silent degradation of rate limiting accuracy (requests would be grouped under an "unknown" IP instead of the actual client IP). Consider adding runtime validation in the decorator to raise a clear error if the request parameter is missing or cannot be resolved, ensuring users catch configuration mistakes early rather than discovering subtle rate-limiting issues later.

Add RATE_LIMIT_ENABLED=false to pytest env to prevent user's .env from
affecting tests. Rate limiting integration tests explicitly enable it via
monkeypatch when needed.
@seapagan seapagan merged commit 836fb18 into main Jan 8, 2026
18 checks passed
@seapagan seapagan deleted the feature/rate-limiting branch January 8, 2026 21:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants