Improve docs and security defaults

This commit is contained in:
2025-10-22 15:22:58 +02:00
parent ebb0769ed4
commit 92795cf9b3
16 changed files with 283 additions and 180 deletions

View File

@@ -5,9 +5,13 @@ This module provides dependency injection functions for the FastAPI
application, including SeriesApp instances, AnimeService, DownloadService,
database sessions, and authentication dependencies.
"""
from typing import TYPE_CHECKING, AsyncGenerator, Optional
import logging
import time
from asyncio import Lock
from dataclasses import dataclass
from typing import TYPE_CHECKING, AsyncGenerator, Dict, Optional
from fastapi import Depends, HTTPException, status
from fastapi import Depends, HTTPException, Request, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
try:
@@ -19,13 +23,15 @@ from src.config.settings import settings
from src.core.SeriesApp import SeriesApp
from src.server.services.auth_service import AuthError, auth_service
logger = logging.getLogger(__name__)
if TYPE_CHECKING:
from src.server.services.anime_service import AnimeService
from src.server.services.download_service import DownloadService
# Security scheme for JWT authentication
# Use auto_error=False to handle errors manually and return 401 instead of 403
security = HTTPBearer(auto_error=False)
http_bearer_security = HTTPBearer(auto_error=False)
# Global SeriesApp instance
@@ -36,6 +42,19 @@ _anime_service: Optional["AnimeService"] = None
_download_service: Optional["DownloadService"] = None
@dataclass
class RateLimitRecord:
"""Track request counts within a fixed time window."""
count: int
window_start: float
_RATE_LIMIT_BUCKETS: Dict[str, RateLimitRecord] = {}
_rate_limit_lock = Lock()
_RATE_LIMIT_WINDOW_SECONDS = 60.0
def get_series_app() -> SeriesApp:
"""
Dependency to get SeriesApp instance.
@@ -104,7 +123,9 @@ async def get_database_session() -> AsyncGenerator:
def get_current_user(
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
credentials: Optional[HTTPAuthorizationCredentials] = Depends(
http_bearer_security
),
) -> dict:
"""
Dependency to get current authenticated user.
@@ -195,7 +216,7 @@ def get_current_user_optional(
class CommonQueryParams:
"""Common query parameters for API endpoints."""
"""Reusable pagination parameters shared across API endpoints."""
def __init__(self, skip: int = 0, limit: int = 100) -> None:
"""Create a reusable pagination parameter container.
@@ -226,23 +247,47 @@ def common_parameters(
# Dependency for rate limiting (placeholder)
async def rate_limit_dependency():
"""
Dependency for rate limiting API requests.
TODO: Implement rate limiting logic
"""
pass
async def rate_limit_dependency(request: Request) -> None:
"""Apply a simple fixed-window rate limit to incoming requests."""
client_id = "unknown"
if request.client and request.client.host:
client_id = request.client.host
max_requests = max(1, settings.api_rate_limit)
now = time.time()
async with _rate_limit_lock:
record = _RATE_LIMIT_BUCKETS.get(client_id)
if not record or now - record.window_start >= _RATE_LIMIT_WINDOW_SECONDS:
_RATE_LIMIT_BUCKETS[client_id] = RateLimitRecord(
count=1,
window_start=now,
)
return
record.count += 1
if record.count > max_requests:
logger.warning("Rate limit exceeded", extra={"client": client_id})
raise HTTPException(
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
detail="Too many requests. Please slow down.",
)
# Dependency for request logging (placeholder)
async def log_request_dependency():
"""
Dependency for logging API requests.
TODO: Implement request logging logic
"""
pass
async def log_request_dependency(request: Request) -> None:
"""Log request metadata for auditing and debugging purposes."""
logger.info(
"API request",
extra={
"method": request.method,
"path": request.url.path,
"client": request.client.host if request.client else "unknown",
"query": dict(request.query_params),
},
)
def get_anime_service() -> "AnimeService":

View File

@@ -251,8 +251,12 @@ class SystemUtilities:
info = SystemUtilities.get_process_info(proc.pid)
if info:
processes.append(info)
except Exception:
pass
except Exception as process_error:
logger.debug(
"Skipping process %s: %s",
proc.pid,
process_error,
)
return processes
except Exception as e: