豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content

Feature/phase2 fastapi core#63

Merged
Nkwenti-Severian-Ndongtsop merged 45 commits intoCameroon-Developer-Network:mainfrom
chojuninengu:feature/phase2-fastapi-core
May 27, 2025
Merged

Feature/phase2 fastapi core#63
Nkwenti-Severian-Ndongtsop merged 45 commits intoCameroon-Developer-Network:mainfrom
chojuninengu:feature/phase2-fastapi-core

Conversation

@chojuninengu
Copy link
Copy Markdown
Member

@chojuninengu chojuninengu commented May 27, 2025

Summary by CodeRabbit

  • New Features

    • Introduced flexible configuration for multiple AI model backends, including Hugging Face, OpenAI, Anthropic, and Ollama, with detailed generation parameters and backend-specific settings.
    • Added support for loading and managing configuration via YAML files and environment variables.
    • Added rate limiting and improved modularity in the example configuration.
    • Added comprehensive security middleware enforcing HTTP headers, role-based access control, and request validation.
    • Implemented role-based access control with new user roles and protected API endpoints.
    • Added an admin-only endpoint to list users and assigned default roles on user registration.
    • Simplified chat endpoint with streamlined access control and response handling.
  • Chores

    • Enhanced code quality and analysis tooling with expanded analyzers and formatters.
    • Updated dependencies and added package metadata.
  • Tests

    • Expanded and improved test coverage for configuration loading, validation, and environment/YAML overrides.
    • Added tests for security middleware, role-based access control, and request validation.
  • Documentation

    • Minor formatting update to the README file.

…settings for Python, shell, test coverage, and Docker. Exclude specific paths from analysis and enable formatting tools like black and isort.
…e. Introduced LLMConfig and BackendConfig classes for managing LLM generation parameters and backend-specific settings. Updated Settings class to include active backend selection and configurations for HuggingFace, OpenAI, Anthropic, and Ollama.
…ver settings, added Redis and LLM configurations, and introduced rate limiting parameters. Enhanced security settings and improved overall structure for clarity.
…settings, LLM configuration validation, backend configuration, environment variable loading, and YAML configuration support. Improved test structure and coverage for better reliability.
…l method for loading settings from a YAML file, updated env_prefix and added env_nested_delimiter for improved configuration management.
… model name in environment variables, improved YAML loading tests, and refined environment variable override tests for better clarity and reliability.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented May 27, 2025

Warning

Rate limit exceeded

@chojuninengu has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 5 minutes and 2 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 8f4bffc and b01e52b.

📒 Files selected for processing (1)
  • backend/security.py (1 hunks)

Walkthrough

This update introduces a comprehensive overhaul of configuration management, including new Pydantic models and YAML loading for LLM backends, expanded and modular config examples, and a richer test suite for configuration logic. The .deepsource.toml is restructured for broader analysis coverage, and a package metadata file is added. Security is enhanced by adding middleware for HTTP headers, RBAC, and request validation, alongside role-based access control in endpoints and authentication updates. New tests verify security middleware and RBAC functionality.

Changes

File(s) Change Summary
.deepsource.toml Expanded and restructured to enable/configure multiple analyzers (Python, shell, docker, test-coverage), add transformers (black, isort), and set global exclude patterns.
README.md Inserted four blank lines between badge images at the top; no other changes.
backend/config.example.yml Extensively restructured: renamed/reorganized sections, added multi-backend LLM config, new rate limit and Redis sections, expanded generation parameters, and modularized settings.
backend/config.py Added LLMConfig and BackendConfig Pydantic models, expanded Settings class with backend configs and YAML loading, and added methods for backend selection and config retrieval.
requirements.txt Removed python>=3.8 requirement; added pydantic-settings>=2.0.0.
src/amega_ai/__init__.py New file with module-level docstring and metadata variables: __version__, __author__, __license__.
tests/unit/test_config.py Significantly expanded and restructured: new tests for LLM/backend config validation, YAML loading, environment variable overrides, and backend selection; improved coverage and cleanup.
backend/app.py Added security middleware integration, role-based access control on endpoints, default user role assignment on registration, simplified chat endpoint, removed chat history endpoint, and simplified health check endpoint.
backend/auth.py Updated to use centralized settings for JWT config, added user role field with literals, made email optional, replaced user DB with predefined users with roles, and adjusted return types and docstrings accordingly.
backend/security.py New module adding SecurityMiddleware (HTTP headers), RBACMiddleware (role-based access control), RequestValidationMiddleware (content length/type checks), and role-based dependency utilities for access control.
tests/unit/test_security.py New test suite verifying security headers, RBAC enforcement for various roles, public and protected endpoint access, and request validation middleware behavior with JWT tokens and simulated requests.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant FastAPI_App
    participant SecurityMiddleware
    participant RBACMiddleware
    participant RequestValidationMiddleware
    participant Auth
    participant Endpoint

    Client->>FastAPI_App: HTTP Request
    FastAPI_App->>SecurityMiddleware: Add security headers
    SecurityMiddleware-->>FastAPI_App: Response with headers
    FastAPI_App->>RBACMiddleware: Check user role & permissions
    RBACMiddleware->>Auth: Validate token and user role
    RBACMiddleware-->>FastAPI_App: Allow or deny request
    FastAPI_App->>RequestValidationMiddleware: Validate content length/type
    RequestValidationMiddleware-->>FastAPI_App: Allow or reject request
    FastAPI_App->>Endpoint: Process request if authorized
    Endpoint-->>Client: Response
Loading
sequenceDiagram
    participant User
    participant Settings
    participant YAML_File
    participant EnvVars

    User->>Settings: from_yaml(yaml_file)
    Settings->>YAML_File: Load and parse YAML
    Settings->>Settings: Map YAML keys to Pydantic models
    Settings->>EnvVars: (optional) Override with environment variables
    Settings-->>User: Return populated Settings instance

    User->>Settings: get_active_backend_config()
    Settings-->>User: Return BackendConfig for selected backend
Loading

Possibly related PRs

Suggested labels

Review effort 1/5

Suggested reviewers

  • Nkwenti-Severian-Ndongtsop

Poem

🐇 In code’s deep forest, I hop and explore,
Settings and security, I craft at the core.
Middleware guards, roles set just right,
YAML and tests make configs bright.
With every commit, the system’s more tight—
This rabbit’s work is a pure delight! 🌿✨

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@deepsource-io
Copy link
Copy Markdown

deepsource-io bot commented May 27, 2025

Here's the code health analysis summary for commits a38e55a..b12f5bc. View details on DeepSource ↗.

Analysis Summary

AnalyzerStatusSummaryLink
DeepSource Docker LogoDocker✅ SuccessView Check ↗

💡 If you’re a repository administrator, you can configure the quality gates from the settings.

…efault_factory for BackendConfig and LLMConfig, added env_file_encoding and validate_default parameters for improved configuration handling.
…del_validator for OpenAI backend to transform environment variable keys for improved compatibility and clarity.
…validation method for clarity, added LLM configuration handling, and simplified backend config assignments for improved readability and maintainability.
… RBACMiddleware, and RequestValidationMiddleware to enforce security headers, role-based access control, and request validation. Introduced convenience dependencies for role checks.
Copy link
Copy Markdown
Contributor

@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: 4

🧹 Nitpick comments (6)
.deepsource.toml (2)

8-10: Specify an exact minor version for runtime_version

"3.x.x" is not accepted. Use "3.11" or "3.10" to match your CI image.


35-43: Excluding tests/** conflicts with the test-coverage analyzer

The Python analyzer ignores tests, yet the coverage analyzer needs them.
Consider removing "tests/**" from exclude_patterns so linting and security
rules cover test code as well.

backend/config.example.yml (2)

29-61: Remove trailing whitespace to satisfy YAML linters

yamllint flags trailing spaces on several lines (29, 36, 42, 47, 52).

-  active_backend: huggingface␠
+  active_backend: huggingface
...
-      timeout: 30␠
+      timeout: 30

Cleaning these will keep CI green when linting is enforced.

🧰 Tools
🪛 YAMLlint (1.37.1)

[error] 29-29: trailing spaces

(trailing-spaces)


[error] 36-36: trailing spaces

(trailing-spaces)


[error] 42-42: trailing spaces

(trailing-spaces)


[error] 47-47: trailing spaces

(trailing-spaces)


[error] 52-52: trailing spaces

(trailing-spaces)


19-20: Avoid shipping placeholder credentials in examples

Static analysis flags the hard-coded postgresql://user:password@... DSN
(CKV_SECRET_4). Even in examples, consider using <username> / <password>
or environment-variable interpolation to discourage accidental commits of
real secrets.

🧰 Tools
🪛 Checkov (3.2.334)

[MEDIUM] 19-20: Basic Auth Credentials

(CKV_SECRET_4)

tests/unit/test_config.py (2)

4-6: Remove unused imports to clean up the code.

The static analysis correctly identifies unused imports that should be removed for cleaner code.

Apply this diff to remove the unused imports:

-from pathlib import Path
-import yaml
 from pydantic import PostgresDsn
-from backend.config import Settings, LLMConfig, BackendConfig
+from backend.config import Settings, LLMConfig, BackendConfig

Wait, let me check if PostgresDsn is actually used... Looking at the test code, it's imported but not used in any of the test functions shown.

-from pathlib import Path
-import yaml
-from pydantic import PostgresDsn
 from backend.config import Settings, LLMConfig, BackendConfig
🧰 Tools
🪛 Ruff (0.11.9)

4-4: pathlib.Path imported but unused

Remove unused import: pathlib.Path

(F401)


5-5: yaml imported but unused

Remove unused import: yaml

(F401)


6-6: pydantic.PostgresDsn imported but unused

Remove unused import: pydantic.PostgresDsn

(F401)


149-151: Fix the assertion order for better readability.

The static analysis correctly identifies a Yoda condition that should be rewritten for better readability.

Apply this diff to fix the assertion order:

-    assert settings.ALLOWED_ORIGINS == custom_origins
+    assert custom_origins == settings.ALLOWED_ORIGINS

Wait, that's not right. The current code has settings.ALLOWED_ORIGINS == custom_origins which is the normal order. The static analysis suggestion is to change it to custom_origins == settings.ALLOWED_ORIGINS, but the current order is actually more readable. The static analysis tool is suggesting the Yoda condition fix incorrectly.

Actually, let me re-read this. The current code is:

assert settings.ALLOWED_ORIGINS == custom_origins

The static analysis says "Yoda condition detected" and suggests "Rewrite as custom_origins == settings.ALLOWED_ORIGINS". But the current code is NOT a Yoda condition. A Yoda condition would be custom_origins == settings.ALLOWED_ORIGINS. The current code settings.ALLOWED_ORIGINS == custom_origins is the normal, readable order.

This appears to be a false positive from the static analysis tool. The current assertion order is correct and readable.

🧰 Tools
🪛 Ruff (0.11.9)

151-151: Yoda condition detected

Rewrite as custom_origins == settings.ALLOWED_ORIGINS

(SIM300)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a38e55a and 66de8b9.

📒 Files selected for processing (7)
  • .deepsource.toml (1 hunks)
  • README.md (1 hunks)
  • backend/config.example.yml (1 hunks)
  • backend/config.py (3 hunks)
  • requirements.txt (1 hunks)
  • src/amega_ai/__init__.py (1 hunks)
  • tests/unit/test_config.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
tests/unit/test_config.py (1)
backend/config.py (5)
  • Settings (78-234)
  • LLMConfig (13-53)
  • BackendConfig (55-76)
  • get_active_backend_config (182-190)
  • from_yaml (193-234)
🪛 Checkov (3.2.334)
backend/config.example.yml

[MEDIUM] 19-20: Basic Auth Credentials

(CKV_SECRET_4)

🪛 YAMLlint (1.37.1)
backend/config.example.yml

[error] 29-29: trailing spaces

(trailing-spaces)


[error] 36-36: trailing spaces

(trailing-spaces)


[error] 42-42: trailing spaces

(trailing-spaces)


[error] 47-47: trailing spaces

(trailing-spaces)


[error] 52-52: trailing spaces

(trailing-spaces)

🪛 Ruff (0.11.9)
backend/config.py

7-7: typing.Dict imported but unused

Remove unused import

(F401)


7-7: typing.Any imported but unused

Remove unused import

(F401)

tests/unit/test_config.py

4-4: pathlib.Path imported but unused

Remove unused import: pathlib.Path

(F401)


5-5: yaml imported but unused

Remove unused import: yaml

(F401)


6-6: pydantic.PostgresDsn imported but unused

Remove unused import: pydantic.PostgresDsn

(F401)


151-151: Yoda condition detected

Rewrite as custom_origins == settings.ALLOWED_ORIGINS

(SIM300)

🔇 Additional comments (7)
README.md (1)

4-7: No functional impact – consider squashing trivial whitespace commits

Four blank lines were added but they do not change rendering. You can keep them, but if the PR is rebased anyway, squashing such formatting-only commits keeps history cleaner.

requirements.txt (1)

5-7: Pin pydantic-settings to the same minor series as pydantic

pydantic>=2.5.2 pairs best with pydantic-settings>=2.1,<2.2 (current latest is 2.2.1). Leaving a loose upper-bound may cause a future 3.x release to break settings parsing.

-pydantic-settings>=2.0.0
+pydantic-settings>=2.1,<3.0

Please confirm compatibility with the version you actually use in CI.

src/amega_ai/__init__.py (1)

1-12: LGTM! Clean package initialization.

The package initialization follows Python packaging conventions well with a clear docstring, appropriate metadata variables, and semantic versioning. The structure is clean and ready for future expansion.

tests/unit/test_config.py (4)

23-47: Excellent validation test coverage.

The LLM configuration validation tests properly cover both valid and invalid parameter ranges, ensuring the Pydantic field constraints are working correctly. The test cases cover edge cases like values above/below the allowed ranges.


75-98: Good environment variable testing with proper cleanup.

The test properly demonstrates environment variable loading with the nested delimiter syntax (__) and includes proper cleanup to avoid affecting other tests. This is a well-structured test.


99-128: Comprehensive YAML configuration test.

The test effectively validates the YAML loading functionality using a temporary file. The YAML structure matches the expected configuration format and covers multiple configuration sections.


1-151: Comprehensive test suite with excellent coverage.

The test suite provides thorough coverage of the configuration system including:

  • Default settings validation
  • LLM and backend configuration validation
  • Environment variable overrides with proper cleanup
  • YAML configuration loading
  • Active backend selection

The tests are well-structured and follow good testing practices with proper setup and teardown.

🧰 Tools
🪛 Ruff (0.11.9)

4-4: pathlib.Path imported but unused

Remove unused import: pathlib.Path

(F401)


5-5: yaml imported but unused

Remove unused import: yaml

(F401)


6-6: pydantic.PostgresDsn imported but unused

Remove unused import: pydantic.PostgresDsn

(F401)


151-151: Yoda condition detected

Rewrite as custom_origins == settings.ALLOWED_ORIGINS

(SIM300)

Comment thread .deepsource.toml
Comment on lines 7 to +17
[analyzers.meta]
runtime_version = "3.x.x"
max_line_length = 88 # Matches black formatter
type_checker = "mypy"

# Enable all available rules for comprehensive analysis
enable = ["all"]

# Dependencies that should be analyzed
dependencies_modules = ["requirements.txt", "requirements-dev.txt"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

dependencies_modules appears to be a typo – use dependency_files

DeepSource expects the key dependency_files (plural) for Python analyzers.

-  dependencies_modules = ["requirements.txt", "requirements-dev.txt"]
+  dependency_files   = ["requirements.txt", "requirements-dev.txt"]

A wrong key silently disables dependency scanning.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[analyzers.meta]
runtime_version = "3.x.x"
max_line_length = 88 # Matches black formatter
type_checker = "mypy"
# Enable all available rules for comprehensive analysis
enable = ["all"]
# Dependencies that should be analyzed
dependencies_modules = ["requirements.txt", "requirements-dev.txt"]
[analyzers.meta]
runtime_version = "3.x.x"
max_line_length = 88 # Matches black formatter
type_checker = "mypy"
# Enable all available rules for comprehensive analysis
enable = ["all"]
# Dependencies that should be analyzed
- dependencies_modules = ["requirements.txt", "requirements-dev.txt"]
+ dependency_files = ["requirements.txt", "requirements-dev.txt"]
🤖 Prompt for AI Agents
In the .deepsource.toml file between lines 7 and 17, the key
`dependencies_modules` is incorrect and should be replaced with
`dependency_files` to enable dependency scanning for Python analyzers. Update
the key name to `dependency_files` and keep the list of files unchanged to
ensure DeepSource correctly analyzes the specified dependency files.

Comment thread backend/config.py
Comment on lines +9 to 10
import yaml
from pydantic_settings import BaseSettings, SettingsConfigDict
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

yaml dependency missing from requirements.txt

config.py imports yaml (PyYAML) yet the project does not list it.
Add a pinned version, e.g.

+PyYAML>=6.0

to requirements.txt to avoid runtime ModuleNotFoundError.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import yaml
from pydantic_settings import BaseSettings, SettingsConfigDict
# requirements.txt
+PyYAML>=6.0
🤖 Prompt for AI Agents
In backend/config.py around lines 9 to 10, the yaml module is imported but the
PyYAML package is not listed in requirements.txt. Add PyYAML with a pinned
version to requirements.txt to ensure the dependency is installed and prevent
runtime ModuleNotFoundError.

Comment thread backend/config.py
Comment on lines +7 to 10
from typing import List, Optional, Literal, Dict, Any
from pathlib import Path
import yaml
from pydantic_settings import BaseSettings, SettingsConfigDict
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove unused imports

Dict and Any are imported but never used, triggering Ruff F401.

-from typing import List, Optional, Literal, Dict, Any
+from typing import List, Optional, Literal
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from typing import List, Optional, Literal, Dict, Any
from pathlib import Path
import yaml
from pydantic_settings import BaseSettings, SettingsConfigDict
from typing import List, Optional, Literal
from pathlib import Path
import yaml
from pydantic_settings import BaseSettings, SettingsConfigDict
🧰 Tools
🪛 Ruff (0.11.9)

7-7: typing.Dict imported but unused

Remove unused import

(F401)


7-7: typing.Any imported but unused

Remove unused import

(F401)

🤖 Prompt for AI Agents
In backend/config.py around lines 7 to 10, the imports Dict and Any from typing
are unused and cause a linting error. Remove Dict and Any from the import
statement to clean up the code and resolve the Ruff F401 warning.

Comment thread backend/config.py
Comment on lines +193 to +234
def from_yaml(cls, yaml_file: str | Path) -> "Settings":
"""Load settings from a YAML file."""
yaml_file = Path(yaml_file)
if not yaml_file.exists():
raise FileNotFoundError(f"YAML configuration file not found: {yaml_file}")

with yaml_file.open("r") as f:
yaml_data = yaml.safe_load(f)

# Transform YAML structure to match our settings format
config_data = {}

# Handle app settings
if "app" in yaml_data:
config_data.update({
"APP_NAME": yaml_data["app"].get("name", cls.model_fields["APP_NAME"].default),
"APP_VERSION": yaml_data["app"].get("version", cls.model_fields["APP_VERSION"].default),
"DEBUG": yaml_data["app"].get("debug", cls.model_fields["DEBUG"].default)
})

# Handle LLM settings
if "llm" in yaml_data:
llm_data = yaml_data["llm"]
if "active_backend" in llm_data:
config_data["ACTIVE_LLM_BACKEND"] = llm_data["active_backend"]

if "backends" in llm_data:
backends = llm_data["backends"]
if "huggingface" in backends:
config_data["HUGGINGFACE_CONFIG"] = BackendConfig(**backends["huggingface"])
if "openai" in backends:
config_data["OPENAI_CONFIG"] = BackendConfig(**backends["openai"])
if "anthropic" in backends:
config_data["ANTHROPIC_CONFIG"] = BackendConfig(**backends["anthropic"])
if "ollama" in backends:
config_data["OLLAMA_CONFIG"] = BackendConfig(**backends["ollama"])

if "generation" in llm_data:
config_data["LLM_CONFIG"] = LLMConfig(**llm_data["generation"])

# Create settings instance with YAML data
return cls(**config_data)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

from_yaml() only maps a subset of settings

The loader ignores server, database, redis, rate_limit, and several
other sections present in config.example.yml, so values in the YAML won’t
override defaults.

Consider iterating generically instead of hard-coding each section, or at
least mapping the remaining keys:

@@
-        # Handle server settings
-        if "server" in yaml_data:
-            config_data.update({
-                "HOST": yaml_data["server"].get("host", cls.model_fields["HOST"].default),
-                "PORT": yaml_data["server"].get("port", cls.model_fields["PORT"].default),
-                "ALLOWED_ORIGINS": yaml_data["server"].get("allowed_origins", cls.model_fields["ALLOWED_ORIGINS"].default),
-            })
+        # Handle server settings
+        if "server" in yaml_data:
+            server = yaml_data["server"]
+            config_data |= {
+                "HOST": server.get("host", cls.model_fields["HOST"].default),
+                "PORT": server.get("port", cls.model_fields["PORT"].default),
+                "ALLOWED_ORIGINS": server.get("allowed_origins", cls.model_fields["ALLOWED_ORIGINS"].default),
+            }

Repeat for database, redis, rate_limit, etc., or build a recursive
flattener using the env_nested_delimiter convention.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In backend/config.py from lines 193 to 234, the from_yaml method only maps a
limited set of configuration sections like app and llm, ignoring others such as
server, database, redis, and rate_limit, which prevents YAML values from
overriding defaults for those sections. To fix this, extend the method to also
map these missing sections by either explicitly adding their keys to config_data
with appropriate transformations or implement a generic recursive function that
flattens nested YAML keys using the env_nested_delimiter convention, ensuring
all relevant configuration sections are loaded and override defaults as
expected.

…ecurity middleware for role-based access control, added user listing endpoint for admins, and refined chat endpoint to improve response generation. Updated health check endpoint for better clarity.
…ld, introduced user role management with Literal type for roles, and updated the authenticate_user function to return a User type instead of UserInDB.
…ented tests for security headers, role-based access control for user, moderator, and admin roles, and request validation middleware. Enhanced test coverage for public and protected endpoints.
…ware: defined public paths for easier management, enhanced token validation, and refined error handling for unauthorized access. Updated request validation middleware to skip validation for GET requests.
…s with a utility function to create test JWT tokens for user, moderator, and admin roles. Updated test cases to use generated tokens for authorization in role-based access control tests, enhancing clarity and maintainability.
…onfiguration: updated SECRET_KEY, ALGORITHM, and ACCESS_TOKEN_EXPIRE_MINUTES to pull from settings. Enhanced user role management by adding an optional role field in TokenData and improved simulated user database structure.
… checks: replaced permission-based access control with role-based access management, updated endpoint permissions to map directly to user roles, and improved error handling for unauthorized access.
Copy link
Copy Markdown
Contributor

@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: 1

♻️ Duplicate comments (2)
backend/config.py (2)

7-11: Address the duplicate issues flagged in previous reviews.

The previous review comments are still valid - there are unused imports and a missing dependency that need to be resolved.


218-259: 💡 Verification agent

🧩 Analysis chain

YAML loading method addresses previous feedback but needs completion.

The from_yaml method partially addresses the previous review comment about incomplete mapping, but still only handles app and llm sections while ignoring server, database, redis, and rate_limit sections.

Complete the YAML mapping to handle all configuration sections:

# Handle app settings
if "app" in yaml_data:
    config_data.update({
        "APP_NAME": yaml_data["app"].get("name", cls.model_fields["APP_NAME"].default),
        "APP_VERSION": yaml_data["app"].get("version", cls.model_fields["APP_VERSION"].default),
        "DEBUG": yaml_data["app"].get("debug", cls.model_fields["DEBUG"].default)
    })

+# Handle server settings
+if "server" in yaml_data:
+    server = yaml_data["server"]
+    config_data.update({
+        "HOST": server.get("host", cls.model_fields["HOST"].default),
+        "PORT": server.get("port", cls.model_fields["PORT"].default),
+        "ALLOWED_ORIGINS": server.get("allowed_origins", cls.model_fields["ALLOWED_ORIGINS"].default),
+    })
+
+# Handle database settings
+if "database" in yaml_data:
+    database = yaml_data["database"]
+    config_data.update({
+        "DATABASE_URL": database.get("url", cls.model_fields["DATABASE_URL"].default),
+    })
+
+# Handle redis settings  
+if "redis" in yaml_data:
+    redis = yaml_data["redis"]
+    config_data.update({
+        "REDIS_URL": redis.get("url", cls.model_fields["REDIS_URL"].default),
+    })
+
+# Handle rate limiting settings
+if "rate_limit" in yaml_data:
+    rate_limit = yaml_data["rate_limit"]
+    config_data.update({
+        "RATE_LIMIT_DEFAULT_RPM": rate_limit.get("default_rpm", cls.model_fields["RATE_LIMIT_DEFAULT_RPM"].default),
+        "RATE_LIMIT_AUTH_RPM": rate_limit.get("auth_rpm", cls.model_fields["RATE_LIMIT_AUTH_RPM"].default),
+        "RATE_LIMIT_CHAT_RPM": rate_limit.get("chat_rpm", cls.model_fields["RATE_LIMIT_CHAT_RPM"].default),
+    })
+
+# Handle JWT settings
+if "jwt" in yaml_data:
+    jwt = yaml_data["jwt"]
+    config_data.update({
+        "SECRET_KEY": jwt.get("secret_key", cls.model_fields["SECRET_KEY"].default),
+        "ALGORITHM": jwt.get("algorithm", cls.model_fields["ALGORITHM"].default),
+        "ACCESS_TOKEN_EXPIRE_MINUTES": jwt.get("access_token_expire_minutes", cls.model_fields["ACCESS_TOKEN_EXPIRE_MINUTES"].default),
+    })

Let me verify which configuration sections are expected in the YAML file:


🏁 Script executed:

#!/bin/bash
# Description: Check for YAML configuration examples to understand expected structure

# Look for YAML configuration files
fd -e yml -e yaml | head -10

# Check if there's a config.example.yml file mentioned in past comments
fd config.example

Length of output: 115


🏁 Script executed:

#!/bin/bash
# Display the contents of the example configuration to verify all sections
cat backend/config.example.yml

Length of output: 2150


#!/bin/bash
# Extract the Settings class field definitions before the from_yaml method
sed -n '1,200p' backend/config.py | sed -n '/class Settings/,/def from_yaml/p'

#!/bin/bash
# Extract class Settings field definitions prior to from_yaml
sed -n '1,300p' backend/config.py | sed -n -e '1,/class Settings/d' -e '/def from_yaml/,$d'

🏁 Script executed:

#!/bin/bash
# Extract class Settings field definitions in backend/config.py before from_yaml
sed -n '/class Settings/,/def from_yaml/p' backend/config.py | sed '1d;$d'

Length of output: 4618


#!/bin/bash
# Check if Settings class defines logging or directories fields
rg -n "LOGGING" backend/config.py || echo "No logging fields found"
rg -n "DIRECTORIES" backend/config.py || echo "No directories fields found"

Ensure complete YAML section mapping in Settings.from_yaml

The from_yaml method currently handles only app and llm sections. It should also map server, database, redis, rate_limit, and the JWT fields under the security key (the example YAML uses security, not jwt).

File: backend/config.py
Location: Settings.from_yaml (around lines 218–259)

Suggested diff:

     # Handle app settings
     if "app" in yaml_data:
         config_data.update({
             "APP_NAME": yaml_data["app"].get("name", cls.model_fields["APP_NAME"].default),
             "APP_VERSION": yaml_data["app"].get("version", cls.model_fields["APP_VERSION"].default),
             "DEBUG": yaml_data["app"].get("debug", cls.model_fields["DEBUG"].default)
         })

+    # Handle server settings
+    if "server" in yaml_data:
+        server = yaml_data["server"]
+        config_data.update({
+            "HOST": server.get("host", cls.model_fields["HOST"].default),
+            "PORT": server.get("port", cls.model_fields["PORT"].default),
+            "ALLOWED_ORIGINS": server.get("allowed_origins", cls.model_fields["ALLOWED_ORIGINS"].default),
+        })
+
+    # Handle database settings
+    if "database" in yaml_data:
+        db = yaml_data["database"]
+        config_data["DATABASE_URL"] = db.get("url", cls.model_fields["DATABASE_URL"].default)
+
+    # Handle redis settings  
+    if "redis" in yaml_data:
+        rd = yaml_data["redis"]
+        config_data["REDIS_URL"] = rd.get("url", cls.model_fields["REDIS_URL"].default)
+
+    # Handle rate limiting settings
+    if "rate_limit" in yaml_data:
+        rl = yaml_data["rate_limit"]
+        config_data.update({
+            "RATE_LIMIT_DEFAULT_RPM": rl.get("default_rpm", cls.model_fields["RATE_LIMIT_DEFAULT_RPM"].default),
+            "RATE_LIMIT_AUTH_RPM": rl.get("auth_rpm", cls.model_fields["RATE_LIMIT_AUTH_RPM"].default),
+            "RATE_LIMIT_CHAT_RPM": rl.get("chat_rpm", cls.model_fields["RATE_LIMIT_CHAT_RPM"].default),
+        })
+
-    # Handle JWT settings
-    if "jwt" in yaml_data:
-        jwt = yaml_data["jwt"]
-        config_data.update({
-            "SECRET_KEY": jwt.get("secret_key", cls.model_fields["SECRET_KEY"].default),
-            "ALGORITHM": jwt.get("algorithm", cls.model_fields["ALGORITHM"].default),
-            "ACCESS_TOKEN_EXPIRE_MINUTES": jwt.get("access_token_expire_minutes", cls.model_fields["ACCESS_TOKEN_EXPIRE_MINUTES"].default),
-        })
+    # Handle security settings (JWT)
+    if "security" in yaml_data:
+        sec = yaml_data["security"]
+        config_data.update({
+            "SECRET_KEY": sec.get("secret_key", cls.model_fields["SECRET_KEY"].default),
+            "ALGORITHM": sec.get("algorithm", cls.model_fields["ALGORITHM"].default),
+            "ACCESS_TOKEN_EXPIRE_MINUTES": sec.get("access_token_expire_minutes", cls.model_fields["ACCESS_TOKEN_EXPIRE_MINUTES"].default),
+        })

Note: The example YAML also includes logging and directories sections, but there are no corresponding fields on Settings. You can omit those or add them once the model fields exist.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 66de8b9 and e33766c.

📒 Files selected for processing (1)
  • backend/config.py (2 hunks)
🔇 Additional comments (6)
backend/config.py (6)

13-53: Well-implemented LLM configuration model with comprehensive validation.

The LLMConfig class provides excellent parameter validation with appropriate bounds for all generation parameters. The field descriptions are clear and helpful for understanding each parameter's purpose.


55-76: BackendConfig model is well-structured with appropriate field validation.

The configuration model properly handles optional fields for API credentials while requiring the essential model_name field. The timeout validation with gt=0 ensures positive values.


94-139: Comprehensive LLM backend configuration with good defaults.

The addition of multiple backend configurations with sensible defaults is well-implemented. The use of default_factory with lambda functions properly creates fresh instances for each setting.


165-169: Enhanced configuration with nested environment variable support.

The addition of env_nested_delimiter="__" enables hierarchical configuration via environment variables, which aligns well with the complex nested structure introduced by the LLM configurations.


200-205: Minor formatting improvement in database URL validator.

The reformatting improves readability while maintaining the same logic. The updated docstring provides better clarity.


207-215: Efficient active backend configuration retrieval.

The method provides a clean way to access the currently active backend configuration using a simple dictionary lookup.

Comment thread backend/config.py
Comment on lines +171 to +192
@model_validator(mode='before')
@classmethod
def validate_configs(cls, values: Dict[str, Any]) -> Dict[str, Any]:
"""Validate and transform configurations from environment variables."""
# Handle OpenAI config
if values.get("ACTIVE_LLM_BACKEND") == "openai":
openai_config = values.get("OPENAI_CONFIG", {})
if isinstance(openai_config, dict):
if "MODEL_NAME" in openai_config:
openai_config["model_name"] = openai_config.pop("MODEL_NAME")
if "API_KEY" in openai_config:
openai_config["api_key"] = openai_config.pop("API_KEY")
values["OPENAI_CONFIG"] = openai_config

# Handle LLM config
llm_config = values.get("LLM_CONFIG", {})
if isinstance(llm_config, dict):
if "TEMPERATURE" in llm_config:
llm_config["temperature"] = float(llm_config.pop("TEMPERATURE"))
values["LLM_CONFIG"] = llm_config

return values
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Model validator implementation needs improvement for robustness.

The validator has several issues that could lead to runtime errors:

  1. Missing type safety: The method assumes values is a dictionary without verification
  2. Hardcoded backend check: Only handles OpenAI, ignoring other backends
  3. No error handling: Missing validation for malformed data

Consider this improved implementation:

@model_validator(mode='before')
@classmethod
def validate_configs(cls, values) -> Dict[str, Any]:
    """Validate and transform configurations from environment variables."""
    if not isinstance(values, dict):
        return values
    
+   # Generic handler for all backend configs
+   backend_fields = ["HUGGINGFACE_CONFIG", "OPENAI_CONFIG", "ANTHROPIC_CONFIG", "OLLAMA_CONFIG"]
+   for backend_field in backend_fields:
+       backend_config = values.get(backend_field, {})
+       if isinstance(backend_config, dict):
+           # Normalize field names from environment variable format
+           for old_key in list(backend_config.keys()):
+               if old_key.isupper():
+                   new_key = old_key.lower()
+                   backend_config[new_key] = backend_config.pop(old_key)
+           values[backend_field] = backend_config
    
-   # Handle OpenAI config
-   if values.get("ACTIVE_LLM_BACKEND") == "openai":
-       openai_config = values.get("OPENAI_CONFIG", {})
-       if isinstance(openai_config, dict):
-           if "MODEL_NAME" in openai_config:
-               openai_config["model_name"] = openai_config.pop("MODEL_NAME")
-           if "API_KEY" in openai_config:
-               openai_config["api_key"] = openai_config.pop("API_KEY")
-           values["OPENAI_CONFIG"] = openai_config

    # Handle LLM config with error handling
    llm_config = values.get("LLM_CONFIG", {})
    if isinstance(llm_config, dict):
-       if "TEMPERATURE" in llm_config:
-           llm_config["temperature"] = float(llm_config.pop("TEMPERATURE"))
+       # Normalize all uppercase keys to lowercase
+       for old_key in list(llm_config.keys()):
+           if old_key.isupper():
+               try:
+                   new_key = old_key.lower()
+                   value = llm_config.pop(old_key)
+                   # Convert numeric fields appropriately
+                   if new_key in ["temperature", "top_p", "repetition_penalty", "presence_penalty", "frequency_penalty"]:
+                       llm_config[new_key] = float(value)
+                   elif new_key in ["max_length", "top_k"]:
+                       llm_config[new_key] = int(value)
+                   else:
+                       llm_config[new_key] = value
+               except (ValueError, TypeError) as e:
+                   # Log error but continue with default values
+                   pass
        values["LLM_CONFIG"] = llm_config

    return values
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@model_validator(mode='before')
@classmethod
def validate_configs(cls, values: Dict[str, Any]) -> Dict[str, Any]:
"""Validate and transform configurations from environment variables."""
# Handle OpenAI config
if values.get("ACTIVE_LLM_BACKEND") == "openai":
openai_config = values.get("OPENAI_CONFIG", {})
if isinstance(openai_config, dict):
if "MODEL_NAME" in openai_config:
openai_config["model_name"] = openai_config.pop("MODEL_NAME")
if "API_KEY" in openai_config:
openai_config["api_key"] = openai_config.pop("API_KEY")
values["OPENAI_CONFIG"] = openai_config
# Handle LLM config
llm_config = values.get("LLM_CONFIG", {})
if isinstance(llm_config, dict):
if "TEMPERATURE" in llm_config:
llm_config["temperature"] = float(llm_config.pop("TEMPERATURE"))
values["LLM_CONFIG"] = llm_config
return values
@model_validator(mode='before')
@classmethod
def validate_configs(cls, values) -> Dict[str, Any]:
"""Validate and transform configurations from environment variables."""
if not isinstance(values, dict):
return values
# Generic handler for all backend configs
backend_fields = ["HUGGINGFACE_CONFIG", "OPENAI_CONFIG", "ANTHROPIC_CONFIG", "OLLAMA_CONFIG"]
for backend_field in backend_fields:
backend_config = values.get(backend_field, {})
if isinstance(backend_config, dict):
# Normalize field names from environment variable format
for old_key in list(backend_config.keys()):
if old_key.isupper():
new_key = old_key.lower()
backend_config[new_key] = backend_config.pop(old_key)
values[backend_field] = backend_config
# Handle LLM config with error handling
llm_config = values.get("LLM_CONFIG", {})
if isinstance(llm_config, dict):
# Normalize all uppercase keys to lowercase
for old_key in list(llm_config.keys()):
if old_key.isupper():
try:
new_key = old_key.lower()
value = llm_config.pop(old_key)
# Convert numeric fields appropriately
if new_key in ["temperature", "top_p", "repetition_penalty", "presence_penalty", "frequency_penalty"]:
llm_config[new_key] = float(value)
elif new_key in ["max_length", "top_k"]:
llm_config[new_key] = int(value)
else:
llm_config[new_key] = value
except (ValueError, TypeError):
# Log error but continue with default values
pass
values["LLM_CONFIG"] = llm_config
return values
🤖 Prompt for AI Agents
In backend/config.py around lines 171 to 192, the validate_configs method lacks
type safety, only handles the "openai" backend, and has no error handling for
malformed data. To fix this, first verify that the input values parameter is a
dictionary before processing. Then, generalize the backend check to support
multiple backends instead of hardcoding "openai". Finally, add try-except blocks
or validation checks to handle and log errors gracefully when data is malformed
or missing expected keys, ensuring the method is robust and does not raise
runtime exceptions.

…'moderator' and 'user' roles with corresponding details and hashed passwords, enhancing user role management for authentication.
Copy link
Copy Markdown
Contributor

@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

🔭 Outside diff range comments (2)
backend/auth.py (1)

120-125: 💡 Verification agent

🧩 Analysis chain

Propagate role claim from the JWT payload

get_current_user() currently ignores the role value embedded in the token.
If downstream code (e.g., RBAC middleware) expects the role claim to be present in the TokenData, its absence forces an extra DB lookup or silently drops custom roles supplied by a trusted IdP.

-        username: str = payload.get("sub")
-        if username is None:
-            raise credentials_exception
-        token_data = TokenData(username=username)
+        username: str | None = payload.get("sub")
+        role: str | None = payload.get("role")
+        if username is None:
+            raise credentials_exception
+        token_data = TokenData(username=username, role=role)

Ensure that RBACMiddleware really relies on current_user.role (fetched from the DB) and not exclusively on the role JWT claim. If it does parse the claim directly, the above change is mandatory to avoid authorization bypasses.


🏁 Script executed:

#!/bin/bash
# Locate TokenData definition
rg -n "class TokenData" -A 5

# Inspect get_current_user function in auth.py
rg -n "def get_current_user" -A 20 backend/auth.py

# Find RBACMiddleware implementation and check how it uses 'role'
rg -n "class RBACMiddleware" -A 20

# Search for any direct references to current_user.role
rg -n "current_user.*role" -A 5

Length of output: 2374


🏁 Script executed:

#!/bin/bash
# Inspect the RBACMiddleware dispatch method to see how it determines the user's role
rg -n "def dispatch" -A 50 backend/security.py

Length of output: 5569


Revise get_current_user to merge the JWT role claim into the returned User

Adding role to TokenData alone doesn’t propagate it—get_current_user still fetches the DB user and returns that, so downstream RBAC always sees the DB-stored role. To honor a trusted IdP’s role claim, update get_current_user in backend/auth.py so that after loading the user you override its .role when the token supplies one:

• File: backend/auth.py, get_current_user (around lines 120–131)

     try:
-        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
-        username: str = payload.get("sub")
-        if username is None:
-            raise credentials_exception
-        token_data = TokenData(username=username)
+        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
+        username: str | None = payload.get("sub")
+        role:     str | None = payload.get("role")
+        if username is None:
+            raise credentials_exception
+        token_data = TokenData(username=username, role=role)
     except JWTError:
         raise credentials_exception

-    user = get_user(username=token_data.username)
+    user = get_user(username=token_data.username)
     if user is None:
         raise credentials_exception
+    # Override DB role with JWT claim when present
+    if token_data.role is not None:
+        user.role = token_data.role

     return user

This ensures RBACMiddleware (and any user.role checks) will use the JWT’s role claim when provided.

backend/app.py (1)

131-134: 🛠️ Refactor suggestion

Include the user’s role when minting access tokens

The login endpoint omits the role claim, yet the test suite (and likely RBAC middleware) relies on its presence. Embedding the claim avoids an extra DB read and keeps prod & test behaviour aligned.

-    access_token = create_access_token(
-        data={"sub": user.username}, expires_delta=access_token_expires
-    )
+    access_token = create_access_token(
+        data={"sub": user.username, "role": user.role},
+        expires_delta=access_token_expires,
+    )
🧹 Nitpick comments (8)
backend/auth.py (2)

7-7: Remove the unused Dict import

Dict is no longer referenced after the refactor. Keeping it violates Ruff’s F401 check and adds noise.

-from typing import Optional, Dict, Literal
+from typing import Optional, Literal
🧰 Tools
🪛 Ruff (0.11.9)

7-7: typing.Dict imported but unused

Remove unused import: typing.Dict

(F401)


49-75: Consider deferring bcrypt hashing to runtime

Hashing three passwords with bcrypt at import-time adds ±200 ms to every cold start.
If startup latency matters (e.g., serverless deployments), move the seeding logic into the lifespan handler or a test fixture.

tests/unit/test_security.py (2)

2-2: Drop unused pytest import

The module doesn’t call any pytest APIs; all tests run via TestClient.
Removing the import silences Ruff F401.

-import pytest
🧰 Tools
🪛 Ruff (0.11.9)

2-2: pytest imported but unused

Remove unused import: pytest

(F401)


10-13: Trim unused get_current_user from the import list

get_current_user is never referenced inside the tests.

-from backend.auth import (
-    User, get_current_user, create_access_token,
-    ACCESS_TOKEN_EXPIRE_MINUTES
-)
+from backend.auth import (
+    User, create_access_token,
+    ACCESS_TOKEN_EXPIRE_MINUTES
+)
🧰 Tools
🪛 Ruff (0.11.9)

11-11: backend.auth.get_current_user imported but unused

Remove unused import: backend.auth.get_current_user

(F401)

backend/app.py (1)

23-26: Remove the unused requires_moderator import

requires_moderator isn’t referenced anywhere in backend/app.py, triggering Ruff F401.

-    SecurityMiddleware, RBACMiddleware, RequestValidationMiddleware,
-    requires_admin, requires_moderator, requires_user
+    SecurityMiddleware, RBACMiddleware, RequestValidationMiddleware,
+    requires_admin, requires_user
🧰 Tools
🪛 Ruff (0.11.9)

25-25: .security.requires_moderator imported but unused

Remove unused import: .security.requires_moderator

(F401)

backend/security.py (3)

7-14: Remove unused imports to keep the module lint-clean.

Optional, OAuth2PasswordBearer, and settings are never referenced in this file, and Ruff is already warning about them. Prune them now to avoid dead code and to keep the import section readable.

-from typing import List, Optional, Callable
-from fastapi.security import OAuth2PasswordBearer
-from .config import settings
+from typing import List, Callable

( Response, Depends, etc. are still required, so keep those. )

🧰 Tools
🪛 Ruff (0.11.9)

7-7: typing.Optional imported but unused

Remove unused import: typing.Optional

(F401)


9-9: fastapi.security.OAuth2PasswordBearer imported but unused

Remove unused import: fastapi.security.OAuth2PasswordBearer

(F401)


13-13: .config.settings imported but unused

Remove unused import: .config.settings

(F401)


68-74: Hard-coded path → role mapping will not scale.

Maintaining endpoint_permissions by hand quickly becomes error-prone as routes multiply. Prefer one of:

  1. Attaching a required_role attribute to each APIRouter/Route and reading it from request.scope["route"].
  2. Relying on dependency-based guards (requires_admin, etc.) per-route (FastAPI idiomatic).
  3. Loading the mapping from configuration rather than code.

This keeps RBAC logic close to the route definitions and avoids diverging source-of-truths.


127-130: startswith may grant unintended public access.

"/test/publicity" would currently match because it starts with "/test/public".
If you intend exact matches, switch to set membership; if you need prefix semantics, tighten with a trailing slash or use FastAPI’s route matching.

Example exact check:

-return any(path.startswith(p) for p in self.public_paths)
+return path in self.public_paths
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e33766c and 7e5664d.

📒 Files selected for processing (4)
  • backend/app.py (5 hunks)
  • backend/auth.py (4 hunks)
  • backend/security.py (1 hunks)
  • tests/unit/test_security.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
backend/security.py (1)
backend/auth.py (2)
  • get_current_user (112-131)
  • User (37-43)
🪛 Ruff (0.11.9)
backend/auth.py

7-7: typing.Dict imported but unused

Remove unused import: typing.Dict

(F401)

tests/unit/test_security.py

2-2: pytest imported but unused

Remove unused import: pytest

(F401)


11-11: backend.auth.get_current_user imported but unused

Remove unused import: backend.auth.get_current_user

(F401)


27-27: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


31-31: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


35-35: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

backend/security.py

7-7: typing.Optional imported but unused

Remove unused import: typing.Optional

(F401)


9-9: fastapi.security.OAuth2PasswordBearer imported but unused

Remove unused import: fastapi.security.OAuth2PasswordBearer

(F401)


13-13: .config.settings imported but unused

Remove unused import: .config.settings

(F401)


110-110: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)


111-115: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


172-172: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

backend/app.py

25-25: .security.requires_moderator imported but unused

Remove unused import: .security.requires_moderator

(F401)


149-149: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


150-150: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


150-150: Do not perform function call rate_limit_dependency in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


158-158: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


159-159: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


159-159: Do not perform function call rate_limit_dependency in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

Comment thread backend/security.py
Comment on lines +30 to +42
def _build_csp(self) -> str:
"""Build Content Security Policy header value."""
policies = [
"default-src 'self'",
"img-src 'self' data: https:",
"script-src 'self'",
"style-src 'self' 'unsafe-inline'",
"font-src 'self'",
"frame-ancestors 'none'",
"base-uri 'self'",
"form-action 'self'"
]
return "; ".join(policies)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Re-evaluate 'unsafe-inline' in the Content-Security-Policy.

Allowing inline styles ('unsafe-inline') substantially weakens XSS protections.
Unless you have a hard requirement for inline styles, consider using a nonce or hashed CSS instead and dropping the directive:

-"style-src 'self' 'unsafe-inline'",
+"style-src 'self'",

If inline styles are unavoidable, limit the CSP to only those hashes/nonces that are strictly necessary.
This is a high-impact security hardening step.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def _build_csp(self) -> str:
"""Build Content Security Policy header value."""
policies = [
"default-src 'self'",
"img-src 'self' data: https:",
"script-src 'self'",
"style-src 'self' 'unsafe-inline'",
"font-src 'self'",
"frame-ancestors 'none'",
"base-uri 'self'",
"form-action 'self'"
]
return "; ".join(policies)
def _build_csp(self) -> str:
"""Build Content Security Policy header value."""
policies = [
"default-src 'self'",
"img-src 'self' data: https:",
"script-src 'self'",
- "style-src 'self' 'unsafe-inline'",
+ "style-src 'self'",
"font-src 'self'",
"frame-ancestors 'none'",
"base-uri 'self'",
"form-action 'self'"
]
return "; ".join(policies)
🤖 Prompt for AI Agents
In backend/security.py around lines 30 to 42, the Content Security Policy
currently allows 'unsafe-inline' styles, which weakens XSS protection. To fix
this, remove 'unsafe-inline' from the style-src directive and instead implement
a nonce or hash-based approach for inline styles if they are necessary. Update
the CSP to include only the specific nonces or hashes required for inline styles
to enhance security.

Comment thread backend/security.py Outdated
Comment thread backend/security.py
Comment on lines +151 to +156
# Check content length
content_length = request.headers.get("content-length")
if content_length and int(content_length) > self.max_content_length:
raise HTTPException(
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
detail="Request too large"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

content-length parsing can raise ValueError.

int(content_length) will fail if the header is non-numeric (seen with some proxies).
Guard the conversion:

-        content_length = request.headers.get("content-length")
-        if content_length and int(content_length) > self.max_content_length:
+        content_length = request.headers.get("content-length")
+        try:
+            if content_length and int(content_length) > self.max_content_length:
+                raise HTTPException(
+                    status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
+                    detail="Request too large"
+                )
+        except ValueError:
+            # Malformed header – reject to be safe
             raise HTTPException(
                 status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
                 detail="Request too large"
             )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Check content length
content_length = request.headers.get("content-length")
if content_length and int(content_length) > self.max_content_length:
raise HTTPException(
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
detail="Request too large"
# Check content length
content_length = request.headers.get("content-length")
try:
if content_length and int(content_length) > self.max_content_length:
raise HTTPException(
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
detail="Request too large"
)
except ValueError:
# Malformed header – reject to be safe
raise HTTPException(
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
detail="Request too large"
)
🤖 Prompt for AI Agents
In backend/security.py around lines 151 to 156, the code converts the
content-length header to an integer without handling potential ValueError if the
header is non-numeric. To fix this, wrap the int conversion in a try-except
block to catch ValueError and handle it gracefully, such as by raising an
HTTPException with a suitable error message or ignoring the header if invalid.

…e retrieval in create_access_token and updated TokenData to include role information, improving role-based access control in authentication.
…oduced test users with roles of admin, moderator, and user, including hashed passwords, to improve unit test coverage and facilitate role management in security tests.
…scheme from the auth module to enhance authentication capabilities and support token-based authorization.
…s: removed role parameter from create_test_token function, simplified token creation for user roles, and improved error message assertions for unauthorized access and request validation tests.
Copy link
Copy Markdown
Contributor

@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 (4)
backend/auth.py (2)

7-7: Remove unused import.

The Dict import from typing is not used anywhere in the code.

-from typing import Optional, Dict, Literal
+from typing import Optional, Literal
🧰 Tools
🪛 Ruff (0.11.9)

7-7: typing.Dict imported but unused

Remove unused import: typing.Dict

(F401)


49-75: Consider performance implications of runtime password hashing.

The current implementation hashes passwords at runtime during module import, which could impact startup performance. For production use, consider pre-computing these hashes or using a proper database.

Consider this approach for better performance:

# Pre-computed hashes (replace with actual database in production)
fake_users_db = {
    "admin": {
        "username": "admin",
        "email": "admin@example.com", 
        "full_name": "Admin User",
        "disabled": False,
        "role": "admin",
-       "hashed_password": pwd_context.hash("admin")
+       "hashed_password": "$2b$12$..."  # Pre-computed hash
    },
    # ... other users
}
tests/unit/test_security.py (2)

2-2: Remove unused imports.

The pytest and get_current_user imports are not used in the test file.

-import pytest
-from fastapi import FastAPI, Depends
+from fastapi import FastAPI, Depends
 from fastapi.testclient import TestClient
 from datetime import timedelta
 from backend.security import (
     SecurityMiddleware, RBACMiddleware, RequestValidationMiddleware,
     requires_admin, requires_moderator, requires_user
 )
 from backend.auth import (
-    User, get_current_user, create_access_token,
+    User, create_access_token,
     ACCESS_TOKEN_EXPIRE_MINUTES, fake_users_db, get_password_hash
 )

Also applies to: 11-11

🧰 Tools
🪛 Ruff (0.11.9)

2-2: pytest imported but unused

Remove unused import: pytest

(F401)


49-50: Consider test isolation concerns.

Modifying the global fake_users_db could cause test isolation issues if other tests depend on the original state. Consider using test fixtures or mocking instead.

Consider using pytest fixtures for better test isolation:

+@pytest.fixture
+def test_users_db():
+    """Fixture providing test users without modifying global state."""
+    original_db = fake_users_db.copy()
+    fake_users_db.update(test_users)
+    yield fake_users_db
+    fake_users_db.clear()
+    fake_users_db.update(original_db)

-# Add test users to fake database
-fake_users_db.update(test_users)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7e5664d and 4e937b0.

📒 Files selected for processing (2)
  • backend/auth.py (5 hunks)
  • tests/unit/test_security.py (1 hunks)
🧰 Additional context used
🪛 Ruff (0.11.9)
backend/auth.py

7-7: typing.Dict imported but unused

Remove unused import: typing.Dict

(F401)

tests/unit/test_security.py

2-2: pytest imported but unused

Remove unused import: pytest

(F401)


11-11: backend.auth.get_current_user imported but unused

Remove unused import: backend.auth.get_current_user

(F401)


58-58: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


62-62: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


66-66: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

🔇 Additional comments (9)
backend/auth.py (4)

22-25: Good practice: Centralized configuration management.

Sourcing JWT configuration from the centralized settings object improves maintainability and follows the DRY principle.


35-35: Well-designed role-based user models.

The enhancements to TokenData and User models properly support RBAC:

  • Optional role field in TokenData for JWT payload extraction
  • Optional email field provides flexibility
  • Literal type restriction on role field ensures type safety
  • Sensible default role of "user"

Also applies to: 40-40, 43-43


128-131: Proper role extraction from JWT payload.

The implementation correctly extracts the role from the JWT payload and includes it in the TokenData instance, enabling downstream role-based access control.


108-113:

Details

✅ Verification successful

Verify role inclusion in JWT tokens.

The implementation correctly retrieves the user role and includes it in the JWT payload. This supports the RBAC middleware functionality.

Let's verify that the role is properly embedded in tokens by checking the token creation flow:


🏁 Script executed:

#!/bin/bash
# Description: Verify role inclusion in JWT token creation
# Expected: Role should be added to JWT payload from user data

# Search for role-related code in auth module
rg -A 3 -B 3 "role.*token|token.*role" --type py

Length of output: 818


🏁 Script executed:

#!/bin/bash
# Locate and inspect the create_access_token function to confirm role is added to payload
rg -C 5 "def create_access_token" --type py backend/auth.py

Length of output: 407


🏁 Script executed:

#!/bin/bash
# Dump full implementation of create_access_token to verify payload encoding
sed -n '/^def create_access_token/,/^$/p' backend/auth.py

Length of output: 600


Confirmed Role Inclusion in JWT Payload

The create_access_token function in backend/auth.py copies the incoming data, looks up the user by sub, and assigns to_encode["role"] = user.role before encoding. On the decode side, payload.get("role") is passed into TokenData. Unit tests in tests/unit/test_security.py exercise this flow. No further changes required.

tests/unit/test_security.py (5)

58-58: Static analysis false positive: Depends usage is correct.

The static analysis tool flags B008 warnings about Depends in argument defaults, but this is the standard and correct FastAPI pattern for dependency injection. These warnings can be safely ignored.

Also applies to: 62-62, 66-66

🧰 Tools
🪛 Ruff (0.11.9)

58-58: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


85-96: Comprehensive security headers testing.

The test properly verifies that all essential security headers are present:

  • X-Content-Type-Options for MIME type sniffing protection
  • X-Frame-Options for clickjacking prevention
  • X-XSS-Protection for XSS mitigation
  • Content-Security-Policy for content restriction
  • Referrer-Policy for referrer information control

112-129: Thorough RBAC access control testing.

The test suite comprehensively validates role-based access control:

  • Users can only access appropriate endpoints based on their role
  • Proper 403 Forbidden responses for insufficient privileges
  • Hierarchical access (admin > moderator > user) working correctly
  • Token-based authentication integration

Also applies to: 131-149, 151-166


168-185: Effective request validation testing.

The tests properly validate the request validation middleware:

  • Content length limits (10MB threshold)
  • Content type validation
  • Proper HTTP status codes (413 for payload too large, 415 for unsupported media type)

76-83: Well-designed test token helper.

The create_test_token function properly creates JWT tokens with embedded roles, supporting the RBAC testing scenarios.

…emoved redundant user retrieval, added role verification against the user object, and improved error handling for invalid tokens and roles.
… for unauthorized access by separating HTTPException handling, ensuring clearer role validation and response for invalid authentication credentials.
…tail from "Authentication required" to "Not authenticated" for clarity, and removed redundant handling of invalid authentication credentials in exception management.
…updated create_test_token function to retrieve and incorporate user role from fake_users_db, improving role-based access control in security tests.
… message in security tests from "Authentication required" to "Not authenticated" for consistency with middleware error handling.
Copy link
Copy Markdown
Contributor

@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

♻️ Duplicate comments (2)
backend/security.py (2)

30-42: Re-evaluate 'unsafe-inline' in the Content-Security-Policy.

Allowing inline styles ('unsafe-inline') substantially weakens XSS protections.
Unless you have a hard requirement for inline styles, consider using a nonce or hashed CSS instead and dropping the directive:

-"style-src 'self' 'unsafe-inline'",
+"style-src 'self'",

If inline styles are unavoidable, limit the CSP to only those hashes/nonces that are strictly necessary.
This is a high-impact security hardening step.


154-159: content-length parsing can raise ValueError.

int(content_length) will fail if the header is non-numeric (seen with some proxies).
Guard the conversion:

        content_length = request.headers.get("content-length")
-        if content_length and int(content_length) > self.max_content_length:
+        try:
+            if content_length and int(content_length) > self.max_content_length:
+                raise HTTPException(
+                    status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
+                    detail="Request too large"
+                )
+        except ValueError:
+            # Malformed header – reject to be safe
             raise HTTPException(
                 status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
                 detail="Request too large"
             )
🧹 Nitpick comments (5)
backend/auth.py (2)

7-7: Remove unused import.

The Dict type from typing is imported but never used in the code.

-from typing import Optional, Dict, Literal
+from typing import Optional, Literal
🧰 Tools
🪛 Ruff (0.11.9)

7-7: typing.Dict imported but unused

Remove unused import: typing.Dict

(F401)


130-140: Redundant role verification may impact performance.

The role verification in get_current_user() requires an additional database lookup to verify the role matches the JWT payload. Since the JWT is already signed and verified, this additional check may be unnecessary and impacts performance.

Consider whether this level of verification is needed, or implement caching for user lookups.

tests/unit/test_security.py (2)

2-2: Remove unused import.

The pytest import is not used in this test file.

-import pytest
-from fastapi import FastAPI, Depends
+from fastapi import FastAPI, Depends
🧰 Tools
🪛 Ruff (0.11.9)

2-2: pytest imported but unused

Remove unused import: pytest

(F401)


11-11: Remove unused import.

The get_current_user function is imported but not directly used in the test file.

from backend.auth import (
-    User, get_current_user, create_access_token,
+    User, create_access_token,
    ACCESS_TOKEN_EXPIRE_MINUTES, fake_users_db, get_password_hash
)
🧰 Tools
🪛 Ruff (0.11.9)

11-11: backend.auth.get_current_user imported but unused

Remove unused import: backend.auth.get_current_user

(F401)

backend/security.py (1)

7-7: Remove unused imports.

Several imports are not used in the code and should be removed for clarity.

-from typing import List, Optional, Callable
+from typing import List, Callable
 from fastapi import Request, Response, HTTPException, status, Depends
-from fastapi.security import OAuth2PasswordBearer
 from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
 from starlette.types import ASGIApp
-from .auth import get_current_user, User, oauth2_scheme
-from .config import settings
+from .auth import get_current_user, User

Also applies to: 9-9, 12-12, 13-13

🧰 Tools
🪛 Ruff (0.11.9)

7-7: typing.Optional imported but unused

Remove unused import: typing.Optional

(F401)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e937b0 and 8116c94.

📒 Files selected for processing (3)
  • backend/auth.py (5 hunks)
  • backend/security.py (1 hunks)
  • tests/unit/test_security.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
backend/security.py (1)
backend/auth.py (2)
  • get_current_user (118-142)
  • User (37-43)
tests/unit/test_security.py (2)
backend/security.py (3)
  • SecurityMiddleware (15-54)
  • RBACMiddleware (56-136)
  • RequestValidationMiddleware (138-170)
backend/auth.py (4)
  • User (37-43)
  • get_current_user (118-142)
  • create_access_token (101-116)
  • get_password_hash (81-83)
🪛 Ruff (0.11.9)
backend/security.py

7-7: typing.Optional imported but unused

Remove unused import: typing.Optional

(F401)


9-9: fastapi.security.OAuth2PasswordBearer imported but unused

Remove unused import: fastapi.security.OAuth2PasswordBearer

(F401)


12-12: .auth.oauth2_scheme imported but unused

Remove unused import: .auth.oauth2_scheme

(F401)


13-13: .config.settings imported but unused

Remove unused import: .config.settings

(F401)


122-126: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


174-174: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

backend/auth.py

7-7: typing.Dict imported but unused

Remove unused import: typing.Dict

(F401)

tests/unit/test_security.py

2-2: pytest imported but unused

Remove unused import: pytest

(F401)


11-11: backend.auth.get_current_user imported but unused

Remove unused import: backend.auth.get_current_user

(F401)


58-58: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


62-62: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


66-66: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

🔇 Additional comments (5)
backend/auth.py (1)

108-113: 🛠️ Refactor suggestion

Potential security issue with role injection.

The current implementation retrieves the user role by calling get_user() inside create_access_token(). This could lead to inconsistencies if the user data changes between authentication and token creation, or if there are race conditions.

Consider passing the role as a parameter or ensuring the user object is passed instead of just the username.

-def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
+def create_access_token(data: dict, expires_delta: Optional[timedelta] = None, user_role: Optional[str] = None) -> str:
     """Create access token."""
     to_encode = data.copy()
     if expires_delta:
         expire = datetime.utcnow() + expires_delta
     else:
         expire = datetime.utcnow() + timedelta(minutes=15)
     
-    # Get user role
-    user = get_user(data.get("sub"))
-    if user:
-        to_encode["role"] = user.role
+    # Add user role if provided
+    if user_role:
+        to_encode["role"] = user_role
     
     to_encode.update({"exp": expire})
     encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
     return encoded_jwt

Likely an incorrect or invalid review comment.

tests/unit/test_security.py (2)

58-58: FastAPI dependency injection in function defaults.

Using Depends() in function argument defaults is flagged by static analysis as it can cause issues with dependency injection timing.

However, this is the standard FastAPI pattern for route dependencies and is working correctly in this context. The static analysis warning can be safely ignored here.

Also applies to: 62-62, 66-66

🧰 Tools
🪛 Ruff (0.11.9)

58-58: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


168-187: Excellent test coverage for request validation.

The request validation tests properly cover both content length limits and content type validation, including edge cases and valid requests.

backend/security.py (2)

174-174: FastAPI dependency injection pattern is correct.

The use of Depends() in the function parameter default is flagged by static analysis, but this is the standard and correct FastAPI pattern for dependency injection. The warning can be safely ignored.

🧰 Tools
🪛 Ruff (0.11.9)

174-174: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


183-186: Well-designed role hierarchy dependencies.

The convenience dependencies properly implement the role hierarchy where admin has access to all levels, moderator to moderator and user levels, and user to user level only.

Comment thread backend/auth.py
Comment on lines +49 to +75
# Simulated database (replace with actual database in production)
fake_users_db = {
"admin": {
"username": "admin",
"email": "admin@example.com",
"full_name": "Admin User",
"disabled": False,
"role": "admin",
"hashed_password": pwd_context.hash("admin")
},
"moderator": {
"username": "moderator",
"email": "moderator@example.com",
"full_name": "Moderator User",
"disabled": False,
"role": "moderator",
"hashed_password": pwd_context.hash("moderator")
},
"user": {
"username": "user",
"email": "user@example.com",
"full_name": "Regular User",
"disabled": False,
"role": "user",
"hashed_password": pwd_context.hash("user")
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Security concern: Passwords hashed at runtime.

The user passwords are being hashed at module import time, which means they're recalculated on every server restart. This is inefficient and could cause performance issues during server startup.

Consider pre-computing these hashes or using a proper database with seeded data.

-# Simulated database (replace with actual database in production)
-fake_users_db = {
-    "admin": {
-        "username": "admin",
-        "email": "admin@example.com", 
-        "full_name": "Admin User",
-        "disabled": False,
-        "role": "admin",
-        "hashed_password": pwd_context.hash("admin")
-    },
-    "moderator": {
-        "username": "moderator",
-        "email": "moderator@example.com",
-        "full_name": "Moderator User", 
-        "disabled": False,
-        "role": "moderator",
-        "hashed_password": pwd_context.hash("moderator")
-    },
-    "user": {
-        "username": "user",
-        "email": "user@example.com",
-        "full_name": "Regular User",
-        "disabled": False,
-        "role": "user", 
-        "hashed_password": pwd_context.hash("user")
-    }
-}
+# Simulated database (replace with actual database in production)
+fake_users_db = {
+    "admin": {
+        "username": "admin",
+        "email": "admin@example.com",
+        "full_name": "Admin User",
+        "disabled": False,
+        "role": "admin",
+        "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"  # "admin"
+    },
+    "moderator": {
+        "username": "moderator", 
+        "email": "moderator@example.com",
+        "full_name": "Moderator User",
+        "disabled": False,
+        "role": "moderator",
+        "hashed_password": "$2b$12$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi"  # "moderator"
+    },
+    "user": {
+        "username": "user",
+        "email": "user@example.com",
+        "full_name": "Regular User", 
+        "disabled": False,
+        "role": "user",
+        "hashed_password": "$2b$12$kixWuVoH.LkXWvRVCrgnPOPVFoD30WBVAQPQzF0yGt6sTPYo4MoXy"  # "user"
+    }
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Simulated database (replace with actual database in production)
fake_users_db = {
"admin": {
"username": "admin",
"email": "admin@example.com",
"full_name": "Admin User",
"disabled": False,
"role": "admin",
"hashed_password": pwd_context.hash("admin")
},
"moderator": {
"username": "moderator",
"email": "moderator@example.com",
"full_name": "Moderator User",
"disabled": False,
"role": "moderator",
"hashed_password": pwd_context.hash("moderator")
},
"user": {
"username": "user",
"email": "user@example.com",
"full_name": "Regular User",
"disabled": False,
"role": "user",
"hashed_password": pwd_context.hash("user")
}
}
# Simulated database (replace with actual database in production)
fake_users_db = {
"admin": {
"username": "admin",
"email": "admin@example.com",
"full_name": "Admin User",
"disabled": False,
"role": "admin",
"hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW" # "admin"
},
"moderator": {
"username": "moderator",
"email": "moderator@example.com",
"full_name": "Moderator User",
"disabled": False,
"role": "moderator",
"hashed_password": "$2b$12$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi" # "moderator"
},
"user": {
"username": "user",
"email": "user@example.com",
"full_name": "Regular User",
"disabled": False,
"role": "user",
"hashed_password": "$2b$12$kixWuVoH.LkXWvRVCrgnPOPVFoD30WBVAQPQzF0yGt6sTPYo4MoXy" # "user"
}
}
🤖 Prompt for AI Agents
In backend/auth.py between lines 49 and 75, the user passwords are hashed at
module import time, causing unnecessary computation on every server restart. To
fix this, pre-compute the password hashes once and replace the pwd_context.hash
calls with the resulting hashed strings in the fake_users_db dictionary.
Alternatively, move to using a proper database with seeded hashed passwords to
avoid runtime hashing.

Comment thread tests/unit/test_security.py Outdated
Comment on lines +49 to +50
# Add test users to fake database
fake_users_db.update(test_users)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Direct mutation of shared state is problematic.

Directly updating fake_users_db from the auth module can cause test isolation issues and side effects between tests. Test users could persist and affect other tests.

Consider using dependency injection or mocking to isolate test data.

+import pytest
+from unittest.mock import patch

+@pytest.fixture(autouse=True)
+def mock_users_db():
+    """Mock the users database for tests."""
+    with patch('backend.auth.fake_users_db', test_users):
+        yield

-# Add test users to fake database
-fake_users_db.update(test_users)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Add test users to fake database
fake_users_db.update(test_users)
import pytest
from unittest.mock import patch
@pytest.fixture(autouse=True)
def mock_users_db():
"""Mock the users database for tests."""
with patch('backend.auth.fake_users_db', test_users):
yield
🤖 Prompt for AI Agents
In tests/unit/test_security.py around lines 49 to 50, avoid directly mutating
the shared fake_users_db dictionary as it can cause test isolation issues.
Instead, refactor the test to use dependency injection or mocking to provide
test-specific user data, ensuring each test has isolated state and does not
affect others. This can be done by injecting a fresh copy of the user database
or mocking the data access layer in the test setup.

Comment thread backend/security.py
Comment on lines +120 to +127
except HTTPException as e:
if e.status_code == status.HTTP_401_UNAUTHORIZED:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"}
)
raise e
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve exception handling specificity.

The current exception handling catches HTTPException and re-raises it with a different message, which could mask the original error details. Also, the raise ... from err pattern should be used for better error traceability.

        except HTTPException as e:
            if e.status_code == status.HTTP_401_UNAUTHORIZED:
                raise HTTPException(
                    status_code=status.HTTP_401_UNAUTHORIZED,
                    detail="Invalid authentication credentials",
                    headers={"WWW-Authenticate": "Bearer"}
-                )
+                ) from e
            raise e
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
except HTTPException as e:
if e.status_code == status.HTTP_401_UNAUTHORIZED:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"}
)
raise e
except HTTPException as e:
if e.status_code == status.HTTP_401_UNAUTHORIZED:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"}
) from e
raise e
🧰 Tools
🪛 Ruff (0.11.9)

122-126: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🤖 Prompt for AI Agents
In backend/security.py around lines 120 to 127, improve exception handling by
using the 'raise ... from e' pattern when re-raising the HTTPException to
preserve the original error context. Modify the code to raise the new
HTTPException with the custom message from the caught exception 'e' using 'raise
... from e' instead of just 'raise e' to enhance error traceability without
masking original details.

…rer from "api/v1/auth/token" to "/api/v1/auth/token" for correct endpoint reference.
…or handling: changed the way test users are added to fake_users_db for clarity, and improved the assertion for handling large content requests to ensure proper error messaging.
…rs to fake_users_db to use a copy of user data, preventing reference issues and ensuring data integrity during tests.
…a dynamic role hierarchy for improved maintainability and scalability.
Copy link
Copy Markdown
Contributor

@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

♻️ Duplicate comments (4)
backend/auth.py (1)

49-75: ⚠️ Potential issue

Critical performance issue: Runtime password hashing.

This is a duplicate of a previously identified issue. The passwords are being hashed at module import time, causing unnecessary computation on every server restart and potential performance issues.

Apply this diff to use pre-computed hashes:

-# Simulated database (replace with actual database in production)
-fake_users_db = {
-    "admin": {
-        "username": "admin",
-        "email": "admin@example.com",
-        "full_name": "Admin User",
-        "disabled": False,
-        "role": "admin",
-        "hashed_password": pwd_context.hash("admin")
-    },
-    "moderator": {
-        "username": "moderator",
-        "email": "moderator@example.com",
-        "full_name": "Moderator User",
-        "disabled": False,
-        "role": "moderator",
-        "hashed_password": pwd_context.hash("moderator")
-    },
-    "user": {
-        "username": "user",
-        "email": "user@example.com",
-        "full_name": "Regular User",
-        "disabled": False,
-        "role": "user",
-        "hashed_password": pwd_context.hash("user")
-    }
-}
+# Simulated database (replace with actual database in production)
+fake_users_db = {
+    "admin": {
+        "username": "admin",
+        "email": "admin@example.com",
+        "full_name": "Admin User",
+        "disabled": False,
+        "role": "admin",
+        "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"  # "admin"
+    },
+    "moderator": {
+        "username": "moderator",
+        "email": "moderator@example.com",
+        "full_name": "Moderator User",
+        "disabled": False,
+        "role": "moderator",
+        "hashed_password": "$2b$12$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi"  # "moderator"
+    },
+    "user": {
+        "username": "user",
+        "email": "user@example.com",
+        "full_name": "Regular User",
+        "disabled": False,
+        "role": "user",
+        "hashed_password": "$2b$12$kixWuVoH.LkXWvRVCrgnPOPVFoD30WBVAQPQzF0yGt6sTPYo4MoXy"  # "user"
+    }
+}
tests/unit/test_security.py (1)

21-64: ⚠️ Potential issue

Critical test isolation issue: Direct mutation of shared state.

This is a duplicate of a previously identified issue. Directly updating fake_users_db from the auth module can cause test isolation problems and side effects between tests.

Apply this diff to use dependency injection for better test isolation:

+import pytest
+from unittest.mock import patch

+@pytest.fixture(autouse=True)
+def mock_users_db():
+    """Mock the users database for tests."""
+    test_users = {
+        "test_admin": {
+            "username": "test_admin",
+            "email": "test_admin@example.com",
+            "full_name": "Test Admin",
+            "disabled": False,
+            "role": "admin",
+            "hashed_password": get_password_hash("test_admin")
+        },
+        "test_moderator": {
+            "username": "test_moderator",
+            "email": "test_moderator@example.com",
+            "full_name": "Test Moderator",
+            "disabled": False,
+            "role": "moderator",
+            "hashed_password": get_password_hash("test_moderator")
+        },
+        "test_user": {
+            "username": "test_user",
+            "email": "test_user@example.com",
+            "full_name": "Test User",
+            "disabled": False,
+            "role": "user",
+            "hashed_password": get_password_hash("test_user")
+        }
+    }
+    with patch('backend.auth.fake_users_db', test_users):
+        yield

-@pytest.fixture(autouse=True)
-def setup_test_users():
-    """Setup test users before each test."""
-    # Create test users
-    test_users = {
-        "test_admin": {
-            "username": "test_admin",
-            "email": "test_admin@example.com",
-            "full_name": "Test Admin",
-            "disabled": False,
-            "role": "admin",
-            "hashed_password": get_password_hash("test_admin")
-        },
-        "test_moderator": {
-            "username": "test_moderator",
-            "email": "test_moderator@example.com",
-            "full_name": "Test Moderator",
-            "disabled": False,
-            "role": "moderator",
-            "hashed_password": get_password_hash("test_moderator")
-        },
-        "test_user": {
-            "username": "test_user",
-            "email": "test_user@example.com",
-            "full_name": "Test User",
-            "disabled": False,
-            "role": "user",
-            "hashed_password": get_password_hash("test_user")
-        }
-    }
-    
-    # Clear existing test users
-    for username in ["test_admin", "test_moderator", "test_user"]:
-        fake_users_db.pop(username, None)
-    
-    # Add test users to fake database
-    fake_users_db.update(test_users)
-    
-    yield
-    
-    # Cleanup after test
-    for username in ["test_admin", "test_moderator", "test_user"]:
-        fake_users_db.pop(username, None)
backend/security.py (2)

30-42: ⚠️ Potential issue

Security concern: CSP allows unsafe inline styles.

This is a duplicate of a previously identified security issue. The Content Security Policy currently allows 'unsafe-inline' styles, which weakens XSS protection.

Apply this diff to enhance security:

        policies = [
            "default-src 'self'",
            "img-src 'self' data: https:",
            "script-src 'self'",
-            "style-src 'self' 'unsafe-inline'",
+            "style-src 'self'",
            "font-src 'self'",
            "frame-ancestors 'none'",
            "base-uri 'self'",
            "form-action 'self'"
        ]

152-158: ⚠️ Potential issue

Potential vulnerability: Content-length parsing can fail.

The int(content_length) conversion will raise a ValueError if the header contains non-numeric values, which can occur with some proxies or malicious requests.

Apply this diff to handle malformed headers safely:

        # Check content length
        content_length = request.headers.get("content-length")
-        if content_length and int(content_length) > self.max_content_length:
-            raise HTTPException(
-                status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
-                detail="Request too large"
-            )
+        try:
+            if content_length and int(content_length) > self.max_content_length:
+                raise HTTPException(
+                    status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
+                    detail="Request too large"
+                )
+        except ValueError:
+            # Malformed header – reject to be safe
+            raise HTTPException(
+                status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
+                detail="Request too large"
+            )
🧹 Nitpick comments (3)
backend/auth.py (1)

7-7: Remove unused import.

The Dict type import is not used anywhere in the code.

Apply this diff to remove the unused import:

-from typing import Optional, Dict, Literal
+from typing import Optional, Literal
🧰 Tools
🪛 Ruff (0.11.9)

7-7: typing.Dict imported but unused

Remove unused import: typing.Dict

(F401)

tests/unit/test_security.py (1)

11-11: Remove unused import.

The get_current_user import is not directly used in the test code, as it's called through the middleware and dependencies.

Apply this diff to remove the unused import:

from backend.auth import (
-    User, get_current_user, create_access_token,
+    User, create_access_token,
    ACCESS_TOKEN_EXPIRE_MINUTES, fake_users_db, get_password_hash
)
🧰 Tools
🪛 Ruff (0.11.9)

11-11: backend.auth.get_current_user imported but unused

Remove unused import: backend.auth.get_current_user

(F401)

backend/security.py (1)

7-13: Remove unused imports.

Several imports are flagged as unused by static analysis and should be removed to keep the code clean.

Apply this diff to remove unused imports:

-from typing import List, Optional, Callable
+from typing import List, Callable
from fastapi import Request, Response, HTTPException, status, Depends
-from fastapi.security import OAuth2PasswordBearer
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.types import ASGIApp
-from .auth import get_current_user, User, oauth2_scheme
+from .auth import get_current_user, User
-from .config import settings
🧰 Tools
🪛 Ruff (0.11.9)

7-7: typing.Optional imported but unused

Remove unused import: typing.Optional

(F401)


9-9: fastapi.security.OAuth2PasswordBearer imported but unused

Remove unused import: fastapi.security.OAuth2PasswordBearer

(F401)


12-12: .auth.oauth2_scheme imported but unused

Remove unused import: .auth.oauth2_scheme

(F401)


13-13: .config.settings imported but unused

Remove unused import: .config.settings

(F401)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8116c94 and 8f4bffc.

📒 Files selected for processing (3)
  • backend/auth.py (5 hunks)
  • backend/security.py (1 hunks)
  • tests/unit/test_security.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
tests/unit/test_security.py (2)
backend/security.py (3)
  • SecurityMiddleware (15-54)
  • RBACMiddleware (56-135)
  • RequestValidationMiddleware (137-169)
backend/auth.py (4)
  • User (37-43)
  • get_current_user (118-142)
  • create_access_token (101-116)
  • get_password_hash (81-83)
backend/security.py (1)
backend/auth.py (2)
  • get_current_user (118-142)
  • User (37-43)
🪛 Ruff (0.11.9)
tests/unit/test_security.py

11-11: backend.auth.get_current_user imported but unused

Remove unused import: backend.auth.get_current_user

(F401)


71-71: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


75-75: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


79-79: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

backend/security.py

7-7: typing.Optional imported but unused

Remove unused import: typing.Optional

(F401)


9-9: fastapi.security.OAuth2PasswordBearer imported but unused

Remove unused import: fastapi.security.OAuth2PasswordBearer

(F401)


12-12: .auth.oauth2_scheme imported but unused

Remove unused import: .auth.oauth2_scheme

(F401)


13-13: .config.settings imported but unused

Remove unused import: .config.settings

(F401)


173-173: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

backend/auth.py

7-7: typing.Dict imported but unused

Remove unused import: typing.Dict

(F401)

🔇 Additional comments (17)
backend/auth.py (6)

22-25: LGTM! Configuration centralization is a good practice.

Moving JWT configuration to the centralized settings object improves maintainability and follows the DRY principle.


35-35: LGTM! Role field addition supports RBAC functionality.

The addition of the role field to TokenData properly supports the role-based access control system being implemented.


40-43: LGTM! User model enhancements support the security framework.

The changes to make email optional and add role constraints are well-designed for the RBAC system.


92-99: LGTM! Return type change aligns with updated architecture.

The change from Optional[UserInDB] to Optional[User] is consistent with the security framework requirements.


108-116: LGTM! Role inclusion in JWT tokens enables RBAC.

The enhancement to include user roles in JWT tokens is correctly implemented and essential for the role-based access control middleware.


130-140: LGTM! Role verification strengthens token security.

The addition of role verification in token validation prevents role manipulation attacks and ensures token integrity.

tests/unit/test_security.py (5)

71-81: Consider using dependency injection for test endpoints.

The static analysis correctly identifies that Depends() calls in argument defaults can cause issues. However, in this test context, these are mock endpoints specifically designed to test the dependency behavior, so this is acceptable.

🧰 Tools
🪛 Ruff (0.11.9)

71-71: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


75-75: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


79-79: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


99-111: LGTM! Comprehensive security headers testing.

The test properly verifies that all expected security headers are present in responses.


118-128: LGTM! Good exception handling in unauthorized tests.

The test correctly handles both direct HTTP responses and exceptions that might be raised during authentication failures.


129-184: LGTM! Thorough RBAC testing.

The tests comprehensively verify the role hierarchy and access control enforcement across different user roles.


185-224: LGTM! Request validation testing covers key scenarios.

The tests properly verify content length limits and content type validation with appropriate exception handling.

backend/security.py (6)

15-54: LGTM! Comprehensive security headers implementation.

The SecurityMiddleware properly implements essential security headers following industry best practices. The modular approach with _build_csp() makes the CSP configuration maintainable.


68-84: LGTM! Well-designed endpoint permission mapping.

The role hierarchy and endpoint permissions are clearly defined and support the RBAC requirements. The public paths list appropriately excludes authentication and documentation endpoints.


107-122: LGTM! Exception handling is appropriately specific.

The exception handling correctly propagates HTTPException while catching other authentication-related errors. This addresses previous concerns about overly broad exception handling.


127-135: LGTM! Role access logic is clear and secure.

The role hierarchy enforcement is correctly implemented with explicit checks for each role level.


137-169: LGTM! Request validation middleware provides essential protections.

The middleware properly validates content length and content types, providing good protection against oversized requests and unsupported media types.


171-185: LGTM! Role-based dependencies are well-designed.

The requires_roles factory function and convenience dependencies provide a clean, reusable way to enforce role-based access control on endpoints.

🧰 Tools
🪛 Ruff (0.11.9)

173-173: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

…chy for improved permission checks and access management.
@chojuninengu chojuninengu dismissed Nkwenti-Severian-Ndongtsop’s stale review May 27, 2025 15:52

The merge-base changed after approval.

@Nkwenti-Severian-Ndongtsop Nkwenti-Severian-Ndongtsop merged commit 0d86111 into Cameroon-Developer-Network:main May 27, 2025
4 checks passed
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.

2 participants