fix: address multiple code audit findings
- CORS: replace wildcard with explicit origin list from CORS_ORIGINS env - Auth: enforce strong defaults, JWT blacklist (RevokedToken model), login rate limiting - Auth: validate password length before bcrypt (72-byte limit) - Scheduler: single-threaded worker to mitigate SQLite write contention - Scheduler: graceful shutdown (wait=True) - Snapshots: add prune_snapshots() with configurable retention count - Storage: isolate localStorage keys via VITE_APP_KEY prefix - Config: add cors_origins, login_rate_limit, snapshot_retention_count settings
This commit is contained in:
@@ -98,9 +98,16 @@ class BrowserSessionService:
|
||||
session = self._get(session_id)
|
||||
async with session.lock:
|
||||
self._ensure_open(session)
|
||||
return await session.page.screenshot(type="jpeg", quality=78, full_page=False)
|
||||
return await session.page.screenshot(type="jpeg", quality=65, full_page=False)
|
||||
|
||||
async def event(self, session_id: str, event_type: str, payload: dict[str, Any]) -> dict[str, Any]:
|
||||
async def event(
|
||||
self,
|
||||
session_id: str,
|
||||
event_type: str,
|
||||
payload: dict[str, Any],
|
||||
*,
|
||||
include_state: bool = True,
|
||||
) -> dict[str, Any] | None:
|
||||
session = self._get(session_id)
|
||||
async with session.lock:
|
||||
self._ensure_open(session)
|
||||
@@ -141,8 +148,17 @@ class BrowserSessionService:
|
||||
await page.set_viewport_size({"width": width, "height": height})
|
||||
else:
|
||||
raise ValueError("Unsupported browser event")
|
||||
if not include_state:
|
||||
return None
|
||||
return await self._session_state(session)
|
||||
|
||||
async def selected_text(self, session_id: str) -> str:
|
||||
session = self._get(session_id)
|
||||
async with session.lock:
|
||||
self._ensure_open(session)
|
||||
value = await session.page.evaluate("() => window.getSelection()?.toString() || ''")
|
||||
return str(value or "")
|
||||
|
||||
async def close(self, session_id: str) -> None:
|
||||
session = self._discard_session(session_id)
|
||||
if not session:
|
||||
|
||||
@@ -5,6 +5,7 @@ import json
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from apscheduler.executors.pool import ThreadPoolExecutor
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -12,14 +13,14 @@ from app.database import SessionLocal
|
||||
from app.models.upstream import Upstream
|
||||
from app.models.snapshot import UpstreamRateSnapshot
|
||||
from app.services.upstream_client import UpstreamClient, UpstreamError, build_snapshot
|
||||
from app.services.snapshot_service import diff_snapshots
|
||||
from app.services.snapshot_service import diff_snapshots, prune_snapshots
|
||||
from app.services import webhook_service
|
||||
from app.services import website_sync
|
||||
from app.config import get_settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_scheduler = BackgroundScheduler(timezone="UTC")
|
||||
_scheduler = BackgroundScheduler(timezone="UTC", executors={"default": ThreadPoolExecutor(max_workers=1)})
|
||||
|
||||
|
||||
def get_scheduler() -> BackgroundScheduler:
|
||||
@@ -95,6 +96,7 @@ def _check_upstream(upstream_id: int) -> None:
|
||||
upstream.last_checked_at = datetime.now(timezone.utc)
|
||||
upstream.last_error = None
|
||||
upstream.consecutive_failures = 0
|
||||
prune_snapshots(db, upstream_id, settings.snapshot_retention_count)
|
||||
db.commit()
|
||||
|
||||
if was_unhealthy:
|
||||
@@ -155,4 +157,4 @@ def start_scheduler() -> None:
|
||||
|
||||
def stop_scheduler() -> None:
|
||||
if _scheduler.running:
|
||||
_scheduler.shutdown(wait=False)
|
||||
_scheduler.shutdown(wait=True)
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
"""Snapshot diff logic."""
|
||||
from typing import Any, Optional
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models.snapshot import UpstreamRateSnapshot
|
||||
|
||||
|
||||
def diff_snapshots(
|
||||
previous: Optional[dict[str, Any]],
|
||||
@@ -37,3 +41,20 @@ def diff_snapshots(
|
||||
"new_rate": None,
|
||||
})
|
||||
return changes
|
||||
|
||||
|
||||
def prune_snapshots(db: Session, upstream_id: int, keep: int) -> None:
|
||||
if keep <= 0:
|
||||
return
|
||||
stale_ids = [
|
||||
row_id
|
||||
for (row_id,) in (
|
||||
db.query(UpstreamRateSnapshot.id)
|
||||
.filter(UpstreamRateSnapshot.upstream_id == upstream_id)
|
||||
.order_by(UpstreamRateSnapshot.captured_at.desc(), UpstreamRateSnapshot.id.desc())
|
||||
.offset(keep)
|
||||
.all()
|
||||
)
|
||||
]
|
||||
if stale_ids:
|
||||
db.query(UpstreamRateSnapshot).filter(UpstreamRateSnapshot.id.in_(stale_ids)).delete(synchronize_session=False)
|
||||
|
||||
Reference in New Issue
Block a user