T-005: Security hardening - CORS, Rate Limit, CSRF
- CORS: Restrict allowed origins to specific frontend URLs, limit methods and headers - Rate Limit: Add fine-grained endpoint-specific rate limits for sensitive operations - Login: 5 requests/minute - Register: 3 requests/hour - Password change: 3 requests/5 minutes - Payment: 20 requests/minute - Admin: 30 requests/minute - CSRF: Add CSRF protection middleware with double-submit cookie pattern - New app/core/csrf.py module with CSRFMiddleware - Require CSRF tokens on sensitive endpoints (auth, payment, profile) - Skip webhook endpoints for CSRF validation - Fix pydantic-settings import in config.py
This commit is contained in:
@@ -7,11 +7,13 @@ import uuid
|
||||
from app.database import get_db
|
||||
from app.models.user import User
|
||||
from app.core.security import hash_password, verify_password, create_access_token, create_refresh_token, decode_token
|
||||
from app.core.csrf import require_csrf_token
|
||||
from pydantic import BaseModel, EmailStr
|
||||
from datetime import datetime, timedelta
|
||||
from app.services.admin import AdminService
|
||||
from app.models.subscription import Subscription
|
||||
from app.api.v1.referral import apply_referral
|
||||
from app.config import settings
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -44,7 +46,12 @@ class RefreshRequest(BaseModel):
|
||||
|
||||
|
||||
@router.post("/register")
|
||||
async def register(data: RegisterRequest, request: Request, db: AsyncSession = Depends(get_db)):
|
||||
async def register(
|
||||
data: RegisterRequest,
|
||||
request: Request,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_csrf: str = Depends(require_csrf_token),
|
||||
):
|
||||
existing = await db.execute(select(User).where(User.phone == data.phone))
|
||||
if existing.scalar_one_or_none():
|
||||
raise HTTPException(status_code=400, detail="Phone already registered")
|
||||
@@ -92,6 +99,7 @@ async def login(
|
||||
data: LoginRequest,
|
||||
request: Request,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_csrf: str = Depends(require_csrf_token),
|
||||
):
|
||||
login_id = data.username or data.phone
|
||||
if not login_id:
|
||||
@@ -269,6 +277,7 @@ async def update_me(
|
||||
data: ProfileUpdate,
|
||||
authorization: Optional[str] = Header(None, alias="Authorization"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_csrf: str = Depends(require_csrf_token),
|
||||
):
|
||||
if not authorization or not authorization.startswith("Bearer "):
|
||||
raise HTTPException(status_code=401, detail="Missing token")
|
||||
@@ -306,6 +315,7 @@ async def change_password(
|
||||
data: PasswordChange,
|
||||
authorization: Optional[str] = Header(None, alias="Authorization"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_csrf: str = Depends(require_csrf_token),
|
||||
):
|
||||
if not authorization or not authorization.startswith("Bearer "):
|
||||
raise HTTPException(status_code=401, detail="Missing token")
|
||||
@@ -340,7 +350,12 @@ async def wechat_config():
|
||||
|
||||
|
||||
@router.post("/wechat-login")
|
||||
async def wechat_login(data: WeChatLoginRequest, request: Request, db: AsyncSession = Depends(get_db)):
|
||||
async def wechat_login(
|
||||
data: WeChatLoginRequest,
|
||||
request: Request,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_csrf: str = Depends(require_csrf_token),
|
||||
):
|
||||
from app.services.wechat import wechat_service
|
||||
|
||||
session = await wechat_service.code2session(data.code)
|
||||
@@ -383,6 +398,7 @@ async def update_settings(
|
||||
data: SettingsUpdate,
|
||||
authorization: Optional[str] = Header(None, alias="Authorization"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_csrf: str = Depends(require_csrf_token),
|
||||
):
|
||||
if not authorization or not authorization.startswith("Bearer "):
|
||||
raise HTTPException(status_code=401, detail="Missing token")
|
||||
|
||||
Reference in New Issue
Block a user