🐐 Several security fixes

This commit is contained in:
2026-07-29 16:30:14 +02:00
parent b42abe5f5e
commit c56498239f
21 changed files with 660 additions and 204 deletions
+17 -5
View File
@@ -1,11 +1,11 @@
from datetime import datetime, timedelta
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Request
from pydantic import BaseModel
from sqlalchemy.orm import Session
from core.database import get_db
from core.dependencies import get_current_user
from core.dependencies import escape_like, get_current_user, limiter
from core.models import Card as CardModel
from core.models import Deck as DeckModel
from core.models import User as UserModel
@@ -96,13 +96,14 @@ def update_profile(req: UpdateProfileRequest, user: UserModel = Depends(get_curr
@router.get("/users")
def search_users(q: str, current_user: UserModel = Depends(get_current_user), db: Session = Depends(get_db)):
@limiter.limit("60/minute")
def search_users(request: Request, q: str, current_user: UserModel = Depends(get_current_user), db: Session = Depends(get_db)):
# Require auth to prevent scraping
if len(q) < 2:
return []
results = (
db.query(UserModel)
.filter(UserModel.username.ilike(f"%{q}%"))
.filter(UserModel.username.ilike(f"%{escape_like(q)}%", escape="\\"))
.limit(20)
.all()
)
@@ -117,8 +118,17 @@ def search_users(q: str, current_user: UserModel = Depends(get_current_user), db
]
# Cap on cards returned per section. These lists are unbounded per user, and the
# frontend only shows one row until you expand it.
PUBLIC_PROFILE_CARD_LIMIT = 60
@router.get("/users/{username}")
def get_public_profile(username: str, db: Session = Depends(get_db)):
# Auth alone doesn't stop enumeration — one account can still walk every profile.
@limiter.limit("60/minute")
def get_public_profile(request: Request, username: str, current_user: UserModel = Depends(get_current_user), db: Session = Depends(get_db)):
# Require auth to prevent scraping, same as /users?q= above. This returns
# strictly more than the search endpoint does, including card ids.
user = db.query(UserModel).filter(UserModel.username == username).first()
if not user:
raise HTTPException(status_code=404, detail="User not found")
@@ -127,12 +137,14 @@ def get_public_profile(username: str, db: Session = Depends(get_db)):
db.query(CardModel)
.filter(CardModel.user_id == user.id, CardModel.is_favorite == True)
.order_by(CardModel.received_at.desc())
.limit(PUBLIC_PROFILE_CARD_LIMIT)
.all()
)
wtt_cards = (
db.query(CardModel)
.filter(CardModel.user_id == user.id, CardModel.willing_to_trade == True)
.order_by(CardModel.received_at.desc())
.limit(PUBLIC_PROFILE_CARD_LIMIT)
.all()
)
return {