feat: SAC 0.7.2 security hardening (rate limit, JWT DB check, DOMPurify)

Path traversal fix in SPA fallback, admin-only host delete, login rate limit
with 3 attempts and Telegram alert, JWT validated against active users in DB,
and DOMPurify for agent report HTML.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-01 11:04:14 +10:00
parent 06a8ed8614
commit d3a337992c
19 changed files with 554 additions and 98 deletions
+106 -5
View File
@@ -1,70 +1,171 @@
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from jose import JWTError, jwt
from sqlalchemy import func, select
from sqlalchemy.orm import Session
from app.config import get_settings
from app.database import get_db
from app.models.user import USER_ROLE_ADMIN, User
bearer_scheme = HTTPBearer(auto_error=False)
username: str
@dataclass(frozen=True)
class CurrentUser:
username: str
role: str
@property
def is_admin(self) -> bool:
return self.role == USER_ROLE_ADMIN
def create_access_token(subject: str, role: str) -> str:
settings = get_settings()
expire = datetime.now(timezone.utc) + timedelta(minutes=settings.jwt_expire_minutes)
payload = {"sub": subject, "role": role, "exp": expire}
return jwt.encode(payload, settings.jwt_secret, algorithm=settings.jwt_algorithm)
def _user_from_db(db: Session, username: str) -> User:
normalized = username.strip()
user = db.scalar(select(User).where(func.lower(User.username) == normalized.lower()))
if user is None or not user.is_active:
raise HTTPException(status_code=401, detail="Invalid token")
def _decode_current_user(token: str) -> CurrentUser:
return user
def _decode_current_user(token: str, db: Session | None = None) -> CurrentUser:
settings = get_settings()