🐐 Several security fixes
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user