feat: comprehensive code review fixes — security, performance, quality
Backend security: - Reject Gitea webhooks when webhook_secret is empty (was silently skipping) - Add slowapi rate limiting on login (5/min) and setup (3/min) endpoints - Add CORS middleware with configurable origins - Mask telegram_webhook_secret in settings API response - Protect system-owned command template configs from regular user modification - Increase minimum password length to 8 characters Backend performance: - Batch queries in _resolve_command_context (3 queries instead of 3N) - Concurrent album fetching with asyncio.gather in immich commands - Singleton Jinja2 SandboxedEnvironment (reuse instead of per-render creation) - TTLCache for rate limits (bounded memory, auto-eviction) - Optional aiohttp session reuse in send_reply/send_media_group Backend code quality: - Extract dispatch_helpers.py (shared link_data loading + event filtering) - Extract database/seeds.py from main.py (490 lines → dedicated module) - Split immich_handler.py (415 lines) into commands/immich/ subpackage - Replace bare except blocks with logged warnings - Add per-provider config validation (Pydantic models) - Truncate command input to 512 chars - Expose usage_* and desc_* slots in capabilities and variables API Frontend security: - CSS.escape() for user-controlled querySelector in highlight.ts - Client-side password min 8 chars validation on setup and password change Frontend code quality: - Replace any types with proper interfaces across top files - Decompose targets/+page.svelte into TargetForm + ReceiverSection - Fix $derived.by usage, $state mutation patterns - Add console.warn to empty catch blocks Frontend UX: - Auth redirect via goto() with "Redirecting..." state - Platform-aware Ctrl/Cmd K keyboard hint - Remove stat-card hover transform Frontend accessibility: - Modal: role=dialog, aria-modal, focus trap, restore focus - EntitySelect/IconGridSelect: listbox/option roles, aria-selected/disabled
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, ValidationError
|
||||
from sqlmodel import select
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
from typing import Any
|
||||
@@ -42,6 +42,48 @@ class ProviderResponse(BaseModel):
|
||||
created_at: str
|
||||
|
||||
|
||||
# -- Per-provider config validation models --
|
||||
|
||||
|
||||
class ImmichProviderConfig(BaseModel):
|
||||
url: str
|
||||
api_key: str
|
||||
external_domain: str | None = None
|
||||
|
||||
|
||||
class GiteaProviderConfig(BaseModel):
|
||||
url: str
|
||||
webhook_secret: str
|
||||
api_token: str | None = None
|
||||
|
||||
|
||||
class SchedulerProviderConfig(BaseModel):
|
||||
"""Scheduler is a virtual provider — no required fields."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
_PROVIDER_CONFIG_MODELS: dict[str, type[BaseModel]] = {
|
||||
"immich": ImmichProviderConfig,
|
||||
"gitea": GiteaProviderConfig,
|
||||
"scheduler": SchedulerProviderConfig,
|
||||
}
|
||||
|
||||
|
||||
def _validate_provider_config(provider_type: str, config: dict[str, Any]) -> None:
|
||||
"""Validate provider config against the schema for the given type."""
|
||||
config_model = _PROVIDER_CONFIG_MODELS.get(provider_type)
|
||||
if config_model is None:
|
||||
return
|
||||
try:
|
||||
config_model.model_validate(config)
|
||||
except ValidationError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"Invalid config for '{provider_type}' provider: {exc}",
|
||||
)
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_providers(
|
||||
user: User = Depends(get_current_user),
|
||||
@@ -62,6 +104,8 @@ async def create_provider(
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
"""Add a new service provider (validates connection for known types)."""
|
||||
_validate_provider_config(body.type, body.config)
|
||||
|
||||
# Validate connection for known provider types
|
||||
if body.type == "immich":
|
||||
from notify_bridge_core.providers.immich import ImmichServiceProvider
|
||||
@@ -177,6 +221,7 @@ async def update_provider(
|
||||
|
||||
config_changed = body.config is not None and body.config != provider.config
|
||||
if body.config is not None:
|
||||
_validate_provider_config(provider.type, body.config)
|
||||
provider.config = body.config
|
||||
|
||||
# Re-validate connection when config changes for known provider types
|
||||
|
||||
Reference in New Issue
Block a user