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
-66
View File
@@ -198,72 +198,6 @@ def create_app(config_name='default'):
except Exception as e:
logger.warning(f"Database initialization note: {e}")
# =====================================================
# Demo Mode Middleware (Read-Only Mode)
# =====================================================
import os
from flask import request, jsonify
# Check environment variable IS_DEMO_MODE
is_demo_mode = os.getenv('IS_DEMO_MODE', 'false').lower() == 'true'
if is_demo_mode:
logger.info("!!! SYSTEM STARTING IN DEMO MODE (READ-ONLY) !!!")
@app.before_request
def global_demo_mode_check():
"""
Global interceptor for demo mode.
Blocks all state-changing methods AND access to sensitive GET endpoints.
"""
path = request.path
# 1. Block access to sensitive settings/config APIs (even if GET)
# These endpoints reveal internal config or allow settings changes
sensitive_endpoints = [
'/api/settings', # All settings routes
'/api/credentials', # Credentials management
'/api/market/watchlist/add', # Modifying watchlist (POST, already blocked but good to be explicit)
'/api/market/watchlist/remove'
]
# Check if path starts with any sensitive prefix
if any(path.startswith(endpoint) for endpoint in sensitive_endpoints):
return jsonify({
'code': 403,
'msg': 'Demo mode: Access to settings and credentials is forbidden.',
'data': None
}), 403
# 2. Allow safe methods (GET, HEAD, OPTIONS)
if request.method in ['GET', 'HEAD', 'OPTIONS']:
return None
# 2. Allow Authentication (Login/Logout)
# The auth routes are mounted at /api/user (see app/routes/__init__.py)
if request.path.endswith('/login') or request.path.endswith('/logout'):
return None
# 3. Allow specific read-only POST endpoints (Whitelist)
# Some search/query endpoints use POST for complex payloads but don't modify state.
whitelist_post_endpoints = [
'/api/indicator/getIndicators', # Search indicators
'/api/market/klines', # Fetch K-lines (sometimes POST)
'/api/ai/chat', # AI Chat (generates response, doesn't mutate system state)
'/api/fast-analysis/analyze', # Fast AI Analysis request
]
# Check if current path ends with any whitelist item
if any(request.path.endswith(endpoint) for endpoint in whitelist_post_endpoints):
return None
# 4. Block everything else
return jsonify({
'code': 403,
'msg': 'Demo mode: Read-only access. Forbidden to modify data.',
'data': None
}), 403
from app.routes import register_routes
register_routes(app)