feat(backend): encrypt exchange credentials, remove IS_DEMO_MODE

- Fernet encrypt qd_exchange_credentials via SECRET_KEY (cryptography)

- Remove global read-only demo middleware; drop is_demo from auth payloads

- Egress whitelist: /api/credentials/egress-ip returns ipv4 + ipv6 (ipify)

- Exchange factory: demo/testnet URLs and OKX simulated-trading header

- Bitget spot connection test; misc route/service fixes

Made-with: Cursor
This commit is contained in:
Dinger
2026-03-24 19:10:18 +08:00
parent 2e9c7cd69e
commit 7626328c9c
20 changed files with 429 additions and 150 deletions
@@ -15,6 +15,7 @@ from typing import Any, Dict
from app.utils.db import get_db_connection
from app.utils.logger import get_logger
from app.utils.credential_crypto import decrypt_credential_blob
logger = get_logger(__name__)
@@ -92,7 +93,7 @@ def load_strategy_configs(strategy_id: int) -> Dict[str, Any]:
def _load_credential_config(credential_id: int, user_id: int = 1) -> Dict[str, Any]:
"""Load credential JSON from qd_exchange_credentials (plaintext in local mode)."""
"""Load credential JSON from qd_exchange_credentials (Fernet via SECRET_KEY)."""
with get_db_connection() as db:
cur = db.cursor()
cur.execute(
@@ -105,7 +106,13 @@ def _load_credential_config(credential_id: int, user_id: int = 1) -> Dict[str, A
)
row = cur.fetchone() or {}
cur.close()
return _safe_json_loads(row.get("encrypted_config"), {}) or {}
raw = row.get("encrypted_config")
try:
plain = decrypt_credential_blob(raw)
except ValueError as e:
logger.warning(f"decrypt credential_id={credential_id}: {e}")
return {}
return _safe_json_loads(plain, {}) or {}
def resolve_exchange_config(exchange_config: Dict[str, Any], user_id: int = 1) -> Dict[str, Any]: