fix: vá lỗ hổng RBAC + hardcode sàn ở /orders/place, nâng cấp mã hoá và CORS
- orders.place_order: yêu cầu role trader/admin (get_current_trader_user) thay vì bất kỳ user đã đăng nhập nào — viewer không còn đặt được lệnh thật - orders.place_order: resolve exchange theo OrderRequest.exchange thay vì hardcode "mexc", fallback về credential active gần nhất nếu không truyền - security.py: mã hoá API key chuyển AES-256-CBC -> AES-256-GCM (có xác thực toàn vẹn), giữ đường giải mã cũ để credential đã lưu trước đây không hỏng - main_api.py: CORS_ORIGINS rỗng -> deny-all thay vì fallback "*" (kèm allow_credentials=True là cấu hình nguy hiểm) - docker-compose.yml: đồng bộ DB_PASSWORD giữa backend-api/scheduler và db - frontend: OrderPanel/DashboardPage truyền kèm exchange đang chọn khi đặt lệnh Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,7 @@ from fastapi import APIRouter, Depends, status
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_current_user, get_db_session
|
||||
from app.core.deps import get_current_trader_user, get_current_user, get_db_session
|
||||
from app.core.exceptions import NotFoundException, ValidationException
|
||||
from app.core.security import decrypt_api_key
|
||||
from app.exchange.factory import factory as exchange_factory
|
||||
@@ -26,12 +26,13 @@ router = APIRouter(prefix="/orders", tags=["orders"])
|
||||
async def place_order(
|
||||
req: OrderRequest,
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Depends(get_current_trader_user),
|
||||
) -> OrderData:
|
||||
"""Place an order on a connected exchange.
|
||||
|
||||
Uses the user's saved API credentials for the exchange.
|
||||
The exchange name is inferred from the symbol or passed explicitly.
|
||||
Uses the user's saved API credentials for the exchange. If ``req.exchange``
|
||||
is given, that exchange's credential is used; otherwise falls back to the
|
||||
user's only (or first) active credential.
|
||||
"""
|
||||
# Currently only market orders are supported via this endpoint
|
||||
if req.order_type not in ("market", "limit"):
|
||||
@@ -41,27 +42,33 @@ async def place_order(
|
||||
if req.amount <= 0:
|
||||
raise ValidationException(detail="Amount must be positive")
|
||||
|
||||
# Infer exchange name from symbol prefix heuristics, or default to mexc
|
||||
# In a more advanced setup the user would specify exchange_id in the request
|
||||
exchange_name = "mexc"
|
||||
|
||||
# Find the user's active credential for this exchange
|
||||
result = await db.execute(
|
||||
# Find the user's active credential — filter by requested exchange if given,
|
||||
# otherwise fall back to whichever active credential the user has.
|
||||
query = (
|
||||
select(ExchangeCredential)
|
||||
.join(Exchange, Exchange.id == ExchangeCredential.exchange_id)
|
||||
.where(
|
||||
ExchangeCredential.user_id == current_user.id,
|
||||
Exchange.name == exchange_name,
|
||||
ExchangeCredential.is_active == True,
|
||||
)
|
||||
)
|
||||
cred = result.scalar_one_or_none()
|
||||
if req.exchange:
|
||||
query = query.where(Exchange.name == req.exchange.lower())
|
||||
else:
|
||||
query = query.order_by(ExchangeCredential.created_at.desc())
|
||||
|
||||
result = await db.execute(query)
|
||||
cred = result.scalars().first()
|
||||
if cred is None:
|
||||
target = req.exchange or "any exchange"
|
||||
raise NotFoundException(
|
||||
detail=f"No active API key found for {exchange_name}. "
|
||||
detail=f"No active API key found for {target}. "
|
||||
f"Go to Profile → API Keys to add one."
|
||||
)
|
||||
|
||||
exchange_result = await db.execute(select(Exchange).where(Exchange.id == cred.exchange_id))
|
||||
exchange_name = exchange_result.scalar_one().name
|
||||
|
||||
# Decrypt the stored API key/secret
|
||||
try:
|
||||
# api_key is stored as plaintext (masked in responses), api_secret is encrypted
|
||||
|
||||
Reference in New Issue
Block a user